// BoBCoBUtils
// Utility functions for BoBCoB

#include <windows.h>
#include <shlobj.h>
#include <stdio.h>
#include "resource.h"

//#include "zlib.h"

typedef void* gzFile;

typedef gzFile (WINAPI* GZOPENFUNC)(const char *, const char *);
typedef int (WINAPI* GZWRITEFUNC)(gzFile, const void *, unsigned);
typedef int (WINAPI* GZCLOSEFUNC)(gzFile);

#define BUFLEN      16384

typedef struct
{
	BITMAPINFOHEADER bmiHeader;
	RGBQUAD bmiColors[256];
} BITMAPINFO_8BIT;

typedef struct
{
	BITMAPV4HEADER bmiHeader;
	RGBQUAD bmiColors[1];
} BITMAPINFO_16BIT;

#define imageTypeSPR 1
#define imageTypeS16_555 2
#define imageTypeS16_565 3
#define imageTypeBMP24 4

const LONG LAST_MATCH_THRESHOLD = 8;

RGBQUAD pCreaturesPalette[256];
HINSTANCE g_hZLibDLL = NULL;

GZOPENFUNC gzopen = NULL;
GZWRITEFUNC gzwrite = NULL;
GZCLOSEFUNC gzclose = NULL;

LPMALLOC g_pMalloc = NULL;
LPSHELLFOLDER g_pShellFolder = NULL;
LPITEMIDLIST g_pidlBrowseStartFolder = NULL;

HANDLE g_hClipboardData = NULL;
UINT g_nClipboardFormat = 0;
LONG g_nClipboardDataPosition = 0;

BITMAPINFO *PrepareBitmapInfo(CONST BYTE nImageType, LONG nImageWidth, LONG nImageHeight)
{
	BITMAPINFO *pBitmapInfo;
	BITMAPV4HEADER *pBitmapInfoHeader;

	switch (nImageType)
	{
		case imageTypeSPR:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO_8BIT));
			pBitmapInfo->bmiHeader.biBitCount = 8;
			CopyMemory(((BITMAPINFO_8BIT *)pBitmapInfo)->bmiColors, pCreaturesPalette, sizeof(RGBQUAD) * 256);
			pBitmapInfo->bmiHeader.biCompression = BI_RGB;
			pBitmapInfo->bmiHeader.biClrUsed = 256;
			pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
			break;

		case imageTypeS16_555:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO_16BIT));
			pBitmapInfoHeader = (LPBITMAPV4HEADER)&pBitmapInfo->bmiHeader;
			pBitmapInfoHeader->bV4BitCount = 16;
			pBitmapInfoHeader->bV4RedMask = 0x7C00;
			pBitmapInfoHeader->bV4GreenMask = 0x03E0;
			pBitmapInfoHeader->bV4BlueMask = 0x001F;
			pBitmapInfoHeader->bV4V4Compression = BI_BITFIELDS;
			pBitmapInfoHeader->bV4ClrUsed = 0;
			pBitmapInfoHeader->bV4AlphaMask = 0;
			pBitmapInfoHeader->bV4CSType = 0;
			pBitmapInfoHeader->bV4Size = sizeof(BITMAPV4HEADER);
			break;

		case imageTypeS16_565:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO_16BIT));
			pBitmapInfoHeader = (LPBITMAPV4HEADER)&pBitmapInfo->bmiHeader;
			pBitmapInfoHeader->bV4BitCount = 16;
			pBitmapInfoHeader->bV4RedMask = 0xF800;
			pBitmapInfoHeader->bV4GreenMask = 0x07E0;
			pBitmapInfoHeader->bV4BlueMask = 0x001F;
			pBitmapInfoHeader->bV4V4Compression = BI_BITFIELDS;
			pBitmapInfoHeader->bV4ClrUsed = 0;
			pBitmapInfoHeader->bV4AlphaMask = 0;
			pBitmapInfoHeader->bV4CSType = 0;
			pBitmapInfoHeader->bV4Size = sizeof(BITMAPV4HEADER);
			break;
	}

	pBitmapInfo->bmiHeader.biWidth = nImageWidth;
	pBitmapInfo->bmiHeader.biHeight = -nImageHeight;
	pBitmapInfo->bmiHeader.biPlanes = 1;
	pBitmapInfo->bmiHeader.biXPelsPerMeter = 3000;
	pBitmapInfo->bmiHeader.biYPelsPerMeter = 3000;
	pBitmapInfo->bmiHeader.biClrImportant = 0;

	return pBitmapInfo;
}

