/* iowin32.c -- IO base function header for compress/uncompress .zip
     Version 1.1, February 14h, 2010
     part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )

         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )

         Modifications for Zip64 support
         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )

     For more info read MiniZip_info.txt

*/

#include <stdlib.h>

#include "zlib.h"
#include "ioapi.h"
#include "iowin32.h"

#ifndef INVALID_HANDLE_VALUE
#define INVALID_HANDLE_VALUE (0xFFFFFFFF)
#endif

#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif


#if defined(WINAPI_FAMILY_PARTITION) && (!(defined(IOWIN32_USING_WINRT_API)))
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
#define IOWIN32_USING_WINRT_API 1
#endif
#endif

voidpf  ZCALLBACK win32_open_file_func  OF((voidpf opaque, const wchar_t* filename, int mode));
uLong   ZCALLBACK win32_read_file_func  OF((voidpf opaque, voidpf stream, void* buf, uLong size));
uLong   ZCALLBACK win32_write_file_func OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
ZPOS64_T ZCALLBACK win32_tell64_file_func  OF((voidpf opaque, voidpf stream));
long    ZCALLBACK win32_seek64_file_func  OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
int     ZCALLBACK win32_close_file_func OF((voidpf opaque, voidpf stream));
int     ZCALLBACK win32_error_file_func OF((voidpf opaque, voidpf stream));

typedef struct
{
	HANDLE hf;
	int error;
} WIN32FILE_IOWIN;

static void win32_translate_open_mode(int mode,
	DWORD* lpdwDesiredAccess,
	DWORD* lpdwCreationDisposition,
	DWORD* lpdwShareMode,
	DWORD* lpdwFlagsAndAttributes)
{
	*lpdwDesiredAccess = *lpdwShareMode = *lpdwFlagsAndAttributes = *lpdwCreationDisposition = 0;

	if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
		*lpdwDesiredAccess = GENERIC_READ;
		*lpdwCreationDisposition = OPEN_EXISTING;
		*lpdwShareMode = FILE_SHARE_READ;
	}
	else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
		*lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
		*lpdwCreationDisposition = OPEN_EXISTING;
	}
	else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
		*lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
		*lpdwCreationDisposition = CREATE_ALWAYS;
	}
}

static voidpf win32_build_iowin(HANDLE hFile)
{
	voidpf ret = NULL;

	if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) {
		WIN32FILE_IOWIN w32fiow;
		w32fiow.hf = hFile;
		w32fiow.error = 0;
		ret = malloc(sizeof(WIN32FILE_IOWIN));

		if (ret == NULL)
			CloseHandle(hFile);
		else
			*((WIN32FILE_IOWIN*)ret) = w32fiow;
	}
	return ret;
}

voidpf ZCALLBACK win32_open64_file_func(voidpf opaque, const void* filename, int mode)
{
	const char* mode_fopen = NULL;
	DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes;
	HANDLE hFile = NULL;

	win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes);

	#ifdef IOWIN32_USING_WINRT_API
	#ifdef UNICODE
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0)) {
		WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
		MultiByteToWideChar(CP_ACP, 0, (const char*)filename, -1, filenameW, FILENAME_MAX + 0x200);
		hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	}
	#endif
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
	#endif

	return win32_build_iowin(hFile);
}

