update for HEAD-2003091401
[reactos.git] / subsys / system / usetup / cabinet.c
diff --git a/subsys/system/usetup/cabinet.c b/subsys/system/usetup/cabinet.c
new file mode 100755 (executable)
index 0000000..129241e
--- /dev/null
@@ -0,0 +1,2094 @@
+/*
+ * COPYRIGHT:   See COPYING in the top level directory
+ * PROJECT:     ReactOS text-mode setup
+ * FILE:        subsys/system/usetup/cabinet.c
+ * PURPOSE:     Cabinet routines
+ * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
+ * REVISIONS:
+ *   CSH 15/08-2003 Created
+ */
+#include <ntos.h>
+#include <stdio.h>
+#include <string.h>
+#include <zlib.h>
+#include "cabinet.h"
+#include "usetup.h"
+
+#define NDEBUG
+#include <debug.h>
+
+#define SEEK_BEGIN    0
+#define SEEK_CURRENT  1
+#define SEEK_END      2
+
+typedef struct __DOSTIME
+{
+  WORD Second:5;
+  WORD Minute:6;
+  WORD Hour:5;
+} DOSTIME, *PDOSTIME;
+
+
+typedef struct __DOSDATE
+{
+  WORD Day:5;
+  WORD Month:4;
+  WORD Year:5;
+} DOSDATE, *PDOSDATE;
+
+static WCHAR CabinetName[256];          // Filename of current cabinet
+static WCHAR CabinetPrev[256];          // Filename of previous cabinet
+static WCHAR DiskPrev[256];             // Label of cabinet in file CabinetPrev
+static WCHAR CabinetNext[256];          // Filename of next cabinet
+static WCHAR DiskNext[256];             // Label of cabinet in file CabinetNext
+static ULONG FolderUncompSize = 0;      // Uncompressed size of folder
+static ULONG BytesLeftInBlock = 0;      // Number of bytes left in current block
+static BOOL ReuseBlock = FALSE;
+static WCHAR DestPath[MAX_PATH];
+static HANDLE FileHandle;
+static BOOL FileOpen = FALSE;
+static CFHEADER CABHeader;
+static ULONG CabinetReserved = 0;
+static ULONG FolderReserved = 0;
+static ULONG DataReserved = 0;
+static PCFFOLDER_NODE FolderListHead = NULL;
+static PCFFOLDER_NODE FolderListTail = NULL;
+static PCFFOLDER_NODE CurrentFolderNode = NULL;
+static PCFDATA_NODE CurrentDataNode = NULL;
+static PCFFILE_NODE FileListHead = NULL;
+static PCFFILE_NODE FileListTail = NULL;
+static ULONG CodecId;
+static PCABINET_CODEC_UNCOMPRESS CodecUncompress = NULL;
+static BOOL CodecSelected = FALSE;
+static PVOID InputBuffer = NULL;
+static PVOID CurrentIBuffer = NULL;       // Current offset in input buffer
+static ULONG CurrentIBufferSize = 0;      // Bytes left in input buffer
+static PVOID OutputBuffer = NULL;
+static PVOID CurrentOBuffer = NULL;       // Current offset in output buffer
+static ULONG CurrentOBufferSize = 0;      // Bytes left in output buffer
+static BOOL RestartSearch = FALSE;
+static ULONG LastFileOffset = 0;          // Uncompressed offset of last extracted file
+static PCABINET_OVERWRITE OverwriteHandler = NULL;
+static PCABINET_EXTRACT ExtractHandler = NULL;
+static PCABINET_DISK_CHANGE DiskChangeHandler = NULL;
+static z_stream ZStream;
+static PVOID CabinetReservedArea = NULL;
+
+
+/* Needed by zlib, but we don't want the dependency on msvcrt.dll */
+
+void free(void* _ptr)
+{
+  RtlFreeHeap(ProcessHeap, 0, _ptr);
+}
+
+void* calloc(size_t _nmemb, size_t _size)
+{
+  return (void*)RtlAllocateHeap (ProcessHeap, HEAP_ZERO_MEMORY, _size);
+}
+
+/* RAW codec */
+
+ULONG
+RawCodecUncompress(PVOID OutputBuffer,
+  PVOID InputBuffer,
+  ULONG InputLength,
+  PULONG OutputLength)
+/*
+ * FUNCTION: Uncompresses data in a buffer
+ * ARGUMENTS:
+ *     OutputBuffer = Pointer to buffer to place uncompressed data
+ *     InputBuffer  = Pointer to buffer with data to be uncompressed
+ *     InputLength  = Length of input buffer
+ *     OutputLength = Address of buffer to place size of uncompressed data
+ */
+{
+  memcpy(OutputBuffer, InputBuffer, InputLength);
+  *OutputLength = InputLength;
+  return CS_SUCCESS;
+}
+
+
+/* MSZIP codec */
+
+ULONG
+MSZipCodecUncompress(PVOID OutputBuffer,
+  PVOID InputBuffer,
+  ULONG InputLength,
+  PULONG OutputLength)
+/*
+ * FUNCTION: Uncompresses data in a buffer
+ * ARGUMENTS:
+ *     OutputBuffer = Pointer to buffer to place uncompressed data
+ *     InputBuffer  = Pointer to buffer with data to be uncompressed
+ *     InputLength  = Length of input buffer
+ *     OutputLength = Address of buffer to place size of uncompressed data
+ */
+{
+  USHORT Magic;
+  INT Status;
+
+  DPRINT("InputLength (%d).\n", InputLength);
+
+  Magic = *((PUSHORT)InputBuffer);
+
+  if (Magic != MSZIP_MAGIC)
+    {
+      DPRINT("Bad MSZIP block header magic (0x%X)\n", Magic);
+      return CS_BADSTREAM;
+    }
+       
+       ZStream.next_in   = (PUCHAR)((ULONG)InputBuffer + 2);
+       ZStream.avail_in  = InputLength - 2;
+       ZStream.next_out  = (PUCHAR)OutputBuffer;
+  ZStream.avail_out = CAB_BLOCKSIZE + 12;
+
+  /* WindowBits is passed < 0 to tell that there is no zlib header.
+   * Note that in this case inflate *requires* an extra "dummy" byte
+   * after the compressed stream in order to complete decompression and
+   * return Z_STREAM_END.
+   */
+  Status = inflateInit2(&ZStream, -MAX_WBITS);
+  if (Status != Z_OK)
+    {
+      DPRINT("inflateInit2() returned (%d).\n", Status);
+      return CS_BADSTREAM;
+    }
+
+  while ((ZStream.total_out < CAB_BLOCKSIZE + 12) &&
+    (ZStream.total_in < InputLength - 2))
+    {
+      Status = inflate(&ZStream, Z_NO_FLUSH);
+      if (Status == Z_STREAM_END) break;
+      if (Status != Z_OK)
+        {
+          DPRINT("inflate() returned (%d) (%s).\n", Status, ZStream.msg);
+          if (Status == Z_MEM_ERROR)
+              return CS_NOMEMORY;
+          return CS_BADSTREAM;
+        }
+    }
+
+  *OutputLength = ZStream.total_out;
+
+  Status = inflateEnd(&ZStream);
+  if (Status != Z_OK)
+    {
+      DPRINT("inflateEnd() returned (%d).\n", Status);
+      return CS_BADSTREAM;
+    }
+  return CS_SUCCESS;
+}
+
+
+
+/* Memory functions */
+
+voidpf MSZipAlloc(voidpf opaque, uInt items, uInt size)
+{
+  return (voidpf)RtlAllocateHeap (ProcessHeap, 0, items * size);
+}
+
+void MSZipFree (voidpf opaque, voidpf address)
+{
+  RtlFreeHeap(ProcessHeap, 0, address);
+}
+
+
+static DWORD
+SeekInFile(HANDLE hFile,
+  LONG lDistanceToMove,
+  PLONG lpDistanceToMoveHigh,
+  DWORD dwMoveMethod,
+  PNTSTATUS Status)
+{
+  FILE_POSITION_INFORMATION FilePosition;
+  FILE_STANDARD_INFORMATION FileStandart;
+  NTSTATUS errCode;
+  IO_STATUS_BLOCK IoStatusBlock;
+  LARGE_INTEGER Distance;
+  
+  DPRINT("SeekInFile(hFile %x, lDistanceToMove %d, dwMoveMethod %d)\n",
+    hFile,lDistanceToMove,dwMoveMethod);
+  
+  Distance.u.LowPart = lDistanceToMove;
+  if (lpDistanceToMoveHigh)
+    {
+      Distance.u.HighPart = *lpDistanceToMoveHigh;
+    }
+  else if (lDistanceToMove >= 0)
+    {
+      Distance.u.HighPart = 0;
+    }
+  else
+    {
+      Distance.u.HighPart = -1;
+    }
+  
+  if (dwMoveMethod == SEEK_CURRENT)
+    {
+      NtQueryInformationFile(hFile,
+        &IoStatusBlock,
+        &FilePosition,
+        sizeof(FILE_POSITION_INFORMATION),
+        FilePositionInformation);
+        FilePosition.CurrentByteOffset.QuadPart += Distance.QuadPart;
+    }
+  else if (dwMoveMethod == SEEK_END)
+    {
+      NtQueryInformationFile(hFile,
+        &IoStatusBlock,
+        &FileStandart,
+        sizeof(FILE_STANDARD_INFORMATION),
+        FileStandardInformation);
+        FilePosition.CurrentByteOffset.QuadPart =
+        FileStandart.EndOfFile.QuadPart + Distance.QuadPart;
+    }
+  else if ( dwMoveMethod == SEEK_BEGIN )
+    {
+      FilePosition.CurrentByteOffset.QuadPart = Distance.QuadPart;
+    }
+  
+//  DPRINT1("GOTO FILE OFFSET: %I64d\n", FilePosition.CurrentByteOffset.QuadPart);
+
+  errCode = NtSetInformationFile(hFile,
+    &IoStatusBlock,
+    &FilePosition,
+    sizeof(FILE_POSITION_INFORMATION),
+    FilePositionInformation);
+  if (!NT_SUCCESS(errCode))
+    {
+      if (Status != NULL)
+        {
+          *Status = errCode;
+        }
+      return -1;
+    }
+  
+  if (lpDistanceToMoveHigh != NULL)
+    {
+      *lpDistanceToMoveHigh = FilePosition.CurrentByteOffset.u.HighPart;
+    }
+  if (Status != NULL)
+    {
+      *Status = STATUS_SUCCESS;
+    }
+  return FilePosition.CurrentByteOffset.u.LowPart;
+}
+
+
+static BOOL
+ConvertSystemTimeToFileTime(
+  CONST SYSTEMTIME *  lpSystemTime,    
+  LPFILETIME  lpFileTime)
+{
+  TIME_FIELDS TimeFields;
+  LARGE_INTEGER liTime;
+  
+  TimeFields.Year = lpSystemTime->wYear;
+  TimeFields.Month = lpSystemTime->wMonth;
+  TimeFields.Day = lpSystemTime->wDay;
+  TimeFields.Hour = lpSystemTime->wHour;
+  TimeFields.Minute = lpSystemTime->wMinute;
+  TimeFields.Second = lpSystemTime->wSecond;
+  TimeFields.Milliseconds = lpSystemTime->wMilliseconds;
+  
+  if (RtlTimeFieldsToTime(&TimeFields, &liTime))
+    {
+      lpFileTime->dwLowDateTime = liTime.u.LowPart;
+      lpFileTime->dwHighDateTime = liTime.u.HighPart;
+      return TRUE;
+    }
+  return FALSE;
+}
+
+
+static BOOL
+ConvertDosDateTimeToFileTime(
+  WORD wFatDate,
+  WORD wFatTime,
+  LPFILETIME lpFileTime)
+{
+  PDOSTIME  pdtime = (PDOSTIME) &wFatTime;
+  PDOSDATE  pddate = (PDOSDATE) &wFatDate;
+  SYSTEMTIME SystemTime;
+  
+  if (lpFileTime == NULL)
+    return FALSE;
+  
+  SystemTime.wMilliseconds = 0;
+  SystemTime.wSecond = pdtime->Second;
+  SystemTime.wMinute = pdtime->Minute;
+  SystemTime.wHour = pdtime->Hour;
+  
+  SystemTime.wDay = pddate->Day;
+  SystemTime.wMonth = pddate->Month;
+  SystemTime.wYear = 1980 + pddate->Year;
+  
+  ConvertSystemTimeToFileTime(&SystemTime,lpFileTime);
+  
+  return TRUE;
+}
+
+
+static PWCHAR
+GetFileName(PWCHAR Path)
+/*
+ * FUNCTION: Returns a pointer to file name
+ * ARGUMENTS:
+ *     Path = Pointer to string with pathname
+ * RETURNS:
+ *     Pointer to filename
+ */
+{
+  ULONG i, j;
+  
+  j = i = 0;
+  
+  while (Path [i++])
+    {
+      if (Path[i - 1] == L'\\') j = i;
+    }
+  return Path + j;
+}
+
+
+static VOID
+RemoveFileName(PWCHAR Path)
+/*
+ * FUNCTION: Removes a file name from a path
+ * ARGUMENTS:
+ *     Path = Pointer to string with path
+ */
+{
+  PWCHAR FileName;
+  DWORD i;
+  
+  i = 0;
+  FileName = GetFileName(Path + i);
+  
+  if ((FileName != (Path + i)) && (FileName [-1] == L'\\'))
+    FileName--;
+  if ((FileName == (Path + i)) && (FileName [0] == L'\\'))
+    FileName++;
+  FileName[0] = 0;
+}
+
+
+static BOOL
+SetAttributesOnFile(PCFFILE_NODE File, HANDLE hFile)
+/*
+ * FUNCTION: Sets attributes on a file
+ * ARGUMENTS:
+ *      File = Pointer to CFFILE node for file
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  FILE_BASIC_INFORMATION FileBasic;
+  IO_STATUS_BLOCK IoStatusBlock;
+  NTSTATUS NtStatus;
+  ULONG Attributes = 0;
+
+  if (File->File.Attributes & CAB_ATTRIB_READONLY)
+    Attributes |= FILE_ATTRIBUTE_READONLY;
+
+  if (File->File.Attributes & CAB_ATTRIB_HIDDEN)
+    Attributes |= FILE_ATTRIBUTE_HIDDEN;
+
+  if (File->File.Attributes & CAB_ATTRIB_SYSTEM)
+    Attributes |= FILE_ATTRIBUTE_SYSTEM;
+
+  if (File->File.Attributes & CAB_ATTRIB_DIRECTORY)
+    Attributes |= FILE_ATTRIBUTE_DIRECTORY;
+
+  if (File->File.Attributes & CAB_ATTRIB_ARCHIVE)
+    Attributes |= FILE_ATTRIBUTE_ARCHIVE;
+
+  NtStatus = NtQueryInformationFile(hFile,
+    &IoStatusBlock,
+    &FileBasic,
+    sizeof(FILE_BASIC_INFORMATION),
+    FileBasicInformation);
+  if (!NT_SUCCESS(NtStatus))
+    {
+      DPRINT("NtQueryInformationFile() failed (%x).\n", NtStatus);
+    }
+  else
+    {
+      FileBasic.FileAttributes = Attributes;
+
+      NtStatus = NtSetInformationFile(hFile,
+        &IoStatusBlock,
+        &FileBasic,
+        sizeof(FILE_BASIC_INFORMATION),
+        FileBasicInformation);
+      if (!NT_SUCCESS(NtStatus))
+        {
+          DPRINT("NtSetInformationFile() failed (%x).\n", NtStatus);
+        }
+    }
+
+  return NT_SUCCESS(NtStatus);
+}
+
+
+static ULONG
+ReadBlock(PVOID Buffer,
+  ULONG Size,
+  PULONG BytesRead)
+/*
+ * FUNCTION: Read a block of data from file
+ * ARGUMENTS:
+ *     Buffer    = Pointer to data buffer
+ *     Size      = Length of data buffer
+ *     BytesRead = Pointer to ULONG that on return will contain
+ *                 number of bytes read
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  IO_STATUS_BLOCK IoStatusBlock;
+  NTSTATUS NtStatus;
+
+  NtStatus = NtReadFile(FileHandle,
+         NULL,
+         NULL,
+         NULL,
+         &IoStatusBlock,
+         Buffer,
+         Size,
+         NULL,
+         NULL);
+  if (!NT_SUCCESS(NtStatus))
+         {
+      DPRINT("ReadBlock for %d bytes failed (%x)\n", Size, NtStatus);
+      *BytesRead = 0;
+      return CAB_STATUS_INVALID_CAB;
+    }
+  *BytesRead = Size;
+  return CAB_STATUS_SUCCESS;
+}
+
+
+static PCFFOLDER_NODE
+NewFolderNode()
+/*
+ * FUNCTION: Creates a new folder node
+ * RETURNS:
+ *     Pointer to node if there was enough free memory available, otherwise NULL
+ */
+{
+  PCFFOLDER_NODE Node;
+
+  Node = (PCFFOLDER_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFFOLDER_NODE));
+  if (!Node)
+    return NULL;
+  
+  RtlZeroMemory(Node, sizeof(CFFOLDER_NODE));
+  
+  Node->Folder.CompressionType = CAB_COMP_NONE;
+  
+  Node->Prev = FolderListTail;
+  
+  if (FolderListTail != NULL)
+    {
+      FolderListTail->Next = Node;
+    }
+  else
+    {
+      FolderListHead = Node;
+    }
+  FolderListTail = Node;
+  
+  return Node;
+}
+
+
+static PCFFILE_NODE
+NewFileNode()
+/*
+ * FUNCTION: Creates a new file node
+ * ARGUMENTS:
+ *     FolderNode = Pointer to folder node to bind file to
+ * RETURNS:
+ *     Pointer to node if there was enough free memory available, otherwise NULL
+ */
+{
+  PCFFILE_NODE Node;
+
+  Node = (PCFFILE_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFFILE_NODE));
+  if (!Node)
+    return NULL;
+
+  RtlZeroMemory(Node, sizeof(CFFILE_NODE));
+
+  Node->Prev = FileListTail;
+
+  if (FileListTail != NULL)
+    {
+      FileListTail->Next = Node;
+    }
+  else
+    {
+      FileListHead = Node;
+    }
+  FileListTail = Node;
+
+  return Node;
+}
+
+
+static PCFDATA_NODE
+NewDataNode(PCFFOLDER_NODE FolderNode)
+/*
+ * FUNCTION: Creates a new data block node
+ * ARGUMENTS:
+ *     FolderNode = Pointer to folder node to bind data block to
+ * RETURNS:
+ *     Pointer to node if there was enough free memory available, otherwise NULL
+ */
+{
+  PCFDATA_NODE Node;
+
+  Node = (PCFDATA_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFDATA_NODE));
+  if (!Node)
+    return NULL;
+
+  RtlZeroMemory(Node, sizeof(CFDATA_NODE));
+
+  Node->Prev = FolderNode->DataListTail;
+
+  if (FolderNode->DataListTail != NULL)
+    {
+      FolderNode->DataListTail->Next = Node;
+    }
+  else
+    {
+      FolderNode->DataListHead = Node;
+    }
+  FolderNode->DataListTail = Node;
+
+  return Node;
+}
+
+
+static VOID
+DestroyDataNodes(PCFFOLDER_NODE FolderNode)
+/*
+ * FUNCTION: Destroys data block nodes bound to a folder node
+ * ARGUMENTS:
+ *     FolderNode = Pointer to folder node
+ */
+{
+  PCFDATA_NODE PrevNode;
+  PCFDATA_NODE NextNode;
+
+  NextNode = FolderNode->DataListHead;
+  while (NextNode != NULL)
+    {
+      PrevNode = NextNode->Next;
+      RtlFreeHeap(ProcessHeap, 0, NextNode);
+      NextNode = PrevNode;
+    }
+  FolderNode->DataListHead = NULL;
+  FolderNode->DataListTail = NULL;
+}
+
+
+static VOID
+DestroyFileNodes()
+/*
+ * FUNCTION: Destroys file nodes
+ * ARGUMENTS:
+ *     FolderNode = Pointer to folder node
+ */
+{
+  PCFFILE_NODE PrevNode;
+  PCFFILE_NODE NextNode;
+
+  NextNode = FileListHead;
+  while (NextNode != NULL)
+    {
+      PrevNode = NextNode->Next;
+      if (NextNode->FileName)
+        RtlFreeHeap(ProcessHeap, 0, NextNode->FileName);
+      RtlFreeHeap(ProcessHeap, 0, NextNode);
+      NextNode = PrevNode;
+    }
+  FileListHead = NULL;
+  FileListTail = NULL;
+}
+
+
+static VOID
+DestroyDeletedFileNodes()
+/*
+ * FUNCTION: Destroys file nodes that are marked for deletion
+ */
+{
+  PCFFILE_NODE CurNode;
+  PCFFILE_NODE NextNode;
+
+  CurNode = FileListHead;
+  while (CurNode != NULL)
+    {
+      NextNode = CurNode->Next;
+
+      if (CurNode->Delete)
+        {
+          if (CurNode->Prev != NULL)
+            {
+              CurNode->Prev->Next = CurNode->Next;
+            }
+          else
+            {
+              FileListHead = CurNode->Next;
+              if (FileListHead)
+                  FileListHead->Prev = NULL;
+            }
+
+          if (CurNode->Next != NULL)
+            {
+              CurNode->Next->Prev = CurNode->Prev;
+            }
+          else
+            {
+              FileListTail = CurNode->Prev;
+              if (FileListTail)
+                  FileListTail->Next = NULL;
+            }
+
+          DPRINT("Deleting file: '%S'\n", CurNode->FileName);
+
+          if (CurNode->FileName)
+            RtlFreeHeap(ProcessHeap, 0, CurNode->FileName);
+          RtlFreeHeap(ProcessHeap, 0, CurNode);
+        }
+      CurNode = NextNode;
+    }
+}
+
+
+static VOID
+DestroyFolderNodes()
+/*
+ * FUNCTION: Destroys folder nodes
+ */
+{
+  PCFFOLDER_NODE PrevNode;
+  PCFFOLDER_NODE NextNode;
+
+  NextNode = FolderListHead;
+  while (NextNode != NULL)
+    {
+      PrevNode = NextNode->Next;
+      DestroyDataNodes(NextNode);
+      RtlFreeHeap(ProcessHeap, 0, NextNode);
+      NextNode = PrevNode;
+    }
+  FolderListHead = NULL;
+  FolderListTail = NULL;
+}
+
+
+static VOID
+DestroyDeletedFolderNodes()
+/*
+ * FUNCTION: Destroys folder nodes that are marked for deletion
+ */
+{
+  PCFFOLDER_NODE CurNode;
+  PCFFOLDER_NODE NextNode;
+
+  CurNode = FolderListHead;
+  while (CurNode != NULL)
+    {
+      NextNode = CurNode->Next;
+
+      if (CurNode->Delete)
+        {
+          if (CurNode->Prev != NULL)
+            {
+              CurNode->Prev->Next = CurNode->Next;
+            }
+          else
+            {
+              FolderListHead = CurNode->Next;
+              if (FolderListHead)
+                FolderListHead->Prev = NULL;
+            }
+
+          if (CurNode->Next != NULL)
+            {
+              CurNode->Next->Prev = CurNode->Prev;
+            }
+          else
+            {
+              FolderListTail = CurNode->Prev;
+              if (FolderListTail)
+                  FolderListTail->Next = NULL;
+            }
+
+          DestroyDataNodes(CurNode);
+          RtlFreeHeap(ProcessHeap, 0, CurNode);
+      }
+      CurNode = NextNode;
+    }
+}
+
+
+static PCFFOLDER_NODE
+LocateFolderNode(ULONG Index)
+/*
+ * FUNCTION: Locates a folder node
+ * ARGUMENTS:
+ *     Index = Folder index
+ * RETURNS:
+ *     Pointer to folder node or NULL if the folder node was not found
+ */
+{
+  PCFFOLDER_NODE Node;
+  
+  switch (Index)
+    {
+      case CAB_FILE_SPLIT:
+        return FolderListTail;
+
+      case CAB_FILE_CONTINUED:
+      case CAB_FILE_PREV_NEXT:
+        return FolderListHead;
+    }
+  
+  Node = FolderListHead;
+  while (Node != NULL)
+    {
+      if (Node->Index == Index)
+        return Node;
+      Node = Node->Next;
+    }
+  return NULL;
+}
+
+
+static ULONG
+GetAbsoluteOffset(PCFFILE_NODE File)
+/*
+ * FUNCTION: Returns the absolute offset of a file
+ * ARGUMENTS:
+ *     File = Pointer to CFFILE_NODE structure for file
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  PCFDATA_NODE Node;
+
+  DPRINT("FileName '%S'  FileOffset (0x%X)  FileSize (%d).\n",
+    (PWCHAR)File->FileName,
+    (UINT)File->File.FileOffset,
+    (UINT)File->File.FileSize);
+
+  Node = CurrentFolderNode->DataListHead;
+  while (Node != NULL)
+    {
+      DPRINT("GetAbsoluteOffset(): Comparing (0x%X, 0x%X) (%d).\n",
+          (UINT)Node->UncompOffset,
+          (UINT)Node->UncompOffset + Node->Data.UncompSize,
+          (UINT)Node->Data.UncompSize);
+
+      /* Node->Data.UncompSize will be 0 if the block is split
+         (ie. it is the last block in this cabinet) */
+      if ((Node->Data.UncompSize == 0) ||
+        ((File->File.FileOffset >= Node->UncompOffset) &&
+        (File->File.FileOffset < Node->UncompOffset +
+        Node->Data.UncompSize)))
+        {
+          File->DataBlock = Node;
+          return CAB_STATUS_SUCCESS;
+        }
+
+      Node = Node->Next;
+    }
+  return CAB_STATUS_INVALID_CAB;
+}
+
+
+static ULONG
+LocateFile(PWCHAR FileName,
+  PCFFILE_NODE *File)
+/*
+ * FUNCTION: Locates a file in the cabinet
+ * ARGUMENTS:
+ *     FileName = Pointer to string with name of file to locate
+ *     File     = Address of pointer to CFFILE_NODE structure to fill
+ * RETURNS:
+ *     Status of operation
+ * NOTES:
+ *     Current folder is set to the folder of the file
+ */
+{
+  PCFFILE_NODE Node;
+  ULONG Status;
+
+  DPRINT("FileName '%S'\n", FileName);
+
+  Node = FileListHead;
+  while (Node != NULL)
+    {
+      if (_wcsicmp(FileName, Node->FileName) == 0)
+        {
+          CurrentFolderNode = LocateFolderNode(Node->File.FileControlID);
+          if (!CurrentFolderNode)
+            {
+              DPRINT("Folder with index number (%d) not found.\n",
+                (UINT)Node->File.FileControlID);
+              return CAB_STATUS_INVALID_CAB;
+            }
+
+          if (Node->DataBlock == NULL)
+            {
+              Status = GetAbsoluteOffset(Node);
+            }
+          else
+            Status = CAB_STATUS_SUCCESS;
+          *File = Node;
+          return Status;
+      }
+      Node = Node->Next;
+  }
+  return CAB_STATUS_NOFILE;
+}
+
+
+static ULONG
+ReadString(PWCHAR String, ULONG MaxLength)
+/*
+ * FUNCTION: Reads a NULL-terminated string from the cabinet
+ * ARGUMENTS:
+ *     String    = Pointer to buffer to place string
+ *     MaxLength = Maximum length of string
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  NTSTATUS NtStatus;
+  ULONG BytesRead;
+  ULONG Offset;
+  ULONG Status;
+  ULONG Size;
+  BOOL Found;
+  CHAR buf[MAX_PATH];
+  ANSI_STRING as;
+  UNICODE_STRING us;
+
+  Offset = 0;
+  Found  = FALSE;
+  do
+    {
+      Size = ((Offset + 32) <= MaxLength)? 32 : MaxLength - Offset;
+
+      if (Size == 0)
+        {
+          DPRINT("Too long a filename.\n");
+          return CAB_STATUS_INVALID_CAB;
+        }
+
+      Status = ReadBlock((PCFDATA)&buf[Offset], Size, &BytesRead);
+      if ((Status != CAB_STATUS_SUCCESS) || (BytesRead != Size))
+        {
+          DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+          return CAB_STATUS_INVALID_CAB;
+        }
+
+      for (Size = Offset; Size < Offset + BytesRead; Size++)
+        {
+          if (buf[Size] == '\0')
+            {
+              Found = TRUE;
+              break;
+            }
+        }
+
+      Offset += BytesRead;
+    } while (!Found);
+
+  /* Back up some bytes */
+  Size = (BytesRead - Size) - 1;
+  SeekInFile(FileHandle, -(LONG)Size, NULL, SEEK_CURRENT, &NtStatus);
+  if (!NT_SUCCESS(NtStatus))
+    {
+      DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+      return CAB_STATUS_INVALID_CAB;
+    }
+
+  RtlInitAnsiString(&as, buf);
+  us.Buffer = String;
+  us.MaximumLength = MaxLength * sizeof(WCHAR);
+  us.Length = 0;
+
+  RtlAnsiStringToUnicodeString(&us, &as, FALSE);
+
+  return CAB_STATUS_SUCCESS;
+}
+
+
+static ULONG
+ReadFileTable()
+/*
+ * FUNCTION: Reads the file table from the cabinet file
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  ULONG i;
+  ULONG Status;
+  ULONG BytesRead;
+  PCFFILE_NODE File;
+  NTSTATUS NtStatus;
+
+  DPRINT("Reading file table at absolute offset (0x%X).\n",
+    CABHeader.FileTableOffset);
+
+  /* Seek to file table */
+  SeekInFile(FileHandle, CABHeader.FileTableOffset, NULL, SEEK_BEGIN, &NtStatus);
+  if (!NT_SUCCESS(NtStatus))
+    {
+      DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+      return CAB_STATUS_INVALID_CAB;
+    }
+
+  for (i = 0; i < CABHeader.FileCount; i++)
+    {
+      File = NewFileNode();
+      if (!File)
+        {
+          DPRINT("Insufficient memory.\n");
+          return CAB_STATUS_NOMEMORY;
+        }
+
+      if ((Status = ReadBlock(&File->File, sizeof(CFFILE),
+          &BytesRead)) != CAB_STATUS_SUCCESS) {
+          DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+          return CAB_STATUS_INVALID_CAB;
+      }
+
+      File->FileName = (PWCHAR)RtlAllocateHeap(ProcessHeap, 0, MAX_PATH * sizeof(WCHAR));
+      if (!File->FileName)
+        {
+          DPRINT("Insufficient memory.\n");
+          return CAB_STATUS_NOMEMORY;
+        }
+
+      /* Read file name */
+      Status = ReadString(File->FileName, MAX_PATH);
+      if (Status != CAB_STATUS_SUCCESS)
+        return Status;
+
+      DPRINT("Found file '%S' at uncompressed offset (0x%X).  Size (%d bytes)  ControlId (0x%X).\n",
+        (PWCHAR)File->FileName,
+        (UINT)File->File.FileOffset,
+        (UINT)File->File.FileSize,
+        (UINT)File->File.FileControlID);
+    }
+  return CAB_STATUS_SUCCESS;
+}
+
+
+static ULONG
+ReadDataBlocks(PCFFOLDER_NODE FolderNode)
+/*
+ * FUNCTION: Reads all CFDATA blocks for a folder from the cabinet file
+ * ARGUMENTS:
+ *     FolderNode = Pointer to CFFOLDER_NODE structure for folder
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  ULONG AbsoluteOffset;
+  ULONG UncompOffset;
+  PCFDATA_NODE Node;
+  NTSTATUS NtStatus;
+  ULONG BytesRead;
+  ULONG Status;
+  ULONG i;
+
+  DPRINT("Reading data blocks for folder (%d)  at absolute offset (0x%X).\n",
+    FolderNode->Index, FolderNode->Folder.DataOffset);
+
+  AbsoluteOffset = FolderNode->Folder.DataOffset;
+  UncompOffset = FolderNode->UncompOffset;
+
+  for (i = 0; i < FolderNode->Folder.DataBlockCount; i++)
+    {
+      Node = NewDataNode(FolderNode);
+      if (!Node)
+        {
+          DPRINT("Insufficient memory.\n");
+          return CAB_STATUS_NOMEMORY;
+        }
+
+      /* Seek to data block */
+      SeekInFile(FileHandle, AbsoluteOffset, NULL, SEEK_BEGIN, &NtStatus);
+      if (!NT_SUCCESS(NtStatus))
+        {
+          DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+          return CAB_STATUS_INVALID_CAB;
+        }
+
+      if ((Status = ReadBlock(&Node->Data, sizeof(CFDATA),
+          &BytesRead)) != CAB_STATUS_SUCCESS)
+        {
+          DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+          return CAB_STATUS_INVALID_CAB;
+        }
+
+      DPRINT("AbsOffset (0x%X)  UncompOffset (0x%X)  Checksum (0x%X)  CompSize (%d)  UncompSize (%d).\n",
+        (UINT)AbsoluteOffset,
+        (UINT)UncompOffset,
+        (UINT)Node->Data.Checksum,
+        (UINT)Node->Data.CompSize,
+        (UINT)Node->Data.UncompSize);
+
+      Node->AbsoluteOffset = AbsoluteOffset;
+      Node->UncompOffset = UncompOffset;
+
+      AbsoluteOffset += sizeof(CFDATA) + Node->Data.CompSize;
+      UncompOffset += Node->Data.UncompSize;
+    }
+
+  FolderUncompSize = UncompOffset;
+
+  return CAB_STATUS_SUCCESS;
+}
+
+
+static ULONG
+ComputeChecksum(PVOID Buffer,
+  UINT Size,
+  ULONG Seed)
+/*
+ * FUNCTION: Computes checksum for data block
+ * ARGUMENTS:
+ *     Buffer = Pointer to data buffer
+ *     Size   = Length of data buffer
+ *     Seed   = Previously computed checksum
+ * RETURNS:
+ *     Checksum of buffer
+ */
+{
+  INT UlongCount; // Number of ULONGs in block
+  ULONG Checksum; // Checksum accumulator
+  PBYTE pb;
+  ULONG ul;
+
+  /* FIXME: Doesn't seem to be correct. EXTRACT.EXE
+     won't accept checksums computed by this routine */
+
+  DPRINT("Checksumming buffer (0x%X)  Size (%d)\n", (UINT)Buffer, Size);
+
+  UlongCount = Size / 4;            // Number of ULONGs
+  Checksum = Seed;                  // Init checksum
+  pb = (PBYTE)Buffer;               // Start at front of data block
+
+  /* Checksum integral multiple of ULONGs */
+  while (UlongCount-- > 0)
+    {
+      /* NOTE: Build ULONG in big/little-endian independent manner */
+      ul = *pb++;                     // Get low-order byte
+      ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
+      ul |= (((ULONG)(*pb++)) << 16); // Add 3nd byte
+      ul |= (((ULONG)(*pb++)) << 24); // Add 4th byte
+
+      Checksum ^= ul;                 // Update checksum
+  }
+
+  /* Checksum remainder bytes */
+  ul = 0;
+  switch (Size % 4)
+    {
+      case 3:
+          ul |= (((ULONG)(*pb++)) << 16); // Add 3rd byte
+      case 2:
+          ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
+      case 1:
+          ul |= *pb++;                    // Get low-order byte
+      default:
+          break;
+    }
+  Checksum ^= ul;                         // Update checksum
+
+  /* Return computed checksum */
+  return Checksum;
+}
+
+
+static ULONG
+CloseCabinet()
+/*
+ * FUNCTION: Closes the current cabinet
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  PCFFOLDER_NODE PrevNode;
+  PCFFOLDER_NODE NextNode;
+  ULONG Status;
+  
+  DestroyFileNodes();
+  
+  DestroyFolderNodes();
+  
+  if (InputBuffer)
+    {
+      RtlFreeHeap(ProcessHeap, 0, InputBuffer);
+      InputBuffer = NULL;
+    }
+  
+  if (OutputBuffer)
+    {
+      RtlFreeHeap(ProcessHeap, 0, OutputBuffer);
+      OutputBuffer = NULL;
+    }
+
+  NtClose(FileHandle);
+}
+
+
+VOID
+CabinetInitialize()
+/*
+ * FUNCTION: Initialize archiver
+ */
+{
+  ZStream.zalloc = MSZipAlloc;
+  ZStream.zfree  = MSZipFree;
+  ZStream.opaque = (voidpf)0;
+
+  FileOpen = FALSE;
+  wcscpy(DestPath, L"");
+  
+  FolderListHead = NULL;
+  FolderListTail = NULL;
+  FileListHead   = NULL;
+  FileListTail   = NULL;
+  
+  CodecId       = CAB_CODEC_RAW;
+  CodecSelected = TRUE;
+  
+  OutputBuffer = NULL;
+  CurrentOBuffer = NULL;
+  CurrentOBufferSize = 0;
+  InputBuffer  = NULL;
+  CurrentIBuffer = NULL;
+  CurrentIBufferSize = 0;
+
+  FolderUncompSize = 0;
+  BytesLeftInBlock = 0;
+  CabinetReserved = 0;
+  FolderReserved = 0;
+  DataReserved = 0;
+  ReuseBlock       = FALSE;
+  CurrentFolderNode = NULL;
+  CurrentDataNode  = NULL;
+  CabinetReservedArea = NULL;
+  RestartSearch = FALSE;
+  LastFileOffset = 0;
+}
+
+
+VOID
+CabinetCleanup()
+/*
+ * FUNCTION: Cleanup archiver
+ */
+{
+  CabinetClose();
+}
+
+
+BOOL
+CabinetNormalizePath(PWCHAR Path,
+  ULONG Length)
+/*
+ * FUNCTION: Normalizes a path
+ * ARGUMENTS:
+ *     Path   = Pointer to string with pathname
+ *     Length = Number of characters in Path
+ * RETURNS:
+ *     TRUE if there was enough room in Path, or FALSE
+ */
+{
+  ULONG n;
+  BOOL OK = TRUE;
+  
+  if ((n = wcslen(Path)) &&
+    (Path[n - 1] != L'\\') &&
+    (OK = ((n + 1) < Length)))
+    {
+      Path[n]     = L'\\';
+      Path[n + 1] = 0;
+    }
+  return OK;
+}
+
+
+PWCHAR
+CabinetGetCabinetName()
+/*
+ * FUNCTION: Returns pointer to cabinet file name
+ * RETURNS:
+ *     Pointer to string with name of cabinet
+ */
+{
+  return CabinetName;
+}
+
+
+VOID
+CabinetSetCabinetName(PWCHAR FileName)
+/*
+ * FUNCTION: Sets cabinet file name
+ * ARGUMENTS:
+ *     FileName = Pointer to string with name of cabinet
+ */
+{
+  wcscpy(CabinetName, FileName);
+}
+
+
+VOID
+CabinetSetDestinationPath(PWCHAR DestinationPath)
+/*
+ * FUNCTION: Sets destination path
+ * ARGUMENTS:
+ *    DestinationPath = Pointer to string with name of destination path
+ */
+{
+  wcscpy(DestPath, DestinationPath);
+  if (wcslen(DestPath) > 0)
+    CabinetNormalizePath(DestPath, MAX_PATH);
+}
+
+
+PWCHAR
+CabinetGetDestinationPath()
+/*
+ * FUNCTION: Returns destination path
+ * RETURNS:
+ *    Pointer to string with name of destination path
+ */
+{
+  return DestPath;
+}
+
+
+ULONG
+CabinetOpen()
+/*
+ * FUNCTION: Opens a cabinet file
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  WCHAR CabinetFileName[256];
+  PCFFOLDER_NODE FolderNode;
+  ULONG Status;
+  ULONG Index;
+  
+  if (!FileOpen)
+    {
+      OBJECT_ATTRIBUTES ObjectAttributes;
+      IO_STATUS_BLOCK IoStatusBlock;
+      UNICODE_STRING FileName;
+      NTSTATUS NtStatus;
+      ULONG BytesRead;
+      ULONG Size;
+    
+      OutputBuffer = RtlAllocateHeap(ProcessHeap, 0, CAB_BLOCKSIZE + 12); // This should be enough
+      if (!OutputBuffer)
+        return CAB_STATUS_NOMEMORY;
+
+      RtlInitUnicodeString(&FileName,
+        CabinetName);
+
+      InitializeObjectAttributes(&ObjectAttributes,
+        &FileName,
+        OBJ_CASE_INSENSITIVE,
+        NULL,
+        NULL);
+
+      NtStatus = NtOpenFile(&FileHandle,
+        FILE_READ_ACCESS,
+                   &ObjectAttributes,
+                   &IoStatusBlock,
+                   FILE_SHARE_READ,
+                   FILE_SYNCHRONOUS_IO_ALERT);
+      if (!NT_SUCCESS(NtStatus))
+        {
+          DPRINT("Cannot open file (%S) (%x).\n", CabinetName, NtStatus);
+          return CAB_STATUS_CANNOT_OPEN;
+        }
+    
+      FileOpen = TRUE;
+    
+      /* Load CAB header */
+      if ((Status = ReadBlock(&CABHeader, sizeof(CFHEADER), &BytesRead)) != CAB_STATUS_SUCCESS)
+        {
+          DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+          return CAB_STATUS_INVALID_CAB;
+        }
+    
+      /* Check header */
+      if ((BytesRead               != sizeof(CFHEADER)) ||
+        (CABHeader.Signature       != CAB_SIGNATURE   ) ||
+        (CABHeader.Version         != CAB_VERSION     ) ||
+        (CABHeader.FolderCount     == 0               ) ||
+        (CABHeader.FileCount       == 0               ) ||
+        (CABHeader.FileTableOffset < sizeof(CFHEADER))) {
+        CloseCabinet();
+        DPRINT("File has invalid header.\n");
+        return CAB_STATUS_INVALID_CAB;
+      }
+  
+      Size = 0;
+  
+      /* Read/skip any reserved bytes */
+      if (CABHeader.Flags & CAB_FLAG_RESERVE)
+        {
+          if ((Status = ReadBlock(&Size, sizeof(ULONG), &BytesRead)) != CAB_STATUS_SUCCESS)
+            {
+              DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+              return CAB_STATUS_INVALID_CAB;
+            }
+          CabinetReserved = Size & 0xFFFF;
+          FolderReserved = (Size >> 16) & 0xFF;
+          DataReserved = (Size >> 24) & 0xFF;
+
+          if (CabinetReserved > 0)
+            {
+              CabinetReservedArea = RtlAllocateHeap(ProcessHeap, 0, CabinetReserved);
+              if (!CabinetReservedArea)
+                {
+                  return CAB_STATUS_NOMEMORY;
+                }
+
+              if ((Status = ReadBlock(CabinetReservedArea, CabinetReserved, &BytesRead)) != CAB_STATUS_SUCCESS)
+                {
+                  DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+                  return CAB_STATUS_INVALID_CAB;
+                }
+            }
+#if 0
+          SeekInFile(FileHandle, CabinetReserved, NULL, SEEK_CURRENT, &NtStatus);
+          if (!NT_SUCCESS(NtStatus))
+            {
+              DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+              return CAB_STATUS_INVALID_CAB;
+            }
+#endif
+        }
+  
+      if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
+        {
+          /* Read name of previous cabinet */
+          Status = ReadString(CabinetFileName, 256);
+          if (Status != CAB_STATUS_SUCCESS)
+            return Status;
+
+          /* The previous cabinet file is in the same directory as the current */
+          wcscpy(CabinetPrev, CabinetName);
+          RemoveFileName(CabinetPrev);
+          CabinetNormalizePath(CabinetPrev, 256);
+          wcscat(CabinetPrev, CabinetFileName);
+
+          /* Read label of previous disk */
+          Status = ReadString(DiskPrev, 256);
+          if (Status != CAB_STATUS_SUCCESS)
+            return Status;
+        }
+      else
+        {
+          wcscpy(CabinetPrev, L"");
+          wcscpy(DiskPrev, L"");
+        }
+    
+      if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
+        {
+          /* Read name of next cabinet */
+          Status = ReadString(CabinetFileName, 256);
+          if (Status != CAB_STATUS_SUCCESS)
+              return Status;
+
+          /* The next cabinet file is in the same directory as the previous */
+          wcscpy(CabinetNext, CabinetName);
+          RemoveFileName(CabinetNext);
+          CabinetNormalizePath(CabinetNext, 256);
+          wcscat(CabinetNext, CabinetFileName);
+
+          /* Read label of next disk */
+          Status = ReadString(DiskNext, 256);
+          if (Status != CAB_STATUS_SUCCESS)
+              return Status;
+        }
+      else
+        {
+          wcscpy(CabinetNext, L"");
+          wcscpy(DiskNext,    L"");
+        }
+  
+      /* Read all folders */
+      for (Index = 0; Index < CABHeader.FolderCount; Index++)
+        {
+          FolderNode = NewFolderNode();
+          if (!FolderNode)
+            {
+              DPRINT("Insufficient resources.\n");
+              return CAB_STATUS_NOMEMORY;
+            }
+  
+          if (Index == 0)
+            FolderNode->UncompOffset = FolderUncompSize;
+    
+          FolderNode->Index = Index;
+  
+          if ((Status = ReadBlock(&FolderNode->Folder,
+            sizeof(CFFOLDER), &BytesRead)) != CAB_STATUS_SUCCESS)
+            {
+              DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+              return CAB_STATUS_INVALID_CAB;
+            }
+        }
+  
+      /* Read file entries */
+      Status = ReadFileTable();
+      if (Status != CAB_STATUS_SUCCESS)
+        {
+          DPRINT("ReadFileTable() failed (%d).\n", (UINT)Status);
+          return Status;
+        }
+  
+      /* Read data blocks for all folders */
+      FolderNode = FolderListHead;
+      while (FolderNode != NULL)
+        {
+          Status = ReadDataBlocks(FolderNode);
+          if (Status != CAB_STATUS_SUCCESS)
+            {
+              DPRINT("ReadDataBlocks() failed (%d).\n", (UINT)Status);
+              return Status;
+            }
+          FolderNode = FolderNode->Next;
+        }
+    }
+  return CAB_STATUS_SUCCESS;
+}
+
+
+VOID
+CabinetClose()
+/*
+ * FUNCTION: Closes the cabinet file
+ */
+{
+  if (FileOpen)
+    {
+      CloseCabinet();
+
+      if (CabinetReservedArea != NULL)
+        {
+          RtlFreeHeap(ProcessHeap, 0, CabinetReservedArea);
+          CabinetReservedArea = NULL;
+        }
+
+      FileOpen = FALSE;
+    }
+}
+
+
+ULONG
+CabinetFindFirst(PWCHAR FileName,
+  PCAB_SEARCH Search)
+/*
+ * FUNCTION: Finds the first file in the cabinet that matches a search criteria
+ * ARGUMENTS:
+ *     FileName = Pointer to search criteria
+ *     Search   = Pointer to search structure
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  RestartSearch = FALSE;
+  wcsncpy(Search->Search, FileName, MAX_PATH);
+  Search->Next = FileListHead;
+  return CabinetFindNext(Search);
+}
+
+
+ULONG
+CabinetFindNext(PCAB_SEARCH Search)
+/*
+ * FUNCTION: Finds next file in the cabinet that matches a search criteria
+ * ARGUMENTS:
+ *     Search = Pointer to search structure
+ * RETURNS:
+ *     Status of operation
+ */
+{
+  ULONG Status;
+
+  if (RestartSearch)
+    {
+      Search->Next = FileListHead;
+
+      /* Skip split files already extracted */
+      while ((Search->Next) &&
+        (Search->Next->File.FileControlID > CAB_FILE_MAX_FOLDER) &&
+        (Search->Next->File.FileOffset <= LastFileOffset))
+        {
+          DPRINT("Skipping file (%s)  FileOffset (0x%X)  LastFileOffset (0x%X).\n",
+            Search->Next->FileName, Search->Next->File.FileOffset, LastFileOffset);
+          Search->Next = Search->Next->Next;
+        }
+
+      RestartSearch = FALSE;
+    }
+
+  /* FIXME: Check search criteria */
+
+  if (!Search->Next)
+    {
+      if (wcslen(DiskNext) > 0)
+        {
+          CloseCabinet();
+
+          CabinetSetCabinetName(CabinetNext);
+
+          if (DiskChangeHandler != NULL)
+            {
+              DiskChangeHandler(CabinetNext, DiskNext);
+            }
+
+          Status = CabinetOpen();
+          if (Status != CAB_STATUS_SUCCESS) 
+            return Status;
+
+          Search->Next = FileListHead;
+          if (!Search->Next)
+            return CAB_STATUS_NOFILE;
+        }
+      else
+        {
+          return CAB_STATUS_NOFILE;
+        }
+  }
+
+  Search->File = &Search->Next->File;
+  Search->FileName = Search->Next->FileName;
+  Search->Next = Search->Next->Next;
+  return CAB_STATUS_SUCCESS;
+}
+
+
+ULONG
+CabinetExtractFile(PWCHAR FileName)
+/*
+ * FUNCTION: Extracts a file from the cabinet
+ * ARGUMENTS:
+ *     FileName = Pointer to buffer with name of file
+ * RETURNS
+ *     Status of operation
+ */
+{
+  ULONG Size;
+  ULONG Offset;
+  ULONG BytesRead;
+  ULONG BytesToRead;
+  ULONG BytesWritten;
+  ULONG BytesSkipped;
+  ULONG BytesToWrite;
+  ULONG TotalBytesRead;
+  ULONG CurrentOffset;
+  PUCHAR Buffer;
+  PUCHAR CurrentBuffer;
+  HANDLE DestFile;
+  PCFFILE_NODE File;
+  CFDATA CFData;
+  ULONG Status;
+  BOOL Skip;
+  FILETIME FileTime;
+  WCHAR DestName[MAX_PATH];
+  WCHAR TempName[MAX_PATH];
+  NTSTATUS NtStatus;
+  UNICODE_STRING UnicodeString;
+  IO_STATUS_BLOCK IoStatusBlock;
+  OBJECT_ATTRIBUTES ObjectAttributes;
+  FILE_BASIC_INFORMATION FileBasic;
+
+  Status = LocateFile(FileName, &File);
+  if (Status != CAB_STATUS_SUCCESS)
+    {
+      DPRINT("Cannot locate file (%d).\n", (UINT)Status);
+      return Status;
+    }
+
+  LastFileOffset = File->File.FileOffset;
+  
+  switch (CurrentFolderNode->Folder.CompressionType & CAB_COMP_MASK)
+    {
+      case CAB_COMP_NONE:
+        CabinetSelectCodec(CAB_CODEC_RAW);
+        break;
+      case CAB_COMP_MSZIP:
+        CabinetSelectCodec(CAB_CODEC_MSZIP);
+        break;
+      default:
+        return CAB_STATUS_UNSUPPCOMP;
+    }
+  
+  DPRINT("Extracting file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
+    (UINT)File->File.FileOffset,
+    (UINT)File->File.FileSize,
+    (UINT)File->DataBlock->AbsoluteOffset,
+    (UINT)File->DataBlock->UncompOffset);
+
+  wcscpy(DestName, DestPath);
+  wcscat(DestName, FileName);
+  
+  /* Create destination file, fail if it already exists */
+  RtlInitUnicodeString(&UnicodeString,
+    DestName);
+
+  InitializeObjectAttributes(&ObjectAttributes,
+    &UnicodeString,
+    OBJ_CASE_INSENSITIVE,
+    NULL,
+    NULL);
+
+  NtStatus = NtCreateFile(&DestFile,
+    FILE_WRITE_ACCESS,
+    &ObjectAttributes,
+    &IoStatusBlock,
+    NULL,
+    FILE_ATTRIBUTE_NORMAL,
+    0,
+    FILE_CREATE,
+    FILE_SYNCHRONOUS_IO_ALERT,
+    NULL,
+    0);
+  if (!NT_SUCCESS(NtStatus))
+    {
+      DPRINT("NtCreateFile() failed (%S) (%x).\n", DestName, NtStatus);
+
+      /* If file exists, ask to overwrite file */
+      if (OverwriteHandler == NULL || OverwriteHandler(&File->File, FileName))
+        {
+          /* Create destination file, overwrite if it already exists */
+          NtStatus = NtCreateFile(&DestFile,
+            FILE_WRITE_ACCESS,
+            &ObjectAttributes,
+            &IoStatusBlock,
+            NULL,
+            FILE_ATTRIBUTE_NORMAL,
+            0,
+            FILE_OVERWRITE,
+            FILE_SYNCHRONOUS_IO_ALERT,
+            NULL,
+            0);
+          if (!NT_SUCCESS(NtStatus))
+            {
+              DPRINT("NtCreateFile() failed 2 (%S) (%x).\n", DestName, NtStatus);
+              return CAB_STATUS_CANNOT_CREATE;
+            }
+        }
+      else
+        {
+          DPRINT("File (%S) exists.\n", DestName);
+          return CAB_STATUS_FILE_EXISTS;
+        }
+    }
+  
+  if (!ConvertDosDateTimeToFileTime(File->File.FileDate, File->File.FileTime, &FileTime))
+    {
+      NtClose(DestFile);
+      DPRINT("DosDateTimeToFileTime() failed.\n");
+      return CAB_STATUS_CANNOT_WRITE;
+    }
+
+  NtStatus = NtQueryInformationFile(DestFile,
+    &IoStatusBlock,
+    &FileBasic,
+    sizeof(FILE_BASIC_INFORMATION),
+    FileBasicInformation);
+  if (!NT_SUCCESS(Status))
+    {
+      DPRINT("NtQueryInformationFile() failed (%x).\n", NtStatus);
+    }
+  else
+    {
+      memcpy(&FileBasic.LastAccessTime, &FileTime, sizeof(FILETIME));
+
+      NtStatus = NtSetInformationFile(DestFile,
+        &IoStatusBlock,
+        &FileBasic,
+        sizeof(FILE_BASIC_INFORMATION),
+        FileBasicInformation);
+      if (!NT_SUCCESS(NtStatus))
+        {
+          DPRINT("NtSetInformationFile() failed (%x).\n", NtStatus);
+        }
+    }
+
+  SetAttributesOnFile(File, DestFile);
+  
+  Buffer = RtlAllocateHeap(ProcessHeap, 0, CAB_BLOCKSIZE + 12); // This should be enough
+  if (!Buffer)
+    {
+      NtClose(DestFile);
+      DPRINT("Insufficient memory.\n");
+      return CAB_STATUS_NOMEMORY; 
+    }
+  
+  /* Call extract event handler */
+  if (ExtractHandler != NULL)
+    {
+      ExtractHandler(&File->File, FileName);
+    }
+  
+  /* Search to start of file */
+  Offset = SeekInFile(FileHandle,
+    File->DataBlock->AbsoluteOffset,
+    NULL,
+    SEEK_BEGIN,
+    &NtStatus);
+  if (!NT_SUCCESS(NtStatus))
+    {
+      DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+      return CAB_STATUS_INVALID_CAB;
+    }
+  
+  Size   = File->File.FileSize;
+  Offset = File->File.FileOffset;
+  CurrentOffset = File->DataBlock->UncompOffset;
+  
+  Skip = TRUE;
+  
+  ReuseBlock = (CurrentDataNode == File->DataBlock);
+  if (Size > 0)
+    {
+      do
+        {
+         DPRINT("CO (0x%X)    ReuseBlock (%d)    Offset (0x%X)   Size (%d)  BytesLeftInBlock (%d)\n",
+                 File->DataBlock->UncompOffset, (UINT)ReuseBlock, Offset, Size,
+                 BytesLeftInBlock);
+      
+         if (/*(CurrentDataNode != File->DataBlock) &&*/ (!ReuseBlock) || (BytesLeftInBlock <= 0))
+            {
+                   DPRINT("Filling buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock);
+      
+              CurrentBuffer  = Buffer;
+              TotalBytesRead = 0;
+              do
+                {
+                  DPRINT("Size (%d bytes).\n", Size);
+  
+                  if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
+                    CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
+                    {
+                      NtClose(DestFile);
+                      RtlFreeHeap(ProcessHeap, 0, Buffer);
+                      DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+                      return CAB_STATUS_INVALID_CAB;
+                    }
+      
+                  DPRINT("Data block: Checksum (0x%X)  CompSize (%d bytes)  UncompSize (%d bytes)  Offset (0x%X).\n",
+                    (UINT)CFData.Checksum,
+                    (UINT)CFData.CompSize,
+                    (UINT)CFData.UncompSize,
+                                     (UINT)SeekInFile(FileHandle, 0, NULL, SEEK_CURRENT, &NtStatus));
+    
+                  //ASSERT(CFData.CompSize <= CAB_BLOCKSIZE + 12);
+    
+                  BytesToRead = CFData.CompSize;
+    
+                               DPRINT("Read: (0x%X,0x%X).\n",
+                                       CurrentBuffer, Buffer);
+
+                  if (((Status = ReadBlock(CurrentBuffer, BytesToRead, &BytesRead)) != 
+                      CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead))
+                    {
+                      NtClose(DestFile);
+                      RtlFreeHeap(ProcessHeap, 0, Buffer);
+                      DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+                      return CAB_STATUS_INVALID_CAB;
+                    }
+
+                    /* FIXME: Does not work with files generated by makecab.exe */
+/*
+                  if (CFData.Checksum != 0)
+                    {
+                      ULONG Checksum = ComputeChecksum(CurrentBuffer, BytesRead, 0);
+                      if (Checksum != CFData.Checksum)
+                        {
+                          NtClose(DestFile);
+                          RtlFreeHeap(ProcessHeap, 0, Buffer);
+                          DPRINT("Bad checksum (is 0x%X, should be 0x%X).\n",
+                            Checksum, CFData.Checksum);
+                          return CAB_STATUS_INVALID_CAB;
+                        }
+                    }
+*/
+                  TotalBytesRead += BytesRead;
+    
+                  CurrentBuffer += BytesRead;
+    
+                  if (CFData.UncompSize == 0)
+                    {
+                      if (wcslen(DiskNext) == 0)
+                          return CAB_STATUS_NOFILE;
+
+                      /* CloseCabinet() will destroy all file entries so in case
+                         FileName refers to the FileName field of a CFFOLDER_NODE
+                         structure, we have to save a copy of the filename */
+                      wcscpy(TempName, FileName);
+  
+                      CloseCabinet();
+  
+                      CabinetSetCabinetName(CabinetNext);
+  
+                      if (DiskChangeHandler != NULL)
+                        {
+                          DiskChangeHandler(CabinetNext, DiskNext);
+                        }
+  
+                      Status = CabinetOpen();
+                      if (Status != CAB_STATUS_SUCCESS) 
+                        return Status;
+  
+                      /* The first data block of the file will not be
+                         found as it is located in the previous file */
+                      Status = LocateFile(TempName, &File);
+                      if (Status == CAB_STATUS_NOFILE)
+                        {
+                          DPRINT("Cannot locate file (%d).\n", (UINT)Status);
+                          return Status;
+                        }
+    
+                      /* The file is continued in the first data block in the folder */
+                      File->DataBlock = CurrentFolderNode->DataListHead;
+  
+                      /* Search to start of file */
+                      SeekInFile(FileHandle,
+                        File->DataBlock->AbsoluteOffset,
+                        NULL,
+                        SEEK_BEGIN,
+                        &NtStatus);
+                      if (!NT_SUCCESS(NtStatus))
+                        {
+                          DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+                          return CAB_STATUS_INVALID_CAB;
+                        }
+    
+                      DPRINT("Continuing extraction of file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
+                          (UINT)File->File.FileOffset,
+                          (UINT)File->File.FileSize,
+                          (UINT)File->DataBlock->AbsoluteOffset,
+                          (UINT)File->DataBlock->UncompOffset);
+    
+                      CurrentDataNode = File->DataBlock;
+                      ReuseBlock = TRUE;
+
+                      RestartSearch = TRUE;
+                    }
+                } while (CFData.UncompSize == 0);
+
+              DPRINT("TotalBytesRead (%d).\n", TotalBytesRead);
+  
+              Status = CodecUncompress(OutputBuffer, Buffer, TotalBytesRead, &BytesToWrite);
+              if (Status != CS_SUCCESS)
+                {
+                  NtClose(DestFile);
+                  RtlFreeHeap(ProcessHeap, 0, Buffer);
+                  DPRINT("Cannot uncompress block.\n");
+                  if (Status == CS_NOMEMORY)
+                    return CAB_STATUS_NOMEMORY;
+                  return CAB_STATUS_INVALID_CAB;
+                }
+
+              if (BytesToWrite != CFData.UncompSize)
+                {
+                  DPRINT("BytesToWrite (%d) != CFData.UncompSize (%d)\n",
+                    BytesToWrite, CFData.UncompSize);
+                  return CAB_STATUS_INVALID_CAB;
+                }
+
+              BytesLeftInBlock = BytesToWrite;
+            }
+          else
+            {
+              DPRINT("Using same buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock);
+    
+              BytesToWrite = BytesLeftInBlock;
+    
+                       DPRINT("Seeking to absolute offset 0x%X.\n",
+                               CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
+                  CurrentDataNode->Data.CompSize);
+    
+                       if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
+                               CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
+                {
+                               NtClose(DestFile);
+                  RtlFreeHeap(ProcessHeap, 0, Buffer);
+                               DPRINT("Cannot read from file (%d).\n", (UINT)Status);
+                               return CAB_STATUS_INVALID_CAB;
+                       }
+    
+                       DPRINT("CFData.CompSize 0x%X  CFData.UncompSize 0x%X.\n",
+                               CFData.CompSize, CFData.UncompSize);
+    
+              /* Go to next data block */
+              SeekInFile(FileHandle,
+                CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
+                CurrentDataNode->Data.CompSize,
+                NULL,
+                SEEK_BEGIN,
+                &NtStatus);
+              if (!NT_SUCCESS(NtStatus))
+                {
+                  DPRINT("SeekInFile() failed (%x).\n", NtStatus);
+                  return CAB_STATUS_INVALID_CAB;
+                }
+
+              ReuseBlock = FALSE;
+            }
+
+          if (Skip)
+              BytesSkipped = (Offset - CurrentOffset);
+          else
+              BytesSkipped = 0;
+  
+             BytesToWrite -= BytesSkipped;
+  
+               if (Size < BytesToWrite)
+            BytesToWrite = Size;
+
+          DPRINT("Offset (0x%X)  CurrentOffset (0x%X)  ToWrite (%d)  Skipped (%d)(%d)  Size (%d).\n",
+            (UINT)Offset,
+            (UINT)CurrentOffset,
+            (UINT)BytesToWrite,
+            (UINT)BytesSkipped, (UINT)Skip,
+            (UINT)Size);
+  
+//          if (!WriteFile(DestFile, (PVOID)((ULONG)OutputBuffer + BytesSkipped),
+//            BytesToWrite, &BytesWritten, NULL) ||
+//              (BytesToWrite != BytesWritten))
+
+          NtStatus = NtWriteFile(DestFile,
+            NULL,
+            NULL,
+            NULL,
+            &IoStatusBlock,
+            (PVOID)((ULONG)OutputBuffer + BytesSkipped), 
+            BytesToWrite,
+            NULL,
+            NULL);
+          BytesWritten = BytesToWrite;
+          if (!NT_SUCCESS(NtStatus))
+            {
+                   DPRINT("Status 0x%X.\n", NtStatus);
+  
+              NtClose(DestFile);
+              RtlFreeHeap(ProcessHeap, 0, Buffer);
+              DPRINT("Cannot write to file.\n");
+              return CAB_STATUS_CANNOT_WRITE;
+            }
+          Size -= BytesToWrite;
+  
+           CurrentOffset += BytesToWrite;
+
+          /* Don't skip any more bytes */
+          Skip = FALSE;
+        } while (Size > 0);
+    }
+
+  NtClose(DestFile);
+
+  RtlFreeHeap(ProcessHeap, 0, Buffer);
+
+  return CAB_STATUS_SUCCESS;
+}
+
+
+VOID
+CabinetSelectCodec(ULONG Id)
+/*
+ * FUNCTION: Selects codec engine to use
+ * ARGUMENTS:
+ *     Id = Codec identifier
+ */
+{
+  if (CodecSelected)
+    {
+      if (Id == CodecId)
+        return;
+
+      CodecSelected = FALSE;
+    }
+
+  switch (Id)
+    {
+      case CAB_CODEC_RAW:
+        CodecUncompress = RawCodecUncompress;
+        break;
+      case CAB_CODEC_MSZIP:
+        CodecUncompress = MSZipCodecUncompress;
+        break;
+      default:
+        return;
+    }
+
+  CodecId = Id;
+  CodecSelected = TRUE;
+}
+
+
+VOID
+CabinetSetEventHandlers(PCABINET_OVERWRITE Overwrite,
+  PCABINET_EXTRACT Extract,
+  PCABINET_DISK_CHANGE DiskChange)
+/*
+ * FUNCTION: Set event handlers
+ * ARGUMENTS:
+ *     Overwrite = Handler called when a file is to be overwritten
+ *     Extract = Handler called when a file is to be extracted
+ *     DiskChange = Handler called when changing the disk
+ */
+{
+  OverwriteHandler = Overwrite;
+  ExtractHandler = Extract;
+  DiskChangeHandler = DiskChange;
+}
+
+
+PVOID
+CabinetGetCabinetReservedArea(PULONG Size)
+/*
+ * FUNCTION: Get pointer to cabinet reserved area. NULL if none
+ */
+{
+  if (CabinetReservedArea != NULL)
+    {
+      if (Size != NULL)
+        {
+          *Size = CabinetReserved;
+        }
+      return CabinetReservedArea;
+    }
+  else
+    {
+      if (Size != NULL)
+        {
+          *Size = 0;
+        }
+      return NULL;
+    }
+}