BYTE *PrepareBitmap(CONST BYTE nImageType, BITMAPINFO *pBitmapInfo, BYTE *pPixels)
{
	DWORD nBytesPerLine;
	DWORD nPaddedBytesPerLine;
	BYTE *pBitmap;

	switch (nImageType)
	{
		case imageTypeSPR:
			nBytesPerLine = pBitmapInfo->bmiHeader.biWidth;
			break;

		case imageTypeS16_555:
		case imageTypeS16_565:
			nBytesPerLine = pBitmapInfo->bmiHeader.biWidth * 2;
			break;
	}

	if (nBytesPerLine % 4)
		nPaddedBytesPerLine = nBytesPerLine + 4 - (nBytesPerLine % 4);
	else
		nPaddedBytesPerLine = nBytesPerLine;

	pBitmapInfo->bmiHeader.biSizeImage = nPaddedBytesPerLine * -pBitmapInfo->bmiHeader.biHeight;

	pBitmap = (BYTE *)calloc(1, pBitmapInfo->bmiHeader.biSizeImage);

	if (nPaddedBytesPerLine != nBytesPerLine)
	{
		LONG nLine;

		for (nLine = 0; nLine < -pBitmapInfo->bmiHeader.biHeight; nLine++)
			CopyMemory(pBitmap + nLine * nPaddedBytesPerLine, pPixels + nLine * nBytesPerLine, nBytesPerLine);
	}
	else
	{
		CopyMemory(pBitmap, pPixels, pBitmapInfo->bmiHeader.biSizeImage);
	}

	return pBitmap;
}

VOID WINAPI DrawBitmapXY(HDC hDC, INT destX, INT destY, CONST BYTE nImageType, LONG nImageWidth, LONG nImageHeight, BYTE *pPixels)
{
	BITMAPINFO *pBitmapInfo;
	DWORD nLastError;
	BYTE *pBitmap;

	pBitmapInfo = PrepareBitmapInfo(nImageType, nImageWidth, nImageHeight);
	pBitmap = PrepareBitmap(nImageType, pBitmapInfo, pPixels);

	if (!SetDIBitsToDevice(hDC, destX, destY, nImageWidth, nImageHeight, 0, 0, 0, nImageHeight, pBitmap, pBitmapInfo, DIB_RGB_COLORS))
	{
		char sError[256];

		nLastError = GetLastError();
		sprintf(sError, "An error occurred while calling SetDIBitsToDevice: %lu", nLastError);
		MessageBox(NULL, sError, "BoBCoBUtils", MB_OK | MB_ICONEXCLAMATION);
	}

	free(pBitmap);
	free(pBitmapInfo);
}

VOID WINAPI DrawBitmapXYScale(HDC hDC, INT destX, INT destY, INT destWidth, INT destHeight, CONST BYTE nImageType, LONG nImageWidth, LONG nImageHeight, BYTE *pPixels)
{
	BITMAPINFO *pBitmapInfo;
	DWORD nLastError;
	BYTE *pBitmap;

	pBitmapInfo = PrepareBitmapInfo(nImageType, nImageWidth, nImageHeight);
	pBitmap = PrepareBitmap(nImageType, pBitmapInfo, pPixels);

	if (!StretchDIBits(hDC, destX, destY, destWidth, destHeight, 0, 0, nImageWidth, nImageHeight, pBitmap, pBitmapInfo, DIB_RGB_COLORS, SRCCOPY))
	{
		char sError[256];

		nLastError = GetLastError();
		sprintf(sError, "An error occurred while calling StretchDIBits: %lu", nLastError);
		MessageBox(NULL, sError, "BoBCoBUtils", MB_OK | MB_ICONEXCLAMATION);
	}

	free(pBitmap);
	free(pBitmapInfo);
}

BYTE WINAPI MatchRGB(LONG nRGB)
{
    BYTE i, nBestIndex, nRed, nGreen, nBlue;
    LONG nDistance, nBestDistance;
	static BYTE nLastIndex = 0, nLastRed = 0, nLastGreen = 0, nLastBlue = 0;
	RGBQUAD *pPalEntry;
    
    nRed = nRGB & 0xFF;
    nGreen = (nRGB >> 8) & 0xFF;
    nBlue = (nRGB >> 16) & 0xFF;
    
    if (nLastIndex != 0)
	{
        if (nRed != 0 || nGreen != 0 || nBlue != 0)
		{
            nDistance = abs(nLastRed - nRed) + abs(nLastGreen - nGreen) + abs(nLastBlue - nBlue) / 3;
            if (nDistance < LAST_MATCH_THRESHOLD)
                return nLastIndex;
        }
		else
		{
            return 0;
		}
    }
    
    nBestDistance = 768;
    nBestIndex = -1;
       
    for (i = 11; i <= 242; i++)
	{
		pPalEntry = &pCreaturesPalette[i];
        nDistance = (abs(pPalEntry->rgbRed - nRed) + abs(pPalEntry->rgbGreen - nGreen) + abs(pPalEntry->rgbBlue - nBlue)) / 3;
        if (nDistance == 0)
		{
            nBestIndex = i;
			break;
		}
        else if (nDistance < nBestDistance)
		{
            nBestDistance = nDistance;
            nBestIndex = i;
		}
    }
    
    nLastRed = nRed;
    nLastGreen = nGreen;
    nLastBlue = nBlue;
    nLastIndex = nBestIndex;

    return nBestIndex;
}