voidpf ZCALLBACK win32_open64_file_funcA(voidpf opaque, const void* filename, int mode)
{
	const char* mode_fopen = NULL;
	DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes;
	HANDLE hFile = NULL;

	win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes);

	#ifdef IOWIN32_USING_WINRT_API
	if ((filename != NULL) && (dwDesiredAccess != 0)) {
		WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
		MultiByteToWideChar(CP_ACP, 0, (const char*)filename, -1, filenameW, FILENAME_MAX + 0x200);
		hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	}
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFileA((LPCSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
	#endif

	return win32_build_iowin(hFile);
}

voidpf ZCALLBACK win32_open64_file_funcW(voidpf opaque, const void* filename, int mode)
{
	const char* mode_fopen = NULL;
	DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes;
	HANDLE hFile = NULL;

	win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes);

	#ifdef IOWIN32_USING_WINRT_API
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFile2((LPCWSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFileW((LPCWSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
	#endif

	return win32_build_iowin(hFile);
}

voidpf ZCALLBACK win32_open_file_func(voidpf opaque, const wchar_t* filename, int mode)
{
	const char* mode_fopen = NULL;
	DWORD dwDesiredAccess, dwCreationDisposition, dwShareMode, dwFlagsAndAttributes;
	HANDLE hFile = NULL;

	win32_translate_open_mode(mode, &dwDesiredAccess, &dwCreationDisposition, &dwShareMode, &dwFlagsAndAttributes);

	#ifdef IOWIN32_USING_WINRT_API
	#ifdef UNICODE
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0)) {
		WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
		MultiByteToWideChar(CP_ACP, 0, (const char*)filename, -1, filenameW, FILENAME_MAX + 0x200);
		hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
	}
	#endif
	#else
	if ((filename != NULL) && (dwDesiredAccess != 0))
		hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
	#endif

	return win32_build_iowin(hFile);
}

uLong ZCALLBACK win32_read_file_func(voidpf opaque, voidpf stream, void* buf, uLong size)
{
	uLong ret = 0;
	HANDLE hFile = NULL;
	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;

	if (hFile != NULL) {
		if (!ReadFile(hFile, buf, size, &ret, NULL)) {
			DWORD dwErr = GetLastError();
			if (dwErr == ERROR_HANDLE_EOF)
				dwErr = 0;
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
		}
	}

	return ret;
}

uLong ZCALLBACK win32_write_file_func(voidpf opaque, voidpf stream, const void* buf, uLong size)
{
	uLong ret = 0;
	HANDLE hFile = NULL;
	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;

	if (hFile != NULL) {
		if (!WriteFile(hFile, buf, size, &ret, NULL)) {
			DWORD dwErr = GetLastError();
			if (dwErr == ERROR_HANDLE_EOF)
				dwErr = 0;
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
		}
	}

	return ret;
}

static BOOL MySetFilePointerEx(HANDLE hFile, LARGE_INTEGER pos, LARGE_INTEGER *newPos, DWORD dwMoveMethod)
{
	#ifdef IOWIN32_USING_WINRT_API
	return SetFilePointerEx(hFile, pos, newPos, dwMoveMethod);
	#else
	LONG lHigh = pos.HighPart;
	DWORD dwNewPos = SetFilePointer(hFile, pos.LowPart, &lHigh, FILE_CURRENT);
	BOOL fOk = TRUE;
	if (dwNewPos == 0xFFFFFFFF)
		if (GetLastError() != NO_ERROR)
			fOk = FALSE;
	if ((newPos != NULL) && (fOk)) {
		newPos->LowPart = dwNewPos;
		newPos->HighPart = lHigh;
	}
	return fOk;
	#endif
}

long ZCALLBACK win32_tell_file_func(voidpf opaque, voidpf stream)
{
	long ret = -1;
	HANDLE hFile = NULL;
	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;
	if (hFile != NULL) {
		LARGE_INTEGER pos;
		pos.QuadPart = 0;

		if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) {
			DWORD dwErr = GetLastError();
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
			ret = -1;
		}
		else
			ret = (long)pos.LowPart;
	}
	return ret;
}

ZPOS64_T ZCALLBACK win32_tell64_file_func(voidpf opaque, voidpf stream)
{
	ZPOS64_T ret = (ZPOS64_T)-1;
	HANDLE hFile = NULL;
	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;

	if (hFile) {
		LARGE_INTEGER pos;
		pos.QuadPart = 0;

		if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) {
			DWORD dwErr = GetLastError();
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
			ret = (ZPOS64_T)-1;
		}
		else
			ret = pos.QuadPart;
	}
	return ret;
}

long ZCALLBACK win32_seek_file_func(voidpf opaque, voidpf stream, uLong offset, int origin)
{
	DWORD dwMoveMethod = 0xFFFFFFFF;
	HANDLE hFile = NULL;

	long ret = -1;
	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;
	switch (origin) {
	case ZLIB_FILEFUNC_SEEK_CUR:
		dwMoveMethod = FILE_CURRENT;
		break;
	case ZLIB_FILEFUNC_SEEK_END:
		dwMoveMethod = FILE_END;
		break;
	case ZLIB_FILEFUNC_SEEK_SET:
		dwMoveMethod = FILE_BEGIN;
		break;
	default: return -1;
	}

	if (hFile != NULL) {
		LARGE_INTEGER pos;
		pos.QuadPart = offset;
		if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod)) {
			DWORD dwErr = GetLastError();
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
			ret = -1;
		}
		else
			ret = 0;
	}
	return ret;
}

