/*
 * HLExtract
 * Copyright (C) 2006-2010 Ryan Gregg

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifdef _WIN32
#	include "..\lib\HLLib.h"
#	ifdef _MSC_VER
#		ifdef _DEBUG
#			ifdef _WIN64
#				pragma comment(lib, "../HLLib/x64/Debug/HLLib.lib")
#			else
#				pragma comment(lib, "../HLLib/Win32/Debug/HLLib.lib")
#			endif
#		else
#			ifdef _WIN64
#				pragma comment(lib, "../HLLib/x64/Release/HLLib.lib")
#			else
#				pragma comment(lib, "../HLLib/Win32/Release/HLLib.lib")
#			endif
#		endif
#	endif
#else
#	include "../lib/HLLib.h"
#endif

#if _MSC_VER
#	define _CRT_SECURE_NO_WARNINGS
#	define _CRT_NONSTDC_NO_DEPRECATE
#endif

#include <assert.h>
#include <ctype.h>
#include <memory.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
#	define WIN32_LEAN_AND_MEAN
#	define UNUSED
#	include <windows.h>
#else
#	include <linux/limits.h>
#	define MAX_PATH PATH_MAX
#	define UNUSED __attribute__((__unused__))

#	define FOREGROUND_BLUE      0x0001
#	define FOREGROUND_GREEN     0x0002
#	define FOREGROUND_RED       0x0004
#	define FOREGROUND_INTENSITY 0x0008

#	define stricmp strcasecmp
#	define strnicmp strncasecmp
#endif

hlUInt16 GetColor();
hlVoid SetColor(hlUInt16 uiColor);
hlVoid Print(hlUInt16 uiColor, const hlChar *lpFormat, ...);
hlVoid PrintUsage();
hlVoid List(FILE *pFile, HLDirectoryItem *pItem, hlBool bListFolders, hlBool bListFiles);
hlVoid ProgressStart();
hlVoid ProgressUpdate(hlULongLong uiBytesDone, hlULongLong uiBytesTotal);
hlVoid ExtractItemStartCallback(HLDirectoryItem *pItem);
hlVoid FileProgressCallback(HLDirectoryItem *pFile, hlUInt uiBytesExtracted, hlUInt uiBytesTotal, hlBool *pCancel);
hlVoid ExtractItemEndCallback(HLDirectoryItem *pItem, hlBool bSuccess);
hlVoid DefragmentProgressCallback(HLDirectoryItem *pFile, hlUInt uiFilesDefragmented, hlUInt uiFilesTotal, hlULongLong uiBytesDefragmented, hlULongLong uiBytesTotal, hlBool *pCancel);
HLValidation Validate(HLDirectoryItem *pItem);
hlVoid PrintAttribute(hlChar *lpPrefix, HLAttribute *pAttribute, hlChar *lpPostfix);
hlVoid PrintValidation(HLValidation eValidation);
hlVoid EnterConsole(hlUInt uiPackage, hlUInt uiConsoleCommands, hlChar *lpConsoleCommands[]);

#define MAX_ITEMS 1024
#define BUFFER_SIZE 1024

static hlChar lpDestination[MAX_PATH] = "";
static hlBool bSilent = hlFalse;
#ifndef _WIN32
	static hlUInt uiProgressLast = 0;
#endif

int main(hlInt argc, hlChar* argv[])
{
	hlUInt i;

	// Arguments.
	hlUInt uiArgumentCount = (hlUInt)argc;
	hlChar *lpPackage = 0;
	hlUInt uiExtractItems = 0;
	hlChar *lpExtractItems[MAX_ITEMS];
	hlUInt uiValidateItems = 0;
	hlChar *lpValidateItems[MAX_ITEMS];
	hlChar *lpList = 0;
	hlBool bDefragment = hlFalse;
	hlChar *lpNCFRootPath = 0;

	hlBool bList = hlFalse;
	hlBool bListFolders = hlFalse;
	hlBool bListFiles = hlFalse;
	FILE *pFile = 0;

	hlBool bConsoleMode = hlFalse;
	hlUInt uiConsoleCommands = 0;
	hlChar *lpConsoleCommands[MAX_ITEMS];
	hlBool bFileMapping = hlFalse;
	hlBool bQuickFileMapping = hlFalse;
	hlBool bVolatileAccess = hlFalse;
	hlBool bOverwriteFiles = hlTrue;
	hlBool bForceDefragment = hlFalse;

	// Package stuff.
	HLPackageType ePackageType = HL_PACKAGE_NONE;
	hlUInt uiPackage = HL_ID_INVALID, uiMode = HL_MODE_INVALID;
	HLDirectoryItem *pItem = 0;

	if(hlGetUnsignedInteger(HL_VERSION) < HL_VERSION_NUMBER)
	{
		printf("Wrong HLLib version: v%s.\n", hlGetString(HL_VERSION));
		return 1;
	}

	// Process switches.
	if(uiArgumentCount == 2)
	{
		// The user just specified a file, drop into console mode.
		lpPackage = argv[1];
		bConsoleMode = hlTrue;
		bVolatileAccess = hlTrue;
	}
	else
	{
		for(i = 1; i < uiArgumentCount; i++)
		{
			if(stricmp(argv[i], "-p") == 0 || stricmp(argv[i], "--package") == 0)
			{
				if(lpPackage == 0 && i + 1 < uiArgumentCount)
				{
					lpPackage = argv[++i];
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(stricmp(argv[i], "-d") == 0 || stricmp(argv[i], "--dest") == 0)
			{
				if(*lpDestination == 0 && i + 1 < uiArgumentCount)
				{
					strcpy(lpDestination, argv[++i]);
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(stricmp(argv[i], "-e") == 0 || stricmp(argv[i], "--extract") == 0)
			{
				if(i + 1 < uiArgumentCount)
				{
					if(uiExtractItems == MAX_ITEMS)
					{
						Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading package:\nMAX_ITEMS\n");
						return 2;
					}
					lpExtractItems[uiExtractItems++] = argv[++i];
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(stricmp(argv[i], "-t") == 0 || stricmp(argv[i], "--validate") == 0)
			{
				if(i + 1 < uiArgumentCount)
				{
					if(uiValidateItems == MAX_ITEMS)
					{
						Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading package:\nMAX_ITEMS\n");
						return 2;
					}
					lpValidateItems[uiValidateItems++] = argv[++i];
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(strnicmp(argv[i], "-l", 2) == 0 || stricmp(argv[i], "--list") == 0)
			{
				if(bList)
				{
					PrintUsage();
					return 2;
				}

				bList = hlTrue;

				if(stricmp(argv[i], "-l") == 0 || stricmp(argv[i], "--list") == 0)
				{
					// By default list everything.
					bListFolders = hlTrue;
					bListFiles = hlTrue;
				}
				else
				{
					// List folders and files if specified.
					bListFolders = strcspn(argv[i], "dD") != strlen(argv[i]);
					bListFiles = strcspn(argv[i], "fF") != strlen(argv[i]);
				}

				// Check to see if we need to dump our list to a file.
				if(i + 1 < uiArgumentCount && *argv[i + 1] != '-')
				{
					lpList = argv[++i];
				}
			}
			else if(stricmp(argv[i], "-f") == 0 || stricmp(argv[i], "--defragment") == 0)
			{
				bDefragment = hlTrue;
			}
			else if(stricmp(argv[i], "-n") == 0 || stricmp(argv[i], "--ncfroot") == 0)
			{
				if(lpNCFRootPath == 0 && i + 1 < uiArgumentCount)
				{
					lpNCFRootPath = argv[++i];
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(stricmp(argv[i], "-s") == 0 || stricmp(argv[i], "--silent") == 0)
			{
				bSilent = hlTrue;
			}
			else if(stricmp(argv[i], "-c") == 0 || stricmp(argv[i], "--console") == 0)
			{
				bConsoleMode = hlTrue;
			}
			else if(stricmp(argv[i], "-x") == 0 || stricmp(argv[i], "--execute") == 0)
			{
				if(i + 1 < uiArgumentCount)
				{
					if(uiConsoleCommands == MAX_ITEMS)
					{
						Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading package:\nMAX_ITEMS\n");
						return 2;
					}
					lpConsoleCommands[uiConsoleCommands++] = argv[++i];
				}
				else
				{
					PrintUsage();
					return 2;
				}
			}
			else if(stricmp(argv[i], "-m") == 0 || stricmp(argv[i], "--filemapping") == 0)
			{
				bFileMapping = hlTrue;
			}
			else if(stricmp(argv[i], "-q") == 0 || stricmp(argv[i], "--quick-filemapping") == 0)
			{
				bFileMapping = hlTrue;
				bQuickFileMapping = hlTrue;
			}
			else if(stricmp(argv[i], "-v") == 0 || stricmp(argv[i], "--volatile") == 0)
			{
				bVolatileAccess = hlTrue;
			}
			else if(stricmp(argv[i], "-o") == 0 || stricmp(argv[i], "--overwrite") == 0)
			{
				bOverwriteFiles = hlFalse;
			}
			else if(stricmp(argv[i], "-r") == 0 || stricmp(argv[i], "--force-defragment") == 0)
			{
				bDefragment = hlTrue;
				bForceDefragment = hlTrue;
			}
			else
			{
				PrintUsage();
				return 2;
			}
		}
	}

	// Make sure we have something to do.
	if(lpPackage == 0 || (uiExtractItems == 0 && uiValidateItems == 0 && !bList && !bDefragment && !bConsoleMode))
	{
		PrintUsage();
		return 2;
	}

	// If the destination directory is not specified, make it the input directory.
	if(*lpDestination == 0)
	{
		const hlChar *pForward = strrchr(lpPackage, '\\');
		const hlChar *pBackward = strrchr(lpPackage, '/');
		const hlChar *pEnd = pForward > pBackward ? pForward : pBackward;
		if(pEnd != 0)
		{
			strncpy(lpDestination, lpPackage, pEnd - lpPackage);
			lpDestination[pEnd - lpPackage] = '\0';
		}
	}

	hlInitialize();

	hlSetBoolean(HL_OVERWRITE_FILES, bOverwriteFiles);
	hlSetBoolean(HL_FORCE_DEFRAGMENT, bForceDefragment);
	hlSetVoid(HL_PROC_EXTRACT_ITEM_START, ExtractItemStartCallback);
	hlSetVoid(HL_PROC_EXTRACT_ITEM_END, ExtractItemEndCallback);
	hlSetVoid(HL_PROC_EXTRACT_FILE_PROGRESS, FileProgressCallback);
	hlSetVoid(HL_PROC_VALIDATE_FILE_PROGRESS, FileProgressCallback);
	hlSetVoid(HL_PROC_DEFRAGMENT_PROGRESS_EX, DefragmentProgressCallback);

	// Get the package type from the filename extension.
	ePackageType = hlGetPackageTypeFromName(lpPackage);

	// If the above fails, try getting the package type from the data at the start of the file.
	if(ePackageType == HL_PACKAGE_NONE)
	{
		pFile = fopen(lpPackage, "rb");
		if(pFile != 0)
		{
			hlByte lpBuffer[HL_DEFAULT_PACKAGE_TEST_BUFFER_SIZE];

			hlUInt uiBufferSize = (hlUInt)fread(lpBuffer, 1, HL_DEFAULT_PACKAGE_TEST_BUFFER_SIZE, pFile);

			ePackageType = hlGetPackageTypeFromMemory(lpBuffer, uiBufferSize);

			fclose(pFile);
			pFile = 0;
		}
	}

	if(ePackageType == HL_PACKAGE_NONE)
	{
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading %s:\nUnsupported package type.\n", lpPackage);

		hlShutdown();
		return 3;
	}

	// Create a package element, the element is allocated by the library and cleaned
	// up by the library.  An ID is generated which must be bound to apply operations
	// to the package.
	if(!hlCreatePackage(ePackageType, &uiPackage))
	{
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading %s:\n%s\n", lpPackage, hlGetString(HL_ERROR_SHORT_FORMATED));

		hlShutdown();
		return 3;
	}

	hlBindPackage(uiPackage);

	uiMode = HL_MODE_READ | (bDefragment ? HL_MODE_WRITE : 0);
	uiMode |= !bFileMapping ? HL_MODE_NO_FILEMAPPING : 0;
	uiMode |= bQuickFileMapping ? HL_MODE_QUICK_FILEMAPPING : 0;
	uiMode |= bVolatileAccess ? HL_MODE_VOLATILE : 0;

	// Open the package.
	// Of the above modes, only HL_MODE_READ is required.  HL_MODE_WRITE is present
	// only for future use.  File mapping is recommended as an efficient way to load
	// packages.  Quick file mapping maps the entire file (instead of bits as they are
	// needed) and thus should only be used in Windows 2000 and up (older versions of
	// Windows have poor virtual memory management which means large files won't be able
	// to find a continues block and will fail to load).  Volatile access allows HLLib
	// to share files with other applications that have those file open for writing.
	// This is useful for, say, loading .gcf files while Steam is running.
	if(!hlPackageOpenFile(lpPackage, uiMode))
	{
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error loading %s:\n%s\n", lpPackage, hlGetString(HL_ERROR_SHORT_FORMATED));

		hlShutdown();
		return 3;
	}

	// If we have a .ncf file, the package file data is stored externally.  In order to
	// validate the file data etc., HLLib needs to know where to look.  Tell it where.
	if(ePackageType == HL_PACKAGE_NCF)
	{
		hlNCFFileSetRootPath(lpNCFRootPath);
	}

	if(!bSilent)
		Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "%s opened.\n", lpPackage);

	// Extract the requested items.
	for(i = 0; i < uiExtractItems; i++)
	{
		// Find the item.
		pItem = hlFolderGetItemByPath(hlPackageGetRoot(), lpExtractItems[i], HL_FIND_ALL);

		if(pItem == 0)
		{
			printf("%s not found in package.\n", lpExtractItems[i]);
			continue;
		}

		if(!bSilent)
		{
			Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "Extracting %s...\n", hlItemGetName(pItem));
			printf("\n");
		}

		// Extract the item.
		// Item is extracted to cDestination\Item->GetName().
		hlItemExtract(pItem, lpDestination);

		if(!bSilent)
		{
			printf("\n");
			printf("Done.\n");
		}
	}

	// Validate the requested items.
	for(i = 0; i < uiValidateItems; i++)
	{
		// Find the item.
		pItem = hlFolderGetItemByPath(hlPackageGetRoot(), lpValidateItems[i], HL_FIND_ALL);

		if(pItem == 0)
		{
			printf("%s not found in package.\n", lpValidateItems[i]);
			continue;
		}

		if(!bSilent)
		{
			Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "Validating %s...\n", hlItemGetName(pItem));
			printf("\n");
		}

		// Validate the item.
		Validate(pItem);

		if(!bSilent)
		{
			printf("\n");
			printf("Done.\n");
		}
	}

	// List items in package.
	if(bList)
	{
		if(!bSilent)
		{
			printf("Listing...\n");
			printf("\n");
		}

		pFile = stdout;

		if(lpList != 0)
		{
			pFile = fopen(lpList, "wt");

			if(pFile == 0)
			{
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error opening %s:\n%s\n", lpList, "fopen() failed.");
				pFile = stdout;
			}
		}

		List(pFile, hlPackageGetRoot(), bListFolders, bListFiles);

		if(lpList != 0)
		{
			fclose(pFile);
			pFile = 0;
		}

		if(!bSilent)
		{
			printf("\n");
			printf("Done.\n");
		}
	}

	if(bDefragment)
	{
		if(!bSilent)
		{
			printf("Defragmenting...\n");
			printf("\n");

			ProgressStart();
			printf("  Progress: ");
		}

		if(!hlPackageDefragment())
		{
			Print(FOREGROUND_RED | FOREGROUND_INTENSITY, " %s", hlGetString(HL_ERROR_SHORT_FORMATED));
		}

		if(!bSilent)
		{
			printf("\n");

			printf("\n");
			printf("Done.\n");
		}
	}

	// Interactive console mode.
	// Commands: dir, cd, root, info, extract, find, type, cls, help, exit.
	if(bConsoleMode)
	{
		EnterConsole(uiPackage, uiConsoleCommands, lpConsoleCommands);
	}

	// Close the package.
	hlPackageClose();

	if(!bSilent)
		Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "%s closed.\n", lpPackage);

	// Free up the allocated memory.
	hlDeletePackage(uiPackage);

	hlShutdown();

	return 0;
}

hlUInt16 GetColor()
{
#ifdef _WIN32
    HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE);
    if (Handle != INVALID_HANDLE_VALUE)
	{
		CONSOLE_SCREEN_BUFFER_INFO Info;
		if(GetConsoleScreenBufferInfo(Handle, &Info))
		{
			return Info.wAttributes;
		}
    }
#endif

	return FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
}

hlVoid SetColor(hlUInt16 uiColor)
{
#ifdef _WIN32
    HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE);
    if (Handle == INVALID_HANDLE_VALUE)
	{
        return;
    }

	SetConsoleTextAttribute(Handle, uiColor);
#else
	(void)uiColor;
#endif
}

hlVoid Print(hlUInt16 uiColor, const hlChar *lpFormat, ...)
{
	hlUInt16 uiDefaultColor;
	va_list List;
	
	uiDefaultColor = GetColor();
	if(uiDefaultColor != uiColor)
	{
		SetColor(uiColor);
	}

	va_start(List, lpFormat);

	vprintf(lpFormat, List);

	va_end(List);

	if(uiDefaultColor != uiColor)
	{
		SetColor(uiDefaultColor);
	}
}

hlVoid PrintUsage()
{
	printf("HLExtract using HLLib v%s\n", hlGetString(HL_VERSION));
	printf("\n");
	printf("Correct HLExtract usage:\n");
	printf(" -p <filepath>       (Package to load.)\n");
	printf(" -d <path>           (Destination extraction directory.)\n");
	printf(" -e <itempath>       (Item in package to extract.)\n");
	printf(" -t <itempath>       (Item in package to validate.)\n");
	printf(" -l[d][f] [filepath] (List the contents of the package.)\n");
	printf(" -f                  (Defragment package.)\n");
	printf(" -c                  (Console mode.)\n");
	printf(" -x <command>        (Execute console command.)\n");
	printf(" -s                  (Silent mode.)\n");
	printf(" -m                  (Use file mapping.)\n");
	printf(" -q                  (Use quick file mapping.)\n");
	printf(" -v                  (Allow volatile access.)\n");
	printf(" -o                  (Don't overwrite files.)\n");
	printf(" -r                  (Force defragmenting on all files.)\n");
	printf(" -n <path>           (NCF file's root path.)\n");
	printf("\n");
	printf("Example HLExtract usage:\n");
#ifdef _WIN32
	printf("HLExtract.exe -p \"C:\\half-life.gcf\" -d \"C:\\backup\" -e \"root\\valve\\models\" -e \"root\\valve\\config.cfg\"\n");
	printf("HLExtract.exe -p \"C:\\half-life.gcf\" -c -m -v\n");
	printf("HLExtract.exe -p \"C:\\half-life.gcf\" -lf \"C:\\half-life.txt\" -m -s\n");
	printf("HLExtract.exe -p \"C:\\half-life.gcf\" -m -f\n");
#else
	printf("HLExtract.exe -p \"~/half-life.gcf\" -d \"~/backup\" -e \"root/valve/models\" -e \"root/valve/config.cfg\"\n");
	printf("HLExtract.exe -p \"~/half-life.gcf\" -c -m -v\n");
	printf("HLExtract.exe -p \"~/half-life.gcf\" -lf \"~/half-life.txt\" -m -s\n");
	printf("HLExtract.exe -p \"~/half-life.gcf\" -m -f\n");
#endif
}

hlVoid List(FILE *pFile, HLDirectoryItem *pItem, hlBool bListFolders, hlBool bListFiles)
{
	hlUInt i, uiItemCount;
	hlChar lpPath[512] = "";

	switch(hlItemGetType(pItem))
	{
	case HL_ITEM_FOLDER:
		if(bListFolders)
		{
			hlItemGetPath(pItem, lpPath, sizeof(lpPath));
			fprintf(pFile, "%s\n", lpPath);
		}

		uiItemCount = hlFolderGetCount(pItem);
		for(i = 0; i < uiItemCount; i++)
		{
			List(pFile, hlFolderGetItem(pItem, i), bListFolders, bListFiles);
		}
		break;
	case HL_ITEM_FILE:
		if(bListFiles)
		{
			hlItemGetPath(pItem, lpPath, sizeof(lpPath));
			fprintf(pFile, "%s\n", lpPath);
		}
		break;
	default:
		break;
	}
}

hlVoid ProgressStart()
{
#ifndef _WIN32
	uiProgressLast = 0;
	printf("0%%");
#endif
}

hlVoid ProgressUpdate(hlULongLong uiBytesDone, hlULongLong uiBytesTotal)
{
	if(!bSilent)
	{
#ifdef _WIN32
		HANDLE Handle = GetStdHandle(STD_OUTPUT_HANDLE);
		if (Handle != INVALID_HANDLE_VALUE)
		{
			CONSOLE_SCREEN_BUFFER_INFO Info;
			if(GetConsoleScreenBufferInfo(Handle, &Info))
			{
				if(uiBytesTotal == 0)
				{
					printf("100.0%%");
				}
				else
				{
					printf("%0.0f%%", (hlSingle)((hlDouble)uiBytesDone / (hlDouble)uiBytesTotal * 100.0));
				}
				SetConsoleCursorPosition(Handle, Info.dwCursorPosition);
			}
		}
#else
		hlUInt uiProgress = uiBytesTotal == 0 ? 100 : (hlUInt)((hlUInt64)uiBytesDone * 100 / (hlUInt64)uiBytesTotal);
		while(uiProgress >= uiProgressLast + 10)
		{
			uiProgressLast += 10;
			if(uiProgressLast == 100)
			{
				printf("100%% ");
			}
			else if(uiProgressLast == 50)
			{
				printf("50%%");
			}
			else
			{
				printf(".");
			}
		}
		fflush(stdout);
#endif
	}
}

hlVoid ExtractItemStartCallback(HLDirectoryItem *pItem)
{
	if(!bSilent)
	{
		if(hlItemGetType(pItem) == HL_ITEM_FILE)
		{
			printf("  Extracting %s: ", hlItemGetName(pItem));
			ProgressStart();
		}
		else
		{
			Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, "  Extracting %s:\n", hlItemGetName(pItem));
		}
	}
}

hlVoid FileProgressCallback(HLDirectoryItem *pFile UNUSED, hlUInt uiBytesExtracted, hlUInt uiBytesTotal, hlBool *pCancel UNUSED)
{
	ProgressUpdate((hlULongLong)uiBytesExtracted, (hlULongLong)uiBytesTotal);
}

hlVoid ExtractItemEndCallback(HLDirectoryItem *pItem, hlBool bSuccess)
{
	hlUInt uiSize = 0;
	hlChar lpPath[512] = "";

	if(bSuccess)
	{
		if(!bSilent)
		{
			hlItemGetSize(pItem, &uiSize);
			if(hlItemGetType(pItem) == HL_ITEM_FILE)
			{
				Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "OK");
				printf(" (%u B)\n", uiSize);
			}
			else
			{
				Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, "  Done %s: ", hlItemGetName(pItem));
				Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "OK");
				Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, " (%u B)\n", uiSize);
			}
		}
	}
	else
	{
		if(!bSilent)
		{
			if(hlItemGetType(pItem) == HL_ITEM_FILE)
			{
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Errored\n");
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "    %s\n", hlGetString(HL_ERROR_SHORT_FORMATED));
			}
			else
			{
				Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, "  Done %s: ", hlItemGetName(pItem));
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Errored\n");
			}
		}
		else
		{
			hlItemGetPath(pItem, lpPath, sizeof(lpPath));
			if(hlItemGetType(pItem) == HL_ITEM_FILE)
			{
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "  Error extracting %s:\n", lpPath);
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "    %s\n", hlGetString(HL_ERROR_SHORT_FORMATED));
			}
			else
			{
				Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "  Error extracting %s.\n", lpPath);
			}
		}
	}
}

hlVoid DefragmentProgressCallback(HLDirectoryItem *pFile UNUSED, hlUInt uiFilesDefragmented UNUSED, hlUInt uiFilesTotal UNUSED, hlULongLong uiBytesDefragmented, hlULongLong uiBytesTotal, hlBool *pCancel UNUSED)
{
	ProgressUpdate(uiBytesDefragmented, uiBytesTotal);
}

HLValidation Validate(HLDirectoryItem *pItem)
{
	hlUInt i, uiItemCount;
	hlChar lpPath[512] = "";
	HLValidation eValidation = HL_VALIDATES_OK, eTest;

	switch(hlItemGetType(pItem))
	{
	case HL_ITEM_FOLDER:
		if(!bSilent)
		{
			Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, "  Validating %s:\n", hlItemGetName(pItem));
		}

		uiItemCount = hlFolderGetCount(pItem);
		for(i = 0; i < uiItemCount; i++)
		{
			eTest = Validate(hlFolderGetItem(pItem, i));
			if(eTest > eValidation)
			{
				eValidation = eTest;
			}
		}

		if(!bSilent)
		{
			Print(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, "  Done %s: ", hlItemGetName(pItem));
			PrintValidation(eValidation);
			printf("\n");
		}
		break;
	case HL_ITEM_FILE:
		if(!bSilent)
		{
			printf("  Validating %s: ", hlItemGetName(pItem));
			ProgressStart();
		}

		eValidation = hlFileGetValidation(pItem);

		if(bSilent)
		{
			switch(eValidation)
			{
			case HL_VALIDATES_INCOMPLETE:
			case HL_VALIDATES_CORRUPT:
				hlItemGetPath(pItem, lpPath, sizeof(lpPath));
				printf("  Validating %s: ", lpPath);
				PrintValidation(eValidation);
				printf("\n");
				break;
			default:
				break;
			}
		}
		else
		{
			PrintValidation(eValidation);
			printf("  \n");
		}
		break;
	default:
		break;
	}

	return eValidation;
}

hlVoid PrintAttribute(hlChar *lpPrefix, HLAttribute *pAttribute, hlChar *lpPostfix)
{
	switch(pAttribute->eAttributeType)
	{
	case HL_ATTRIBUTE_BOOLEAN:
		printf("%s%s: %s%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.Boolean.bValue ? "True" : "False", lpPostfix);
		break;
	case HL_ATTRIBUTE_INTEGER:
		printf("%s%s: %i%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.Integer.iValue, lpPostfix);
		break;
	case HL_ATTRIBUTE_UNSIGNED_INTEGER:
		if(pAttribute->Value.UnsignedInteger.bHexadecimal)
		{
			printf("%s%s: %#.8x%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.UnsignedInteger.uiValue, lpPostfix);
		}
		else
		{
			printf("%s%s: %u%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.UnsignedInteger.uiValue, lpPostfix);
		}
		break;
	case HL_ATTRIBUTE_FLOAT:
		printf("%s%s: %f%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.Float.fValue, lpPostfix);
		break;
	case HL_ATTRIBUTE_STRING:
		printf("%s%s: %s%s\n", lpPrefix, pAttribute->lpName, pAttribute->Value.String.lpValue, lpPostfix);
		break;
	default:
		break;
	}
}

hlVoid PrintValidation(HLValidation eValidation)
{
	switch(eValidation)
	{
	case HL_VALIDATES_ASSUMED_OK:
		Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "Assumed OK");
		break;
	case HL_VALIDATES_OK:
		Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "OK");
		break;
	case HL_VALIDATES_INCOMPLETE:
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Incomplete");
		break;
	case HL_VALIDATES_CORRUPT:
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Corrupt");
		break;
	case HL_VALIDATES_CANCELED:
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Canceled");
		break;
	case HL_VALIDATES_ERROR:
		Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error");
		break;
	default:
		printf("Unknown");
		break;
	}
}

hlVoid EnterConsole(hlUInt uiPackage, hlUInt uiConsoleCommands, hlChar *lpConsoleCommands[])
{
	hlUInt i;

	hlChar lpBuffer[BUFFER_SIZE];	// Input string.
	hlChar lpCommand[BUFFER_SIZE];	// Input command  (i.e. first word in input string).
	hlChar lpArgument[BUFFER_SIZE];	// Input argument (i.e. rest of input string).
	hlChar *lpTemp;
	hlChar lpTempBuffer[BUFFER_SIZE];

	hlUInt16 uiColor;
	HLDirectoryItem *pItem = 0, *pSubItem = 0;

	hlBool bFound;
	hlUInt uiItemCount, uiFolderCount, uiFileCount;
	hlChar iChar;
	HLStream *pStream = 0;
	HLAttribute Attribute;
	HLPackageType ePackageType = HL_PACKAGE_NONE;
	hlUInt uiSubPackage = HL_ID_INVALID;

	pItem = hlPackageGetRoot();

	while(hlTrue)
	{
		uiColor = GetColor();
		SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);

		if(uiConsoleCommands > 0)
		{
			printf("%s>%s\n", hlItemGetName(pItem), *lpConsoleCommands);

			strncpy(lpBuffer, *lpConsoleCommands, sizeof(lpBuffer));
			lpBuffer[sizeof(lpBuffer) - 1] = '\0';

			uiConsoleCommands--;
			lpConsoleCommands++;
		}
		else
		{
			// Command prompt.
			printf("%s>", hlItemGetName(pItem));

			// Get and parse line.
			fgets(lpBuffer, sizeof(lpBuffer), stdin);
		}

		SetColor(uiColor);

		i = (hlUInt)strlen(lpBuffer);
		while(i > 0 && (lpBuffer[i - 1] == '\r' || lpBuffer[i - 1] == '\n'))
		{
			i--;
			lpBuffer[i] = '\0';
		}
		
		*lpCommand = *lpArgument = 0;

		strcpy(lpCommand, lpBuffer);
		lpTemp = strchr(lpCommand, ' ');
		if(lpTemp != 0)
		{
			strcpy(lpArgument, lpTemp + 1);
			*lpTemp = 0;
		}

		// Cycle through commands.

		//
		// Directory listing.
		// Good example of CDirectoryItem::GetType().
		//
#ifdef _WIN32
		if(stricmp(lpCommand, "dir") == 0)
#else
		if(stricmp(lpCommand, "ls") == 0)
#endif
		{
			uiItemCount = hlFolderGetCount(pItem);
			uiFolderCount = 0, uiFileCount = 0;

			*lpTempBuffer = 0;
			hlItemGetPath(pItem, lpTempBuffer, sizeof(lpTempBuffer));

			printf("Directory of %s:\n", lpTempBuffer);

			printf("\n");

			if(*lpArgument == 0)
			{
				// List all items in the current folder.
				for(i = 0; i < uiItemCount; i++)
				{
					pSubItem = hlFolderGetItem(pItem, i);
					if(hlItemGetType(pSubItem) == HL_ITEM_FOLDER)
					{
						uiFolderCount++;
						printf("  <%s>\n", hlItemGetName(pSubItem));
					}
					else if(hlItemGetType(pSubItem) == HL_ITEM_FILE)
					{
						uiFileCount++;
						printf("  %s\n", hlItemGetName(pSubItem));
					}
				}
			}
			else
			{
				pSubItem = hlFolderFindFirst(pItem, lpArgument, HL_FIND_ALL | HL_FIND_NO_RECURSE);
				while(pSubItem)
				{
					if(hlItemGetType(pSubItem) == HL_ITEM_FOLDER)
					{
						uiFolderCount++;
						printf("  <%s>\n", hlItemGetName(pSubItem));
					}
					else if(hlItemGetType(pSubItem) == HL_ITEM_FILE)
					{
						uiFileCount++;
						printf("  %s\n", hlItemGetName(pSubItem));
					}

					pSubItem = hlFolderFindNext(pItem, pSubItem, lpArgument, HL_FIND_ALL | HL_FIND_NO_RECURSE);
				}
			}

			printf("\n");

			// Could also have used hlFolderGetFolderCount() and
			// hlFolderGetFileCount().

			printf("Summary:\n");
			printf("\n");
			printf("  %u Folder%s.\n", uiFolderCount, uiFolderCount != 1 ? "s" : "");
			printf("  %u File%s.\n", uiFileCount, uiFileCount != 1 ? "s" : "");
			printf("\n");
		}
		//
		// Change directory.
		// Good example of CDirectoryFolder::GetParent() and item casting.
		//
		else if(stricmp(lpCommand, "cd") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command cd supplied.\n");
			}
			else
			{
				if(stricmp(lpArgument, ".") == 0)
				{

				}
				else if(stricmp(lpArgument, "..") == 0)
				{
					if(hlItemGetParent(pItem) != 0)
					{
						pItem = hlItemGetParent(pItem);
					}
					else
					{
						printf("Folder does not have a parent.\n");
					}
				}
				else
				{
					bFound = hlFalse;
					uiItemCount = hlFolderGetCount(pItem);
					for(i = 0; i < uiItemCount; i++)
					{
						pSubItem = hlFolderGetItem(pItem, i);
						if(hlItemGetType(pSubItem) == HL_ITEM_FOLDER && stricmp(lpArgument, hlItemGetName(pSubItem)) == 0)
						{
							bFound = hlTrue;
							pItem = pSubItem;
							break;
						}
					}

					if(!bFound)
					{
						printf("%s not found.\n", lpArgument);
					}
				}
			}
		}
		//
		// Go to the root folder.
		//
		else if(stricmp(lpCommand, "root") == 0)
		{
			pItem = hlPackageGetRoot();
		}
		//
		// Item information.
		// Good example of CPackageUtility helper functions.
		//
		else if(stricmp(lpCommand, "info") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command info supplied.\n");
			}
			else
			{
				pSubItem = hlFolderGetItemByPath(pItem, lpArgument, HL_FIND_ALL);

				if(pSubItem != 0)
				{
					*lpTempBuffer = 0;
					hlItemGetPath(pSubItem, lpTempBuffer, sizeof(lpTempBuffer));

					printf("Information for %s:\n", lpTempBuffer);
					printf("\n");

					switch(hlItemGetType(pSubItem))
					{
					case HL_ITEM_FOLDER:
						printf("  Type: Folder\n");
#ifdef _WIN32
						printf("  Size: %I64u B\n", hlFolderGetSizeEx(pSubItem, hlTrue));
						printf("  Size On Disk: %I64u B\n", hlFolderGetSizeOnDiskEx(pSubItem, hlTrue));
#else
						printf("  Size: %llu B\n", hlFolderGetSizeEx(pSubItem, hlTrue));
						printf("  Size On Disk: %llu B\n", hlFolderGetSizeOnDiskEx(pSubItem, hlTrue));
#endif
						printf("  Folders: %u\n", hlFolderGetFolderCount(pSubItem, hlTrue));
						printf("  Files: %u\n", hlFolderGetFileCount(pSubItem, hlTrue));
						break;
					case HL_ITEM_FILE:
						printf("  Type: File\n");
						printf("  Extractable: %s\n", hlFileGetExtractable(pSubItem) ? "True" : "False");
						//printf("  Validates: %s\n", hlFileGetValidates(pSubItem) ? "True" : "False");
						printf("  Size: %u B\n", hlFileGetSize(pSubItem));
						printf("  Size On Disk: %u B\n", hlFileGetSizeOnDisk(pSubItem));
						break;
					default:
						break;
					}

					uiItemCount = hlPackageGetItemAttributeCount();
					for(i = 0; i < uiItemCount; i++)
					{
						if(hlPackageGetItemAttribute(pSubItem, i, &Attribute))
						{
							PrintAttribute("  ", &Attribute, "");
						}
					}

					printf("\n");
				}
				else
				{
					printf("%s not found.\n", lpArgument);
				}
			}
		}
		//
		// Extract item.
		// Good example of CPackageUtility extract functions.
		//
		else if(stricmp(lpCommand, "extract") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command extract supplied.\n");
			}
			else
			{
				if(stricmp(lpArgument, ".") == 0)
				{
					pSubItem = pItem;
				}
				else
				{
					pSubItem = hlFolderGetItemByName(pItem, lpArgument, HL_FIND_ALL);
				}

				if(pSubItem)
				{
					// Extract the item.
					// Item is extracted to cDestination\Item->GetName().
					if(!bSilent)
					{
						printf("Extracting %s...\n", hlItemGetName(pSubItem));
						printf("\n");
					}

					hlItemExtract(pSubItem, lpDestination);

					if(!bSilent)
					{
						printf("\n");
						printf("Done.\n");
					}
				}
				else
				{
					printf("%s not found.\n", lpArgument);
				}
			}
		}
		//
		// Validate item.
		// Validates the checksums of each item.
		//
		else if(stricmp(lpCommand, "validate") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command extract supplied.\n");
			}
			else
			{
				if(stricmp(lpArgument, ".") == 0)
				{
					pSubItem = pItem;
				}
				else
				{
					pSubItem = hlFolderGetItemByName(pItem, lpArgument, HL_FIND_ALL);
				}

				if(pSubItem)
				{
					if(!bSilent)
					{
						printf("Validating %s...\n", hlItemGetName(pSubItem));
						printf("\n");
					}

					Validate(pSubItem);

					if(!bSilent)
					{
						printf("\n");
						printf("Done.\n");
					}
				}
				else
				{
					printf("%s not found.\n", lpArgument);
				}
			}
		}
		//
		// Find items.
		// Good example of recursive directory navigation (Search() function).
		//
		else if(stricmp(lpCommand, "find") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command find supplied.\n");
			}
			else
			{
				// Search for the requested items.
				if(!bSilent)
				{
					printf("Searching for %s...\n", lpArgument);
					printf("\n");
				}

				uiItemCount = 0;
				pSubItem = hlFolderFindFirst(pItem, lpArgument, HL_FIND_ALL);
				while(pSubItem)
				{
					hlItemGetPath(pSubItem, lpTempBuffer, sizeof(lpTempBuffer));

					// Print the path.
					uiItemCount++;
					Print(hlItemGetType(pSubItem) == HL_ITEM_FILE ? FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY : GetColor(), "  Found %s: %s\n", hlItemGetType(pSubItem) == HL_ITEM_FOLDER ? "folder" : "file", lpTempBuffer);

					pSubItem = hlFolderFindNext(pItem, pSubItem, lpArgument, HL_FIND_ALL);
				}

				if(!bSilent)
				{
					if(uiItemCount != 0)
					{
						printf("\n");
					}

					printf("  %u item%s found.\n", uiItemCount, uiItemCount != 1 ? "s" : "");
					printf("\n");
				}
			}
		}
		//
		// Type files.
		// Good example of reading files into memory.
		//
		else if(stricmp(lpCommand, "type") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command type supplied.\n");
			}
			else
			{
				pSubItem = hlFolderGetItemByName(pItem, lpArgument, HL_FIND_FILES);

				if(pSubItem)
				{
					*lpTempBuffer = 0;
					hlItemGetPath(pSubItem, lpTempBuffer, sizeof(lpTempBuffer));

					if(!bSilent)
					{
						printf("Type for %s:\n", lpTempBuffer);
						printf("\n");
					}

					if(hlFileCreateStream(pSubItem, &pStream))
					{
						if(hlStreamOpen(pStream, HL_MODE_READ))
						{
							uiColor = GetColor();
							SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);

							while(hlStreamReadChar(pStream, &iChar))
							{
								if((iChar >= ' ' && iChar <= '~') || iChar == '\n' || iChar == '\t')
								{
									putc(iChar, stdout);
								}
							}

							SetColor(uiColor);

							hlStreamClose(pStream);
						}
						else
						{
							Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error typing %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
						}

						hlFileReleaseStream(pSubItem, pStream);
						pStream = 0;
					}
					else
					{
						Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error typing %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
					}

					if(!bSilent)
					{
						printf("\n");
						printf("Done.\n");
					}
				}
				else
				{
					printf("%s not found.\n", lpArgument);
				}
			}
		}
		//
		// Open item.
		// Good example of opening packages inside packages.
		//
		else if(stricmp(lpCommand, "open") == 0)
		{
			if(*lpArgument == 0)
			{
				printf("No argument for command open supplied.\n");
			}
			else
			{
				pSubItem = hlFolderGetItemByName(pItem, lpArgument, HL_FIND_FILES);

				if(pSubItem)
				{
					if(hlFileCreateStream(pSubItem, &pStream))
					{
						if(hlStreamOpen(pStream, HL_MODE_READ))
						{
							ePackageType = hlGetPackageTypeFromStream(pStream);

							if(hlCreatePackage(ePackageType, &uiSubPackage))
							{
								hlBindPackage(uiSubPackage);
								if(hlPackageOpenStream(pStream, HL_MODE_READ))
								{
									if(!bSilent)
										Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "%s opened.\n", hlItemGetName(pSubItem));

									EnterConsole(uiSubPackage, uiConsoleCommands, lpConsoleCommands);

									hlPackageClose();

									if(!bSilent)
										Print(FOREGROUND_GREEN | FOREGROUND_INTENSITY, "%s closed.\n", hlItemGetName(pSubItem));
								}
								else
								{
									Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error opening %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
								}

								hlDeletePackage(uiSubPackage);

								hlBindPackage(uiPackage);
							}
							else
							{
								Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error opening %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
							}

							hlStreamClose(pStream);
						}
						else
						{
							Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error opening %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
						}

						hlFileReleaseStream(pSubItem, pStream);
						pStream = 0;
					}
					else
					{
						Print(FOREGROUND_RED | FOREGROUND_INTENSITY, "Error opening %s:\n%s\n", hlItemGetName(pSubItem), hlGetString(HL_ERROR_SHORT_FORMATED));
					}
				}
				else
				{
					printf("%s not found.\n", lpArgument);
				}
			}
		}
		//
		// Clear screen.
		//
		else if(stricmp(lpCommand, "status") == 0)
		{
#ifdef _WIN32
			printf("Total size: %I64u B\n", hlGetUnsignedLongLong(HL_PACKAGE_SIZE));
#else
			printf("Total size: %llu B\n", hlGetUnsignedLongLong(HL_PACKAGE_SIZE));
#endif
			printf("Total mapping allocations: %u\n", hlGetUnsignedInteger(HL_PACKAGE_TOTAL_ALLOCATIONS));
#ifdef _WIN32
			printf("Total mapping memory allocated: %I64u B\n", hlGetUnsignedLongLong(HL_PACKAGE_TOTAL_MEMORY_ALLOCATED));
			printf("Total mapping memory used: %I64u B\n", hlGetUnsignedLongLong(HL_PACKAGE_TOTAL_MEMORY_USED));
#else
			printf("Total mapping memory allocated: %llu B\n", hlGetUnsignedLongLong(HL_PACKAGE_TOTAL_MEMORY_ALLOCATED));
			printf("Total mapping memory used: %llu B\n", hlGetUnsignedLongLong(HL_PACKAGE_TOTAL_MEMORY_USED));
#endif

			uiItemCount = hlPackageGetAttributeCount();
			for(i = 0; i < uiItemCount; i++)
			{
				if(hlPackageGetAttribute(i, &Attribute))
				{
					PrintAttribute("", &Attribute, "");
				}
			}
		}
#ifdef _WIN32
		else if(stricmp(lpCommand, "cls") == 0)
		{
			system("cls");
		}
#endif
		else if(stricmp(lpCommand, "help") == 0)
		{
			printf("Valid commands:\n");
			printf("\n");
#ifdef _WIN32
			printf("dir <filter>    (Directory list.)\n");
#else
			printf("ls <filter>     (Directory list.)\n");
#endif
			printf("cd <folder>     (Change directroy.)\n");
			printf("info <item>     (Item information.)\n");
			printf("extract <item>  (Extract item.)\n");
			printf("validate <item> (Validate item.)\n");
			printf("find <filter>   (Find item.)\n");
			printf("type <file>     (Type a file.)\n");
			printf("open <file>     (Open a nested package.)\n");
			printf("root            (Go to the root folder.)\n");
			printf("status          (Package information.)\n");
#ifdef _WIN32
			printf("cls             (Clear the screen.)\n");
#endif
			printf("help            (Program help.)\n");
			printf("exit            (Quit program.)\n");
			printf("\n");
		}
		else if(stricmp(lpCommand, "exit") == 0)
		{
			break;
		}
		else
		{
			printf("Unkown command: %s\n", lpCommand);
		}
	}
}