VOID ConvertImageSPR2S16(LONG nImageWidth, LONG nImageHeight, CONST BYTE nS16Type, BYTE *pSourcePixels, BYTE *pDestPixels)
{
	BYTE nRedShift, nGreenShift, nBlueShift, nRedMask, nGreenMask, nBlueMask;
	LONG nOffset, x, y;
	WORD *pDestPixels2;

	pDestPixels2 = (WORD *)pDestPixels;

	switch (nS16Type)
	{
		case imageTypeS16_555:
			nRedShift = 7;
			nGreenShift = 2;
			nBlueShift = 3;
			nRedMask = 0xF8;
			nGreenMask = 0xF8;
			nBlueMask = 0xF8;
			break;

		case imageTypeS16_565:
			nRedShift = 8;
			nGreenShift = 3;
			nBlueShift = 3;
			nRedMask = 0xF8;
			nGreenMask = 0xFC;
			nBlueMask = 0xF8;
			break;
	}

	for (nOffset = 0, y = 0; y < nImageHeight; y++)
	{
		nOffset = y * nImageWidth;

		for (x = 0; x < nImageWidth; x++, nOffset++)
		{
			BYTE nSourcePixel;
			WORD nRed, nGreen, nBlue;

			nSourcePixel = pSourcePixels[nOffset];
			nRed = (WORD)(pCreaturesPalette[nSourcePixel].rgbRed & nRedMask) << nRedShift;
			nGreen = (WORD)(pCreaturesPalette[nSourcePixel].rgbGreen & nGreenMask) << nGreenShift;
			nBlue = (WORD)(pCreaturesPalette[nSourcePixel].rgbBlue & nBlueMask) >> nBlueShift;
			pDestPixels2[nOffset] = nRed | nGreen | nBlue;
		}
	}
}

VOID ConvertImageS162SPR(LONG nImageWidth, LONG nImageHeight, CONST BYTE nS16Type, BYTE *pSourcePixels, BYTE *pDestPixels)
{
	BYTE nRedShift, nGreenShift, nBlueShift, nRedMask, nGreenMask, nBlueMask;
	LONG nOffset, x, y;
	WORD *pSourcePixels2;

	pSourcePixels2 = (WORD *)pSourcePixels;

	switch (nS16Type)
	{
		case imageTypeS16_555:
			nRedShift = 7;
			nGreenShift = 2;
			nBlueShift = 3;
			nRedMask = 0xF8;
			nGreenMask = 0xF8;
			nBlueMask = 0xF8;
			break;

		case imageTypeS16_565:
			nRedShift = 8;
			nGreenShift = 3;
			nBlueShift = 3;
			nRedMask = 0xF8;
			nGreenMask = 0xFC;
			nBlueMask = 0xF8;
			break;
	}

	for (nOffset = 0, y = 0; y < nImageHeight; y++)
	{
		nOffset = y * nImageWidth;

		for (x = 0; x < nImageWidth; x++, nOffset++)
		{
			WORD nSourcePixel;
			LONG nRed, nGreen, nBlue;

			nSourcePixel = pSourcePixels2[nOffset];
			nRed = (nSourcePixel >> nRedShift) & nRedMask;
			nGreen = (nSourcePixel >> nGreenShift) & nGreenMask;
			nBlue = (nSourcePixel << nBlueShift) & nBlueMask;
			pDestPixels[nOffset] = MatchRGB((nBlue << 16) | (nGreen << 8) | nRed);
		}
	}
}

VOID ConvertImageS162S16(LONG nImageWidth, LONG nImageHeight, CONST BYTE nSourceS16Type, BYTE *pSourcePixels, CONST BYTE nDestS16Type, BYTE *pDestPixels)
{
	BYTE nSourceRedShift, nSourceGreenShift, nSourceBlueShift, nSourceRedMask, nSourceGreenMask, nSourceBlueMask;
	BYTE nDestRedShift, nDestGreenShift, nDestBlueShift, nDestRedMask, nDestGreenMask, nDestBlueMask;
	LONG nOffset, x, y;
	WORD *pSourcePixels2;
	WORD *pDestPixels2;

	pSourcePixels2 = (WORD *)pSourcePixels;
	pDestPixels2 = (WORD *)pDestPixels;

	switch (nSourceS16Type)
	{
		case imageTypeS16_555:
			nSourceRedShift = 7;
			nSourceGreenShift = 2;
			nSourceBlueShift = 3;
			nSourceRedMask = 0xF8;
			nSourceGreenMask = 0xF8;
			nSourceBlueMask = 0xF8;
			break;

		case imageTypeS16_565:
			nSourceRedShift = 8;
			nSourceGreenShift = 3;
			nSourceBlueShift = 3;
			nSourceRedMask = 0xF8;
			nSourceGreenMask = 0xFC;
			nSourceBlueMask = 0xF8;
			break;
	}

	switch (nDestS16Type)
	{
		case imageTypeS16_555:
			nDestRedShift = 7;
			nDestGreenShift = 2;
			nDestBlueShift = 3;
			nDestRedMask = 0xF8;
			nDestGreenMask = 0xF8;
			nDestBlueMask = 0xF8;
			break;

		case imageTypeS16_565:
			nDestRedShift = 8;
			nDestGreenShift = 3;
			nDestBlueShift = 3;
			nDestRedMask = 0xF8;
			nDestGreenMask = 0xFC;
			nDestBlueMask = 0xF8;
			break;
	}

	for (nOffset = 0, y = 0; y < nImageHeight; y++)
	{
		nOffset = y * nImageWidth;

		for (x = 0; x < nImageWidth; x++, nOffset++)
		{
			WORD nSourcePixel;
			LONG nRed, nGreen, nBlue;

			nSourcePixel = pSourcePixels2[nOffset];
			nRed = (nSourcePixel >> nSourceRedShift) & nSourceRedMask;
			nGreen = (nSourcePixel >> nSourceGreenShift) & nSourceGreenMask;
			nBlue = (nSourcePixel << nSourceBlueShift) & nSourceBlueMask;
			pDestPixels2[nOffset] = ((nRed & nDestRedMask) << nDestRedShift) | ((nGreen & nDestGreenMask) << nDestGreenShift) | ((nBlue & nDestBlueMask) >> nDestBlueShift);
		}
	}
}