long ZCALLBACK win32_seek64_file_func(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)
{
	DWORD dwMoveMethod = 0xFFFFFFFF;
	HANDLE hFile = NULL;
	long ret = -1;

	if (stream != NULL)
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;

	switch (origin) {
	case ZLIB_FILEFUNC_SEEK_CUR:
		dwMoveMethod = FILE_CURRENT;
		break;
	case ZLIB_FILEFUNC_SEEK_END:
		dwMoveMethod = FILE_END;
		break;
	case ZLIB_FILEFUNC_SEEK_SET:
		dwMoveMethod = FILE_BEGIN;
		break;
	default: return -1;
	}

	if (hFile) {
		LARGE_INTEGER pos;
		pos.QuadPart = offset;
		if (!MySetFilePointerEx(hFile, pos, NULL, FILE_CURRENT)) {
			DWORD dwErr = GetLastError();
			((WIN32FILE_IOWIN*)stream)->error = (int)dwErr;
			ret = -1;
		}
		else
			ret = 0;
	}
	return ret;
}

int ZCALLBACK win32_close_file_func(voidpf opaque, voidpf stream)
{
	int ret = -1;

	if (stream != NULL) {
		HANDLE hFile;
		hFile = ((WIN32FILE_IOWIN*)stream)->hf;
		if (hFile != NULL) {
			CloseHandle(hFile);
			ret = 0;
		}
		free(stream);
	}
	return ret;
}

int ZCALLBACK win32_error_file_func(voidpf opaque, voidpf stream)
{
	int ret = -1;
	if (stream != NULL) {
		ret = ((WIN32FILE_IOWIN*)stream)->error;
	}
	return ret;
}

void fill_win32_filefunc(zlib_filefunc_def* pzlib_filefunc_def)
{
	pzlib_filefunc_def->zopen_file = win32_open_file_func;
	pzlib_filefunc_def->zread_file = win32_read_file_func;
	pzlib_filefunc_def->zwrite_file = win32_write_file_func;
	pzlib_filefunc_def->ztell_file = win32_tell_file_func;
	pzlib_filefunc_def->zseek_file = win32_seek_file_func;
	pzlib_filefunc_def->zclose_file = win32_close_file_func;
	pzlib_filefunc_def->zerror_file = win32_error_file_func;
	pzlib_filefunc_def->opaque = NULL;
}

void fill_win32_filefunc64(zlib_filefunc64_def* pzlib_filefunc_def)
{
	pzlib_filefunc_def->zopen64_file = win32_open64_file_func;
	pzlib_filefunc_def->zread_file = win32_read_file_func;
	pzlib_filefunc_def->zwrite_file = win32_write_file_func;
	pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
	pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
	pzlib_filefunc_def->zclose_file = win32_close_file_func;
	pzlib_filefunc_def->zerror_file = win32_error_file_func;
	pzlib_filefunc_def->opaque = NULL;
}

void fill_win32_filefunc64A(zlib_filefunc64_def* pzlib_filefunc_def)
{
	pzlib_filefunc_def->zopen64_file = win32_open64_file_funcA;
	pzlib_filefunc_def->zread_file = win32_read_file_func;
	pzlib_filefunc_def->zwrite_file = win32_write_file_func;
	pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
	pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
	pzlib_filefunc_def->zclose_file = win32_close_file_func;
	pzlib_filefunc_def->zerror_file = win32_error_file_func;
	pzlib_filefunc_def->opaque = NULL;
}

void fill_win32_filefunc64W(zlib_filefunc64_def* pzlib_filefunc_def)
{
	pzlib_filefunc_def->zopen64_file = win32_open64_file_funcW;
	pzlib_filefunc_def->zread_file = win32_read_file_func;
	pzlib_filefunc_def->zwrite_file = win32_write_file_func;
	pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
	pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
	pzlib_filefunc_def->zclose_file = win32_close_file_func;
	pzlib_filefunc_def->zerror_file = win32_error_file_func;
	pzlib_filefunc_def->opaque = NULL;
}