#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "..\..\..\ExDLL\exdll.h"

int DoPatch(HANDLE hPatch, HANDLE hSource, HANDLE hDest);
void strcopy(char *tgt, const char *src);
char* chop_arg(char **args);

#define PATCH_SUCCESS    0
#define PATCH_ERROR      1
#define PATCH_CORRUPT    2
#define PATCH_NOMATCH    3
#define PATCH_UPTODATE   4
#define FILE_ERR_PATCH   5
#define FILE_ERR_SOURCE  6
#define FILE_ERR_DEST    7

HINSTANCE g_hInstance;

HWND g_hwndParent;

void __declspec(dllexport) vpatchfile(HWND hwndParent, int string_size, 
                                      char *variables, stack_t **stacktop)
{
  g_hwndParent=hwndParent;

  EXDLL_INIT();


  // note if you want parameters from the stack, pop them off in order.
  // i.e. if you are called via exdll::myFunction file.dat poop.dat
  // calling popstring() the first time would give you file.dat,
  // and the second time would give you poop.dat. 
  // you should empty the stack of your parameters, and ONLY your
  // parameters.

  // do your stuff here
  {
    static char source[1024];
    static char dest[1024];
    static char exename[1024];
    HANDLE hPatch, hSource, hDest;
    int result;

    popstring(exename);
    popstring(source);
    popstring(dest);

    hPatch = CreateFile(exename, GENERIC_READ, FILE_SHARE_READ, NULL,
                                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (hPatch == INVALID_HANDLE_VALUE) {
      pushstring("Unable to open patch file");
      return;
    }

    hSource = CreateFile(source, GENERIC_READ, FILE_SHARE_READ, NULL,
                                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (hSource == INVALID_HANDLE_VALUE) {
      CloseHandle(hPatch);
      pushstring("Unable to open source file");
      return;
    }
    
    hDest = CreateFile(dest, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (hDest == INVALID_HANDLE_VALUE) {
      CloseHandle(hPatch);
      CloseHandle(hSource);
      pushstring("Unable to open output file");
      return;
    }
        
    result = DoPatch(hPatch, hSource, hDest);

    CloseHandle(hDest);
    CloseHandle(hSource);
    CloseHandle(hPatch);

    if ((result != PATCH_SUCCESS)) {
      if (result == PATCH_ERROR)
        pushstring("An error occured while patching");
      else if (result == PATCH_CORRUPT)
        pushstring("Patch data is invalid or corrupt");
      else if (result == PATCH_NOMATCH)
        pushstring("No suitable patches were found");
      else if (result == PATCH_UPTODATE)
        pushstring("OK, new version already installed");
      DeleteFile(dest);
    } else {
      pushstring("OK");
    }

    return;
  }
}



BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
{
  g_hInstance=hInst;
  return TRUE;
}



UINT CRCTable[256];
BOOL bInitCRC = FALSE;

_inline void InitCRC() {
  int i, j; unsigned long c;
  for (c = i = 0; i < 256; c = ++i) {
    for (j = 0; j < 8; j++) {
      if (c & 1) c = (c>>1) ^ 0xEDB88320;
      else       c >>= 1;
    }
    CRCTable[i] = c;
  }
  bInitCRC = TRUE;
}

#define CRCBLOCKSIZE    4096

BOOL FileCRC(HANDLE hFile, DWORD *crc) {
  static BYTE crcblock[CRCBLOCKSIZE];
  DWORD read;
  BYTE *p;

  UINT c = 0xFFFFFFFF;
  if (bInitCRC == FALSE)
    InitCRC();
  
  SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  do {
    if (ReadFile(hFile, crcblock, CRCBLOCKSIZE, &read, NULL) == FALSE)
      return FALSE;
    for (p = crcblock; p < crcblock + read; p++)
      c = CRCTable[(c & 0xFF) ^ *p] ^ (c >> 8);
  } while (read);

  *crc = (c ^ 0xFFFFFFFF);

  return TRUE;
}

#define BLOCKSIZE    16384

int DoPatch(HANDLE hPatch, HANDLE hSource, HANDLE hDest) {

  static char block[BLOCKSIZE];
    
  unsigned long temp = 0;
  unsigned long read;
  unsigned long source_crc = 0;
  unsigned long patch_dest_crc = 0;
  long patches = 0;
  int already_uptodate = 0;
  // special 'addition' for the dll: since the patch file is now
  // in a seperate file, the VPAT header might be right at the start
  // of the file, and a pointer at the end of the file is probably missing
  // (because all patch generator versions don't append it, the linker/gui
  //  does this).
  SetFilePointer(hPatch, 0, NULL, FILE_BEGIN);
  ReadFile(hPatch, &temp, 4, &read, NULL);
  // it's not at the start of file -> there must be a pointer at the end of
  // file then
  if (temp != 0x54415056) {
    SetFilePointer(hPatch, -4, NULL, FILE_END);
    ReadFile(hPatch, &temp, 4, &read, NULL);

    SetFilePointer(hPatch, temp, NULL, FILE_BEGIN);
    ReadFile(hPatch, &temp, 4, &read, NULL);
    if (temp != 0x54415056)
      return PATCH_CORRUPT;
  }

  if (!FileCRC(hSource, &source_crc))
    return PATCH_ERROR;

    
  ReadFile(hPatch, &patches, 4, &read, NULL);

  while (patches--) {
    long patch_blocks = 0, patch_size = 0;
    unsigned long patch_source_crc = 0;

    ReadFile(hPatch, &patch_blocks, 4, &read, NULL);
    ReadFile(hPatch, &patch_source_crc, 4, &read, NULL);
    ReadFile(hPatch, &patch_dest_crc, 4, &read, NULL);
    ReadFile(hPatch, &patch_size, 4, &read, NULL);
    
    //added by Koen - check to see if it's already up-to-date for some patch (so
    //we can tell NSIS this isn't an error, but we already have the latest version)
    if (source_crc == patch_dest_crc) {
      already_uptodate = 1;
    }

    if (source_crc == patch_source_crc) {
      while (patch_blocks--) {
        unsigned char blocktype = 0;
        unsigned long blocksize = 0;
        ReadFile(hPatch, &blocktype, 1, &read, NULL);

        switch (blocktype) {
        case 1:
        case 2:
        case 3:
          if (blocktype == 1)
            { unsigned char x; blocksize = ReadFile(hPatch,&x,1,&read,NULL)? x:0; }
          else if (blocktype == 2)
            { unsigned short x; blocksize = ReadFile(hPatch,&x,2,&read,NULL)? x:0; }
          else
            { unsigned long x; blocksize = ReadFile(hPatch,&x,4,&read,NULL)? x:0; }

          if (!blocksize || !ReadFile(hPatch, &temp, 4, &read, NULL) || read != 4)
            return PATCH_CORRUPT;
    
          SetFilePointer(hSource, temp, 0, FILE_BEGIN);

          do {
            ReadFile(hSource, block, min(BLOCKSIZE, blocksize), &read, NULL);
            WriteFile(hDest, block, read, &temp, NULL);
            if (temp != min(BLOCKSIZE, blocksize))
              return PATCH_ERROR;
            blocksize -= temp;
          } while (temp);

          break;

        case 5:
        case 6:
        case 7:
          if (blocktype == 5)
            { unsigned char x; blocksize = ReadFile(hPatch,&x,1,&read,NULL)? x:0; }
          else if (blocktype == 6)
            { unsigned short x; blocksize = ReadFile(hPatch,&x,2,&read,NULL)? x:0; }
          else
            { unsigned long x; blocksize = ReadFile(hPatch,&x,4,&read,NULL)? x:0; }

          if (!blocksize)
            return PATCH_CORRUPT;
          
          do {
            ReadFile(hPatch, block, min(BLOCKSIZE, blocksize), &read, NULL);
            WriteFile(hDest, block, read, &temp, NULL);
            if (temp != min(BLOCKSIZE, blocksize))
              return PATCH_ERROR;
            blocksize -= temp;
          } while (temp);

          break;

        default:
          return PATCH_CORRUPT;
        }
      }
      {
        unsigned long dest_crc = 0;
        FileCRC(hDest, &dest_crc);
        if (dest_crc != patch_dest_crc)
          return PATCH_ERROR;

        return PATCH_SUCCESS;
      }
    } else {
      SetFilePointer(hPatch, patch_size, NULL, FILE_CURRENT);
    }
  }
  
  //added by Koen - if already up to date, it doesn't matter that we didn't match
  if(already_uptodate) {
    return PATCH_UPTODATE;  
  } else {
    return PATCH_NOMATCH;
  }
}