VOID ConvertImageBMP2SPR(LONG nImageWidth, LONG nImageHeight, CONST BYTE nSourceType, BYTE *pSourcePixels, BYTE *pDestPixels)
{
	LONG x, y;
	BYTE *pSourcePixels2;
	LONG nSourceOffset, nDestOffset;
	LONG nImageBytesPerLine;

	pSourcePixels2 = (BYTE *)pSourcePixels;
    nImageBytesPerLine = nImageWidth * 3;
    if (nImageBytesPerLine % 4)
        nImageBytesPerLine = nImageBytesPerLine + 4 - (nImageBytesPerLine % 4);

	for (nSourceOffset = 0, nDestOffset, y = 0; y < nImageHeight; y++)
	{
		nSourceOffset = (nImageHeight - y - 1) * nImageBytesPerLine;
		nDestOffset = y * nImageWidth;

		for (x = 0; x < nImageWidth; x++, nSourceOffset += 3, nDestOffset++)
		{
			LONG nRed, nGreen, nBlue;

			nRed = pSourcePixels2[nSourceOffset + 2];
			nGreen = pSourcePixels2[nSourceOffset + 1];
			nBlue = pSourcePixels2[nSourceOffset];
			pDestPixels[nDestOffset] = MatchRGB((nBlue << 16) | (nGreen << 8) | nRed);
		}
	}
}

VOID ConvertImageBMP2S16(LONG nImageWidth, LONG nImageHeight, CONST BYTE nSourceType, BYTE *pSourcePixels, CONST BYTE nDestS16Type, BYTE *pDestPixels)
{
	BYTE nDestRedShift, nDestGreenShift, nDestBlueShift, nDestRedMask, nDestGreenMask, nDestBlueMask;
	LONG x, y;
	BYTE *pSourcePixels2;
	WORD *pDestPixels2;
	LONG nSourceOffset, nDestOffset;
	LONG nImageBytesPerLine;

	pSourcePixels2 = (BYTE *)pSourcePixels;
	pDestPixels2 = (WORD *)pDestPixels;
    nImageBytesPerLine = nImageWidth * 3;
    if (nImageBytesPerLine % 4)
        nImageBytesPerLine = nImageBytesPerLine + 4 - (nImageBytesPerLine % 4);

	switch (nDestS16Type)
	{
		case imageTypeS16_555:
			nDestRedShift = 7;
			nDestGreenShift = 2;
			nDestBlueShift = 3;
			nDestRedMask = 0xF8;
			nDestGreenMask = 0xF8;
			nDestBlueMask = 0xF8;
			break;

		case imageTypeS16_565:
			nDestRedShift = 8;
			nDestGreenShift = 3;
			nDestBlueShift = 3;
			nDestRedMask = 0xF8;
			nDestGreenMask = 0xFC;
			nDestBlueMask = 0xF8;
			break;
	}

	for (nSourceOffset = 0, nDestOffset, y = 0; y < nImageHeight; y++)
	{
		nSourceOffset = (nImageHeight - y - 1) * nImageBytesPerLine;
		nDestOffset = y * nImageWidth;

		for (x = 0; x < nImageWidth; x++, nSourceOffset += 3, nDestOffset++)
		{
			LONG nRed, nGreen, nBlue;

			nRed = pSourcePixels2[nSourceOffset + 2];
			nGreen = pSourcePixels2[nSourceOffset + 1];
			nBlue = pSourcePixels2[nSourceOffset];
			pDestPixels2[nDestOffset] = ((nRed & nDestRedMask) << nDestRedShift) | ((nGreen & nDestGreenMask) << nDestGreenShift) | ((nBlue & nDestBlueMask) >> nDestBlueShift);
		}
	}
}

VOID WINAPI ConvertImage(LONG nImageWidth, LONG nImageHeight, CONST BYTE nSourceType, BYTE *pSourcePixels, CONST BYTE nDestType, BYTE *pDestPixels)
{
	switch (nSourceType)
	{
		case imageTypeSPR:
			switch (nDestType)
			{
				case imageTypeSPR:
					CopyMemory(pDestPixels, pSourcePixels, nImageWidth * nImageHeight);
					break;

				case imageTypeS16_555:
					ConvertImageSPR2S16(nImageWidth, nImageHeight, imageTypeS16_555, pSourcePixels, pDestPixels);
					break;

				case imageTypeS16_565:
					ConvertImageSPR2S16(nImageWidth, nImageHeight, imageTypeS16_565, pSourcePixels, pDestPixels);
					break;
			}
			break;

		case imageTypeS16_555:
			switch (nDestType)
			{
				case imageTypeSPR:
					ConvertImageS162SPR(nImageWidth, nImageHeight, imageTypeS16_555, pSourcePixels, pDestPixels);
					break;

				case imageTypeS16_555:
					CopyMemory(pDestPixels, pSourcePixels, nImageWidth * nImageHeight * 2);
					break;

				case imageTypeS16_565:
					ConvertImageS162S16(nImageWidth, nImageHeight, imageTypeS16_555, pSourcePixels, imageTypeS16_565, pDestPixels);
			}
			break;

		case imageTypeS16_565:
			switch (nDestType)
			{
				case imageTypeSPR:
					ConvertImageS162SPR(nImageWidth, nImageHeight, imageTypeS16_565, pSourcePixels, pDestPixels);
					break;

				case imageTypeS16_555:
					ConvertImageS162S16(nImageWidth, nImageHeight, imageTypeS16_565, pSourcePixels, imageTypeS16_555, pDestPixels);
					break;

				case imageTypeS16_565:
					CopyMemory(pDestPixels, pSourcePixels, nImageWidth * nImageHeight * 2);
			}
			break;

		case imageTypeBMP24:
			switch (nDestType)
			{
				case imageTypeSPR:
					ConvertImageBMP2SPR(nImageWidth, nImageHeight, imageTypeS16_565, pSourcePixels, pDestPixels);
					break;

				case imageTypeS16_555:
					ConvertImageBMP2S16(nImageWidth, nImageHeight, imageTypeBMP24, pSourcePixels, imageTypeS16_555, pDestPixels);
					break;

				case imageTypeS16_565:
					ConvertImageBMP2S16(nImageWidth, nImageHeight, imageTypeBMP24, pSourcePixels, imageTypeS16_565, pDestPixels);
			}
	}
}

int CALLBACK BrowseForFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
	if (uMsg == BFFM_INITIALIZED)
	{
		SendMessage(hwnd, BFFM_SETSELECTION, (WPARAM)FALSE, (LPARAM)g_pidlBrowseStartFolder);
	}

	return 0;
}

LONG WINAPI BrowseForFolder(HWND hwndOwner, LPSTR sCaption, LPSTR sPath)
{
	BROWSEINFO bi;
	LPSTR lpBuffer;
    LPITEMIDLIST pidlBrowse;    // PIDL selected by user
	LONG nSuccess;
	LPOLESTR sStartPath;
	ULONG nPathProcessed;

	if (g_pMalloc == NULL)
		SHGetMalloc(&g_pMalloc);

	if (g_pShellFolder == NULL)
		SHGetDesktopFolder(&g_pShellFolder);
	
    // Allocate a buffer to receive browse information.
    if ((lpBuffer = (LPSTR)g_pMalloc->Alloc(MAX_PATH)) == NULL)
		return FALSE;

	sStartPath = new WCHAR[strlen(sPath) + 1];
	MultiByteToWideChar(CP_ACP, 0, sPath, -1, sStartPath, strlen(sPath) + 1);

	g_pShellFolder->ParseDisplayName(hwndOwner, NULL, sStartPath, &nPathProcessed, &g_pidlBrowseStartFolder, NULL);

    // Fill in the BROWSEINFO structure.
	bi.hwndOwner = hwndOwner;
    bi.pidlRoot = NULL;
	bi.pszDisplayName = lpBuffer;
    //bi.lpszTitle = "Select folder to extract files into:";
	bi.lpszTitle = sCaption;
	bi.ulFlags = BIF_RETURNONLYFSDIRS;
    bi.lpfn = &BrowseForFolderCallback;
	bi.lParam = 0; 

    nSuccess = 0;

	// Browse for a folder and return its PIDL.
    pidlBrowse = SHBrowseForFolder(&bi);
	if (pidlBrowse != NULL)
	{
		// Get the path from the PIDL return by SHBrowseForFolder
		if (SHGetPathFromIDList(pidlBrowse, lpBuffer))
			strcpy(sPath, lpBuffer);

        // Free the PIDL returned by SHBrowseForFolder.
        g_pMalloc->Free(pidlBrowse);

		nSuccess = 1;
	}
	
	// Clean up.
    g_pMalloc->Free(lpBuffer);

	return nSuccess;
}

LONG WINAPI GZipFile(LPSTR sFilename, LONG nLevel)
{
	char sOutFile[_MAX_PATH];
	char sMode[3];
	char sLevel[2];
	FILE *fileIn;
	gzFile fileOut;
    char buf[BUFLEN];
    int len;

	if (nLevel < 1 || nLevel > 9)
		return 0;

	strcpy(sMode, "wb");
	strcat(sMode, itoa(nLevel, sLevel, 10));

	strcpy(sOutFile, sFilename);
	strcat(sOutFile, ".gz");

	fileIn = fopen(sFilename, "rb");
	if (fileIn == NULL)
		return 0;

	fileOut = gzopen(sOutFile, sMode);
	if (fileOut == NULL)
		return 0;

    for (;;) {
        len = fread(buf, 1, sizeof(buf), fileIn);
        
		if (ferror(fileIn))
		{
			fclose(fileIn);
			gzclose(fileOut);
			return 0;
        }

        if (len == 0) break;

        if (gzwrite(fileOut, buf, (unsigned)len) != len)
		{
			fclose(fileIn);
			gzclose(fileOut);
			return 0;
		}
    }

    fclose(fileIn);

    if (gzclose(fileOut))
		return 0;

	return 1;
}

LONG WINAPI StartClipboardCopy(LPSTR sFormat, LONG nFormat)
{
	if (g_hClipboardData)
		if (GlobalFree(g_hClipboardData))
			return 0;

	if (sFormat)
		g_nClipboardFormat = RegisterClipboardFormat(sFormat);
	else
		g_nClipboardFormat = nFormat;

	return 1;
}

LONG WINAPI AddToClipboard(VOID *pData, DWORD nLen)
{
	VOID *pClipboardData = NULL;

	if (!g_hClipboardData)
	{
		g_hClipboardData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, nLen);

		if (!g_hClipboardData)
			return 0;

		g_nClipboardDataPosition = 0;
	}
	else
	{
		if (!GlobalReAlloc(g_hClipboardData, g_nClipboardDataPosition + nLen, GMEM_MOVEABLE))
		{
			GlobalFree(g_hClipboardData);
			g_hClipboardData = NULL;
			return 0;
		}
	}

	pClipboardData = GlobalLock(g_hClipboardData);
	if (!pClipboardData)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		return 0;
	}

	CopyMemory((BYTE *)pClipboardData + g_nClipboardDataPosition, pData, nLen);

	g_nClipboardDataPosition += nLen;

	GlobalUnlock(g_hClipboardData);

	return 1;
}

LONG WINAPI AddImageToClipboard(CONST BYTE nImageType, LONG nImageWidth, LONG nImageHeight, BYTE *pPixels)
{
	BITMAPINFO *pBitmapInfo;
	BYTE *pBitmap;
	BYTE *pClipboardData;
	DWORD nBytesPerLine;
	DWORD nPaddedBytesPerLine;
	WORD nRedMask, nGreenMask, nBlueMask;
	int nRedShift, nGreenShift, nBlueShift;
	LONG X, Y;
	WORD *pSourcePixel;
	BYTE *pDestPixel;

	if (g_hClipboardData)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
	}

	switch (nImageType)
	{
		case imageTypeSPR:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO_8BIT));
			pBitmapInfo->bmiHeader.biBitCount = 8;
			CopyMemory(((BITMAPINFO_8BIT *)pBitmapInfo)->bmiColors, pCreaturesPalette, sizeof(RGBQUAD) * 256);
			pBitmapInfo->bmiHeader.biCompression = BI_RGB;
			pBitmapInfo->bmiHeader.biClrUsed = 256;
			pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
			break;

		case imageTypeS16_555:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO));
			pBitmapInfo->bmiHeader.biBitCount = 24;
			pBitmapInfo->bmiHeader.biCompression = BI_RGB;
			pBitmapInfo->bmiHeader.biClrUsed = 0;
			pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

			nRedMask = 0xF8;
			nGreenMask = 0xF8;
			nBlueMask = 0xF8;
			nRedShift = 7;
			nGreenShift = 2;
			nBlueShift = 3;
			break;

		case imageTypeS16_565:
			pBitmapInfo = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO));
			pBitmapInfo->bmiHeader.biBitCount = 24;
			pBitmapInfo->bmiHeader.biCompression = BI_RGB;
			pBitmapInfo->bmiHeader.biClrUsed = 0;
			pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

			nRedMask = 0xF8;
			nGreenMask = 0xFC;
			nBlueMask = 0xF8;
			nRedShift = 8;
			nGreenShift = 3;
			nBlueShift = 3;
			break;
	}

	pBitmapInfo->bmiHeader.biWidth = nImageWidth;
	pBitmapInfo->bmiHeader.biHeight = nImageHeight;
	pBitmapInfo->bmiHeader.biPlanes = 1;
	pBitmapInfo->bmiHeader.biXPelsPerMeter = 3000;
	pBitmapInfo->bmiHeader.biYPelsPerMeter = 3000;
	pBitmapInfo->bmiHeader.biClrImportant = 0;

	switch (nImageType)
	{
		case imageTypeSPR:
			nBytesPerLine = pBitmapInfo->bmiHeader.biWidth;
			break;

		case imageTypeS16_555:
		case imageTypeS16_565:
			nBytesPerLine = pBitmapInfo->bmiHeader.biWidth * 3;
			break;
	}

	if (nBytesPerLine % 4)
		nPaddedBytesPerLine = nBytesPerLine + 4 - (nBytesPerLine % 4);
	else
		nPaddedBytesPerLine = nBytesPerLine;

	pBitmapInfo->bmiHeader.biSizeImage = nPaddedBytesPerLine * pBitmapInfo->bmiHeader.biHeight;

	pBitmap = (BYTE *)calloc(1, pBitmapInfo->bmiHeader.biSizeImage);

	if (nImageType == imageTypeSPR)
	{
		LONG nLine;

		for (nLine = 0; nLine < pBitmapInfo->bmiHeader.biHeight; nLine++)
			CopyMemory(pBitmap + (pBitmapInfo->bmiHeader.biHeight - nLine - 1) * nPaddedBytesPerLine, pPixels + nLine * nBytesPerLine, nBytesPerLine);
	}
	else
	{
		for (Y = 0; Y < pBitmapInfo->bmiHeader.biHeight; Y++)
		{
			pSourcePixel = (WORD *)pPixels + Y * pBitmapInfo->bmiHeader.biWidth;
			pDestPixel = pBitmap + (pBitmapInfo->bmiHeader.biHeight - Y - 1) * nPaddedBytesPerLine;
			for (X = 0; X < pBitmapInfo->bmiHeader.biWidth; X++, pSourcePixel++, pDestPixel += 3)
			{
				pDestPixel[0] = (*pSourcePixel << nBlueShift) & nBlueMask;
				pDestPixel[1] = (*pSourcePixel >> nGreenShift) & nGreenMask;
				pDestPixel[2] = (*pSourcePixel >> nRedShift) & nRedMask;
			}
		}
	}

	g_hClipboardData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, pBitmapInfo->bmiHeader.biSize + sizeof(RGBQUAD) * 256 + pBitmapInfo->bmiHeader.biSizeImage);

	if (!g_hClipboardData)
		return 0;

	pClipboardData = (BYTE *)GlobalLock(g_hClipboardData);
	if (!pClipboardData)
	{
		GlobalFree(g_hClipboardData);
		return 0;
	}

	CopyMemory(pClipboardData, pBitmapInfo, pBitmapInfo->bmiHeader.biSize);

	pClipboardData += pBitmapInfo->bmiHeader.biSize;

	if (pBitmapInfo->bmiHeader.biBitCount == 8)
	{
		CopyMemory(pClipboardData, pBitmapInfo->bmiColors, sizeof(RGBQUAD) * 256);
		pClipboardData += sizeof(RGBQUAD) * 256;
		CopyMemory(pClipboardData, pBitmap, pBitmapInfo->bmiHeader.biSizeImage);
	}
	else
	{
		CopyMemory(pClipboardData, pBitmap, pBitmapInfo->bmiHeader.biSizeImage);
	}

	GlobalUnlock(g_hClipboardData);

	free(pBitmap);
	free(pBitmapInfo);

	return 1;
}

LONG WINAPI FinishClipboardCopy()
{
	if (!g_hClipboardData)
		return 0;

	if (!OpenClipboard(NULL))
		return 0;

	/*if (!EmptyClipboard())
		return 0;*/

	if (!SetClipboardData(g_nClipboardFormat, g_hClipboardData))
	{
		if (!CloseClipboard())
			return 0;

		return 0;
	}

	g_hClipboardData = NULL;

	if (!CloseClipboard())
		return 0;

	return 1;
}

LONG WINAPI StartClipboardPaste(LPSTR sFormat, LONG nFormat)
{
	HANDLE hData = NULL;
	VOID *pData = NULL;
	VOID *pClipboardData = NULL;

	if (g_hClipboardData)
	{
		if (GlobalFree(g_hClipboardData))
			return 0;
	}

	if (sFormat)
		g_nClipboardFormat = RegisterClipboardFormat(sFormat);
	else
		g_nClipboardFormat = nFormat;

	g_nClipboardDataPosition = 0;

	if (!OpenClipboard(NULL))
		return 0;

	if (!IsClipboardFormatAvailable(g_nClipboardFormat))
	{
		if (!CloseClipboard())
			return 0;
	}

	hData = GetClipboardData(g_nClipboardFormat);

	if (!hData)
	{
		if (!CloseClipboard())
			return 0;
	}

	g_hClipboardData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, GlobalSize(hData));

	if (!g_hClipboardData)
	{
		if (!CloseClipboard())
			return 0;
	}

	pData = GlobalLock(hData);
	
	if (!pData)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		if (!CloseClipboard())
			return 0;
	}

	pClipboardData = GlobalLock(g_hClipboardData);

	if (!pClipboardData)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		if (!CloseClipboard())
			return 0;
	}

	CopyMemory(pClipboardData, pData, GlobalSize(hData));

	GlobalUnlock(g_hClipboardData);

	GlobalUnlock(hData);

	if (!CloseClipboard())
		return 0;

	return 1;
}

LONG WINAPI GetFromClipboard(VOID *pData, LONG nLen)
{
	VOID *pClipboardData = NULL;

	if (!g_hClipboardData)
		return 0;

	pClipboardData = GlobalLock(g_hClipboardData);

	if (!pClipboardData)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		return 0;
	}

	CopyMemory(pData, (BYTE *)pClipboardData + g_nClipboardDataPosition, nLen);

	GlobalUnlock(g_hClipboardData);

	g_nClipboardDataPosition += nLen;

	return 1;
}

LONG WINAPI GetImageInfoFromClipboard(WORD *nBitsPerPixel, LONG *nImageWidth, LONG *nImageHeight)
{
	BITMAPINFO *pBitmapInfo;
	BITMAPV4HEADER *pBitmapInfoHeader;

	if (!g_hClipboardData)
		return 0;

	pBitmapInfo = (BITMAPINFO *)GlobalLock(g_hClipboardData);

	if (!pBitmapInfo)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		return 0;
	}

	pBitmapInfoHeader = (BITMAPV4HEADER *)&pBitmapInfo->bmiHeader;

	if (pBitmapInfo->bmiHeader.biBitCount == 8)
		*nBitsPerPixel = 24;
	else
		*nBitsPerPixel = pBitmapInfo->bmiHeader.biBitCount;

	*nImageWidth = pBitmapInfo->bmiHeader.biWidth;
	*nImageHeight = labs(pBitmapInfo->bmiHeader.biHeight);

	GlobalUnlock(g_hClipboardData);

	return 1;
}

LONG WINAPI GetImagePaletteFromClipboard(RGBQUAD *pPalette)
{
	BITMAPINFO *pBitmapInfo;

	if (!g_hClipboardData)
		return 0;

	pBitmapInfo = (BITMAPINFO *)GlobalLock(g_hClipboardData);

	if (!pBitmapInfo)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		return 0;
	}
	
	if (pBitmapInfo->bmiHeader.biClrUsed > 0)
		CopyMemory(pPalette, pBitmapInfo->bmiColors, (DWORD)(pBitmapInfo->bmiHeader.biClrUsed * sizeof(RGBQUAD)));
	else
		CopyMemory(pPalette, pBitmapInfo->bmiColors, (DWORD)((1 << pBitmapInfo->bmiHeader.biBitCount) * sizeof(RGBQUAD)));

	return 1;
}

LONG WINAPI GetImagePixelsFromClipboard(BYTE *pPixels)
{
	BITMAPINFO *pBitmapInfo;
	BYTE *pBitmapPixels;
	RGBQUAD *pBitmapPalette;
	LONG nLine;
	LONG x;
	DWORD nBytesPerLine;
	DWORD nPaddedBytesPerLine;

	if (!g_hClipboardData)
		return 0;

	pBitmapInfo = (BITMAPINFO *)GlobalLock(g_hClipboardData);

	if (!pBitmapInfo)
	{
		GlobalFree(g_hClipboardData);
		g_hClipboardData = NULL;
		return 0;
	}

	if (pBitmapInfo->bmiHeader.biBitCount == 8)
	{
		pBitmapPalette = (RGBQUAD *)pBitmapInfo->bmiColors;
		pBitmapPixels = (BYTE *)(pBitmapPalette) + 256 * sizeof(RGBQUAD);
	}
	else
	{
		pBitmapPixels = (BYTE *)pBitmapInfo + pBitmapInfo->bmiHeader.biSize;
	}

	nBytesPerLine = pBitmapInfo->bmiHeader.biWidth * (long)(pBitmapInfo->bmiHeader.biBitCount / 8);

	if (nBytesPerLine % 4)
		nPaddedBytesPerLine = nBytesPerLine + 4 - (nBytesPerLine % 4);
	else
		nPaddedBytesPerLine = nBytesPerLine;

	if (pBitmapInfo->bmiHeader.biBitCount != 8)
	{
		for (nLine = 0; nLine < pBitmapInfo->bmiHeader.biHeight; nLine++)
			CopyMemory(pPixels + nLine * nBytesPerLine, pBitmapPixels + nLine * nPaddedBytesPerLine, nBytesPerLine);
	}
	else
	{
		for (nLine = 0; nLine < pBitmapInfo->bmiHeader.biHeight; nLine++)
		{
			for (x = 0; x < pBitmapInfo->bmiHeader.biWidth; x++)
			{
				pPixels[(nLine * nBytesPerLine + x) * 3 + 2] = pBitmapPalette[pBitmapPixels[nLine * nPaddedBytesPerLine + x]].rgbRed;
				pPixels[(nLine * nBytesPerLine + x) * 3 + 1] = pBitmapPalette[pBitmapPixels[nLine * nPaddedBytesPerLine + x]].rgbGreen;
				pPixels[(nLine * nBytesPerLine + x) * 3] = pBitmapPalette[pBitmapPixels[nLine * nPaddedBytesPerLine + x]].rgbBlue;
			}
		}
	}

	return 1;
}

LONG WINAPI FinishClipboardPaste()
{
	if (!g_hClipboardData)
		return 0;

	if (GlobalFree(g_hClipboardData))
		return 0;

	g_hClipboardData = NULL;

	return 1;
}

VOID LoadCreaturesPalette(HANDLE hModule)
{
	HRSRC hPaletteResource;
	HGLOBAL hPaletteMemoryBlock;
	RGBQUAD *pPalette;

	hPaletteResource = FindResource(hModule, MAKEINTRESOURCE(IDR_PALETTE), "BINARY");
	hPaletteMemoryBlock = LoadResource(hModule, hPaletteResource);
	pPalette = (RGBQUAD *)LockResource(hPaletteMemoryBlock);
	CopyMemory(pCreaturesPalette, pPalette, sizeof(RGBQUAD) * 256);
}

BOOL APIENTRY DllMain(HANDLE hModule, 
                      DWORD  ul_reason_for_call, 
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			LoadCreaturesPalette(hModule);

			g_hZLibDLL = LoadLibrary("zlib.dll");
			if (g_hZLibDLL)
			{
				gzopen = (GZOPENFUNC)GetProcAddress(g_hZLibDLL, "gzopen");
				gzwrite = (GZWRITEFUNC)GetProcAddress(g_hZLibDLL, "gzwrite");
				gzclose= (GZCLOSEFUNC)GetProcAddress(g_hZLibDLL, "gzclose");
			}
			break;

		case DLL_THREAD_ATTACH:
			break;

		case DLL_THREAD_DETACH:
			break;

		case DLL_PROCESS_DETACH:
			if (g_hClipboardData)
				GlobalFree(g_hClipboardData);

			if (g_hZLibDLL)
				FreeLibrary(g_hZLibDLL);
			break;
    }

    return TRUE;
}
