/*
	vfdcmd.c

	Virtual Floppy Drive for Windows
	Driver control program (console version)

	Copyright (C) 2003-2008 Ken Kato
*/

#ifdef __cplusplus
#pragma message(__FILE__": Compiled as C++ for testing purpose.")
#endif	// __cplusplus

#define WIN32_LEAN_AND_MEAN
#define _CRTDBG_MAP_ALLOC
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <crtdbg.h>

#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
#endif	// INVALID_FILE_ATTRIBUTES

#include "vfdtypes.h"
#include "vfdapi.h"
#include "vfdver.h"
#include "vfdmsg.h"

//
//	current driver state
//
static DWORD driver_state = VFD_NOT_INSTALLED;

//
//	interactive flag
//
static const char *help_progname = "VFD.EXE ";

//
//	command functions return value
//
#define VFD_OK	0
#define VFD_NG	1

//
//	operation mode
//
#define OPERATION_ASK	0		//	ask user on error
#define OPERATION_QUIT	1		//	quits on error
#define OPERATION_FORCE 2		//	force on error

//
//	invalid target number
//
#define TARGET_NONE		(ULONG)-1

//
//	command processing functions
//
typedef int (*cmdfnc)(const char **args);

static int Install(const char **args);
static int Remove(const char **args);
static int Config(const char **args);
static int Start(const char **args);
static int Stop(const char **args);
static int Shell(const char **args);
static int Open(const char **args);
static int Close(const char **args);
static int Save(const char **args);
static int Protect(const char **args);
static int Format(const char **args);
static int Link(const char **args);
static int Unlink(const char **args);
static int Status(const char **args);
static int Help(const char **args);
static int Version(const char **args);

//
//	Command table
//
static const struct {
	char	*cmd;				// command string
	int		max_args;			// maximum allowed number of argc
	cmdfnc	func;				// command processing function
	DWORD	hint;				// command hint message id
}
Commands[] = {
	{"INSTALL", 2, Install,	MSG_HINT_INSTALL},
	{"REMOVE",	1, Remove,	MSG_HINT_REMOVE	},
	{"CONFIG",	1, Config,	MSG_HINT_CONFIG	},
	{"START",	0, Start,	MSG_HINT_START	},
	{"STOP",	1, Stop,	MSG_HINT_STOP	},
	{"SHELL",	1, Shell,	MSG_HINT_SHELL	},
	{"OPEN",	6, Open,	MSG_HINT_OPEN	},
	{"CLOSE",	2, Close,	MSG_HINT_CLOSE	},
	{"SAVE",	3, Save,	MSG_HINT_SAVE,	},
	{"PROTECT", 2, Protect,	MSG_HINT_PROTECT},
	{"FORMAT",	2, Format,	MSG_HINT_FORMAT	},
	{"LINK",	3, Link,	MSG_HINT_LINK	},
	{"ULINK",	1, Unlink,	MSG_HINT_ULINK	},
	{"STATUS",	0, Status,	MSG_HINT_STATUS	},
	{"HELP",	1, Help,	MSG_HELP_HELP	},
	{"?",		1, Help,	MSG_HELP_HELP	},
	{"VERSION", 0, Version,	MSG_HINT_VERSION},
	{0, 0, 0, 0}
};

//
//	Help message table
//
static const struct {
	char	*keyword;			//	help keyword
	DWORD	help;				//	help message id
}
HelpMsg[] = {
	{"GENERAL", MSG_HELP_GENERAL},
	{"CONSOLE",	MSG_HELP_CONSOLE},
	{"INSTALL", MSG_HELP_INSTALL},
	{"REMOVE",	MSG_HELP_REMOVE	},
	{"CONFIG",	MSG_HELP_CONFIG	},
	{"START",	MSG_HELP_START	},
	{"STOP",	MSG_HELP_STOP	},
	{"SHELL",	MSG_HELP_SHELL	},
	{"OPEN",	MSG_HELP_OPEN	},
	{"CLOSE",	MSG_HELP_CLOSE	},
	{"SAVE",	MSG_HELP_SAVE	},
	{"PROTECT", MSG_HELP_PROTECT},
	{"FORMAT",	MSG_HELP_FORMAT	},
	{"LINK",	MSG_HELP_LINK	},
	{"ULINK",	MSG_HELP_ULINK	},
	{"STATUS",	MSG_HELP_STATUS	},
	{"HELP",	MSG_HELP_HELP	},
	{"VERSION", MSG_HINT_VERSION},
	{0, 0}
};

//
//	local functions
//
static int InteractiveConsole();
static int ProcessCommandLine(int argc, const char **args);
static int ParseCommand(const char *cmd);
static int ParseHelpTopic(const char *topic);
static int CheckDriver();
static int InputChar(ULONG msg, PCSTR ans);
static void PrintImageInfo(HANDLE hDevice);
static void PrintDriveLetter(HANDLE hDevice, ULONG nDrive);
static void PrintMessage(UINT msg, ...);
static BOOL ConsolePager(char *pBuffer, BOOL bReset);
static const char *SystemError(DWORD err);
static void ConvertPathCase(char *src, char *dst);

//
//	utility macro
//
#define IS_WINDOWS_NT()		((GetVersion() & 0xff) < 5)

//
//	main
//
int main(int argc, const char **argv)
{
#ifdef _DEBUG

	//	output vfd.exe command reference text

	if (*(argv + 1) && !_stricmp(*(argv + 1), "doc")) {
		int idx = 0;
		char *buf = "";

		printf("\r\n  VFD.EXE Command Reference\r\n");

		while (HelpMsg[idx].keyword) {
			int len = strlen(HelpMsg[idx].keyword);

			printf(
				"\r\n\r\n"
				"====================\r\n"
				"%*s\r\n"
				"====================\r\n"
				"\r\n",
				(20 + len) / 2, HelpMsg[idx].keyword);

			FormatMessage(
				FORMAT_MESSAGE_FROM_HMODULE |
				FORMAT_MESSAGE_ALLOCATE_BUFFER |
				FORMAT_MESSAGE_ARGUMENT_ARRAY,
				NULL, HelpMsg[idx].help, 0,
				(LPTSTR)&buf, 0, (va_list *)&help_progname);
			
			printf("%s", buf);

			LocalFree(buf);

			idx++;
		}

		return 0;
	}
#endif

	//	Reports memory leaks at process termination

	_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);

	//	Check the operating system version

	if (!VfdIsValidPlatform()) {
		PrintMessage(MSG_WRONG_PLATFORM);
		return VFD_NG;
	}

	if (argc < 2) {
		//	If no parameter is given, enter the interactive mode

		return InteractiveConsole();
	}
	else {
		//	Perform a single operation

		return ProcessCommandLine(argc - 1, argv + 1);
	}
}

//
//	VFD interactive console
//
int InteractiveConsole()
{
	char		input[1024];	//	user input buffer

	int			argc;			//	number of args in the user input
	char		*args[10];		//	args to pass to command functions

	char		sepa;			//	argument separator
	char		*p;				//	work pointer

	//	Disable the system default Ctrl+C handler

	SetConsoleCtrlHandler(NULL, TRUE);

	//	Set the console title

	SetConsoleTitle(VFD_PRODUCT_DESC);

	//	print version information and the console hint text

	Version(NULL);

	PrintMessage(MSG_CONSOLE_HINT);

	//	set interactive flag to exclude "VFD.EXE" from help text

	help_progname = "";

	//	process user input

	for (;;) {

		//	print the prompt

		printf("[VFD] ");
		fflush(stdout);

		//	read user input

		fflush(stdin);
		p = fgets(input, sizeof(input), stdin);

		if (p == NULL) {

			//	most likely <ctrl+c>

			printf("exit\n");
			break;
		}

		//	skip leading blank characters

		while (*p == ' ' || *p == '\t' || *p == '\n') {
			p++;
		}
		
		if (*p == '\0') {

			//	empty input

			continue;
		}

		//	handle external commands

		if (!_strnicmp(p, "dir", 3) ||
			!_strnicmp(p, "attrib", 6)) {

			//	special cases - frequently used commands
			//	pass these to system() even without '.'

			system(p);
			printf("\n");
			continue;
		}
		else if (*p == '.') {

			//	external command

			system(p + 1);
			printf("\n");
			continue;
		}

		//	split the input line into parameters (10 parameters max)

		argc = 0;
		ZeroMemory(args, sizeof(args));

		do {
			//	top of a parameter

			args[argc++] = p;

			//	is the parameter quoted?

			if (*p == '\"' || *p == '\'') {
				sepa = *(p++);
			}
			else {
				sepa = ' ';
			}

			//	search the end of the parameter

			while (*p && *p != '\n') {
				if (sepa == ' ') {
					if (*p == '\t' || *p == ' ') {
						break;			//	tail of a non-quoted parameter
					}
				}
				else {
					if (*p == sepa) {
						sepa = ' ';		//	close quote
					}
				}
				p++;
			}

			//	terminate the parameter

			if (*p) {
				*(p++) = '\0';
			}

			//	skip trailing blank characters

			while (*p == ' ' || *p == '\t' || *p == '\n') {
				p++;
			}

			if (*p == '\0') {

				//	end of the input line - no more args

				break;
			}
		}
		while (argc < sizeof(args) / sizeof(args[0]));

		//	check the first parameter for special commands

		if (!_stricmp(args[0], "exit") ||
			!_stricmp(args[0], "quit") ||
			!_stricmp(args[0], "bye")) {

			//	exit command

			break;
		}
		else if (!_stricmp(args[0], "cd") ||
			!_stricmp(args[0], "chdir")) {

			//	internal change directory command

			if (args[1]) {
				char path[MAX_PATH];
				int i;

				//	ignore the /d option (of the standard cd command)

				if (_stricmp(args[1], "/d")) {
					i = 1;
				}
				else {
					i = 2;
				}

				p = args[i];

				if (*p == '\"' || *p == '\'') {

					//	the parameter is quoted -- remove quotations

					p++;

					while (*p && *p != *args[i]) {
						p++;
					}

					args[i]++;		// skip a leading quote
					*p = '\0';		// remove a trailing quote
				}
				else {

					//	the parameter is not quoted
					//	-- concatenate params to allow spaces in unquoted path

					while (i < argc - 1) {
						*(args[i] + strlen(args[i])) = ' ';
						i++;
					}
				}

				//	Match the case of the path to the name on the disk
				
				ConvertPathCase(p, path);

				if (!SetCurrentDirectory(path)) {
					DWORD ret = GetLastError();

					if (ret == ERROR_FILE_NOT_FOUND) {
						ret = ERROR_PATH_NOT_FOUND;
					}

					printf("%s", SystemError(ret));
				}
			}
			else {
				if (!GetCurrentDirectory(sizeof(input), input)) {
					printf("%s", SystemError(GetLastError()));
				}
				else {
					printf("%s\n", input);
				}
			}
		}
		else if (isalpha(*args[0]) &&
			*(args[0] + 1) == ':' &&
			*(args[0] + 2) == '\0') {

			//	internal change drive command

			*args[0] = (char)toupper(*args[0]);
			*(args[0] + 2) = '\\';
			*(args[0] + 3) = '\0';

			if (!SetCurrentDirectory(args[0])) {
				printf("%s", SystemError(GetLastError()));
			}
		}
		else {

			//	perform the requested VFD command

			ProcessCommandLine(argc, (const char **)args);
		}

		printf("\n");
	}
	
	return VFD_OK;
}

//
//	process a single command
//
int ProcessCommandLine(int argc, const char **args)
{
	int		cmd;
	DWORD	ret;

	//
	//	Decide a command to perform
	//
	cmd = ParseCommand(*args);

	if (cmd < 0) {

		//	no matching command

		return VFD_NG;
	}

	if (*(++args) &&
		(!strcmp(*args, "/?") ||
		!_stricmp(*args, "/h"))) {

		//	print a short hint for the command

		PrintMessage(Commands[cmd].hint);
		return VFD_NG;
	}

	if (--argc > Commands[cmd].max_args) {

		// too many parameters for the command

		PrintMessage(MSG_TOO_MANY_ARGS);
		PrintMessage(Commands[cmd].hint);
		return VFD_NG;
	}

	//	Get the current driver state

	ret = VfdGetDriverState(&driver_state);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_STAT_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	Perform the requested operation

	return (*Commands[cmd].func)(args);
}

//
//	Install the Virtual Floppy Driver
//	Command Line Parameters:
//	(optional) driver file path	- default to executive's dir
//	(optional) auto start switch - default to demand start
//
int	Install(const char **args)
{
	const char *install_path = NULL;
	DWORD start_type = SERVICE_DEMAND_START;

	DWORD ret;

	//	process parameters

	while (args && *args) {

		if (!_stricmp(*args, "/a") ||
			!_stricmp(*args, "/auto")) {

			if (start_type != SERVICE_DEMAND_START) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}
/*
			if (IS_WINDOWS_NT()) {

				//	On Windows NT, SYSTEM start drivers must be placed
				//	under the winnt\system32 directory.  Since I don't
				//	care to handle driver file copying, I use the AUTO
				//	start method for Windows NT.

				start_type = SERVICE_AUTO_START;
			}
			else {

				//	On Windows XP, the VFD driver must be running when
				//	the shell starts -- otherwise the shell doesn't
				//	recognize the VFD drives.  Since Windows XP allows
				//	SYSTEM start drivers to be placed in any local
				//	directories, I use the SYSTEM start method here.
				//
				//	This is not an issue when the driver is started
				//	manually because in that case VFD.EXE and VFDWIN.EXE
				//	notify the shell of the VFD drives.
				//
				//	On Windows 2000 both SYSTEM and AUTO work fine.

				start_type = SERVICE_SYSTEM_START;
			}
*/
			//	On second thought -- Win2K / XP mount manager assigns
			//	arbitrary drive letters to all drives it finds during
			//	the system start up.  There is no way to prevent it
			//	until the driver is fully PnP compatible, so I'd settle
			//	for AUTO start for the time being.

			start_type = SERVICE_AUTO_START;
		}
		else if (**args == '/') {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_INSTALL, help_progname);
			return VFD_NG;
		}
		else {
			if (install_path) {
				PrintMessage(MSG_DUPLICATE_ARGS, "path");
				return VFD_NG;
			}

			install_path = *args;
		}

		args++;
	}

	//	already installed?

	if (driver_state != VFD_NOT_INSTALLED) {
		PrintMessage(MSG_DRIVER_EXISTS);
		return VFD_NG;
	}

	//	install the driver

	ret = VfdInstallDriver(
		install_path,
		start_type);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_INSTALL_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	Get the latest driver state

	ret = VfdGetDriverState(&driver_state);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_STAT_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	operation successfull

	PrintMessage(MSG_INSTALL_OK);

	return VFD_OK;
}

//
//	Remove Virtual Floppy Driver from system
//	Command Line Parameters: 
//		[/F | /FORCE | /Q | /QUIT]
//		/F	forces remove operation if the driver cannot be stopped
//		/Q	quits remove operation if the driver cannot be stopped
//
int	Remove(const char **args)
{
	int			mode = OPERATION_ASK;
	const char	*stop_params[] = { NULL, NULL };
	DWORD		ret;
	int			idx;

	//	parse parameters

	while (args && *args) {

		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;
			stop_params[0] = *args;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;
			stop_params[0] = *args;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_REMOVE, help_progname);
			return VFD_NG;
		}

		args++;
	}

	//	ensure the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure the driver is stopped

	if (driver_state == SERVICE_RUNNING) {

		//	Try to stop with the same command line option (/F or /Q)

		while (Stop(stop_params) != VFD_OK) {

			//	stop failed

			if (mode == OPERATION_FORCE) {
				PrintMessage(MSG_REMOVE_FORCE);
				break;
			}
			else if (mode == OPERATION_QUIT) {
				PrintMessage(MSG_REMOVE_QUIT);
				return VFD_NG;
			}
			else {
				int c;

				PrintMessage(MSG_REMOVE_WARN);

				c = InputChar(MSG_RETRY_FORCE_CANCEL, "rfc");

				if (c == 'f') {			//	force
					break;
				}
				else if (c == 'c') {	//	cancel
					return VFD_NG;
				}
			}
		}
	}

	//	remove the driver

	ret = VfdRemoveDriver();

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_REMOVE_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	// Wait for the driver to be actually removed for 3 secs Max.

	for (idx = 0; idx < 10; idx++) {

		ret = VfdGetDriverState(&driver_state);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_STAT_NG);
			printf("%s", SystemError(ret));
			return VFD_NG;
		}

		if (driver_state == VFD_NOT_INSTALLED) {
			break;
		}

		Sleep(300);
	}

	if (driver_state != VFD_NOT_INSTALLED) {
		PrintMessage(MSG_REMOVE_PENDING);
		return VFD_NG;
	}

	//	operation successful

	PrintMessage(MSG_REMOVE_OK);

	return VFD_OK;
}

//
//	Configure the Virtual Floppy Driver
//	Command Line Parameters:
//	/auto, /manual
//
int	Config(const char **args)
{
	DWORD	start_type = SERVICE_DISABLED;
	DWORD	ret;

	while (args && *args) {
		if (!_stricmp(*args, "/a") ||
			!_stricmp(*args, "/auto")) {

			if (start_type != SERVICE_DISABLED) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			start_type = SERVICE_AUTO_START;
		}
		else if (!_stricmp(*args, "/m") ||
			!_stricmp(*args, "/manual")) {

			if (start_type != SERVICE_DISABLED) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			start_type = SERVICE_DEMAND_START;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_CONFIG, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (start_type == SERVICE_DISABLED) {
		//	no parameter is specified
		PrintMessage(MSG_HINT_CONFIG, help_progname);
		return VFD_NG;
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	configure the driver

	ret = VfdConfigDriver(start_type);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_CONFIG_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	operation successfull

	PrintMessage(MSG_CONFIG_OK);

	return VFD_OK;
}

//
//	Start the Virtual Floppy Driver
//	Command Line Parameters: None
//
int	Start(const char **args)
{
	DWORD	ret;

	UNREFERENCED_PARAMETER(args);

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED &&
		Install(NULL) != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is not running

	if (driver_state == SERVICE_RUNNING) {
		PrintMessage(MSG_ALREADY_RUNNING);
		return VFD_NG;
	}

	//	start the driver

	ret = VfdStartDriver(&driver_state);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_START_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	operation successfull

	PrintMessage(MSG_START_OK);

	return VFD_OK;
}

//
//	Stop the Virtual Floppy Driver
//	Command Line Parameters:
//		/FORCE | /F		Forces the operation on error
//		/QUIT  | /Q		Quits the operation on error
//
int	Stop(const char **args)
{
	int			mode = OPERATION_ASK;
	const char	*close_params[] = { "*", NULL, NULL };
	DWORD		ret;

	while (args && *args) {
		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;

			//	parameter to pass to the Close() function
			close_params[1] = *args;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;

			//	parameter to pass to the Close() function
			close_params[1] = *args;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_STOP, help_progname);
			return VFD_NG;
		}

		args++;
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state == SERVICE_STOPPED) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	//	ensure that all drives are empty

	if (driver_state == SERVICE_RUNNING) {

		//	Try to close drives with the same operation mode (/F or /Q)

		while (Close(close_params) != VFD_OK) {

			//	close failed

			if (mode == OPERATION_FORCE) {
				PrintMessage(MSG_STOP_FORCE);
				break;
			}
			else if (mode == OPERATION_QUIT) {
				PrintMessage(MSG_STOP_QUIT);
				return VFD_NG;
			}
			else {
				int c;

				PrintMessage(MSG_STOP_WARN);

				c = InputChar(MSG_RETRY_FORCE_CANCEL, "rfc");

				if (c == 'f') {			//	force
					break;
				}
				else if (c == 'c') {	//	cancel
					return VFD_NG;
				}
			}
		}
	}

	//	stop the driver

	ret = VfdStopDriver(&driver_state);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_STOP_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	if (driver_state != SERVICE_STOPPED) {
		PrintMessage(MSG_STOP_PENDING);
		return VFD_NG;
	}

	//	operation successful

	PrintMessage(MSG_STOP_OK);

	return VFD_OK;
}

//
//	Enable / Disable the shell extension
//	Command Line Parameters:
//	(optional) /ON or /OFF
//
int Shell(const char **args)
{
	DWORD ret;

	ret = VfdCheckHandlers();

	if (ret != ERROR_SUCCESS &&
		ret != ERROR_PATH_NOT_FOUND &&
		ret != ERROR_FILE_NOT_FOUND) {
		PrintMessage(MSG_GET_SHELLEXT_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	if (args && *args) {
		if (_stricmp(*args, "/on") == 0) {
			if (ret != ERROR_SUCCESS) {
				ret = VfdRegisterHandlers();

				if (ret != ERROR_SUCCESS) {
					PrintMessage(MSG_SET_SHELLEXT_NG);
					printf("%s", SystemError(ret));
					return VFD_NG;
				}
			}
		}
		else if (_stricmp(*args, "/off") == 0) {
			if (ret == ERROR_SUCCESS) {
				ret = VfdUnregisterHandlers();

				if (ret != ERROR_SUCCESS) {
					PrintMessage(MSG_SET_SHELLEXT_NG);
					printf("%s", SystemError(ret));
					return VFD_NG;
				}
			}
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_SHELL, help_progname);
			return VFD_NG;
		}

		ret = VfdCheckHandlers();
	}

	if (ret == ERROR_PATH_NOT_FOUND ||
		ret == ERROR_FILE_NOT_FOUND) {
		PrintMessage(MSG_SHELLEXT_DISABLED);
	}
	else if (ret == ERROR_SUCCESS) {
		PrintMessage(MSG_SHELLEXT_ENABLED);
	}
	else {
		PrintMessage(MSG_GET_SHELLEXT_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	return VFD_OK;
}

//
//	Open an image file to a Virtual Floppy Drive
//	Command Line Parameters:
//	[drive:] [file] [/NEW] [/RAM] [/P | /W]
//	[/size] [/media] [/F | /FORCE | /Q | /QUIT]

int	Open(const char **args)
{
	int				mode		= OPERATION_ASK;
	BOOL			create		= FALSE;
	ULONG			target		= TARGET_NONE;
	PCSTR			file_name	= NULL;
	VFD_DISKTYPE	disk_type	= VFD_DISKTYPE_FILE;
	CHAR			protect		= '\0';
	VFD_MEDIA		media_type	= VFD_MEDIA_NONE;
	BOOL			five_inch	= FALSE;
	VFD_FLAGS		media_flags	= 0;
	HANDLE			hDevice;
	CHAR			letter;
	DWORD			ret;

	//	process parameters

	while (args && *args) {

		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;
		}

		else if (!_stricmp(*args, "/new")) {

			if (create) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			create = TRUE;
		}

		//	Disk type options

		else if (_stricmp(*args, "/ram") == 0) {

			if (disk_type != VFD_DISKTYPE_FILE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			disk_type = VFD_DISKTYPE_RAM;
		}

		//	Protect options
		else if (_stricmp(*args, "/p") == 0 ||
			_stricmp(*args, "/w") == 0) {

			if (protect) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			protect = (CHAR)toupper(*(*args + 1));
		}

		//	media size options

		else if (strcmp(*args, "/160") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F5_160;
		}
		else if (strcmp(*args, "/180") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F5_180;
		}
		else if (strcmp(*args, "/320") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F5_320;
		}
		else if (strcmp(*args, "/360") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F5_360;
		}
		else if (strcmp(*args, "/640") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_640;
		}
		else if (strcmp(*args, "/720") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_720;
		}
		else if (strcmp(*args, "/820") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_820;
		}
		else if (strcmp(*args, "/120") == 0 ||
			strcmp(*args, "/1.20") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_1P2;
		}
		else if (strcmp(*args, "/144") == 0 ||
			strcmp(*args, "/1.44") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_1P4;
		}
		else if (strcmp(*args, "/168") == 0 ||
			strcmp(*args, "/1.68") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_1P6;
		}
		else if (strcmp(*args, "/172") == 0 ||
			strcmp(*args, "/1.72") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_1P7;
		}
		else if (strcmp(*args, "/288") == 0 ||
			strcmp(*args, "/2.88") == 0) {
			if (media_type) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			media_type = VFD_MEDIA_F3_2P8;
		}

		//	5.25 inch media

		else if (strcmp(*args, "/5") == 0 ||
			strcmp(*args, "/525") == 0 ||
			strcmp(*args, "/5.25") == 0) {

			if (five_inch) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			five_inch = TRUE;
		}

		//	target option

		else if (isalnum(**args) &&
			*(*args + 1) == ':' &&
			*(*args + 2) == '\0') {

			if (target != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			target = toupper(**args);
		}

		//	filename

		else if (**args != '/') {
			if (file_name) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			file_name = *args;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_OPEN, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target == TARGET_NONE) {
		// default target
		target = '0';
		PrintMessage(MSG_TARGET_NOTICE, target);
	}

	//	check target file

	if (file_name) {
		DWORD			file_attr;
		VFD_FILETYPE	file_type;
		ULONG			image_size;
		BOOL			overwrite = FALSE;

		ret = VfdCheckImageFile(
			file_name, &file_attr, &file_type, &image_size);

		if (ret == ERROR_FILE_NOT_FOUND) {
			
			//	the target file does not exist
			
			if (!create) {				// create option not specified

				if (mode == OPERATION_FORCE) {
					PrintMessage(MSG_CREATE_NOTICE);
				}
				else {
					printf("%s", SystemError(ret));

					if (mode == OPERATION_QUIT ||
						InputChar(MSG_CREATE_CONFIRM, "yn") == 'n') {
						return VFD_NG;
					}
				}

				create = TRUE;
			}
		}
		else if (ret == ERROR_SUCCESS) {
			
			//	the target file exists

			if (create) {				//	create option is specified

				if (mode == OPERATION_FORCE) {
					PrintMessage(MSG_OVERWRITE_NOTICE);
				}
				else {
					printf("%s", SystemError(ERROR_FILE_EXISTS));

					if (mode == OPERATION_QUIT ||
						InputChar(MSG_OVERWRITE_CONFIRM, "yn") == 'n') {
						return VFD_NG;
					}
				}

				overwrite = TRUE;
			}
		}
		else {
			PrintMessage(MSG_OPEN_NG, file_name);
			printf("%s", SystemError(ret));
			return VFD_NG;
		}

		//
		//	create or overwrite the target file
		//

		if (create) {

			if (media_type == VFD_MEDIA_NONE) {

				if (mode == OPERATION_FORCE) {
					PrintMessage(MSG_CREATE144_NOTICE);
				}
				else {
					PrintMessage(MSG_FILE_MEDIA_UNKNOWN);

					if (mode == OPERATION_QUIT ||
						InputChar(MSG_CREATE144_CONFIRM, "yn") == 'n') {
						return VFD_NG;
					}
				}

				media_type = VFD_MEDIA_F3_1P4;
			}

			ret = VfdCreateImageFile(
				file_name, media_type, VFD_FILETYPE_RAW, overwrite);

			if (ret != ERROR_SUCCESS) {
				PrintMessage(MSG_CREATE_NG, file_name);
				printf("%s", SystemError(ret));
				return VFD_NG;
			}

			PrintMessage(MSG_FILE_CREATED);

			ret = VfdCheckImageFile(
				file_name, &file_attr, &file_type, &image_size);

			if (ret != ERROR_SUCCESS) {
				PrintMessage(MSG_OPEN_NG, file_name);
				printf("%s", SystemError(ret));
				return VFD_NG;
			}
		}
		else {
			//
			//	use the existing target file
			//	check image size and the media type
			//

			VFD_MEDIA	def_media;	//	default media for image size
			ULONG		media_size;	//	specified media size

			media_size	= VfdGetMediaSize(media_type);

			if (media_size > image_size) {

				//	specified media is too large for the image

				PrintMessage(MSG_IMAGE_TOO_SMALL);
				return VFD_NG;
			}

			def_media	= VfdLookupMedia(image_size);

			if (def_media == VFD_MEDIA_NONE) {

				//	image is too small for the smallest media

				PrintMessage(MSG_IMAGE_TOO_SMALL);
				return VFD_NG;
			}

			if (media_type == VFD_MEDIA_NONE) {

				//	media type is not specified

				ULONG def_size = VfdGetMediaSize(def_media);

				if (def_size != image_size) {

					//	image size does not match the largest media size

					PrintMessage(MSG_NO_MATCHING_MEDIA, image_size);

					if (mode == OPERATION_FORCE) {
						PrintMessage(MSG_MEDIATYPE_NOTICE, 
							VfdMediaTypeName(def_media), def_size);
					}
					else if (mode == OPERATION_QUIT) {
						return VFD_NG;
					}
					else {
						PrintMessage(MSG_MEDIATYPE_SUGGEST,
							VfdMediaTypeName(def_media), def_size);

						if (InputChar(MSG_MEDIATYPE_CONFIRM, "yn") == 'n') {
							return VFD_NG;
						}
					}
				}

				media_type = def_media;
			}
		}

		//	check file attributes against the disk type

		if (file_type == VFD_FILETYPE_ZIP ||
			(file_attr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ENCRYPTED))) {

			if (disk_type != VFD_DISKTYPE_RAM) {

				if (mode == OPERATION_FORCE) {
					PrintMessage(MSG_RAM_MODE_NOTICE);
				}
				else {
					PrintMessage(MSG_RAM_MODE_ONLY);

					if (mode == OPERATION_QUIT ||
						InputChar(MSG_RAM_MODE_CONFIRM, "yn") == 'n') {
						return VFD_NG;
					}
				}

				disk_type = VFD_DISKTYPE_RAM;
			}
		}

		if (disk_type != VFD_DISKTYPE_FILE) {
			if (!protect) {
				PrintMessage(MSG_DEFAULT_PROTECT);
				protect = 'P';
			}
		}
	}
	else {
		//
		//	pure RAM disk
		//
		disk_type = VFD_DISKTYPE_RAM;

		if (media_type == VFD_MEDIA_NONE) {

			if (mode == OPERATION_FORCE) {
				PrintMessage(MSG_CREATE144_NOTICE);
			}
			else {
				PrintMessage(MSG_RAM_MEDIA_UNKNOWN);

				if (mode == OPERATION_QUIT ||
					InputChar(MSG_CREATE144_CONFIRM, "yn") == 'n') {
					return VFD_NG;
				}
			}

			media_type = VFD_MEDIA_F3_1P4;
		}
	}

	if (protect == 'P') {
		media_flags |= VFD_FLAG_WRITE_PROTECTED;
	}

	if (five_inch &&
		VfdGetMediaSize(media_type) ==
		VfdGetMediaSize((VFD_MEDIA)(media_type + 1))) {
		media_type = (VFD_MEDIA)(media_type + 1);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED &&
		Install(NULL) != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING &&
		Start(NULL) != VFD_OK) {
		return VFD_NG;
	}

	//	Open the target device

	hDevice = VfdOpenDevice(target);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();
		PrintMessage(MSG_ACCESS_NG, target);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	Ensure that the drive is empty

	ret = VfdGetMediaState(hDevice);

	if (ret != ERROR_NOT_READY) {
		if (ret == ERROR_SUCCESS ||
			ret == ERROR_WRITE_PROTECT) {
			PrintMessage(MSG_DRIVE_BUSY);
		}
		else {
			PrintMessage(MSG_GET_MEDIA_NG);
			printf("%s", SystemError(ret));
		}

		CloseHandle(hDevice);
		return VFD_NG;
	}

	//	Open the image file

	ret = VfdOpenImage(hDevice, file_name,
		disk_type, media_type, media_flags);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_OPEN_NG, file_name ? file_name : "<RAM>");
		printf("%s", SystemError(ret));

		CloseHandle(hDevice);
		return VFD_NG;
	}

	//	assign a drive letter if the drive has none

	VfdGetGlobalLink(hDevice, &letter);

	if (!isalpha(letter)) {
		VfdGetLocalLink(hDevice, &letter);
	}

	if (!isalpha(letter)) {
		VfdSetLocalLink(hDevice, VfdChooseLetter());
	}

	//	Get the actually opened image information.

	PrintImageInfo(hDevice);

	CloseHandle(hDevice);

	return VFD_OK;
}

//
//	Close the current virtual floppy image
//	Command Line Parameters:
//		drive number or drive letter
//		/F | /FORCE | /Q | /QUIT
//
int	Close(const char **args)
{
	ULONG			mode = OPERATION_ASK;

	ULONG			target_min = TARGET_NONE;
	ULONG			target_max = TARGET_NONE;
	HANDLE			hDevice;

	VFD_MEDIA		media_type;
	VFD_FLAGS		media_flags;

	DWORD			ret;

	//	check parameterS

	while (args && *args) {

		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;
		}
		else if ((isalnum(**args) || **args == '*') &&
			(*(*args + 1) == ':' || *(*args + 1) == '\0')) {

			if (target_min != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			if (**args == '*') {
				target_min = '0';
				target_max = '0' + VFD_MAXIMUM_DEVICES;
			}
			else {
				target_min = toupper(**args);
				target_max = target_min + 1;
			}
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_CLOSE, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target_min == TARGET_NONE) {
		// default target = drive 0
		target_min = '0';
		target_max = '1';
		PrintMessage(MSG_TARGET_NOTICE, target_min);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	//	Close the drive(s)

	while (target_min < target_max) {

		//	open the target device

		hDevice = VfdOpenDevice(target_min);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();

			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));

			if (mode != OPERATION_FORCE) {
				return VFD_NG;
			}

			target_min++;
			continue;
		}

		//	get the current image information

		ret = VfdGetImageInfo(hDevice, NULL, NULL,
			&media_type, &media_flags, NULL, NULL);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));

			CloseHandle(hDevice);

			if (mode != OPERATION_FORCE) {
				return VFD_NG;
			}

			target_min++;
			continue;
		}

		if (media_type == VFD_MEDIA_NONE) {

			//	drive is empty

			CloseHandle(hDevice);
			target_min++;
			continue;
		}

		if (media_flags & VFD_FLAG_DATA_MODIFIED) {

			//	RAM disk data is modified

			PrintMessage(MSG_MEDIA_MODIFIED, target_min);

			if (mode == OPERATION_FORCE) {
				PrintMessage(MSG_CLOSE_FORCE);
			}
			else if (mode == OPERATION_QUIT) {
				PrintMessage(MSG_CLOSE_QUIT);
				CloseHandle(hDevice);
				return VFD_NG;
			}
			else {
				if (InputChar(MSG_CLOSE_CONFIRM, "yn") == 'n') {
					CloseHandle(hDevice);
					return VFD_NG;
				}
			}
		}

retry:
		ret = VfdCloseImage(
			hDevice, (mode == OPERATION_FORCE));
			
		if (ret == ERROR_ACCESS_DENIED) {

			PrintMessage(MSG_LOCK_NG, target_min);

			if (mode == OPERATION_QUIT) {
				CloseHandle(hDevice);
				return VFD_NG;
			}
			else if (mode == OPERATION_ASK) {

				int c;

				if (IS_WINDOWS_NT()) {
					c = InputChar(MSG_RETRY_CANCEL, "rc");
				}
				else {
					c = InputChar(MSG_RETRY_FORCE_CANCEL, "rfc");
				}

				if (c == 'f') {				//	force
					ret = VfdCloseImage(hDevice, TRUE);
				}
				else if (c == 'c') {		//	cancel
					CloseHandle(hDevice);
					return VFD_NG;
				}
				else {
					goto retry;
				}
			}
		}

		CloseHandle(hDevice);

		if (ret == ERROR_SUCCESS) {
			PrintMessage(MSG_CLOSE_OK, target_min);
		}
		else if (ret != ERROR_NOT_READY) {
			PrintMessage(MSG_CLOSE_NG, target_min);
			printf("%s", SystemError(ret));

			if (mode != OPERATION_FORCE) {
				return VFD_NG;
			}
		}

		target_min++;
	}

	return VFD_OK;
}

//
//	Save the current image into a file
//
int	Save(const char **args)
{
	int				mode		= OPERATION_ASK;
	ULONG			target		= TARGET_NONE;
	CHAR			file_name[MAX_PATH]	= {0};
	BOOL			overwrite	= FALSE;
	BOOL			truncate	= FALSE;

	HANDLE			hDevice;
	CHAR			current[MAX_PATH] = {0};
	VFD_MEDIA		media_type;
	VFD_FLAGS		media_flags;
	VFD_FILETYPE	file_type;
	DWORD			file_attr;
	ULONG			image_size;
	DWORD			ret;

	//	check parameters

	while (args && *args) {

		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;
		}
		else if (!_stricmp(*args, "/o") ||
			!_stricmp(*args, "/over")) {

			if (truncate || overwrite) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			overwrite = TRUE;
		}
		else if (!_stricmp(*args, "/t") ||
			!_stricmp(*args, "/trunc")) {

			if (truncate || overwrite) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			truncate = TRUE;
		}
		else if (isalnum(**args) &&
			*(*args + 1) == ':' &&
			*(*args + 2) == '\0') {

			if (target != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			target = toupper(**args);
		}
		else if (**args == '/') {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_SAVE, help_progname);
			return VFD_NG;
		}
		else {
			strcpy(file_name, *args);
		}

		args++;
	}

	if (target == TARGET_NONE) {
		target = '0';
		PrintMessage(MSG_TARGET_NOTICE, target);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	//	Open the target device

	hDevice = VfdOpenDevice(target);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();
		PrintMessage(MSG_ACCESS_NG, target);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	Get the current image info

	ret = VfdGetImageInfo(hDevice, current, NULL,
		&media_type, &media_flags, NULL, NULL);

	if (ret != ERROR_SUCCESS) {
		printf("%s", SystemError(ret));
		CloseHandle(hDevice);
		return VFD_NG;
	}

	if (media_type == VFD_MEDIA_NONE) {
		printf("%s", SystemError(ERROR_NOT_READY));
		CloseHandle(hDevice);
		return VFD_NG;
	}

	if (file_name[0] == '\0') {

		if (current[0] == '\0') {

			PrintMessage(MSG_TARGET_REQUIRED);
			CloseHandle(hDevice);

			return VFD_NG;
		}

		strcpy(file_name, current);
	}

	if (!_stricmp(file_name, current)) {

		//	target is the current image file

		if (!(media_flags & VFD_FLAG_DATA_MODIFIED)) {

			//	FILE disk (always up to date) or RAM disk is not modified

			PrintMessage(MSG_TARGET_UP_TO_DATE);
			CloseHandle(hDevice);

			return VFD_OK;
		}

		overwrite = TRUE;
	}

	//	check target file

	ret = VfdCheckImageFile(file_name,
		&file_attr, &file_type, &image_size);

	if (ret == ERROR_SUCCESS) {
		
		if (!overwrite && !truncate) {

			if (mode == OPERATION_FORCE) {
				PrintMessage(MSG_OVERWRITE_NOTICE);
				overwrite = TRUE;
			}
			else if (mode == OPERATION_QUIT) {
				printf("%s", SystemError(ERROR_FILE_EXISTS));
				CloseHandle(hDevice);

				return VFD_NG;
			}
			else {
				int c;

				printf("%s", SystemError(ERROR_FILE_EXISTS));

				c = InputChar(MSG_OVERWRITE_PROMPT, "otc");

				if (c == 'o') {
					overwrite = TRUE;
				}
				else if (c == 't') {
					truncate = TRUE;
				}
				else {
					CloseHandle(hDevice);
					return VFD_NG;
				}
			}
		}
	}
	else if (ret != ERROR_FILE_NOT_FOUND) {

		printf("%s", SystemError(ret));
		CloseHandle(hDevice);

		return VFD_NG;
	}

	if (file_type == VFD_FILETYPE_ZIP) {

		//	Cannot update a zip file

		PrintMessage(MSG_TARGET_IS_ZIP);
		CloseHandle(hDevice);

		return VFD_NG;
	}

retry:
	ret = VfdDismountVolume(
		hDevice, (mode == OPERATION_FORCE));

	if (ret == ERROR_ACCESS_DENIED) {

		PrintMessage(MSG_LOCK_NG, target);

		if (mode == OPERATION_FORCE) {
			PrintMessage(MSG_SAVE_FORCE);
		}
		else if (mode == OPERATION_QUIT) {
			PrintMessage(MSG_SAVE_QUIT);
			CloseHandle(hDevice);
			return VFD_NG;
		}
		else {
			int c = InputChar(MSG_RETRY_FORCE_CANCEL, "rfc");

			if (c == 'r') {			// retry
				goto retry;
			}
			else if (c == 'f') {	//	force
				VfdDismountVolume(hDevice, TRUE);
			}
			else {					//	cancel
				CloseHandle(hDevice);
				return VFD_NG;
			}
		}
	}
	else if (ret != ERROR_SUCCESS) {
		printf("%s", SystemError(ret));
		CloseHandle(hDevice);
		return VFD_NG;
	}

	ret = VfdSaveImage(hDevice, file_name,
		(overwrite || truncate), truncate);

	CloseHandle(hDevice);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_SAVE_NG, target, file_name);
		printf("%s", SystemError(ret));

		return VFD_NG;
	}

	PrintMessage(MSG_SAVE_OK, target, file_name);

	return VFD_OK;
}

//
//	Enable/disable virtual media write protection
//
int	Protect(const char **args)
{
#define PROTECT_NONE	0
#define PROTECT_ON		1
#define PROTECT_OFF		2
	ULONG	protect	= PROTECT_NONE;
	ULONG	target	= TARGET_NONE;
	HANDLE	hDevice;
	DWORD	ret;

	//	check parameters

	while (args && *args) {

		//	Disk type options

		if (_stricmp(*args, "/on") == 0) {

			if (protect) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			protect = PROTECT_ON;
		}
		else if (_stricmp(*args, "/off") == 0) {

			if (protect) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			protect = PROTECT_OFF;
		}
		else if (isalnum(**args)) {
			
			if (target != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			target = toupper(**args);
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_PROTECT, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target == TARGET_NONE) {
		target = '0';
		PrintMessage(MSG_TARGET_NOTICE, target);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	//	open the target drive

	hDevice = VfdOpenDevice(target);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();
		PrintMessage(MSG_ACCESS_NG, target);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	if (protect) {
		//	change protect state

		ret = VfdWriteProtect(
			hDevice, (protect == PROTECT_ON));

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_PROTECT_NG, target);
			printf("%s", SystemError(ret));

			CloseHandle(hDevice);
			return VFD_NG;
		}
	}

	//	get the current protect state

	ret = VfdGetMediaState(hDevice);

	CloseHandle(hDevice);

	if (ret == ERROR_SUCCESS) {
		PrintMessage(MSG_MEDIA_WRITABLE);
	}
	else if (ret == ERROR_WRITE_PROTECT) {
		PrintMessage(MSG_MEDIA_PROTECTED);
	}
	else {
		PrintMessage(MSG_GET_MEDIA_NG);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	return VFD_OK;
}

//
//	Format the virtual media with FAT12
//
int	Format(const char **args)
{
	int		mode = OPERATION_ASK;
	ULONG	target	= TARGET_NONE;
	HANDLE	hDevice;
	DWORD	ret;

	//	check parameters

	while (args && *args) {

		if (!_stricmp(*args, "/f") ||
			!_stricmp(*args, "/force")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_FORCE;
		}
		else if (!_stricmp(*args, "/q") ||
			!_stricmp(*args, "/quit")) {

			if (mode != OPERATION_ASK) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			mode = OPERATION_QUIT;
		}
		else if (isalnum(**args)) {
			if (target != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			target = toupper(**args);
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_FORMAT, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target == TARGET_NONE) {
		target = '0';
		PrintMessage(MSG_TARGET_NOTICE, target);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	//	Open the device

	hDevice = VfdOpenDevice(target);

	if (hDevice == INVALID_HANDLE_VALUE) {
		ret = GetLastError();
		PrintMessage(MSG_ACCESS_NG, target);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	check if the media is writable

	ret = VfdGetMediaState(hDevice);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_FORMAT_NG, target);
		printf("%s", SystemError(ret));

		CloseHandle(hDevice);
		return VFD_NG;
	}

	//	format the media

retry:
	ret = VfdDismountVolume(
		hDevice, (mode == OPERATION_FORCE));

	if (ret == ERROR_ACCESS_DENIED) {

		PrintMessage(MSG_LOCK_NG, target);

		if (mode == OPERATION_FORCE) {
			PrintMessage(MSG_FORMAT_FORCE);
		}
		else if (mode == OPERATION_QUIT) {
			PrintMessage(MSG_FORMAT_QUIT);
			CloseHandle(hDevice);
			return VFD_NG;
		}
		else {
			int c = InputChar(MSG_RETRY_FORCE_CANCEL, "rfc");

			if (c == 'r') {			// retry
				goto retry;
			}
			else if (c == 'f') {	//	force
				VfdDismountVolume(hDevice, TRUE);
			}
			else {					//	cancel
				CloseHandle(hDevice);
				return VFD_NG;
			}
		}
	}
	else if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_LOCK_NG, target);
		CloseHandle(hDevice);
		return VFD_NG;
	}

	ret = VfdFormatMedia(hDevice);

	CloseHandle(hDevice);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_FORMAT_NG, target);
		printf("%s", SystemError(ret));
		return VFD_NG;
	}

	//	successful operation

	PrintMessage(MSG_FORMAT_OK);

	return VFD_OK;
}

//
//	Assign a drive letter to a Virtual Floppy Drive
//
int Link(const char **args)
{
	ULONG	target_min = TARGET_NONE;
	ULONG	target_max = TARGET_NONE;
	PCSTR	letters = NULL;
	BOOL	global	= TRUE;
	HANDLE	hDevice;
	DWORD	ret;

	while (args && *args) {
		if (!_stricmp(*args, "/g")) {
			global = TRUE;
		}
		else if (!_stricmp(*args, "/l")) {
			global = FALSE;
		}
		else if (isdigit(**args) || **args == '*') {
			if (target_min != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			if (**args == '*') {
				target_min = '0';
				target_max = '0' + VFD_MAXIMUM_DEVICES;
			}
			else {
				target_min = **args;
				target_max = target_min + 1;
			}
		}
		else if (isalpha(**args)) {
			if (letters) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}
			letters = *args;
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_LINK, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target_min == TARGET_NONE) {
		// default: drive 0
		target_min = '0';
		target_max = '1';
		PrintMessage(MSG_TARGET_NOTICE, target_min);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	while (target_min < target_max) {
		ULONG number;
		CHAR letter;

		hDevice = VfdOpenDevice(target_min);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();
			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));
			target_min++;
			continue;
		}

		ret = VfdGetDeviceNumber(hDevice, &number);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));
			CloseHandle(hDevice);
			target_min++;
			continue;
		}

		if (letters && isalpha(*letters)) {
			letter = (CHAR)toupper(*(letters++));
		}
		else {
			letter = VfdChooseLetter();
		}

		if (letter) {
			if (global) {
				ret = VfdSetGlobalLink(hDevice, letter);
			}
			else {
				ret = VfdSetLocalLink(hDevice, letter);
			}

			if (ret != ERROR_SUCCESS) {
				PrintMessage(MSG_LINK_NG, number, letter);
				printf("%s", SystemError(ret));
			}
		}
		else {
			PrintMessage(MSG_LINK_FULL);
		}

		PrintDriveLetter(hDevice, number);

		CloseHandle(hDevice);

		target_min++;
	}

	return VFD_OK;
}

//
//	Remove a drive letter from a Virtual Floppy Drive
//
int Unlink(const char **args)
{
	ULONG	target_min = TARGET_NONE;
	ULONG	target_max = TARGET_NONE;
	HANDLE	hDevice;
	DWORD	ret;

	while (args && *args) {
		if ((isalnum(**args) || **args == '*') &&
			(*(*args + 1) == ':' || *(*args + 1) == '\0')) {

			if (target_min != TARGET_NONE) {
				PrintMessage(MSG_DUPLICATE_ARGS, *args);
				return VFD_NG;
			}

			if (**args == '*') {
				target_min = '0';
				target_max = '0' + VFD_MAXIMUM_DEVICES;
			}
			else {
				target_min = **args;
				target_max = target_min + 1;
			}
		}
		else {
			PrintMessage(MSG_UNKNOWN_OPTION, *args);
			PrintMessage(MSG_HINT_ULINK, help_progname);
			return VFD_NG;
		}

		args++;
	}

	if (target_min == TARGET_NONE) {
		// default: drive 0
		target_min = '0';
		target_max = '1';
		PrintMessage(MSG_TARGET_NOTICE, target_min);
	}

	//	ensure that the driver is installed

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
		return VFD_NG;
	}

	//	ensure that the driver is up to date

	if (CheckDriver() != VFD_OK) {
		return VFD_NG;
	}

	//	ensure that the driver is running

	if (driver_state != SERVICE_RUNNING) {
		PrintMessage(MSG_NOT_STARTED);
		return VFD_NG;
	}

	while (target_min < target_max) {
		ULONG number;

		hDevice = VfdOpenDevice(target_min);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();
			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));
			target_min++;
			continue;
		}

		ret = VfdGetDeviceNumber(hDevice, &number);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_ACCESS_NG, target_min);
			printf("%s", SystemError(ret));
			CloseHandle(hDevice);
			target_min++;
			continue;
		}

		VfdSetGlobalLink(hDevice, 0);
		VfdSetLocalLink(hDevice, 0);

		PrintDriveLetter(hDevice, number);

		CloseHandle(hDevice);

		target_min++;
	}

	return VFD_OK;
}

//
//	Print current driver state
//	Command Line Parameters: None
//
int	Status(const char **args)
{
	HANDLE	hDevice;
	TCHAR	path[MAX_PATH];
	DWORD	start_type;
	DWORD	version;
	ULONG	target;
	DWORD	ret;

	UNREFERENCED_PARAMETER(args);

	if (driver_state == VFD_NOT_INSTALLED) {
		PrintMessage(MSG_NOT_INSTALLED);
	}
	else {

		//	get current driver config

		ret = VfdGetDriverConfig(path, &start_type);

		if (ret != ERROR_SUCCESS) {
			PrintMessage(MSG_GET_CONFIG_NG);
			printf("%s", SystemError(ret));
			return VFD_NG;
		}

		//	print driver file path

		PrintMessage(MSG_DRIVER_FILE, path);

		//	print driver version
		version = 0;

		if (driver_state == SERVICE_RUNNING) {

			hDevice = VfdOpenDevice(0);

			if (hDevice != INVALID_HANDLE_VALUE) {
				ret = VfdGetDriverVersion(hDevice, &version);

				CloseHandle(hDevice);
			}

		}

		if (version == 0) {
			ret = VfdCheckDriverFile(path, &version);
		}

		if (ret == ERROR_SUCCESS) {
			PrintMessage(MSG_DRIVER_VERSION,
				HIWORD(version) & 0x7fff,
				LOWORD(version),
				(version & 0x80000000) ? "(debug)" : "");
		}
		else {
			PrintMessage(MSG_GET_VERSION_NG);
			printf("%s", SystemError(ret));
		}


		//	print driver start type

		PrintMessage(MSG_START_TYPE);

		switch (start_type) {
		case SERVICE_AUTO_START:
			PrintMessage(MSG_START_AUTO);
			break;

		case SERVICE_BOOT_START:
			PrintMessage(MSG_START_BOOT);
			break;

		case SERVICE_DEMAND_START:
			PrintMessage(MSG_START_DEMAND);
			break;

		case SERVICE_DISABLED:
			PrintMessage(MSG_START_DISABLED);
			break;

		case SERVICE_SYSTEM_START :
			PrintMessage(MSG_START_SYSTEM);
			break;

		default:
			PrintMessage(MSG_UNKNOWN_LONG, start_type);
			break;
		}

		//	print current driver state

		PrintMessage(MSG_DRIVER_STATUS);

		switch (driver_state) {
		case SERVICE_STOPPED:
			PrintMessage(MSG_STATUS_STOPPED);
			break;

		case SERVICE_START_PENDING:
			PrintMessage(MSG_STATUS_START_P);
			break;

		case SERVICE_STOP_PENDING:
			PrintMessage(MSG_STATUS_STOP_P);
			break;

		case SERVICE_RUNNING:
			PrintMessage(MSG_STATUS_RUNNING);
			break;

		case SERVICE_CONTINUE_PENDING:
			PrintMessage(MSG_STATUS_CONT_P);
			break;

		case SERVICE_PAUSE_PENDING:
			PrintMessage(MSG_STATUS_PAUSE_P);
			break;

		case SERVICE_PAUSED:
			PrintMessage(MSG_STATUS_PAUSED);
			break;

		default:
			PrintMessage(MSG_UNKNOWN_LONG, driver_state);
			break;
		}
	}

	//	print shell extension status

	printf("\n");

	if (VfdCheckHandlers() == ERROR_SUCCESS) {
		PrintMessage(MSG_SHELLEXT_ENABLED);
	}
	else {
		PrintMessage(MSG_SHELLEXT_DISABLED);
	}

	//	if driver is not running, no more info

	if (driver_state != SERVICE_RUNNING) {
		return VFD_OK;
	}

	//	print image information

	for (target = 0; target < VFD_MAXIMUM_DEVICES; target++) {
		HANDLE hDevice = VfdOpenDevice(target);

		if (hDevice == INVALID_HANDLE_VALUE) {
			ret = GetLastError();
			PrintMessage(MSG_ACCESS_NG, target + '0');
			printf("%s", SystemError(ret));
			return VFD_NG;
		}

		PrintImageInfo(hDevice);

		CloseHandle(hDevice);
	}

	return VFD_OK;
}

//
//	Print usage help
//
int	Help(const char **args)
{
	DWORD	msg = MSG_HELP_GENERAL;
	char	*buf = NULL;

	if (args && *args) {
		int cmd = ParseHelpTopic(*args);

		if (cmd < 0) {
			msg = MSG_HELP_HELP;
		}
		else {
			msg = HelpMsg[cmd].help;
		}
	}

	FormatMessage(
		FORMAT_MESSAGE_FROM_HMODULE |
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_ARGUMENT_ARRAY,
		NULL, msg, 0, (LPTSTR)&buf, 0,
		(va_list *)&help_progname);
	
	if (buf == NULL) {
		printf("%s", SystemError(GetLastError()));
		return VFD_NG;
	}

	ConsolePager(buf, TRUE);
	LocalFree(buf);

	return VFD_OK;
}

//
//	Print version information
//
int	Version(const char **args)
{
	UNREFERENCED_PARAMETER(args);

	printf(VFD_PRODUCT_DESC "\n" VFD_COPYRIGHT_STR "\n"
		"http://chitchat.at.infoseek.co.jp/vmware/vfd.html\n");

	return VFD_OK;
}

//
//	Parse command parameter
//
int ParseCommand(const char *cmd)
{
#define CMD_MATCH_NONE	-1
#define CMD_MATCH_MULTI	-2

	size_t len;
	int idx;
	int match;

	//	skip a leading '/'

	if (*cmd == '/') {
		cmd++;
	}

	if (*cmd == '\0') {

		//	empty command

		return CMD_MATCH_NONE;
	}

	//	find a match
	len = strlen(cmd);
	idx = 0;
	match = CMD_MATCH_NONE;

	while (Commands[idx].cmd) {

		if (strlen(Commands[idx].cmd) >= len &&
			!_strnicmp(cmd, Commands[idx].cmd, len)) {

			if (match == CMD_MATCH_NONE) {		//	first match
				match = idx;
			}
			else {								//	multiple matches
				if (match != CMD_MATCH_MULTI) {	//	first time
					PrintMessage(MSG_AMBIGUOUS_COMMAND, cmd);
					printf("> %s ", Commands[match].cmd);
					match = CMD_MATCH_MULTI;
				}

				printf("%s ", Commands[idx].cmd);
			}
		}

		idx++;
	}

	if (match == CMD_MATCH_NONE) {				//	match not found
		PrintMessage(MSG_UNKNOWN_COMMAND, cmd);
	}
	else if (match == CMD_MATCH_MULTI) {		//	multiple matches
		printf("\n");
	}

	return match;
}

int ParseHelpTopic(const char *topic)
{
	size_t	len;
	int		idx;
	int		match;

	if (*topic == '\0') {

		//	empty command

		return CMD_MATCH_NONE;
	}

	//	find a match
	len = strlen(topic);
	idx = 0;
	match = CMD_MATCH_NONE;

	while (HelpMsg[idx].keyword) {

		if (strlen(HelpMsg[idx].keyword) >= len &&
			!_strnicmp(topic, HelpMsg[idx].keyword, len)) {

			if (match == CMD_MATCH_NONE) {		//	first match
				match = idx;
			}
			else {								//	multiple matches
				if (match != CMD_MATCH_MULTI) {	//	first time
					PrintMessage(MSG_AMBIGUOUS_COMMAND, topic);
					printf("> %s ", HelpMsg[match].keyword);
					match = CMD_MATCH_MULTI;
				}

				printf("%s ", HelpMsg[idx].keyword);
			}
		}

		idx++;
	}

	if (match == CMD_MATCH_NONE) {				//	match not found
		PrintMessage(MSG_UNKNOWN_COMMAND, topic);
	}
	else if (match == CMD_MATCH_MULTI) {		//	multiple matches
		printf("\n");
	}

	return match;
}

//
//	Check driver version and update if necessary
//
int CheckDriver()
{
	char	path[MAX_PATH];
	DWORD	start;

	//	check installed driver file version

	if (VfdGetDriverConfig(path, &start) == ERROR_SUCCESS &&
		VfdCheckDriverFile(path, NULL) == ERROR_SUCCESS) {

		HANDLE hDevice;

		if (driver_state != SERVICE_RUNNING) {
			return VFD_OK;
		}

		//	check running driver version

		hDevice = VfdOpenDevice(0);

		if (hDevice != INVALID_HANDLE_VALUE) {
			CloseHandle(hDevice);
			return VFD_OK;
		}
	}

	PrintMessage(MSG_WRONG_DRIVER);
	return VFD_NG;
}

//
//	Print a prompt message and accept the reply input
//
int InputChar(ULONG msg, PCSTR ans)
{
	HANDLE			hStdIn;
	INPUT_RECORD	input;
	DWORD			result;
	int				reply;

	PrintMessage(msg);
	fflush(NULL);

	hStdIn	= GetStdHandle(STD_INPUT_HANDLE);

	FlushConsoleInputBuffer(hStdIn);

	for (;;) {
		ReadConsoleInput(hStdIn, &input, sizeof(input), &result);

		if (input.EventType == KEY_EVENT &&
			input.Event.KeyEvent.bKeyDown) {

			reply = tolower(input.Event.KeyEvent.uChar.AsciiChar);

			if (strchr(ans, reply)) {
				break;
			}
		}
	}

	printf("%c\n", reply);

	return reply;
}

//
//	Print image information on a Virtual Floppy Drive
//
void PrintImageInfo(
	HANDLE			hDevice)
{
	ULONG			device_number;
	CHAR			file_name[MAX_PATH];
	CHAR			file_desc[MAX_PATH];
	VFD_DISKTYPE	disk_type;
	VFD_MEDIA		media_type;
	VFD_FLAGS		media_flags;
	VFD_FILETYPE	file_type;
	ULONG			image_size;
	DWORD			ret;

	printf("\n");

	//	get current device number

	ret = VfdGetDeviceNumber(hDevice, &device_number);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_LINK_NG);
		printf("%s", SystemError(ret));
		device_number = (ULONG)-1;
	}

	//	get current drive letters

	PrintDriveLetter(hDevice, device_number);

	//	image file information

	ret = VfdGetImageInfo(hDevice, file_name, &disk_type,
		&media_type, &media_flags, &file_type, &image_size);

	if (ret != ERROR_SUCCESS) {
		PrintMessage(MSG_GET_FILE_NG);
		printf("%s", SystemError(ret));
		return;
	}

	//	print image file information
	if (media_type == VFD_MEDIA_NONE) {
		PrintMessage(MSG_IMAGE_NONE);
		return;
	}

	if (file_name[0]) {
		PrintMessage(MSG_IMAGE_NAME, file_name);

		VfdMakeFileDesc(file_desc, sizeof(file_desc),
			file_type, image_size, GetFileAttributes(file_name));
	}
	else {
		PrintMessage(MSG_IMAGE_NAME, "<RAM>");

		VfdMakeFileDesc(file_desc, sizeof(file_desc),
			VFD_FILETYPE_NONE, image_size, 0);
	}

	PrintMessage(MSG_FILE_DESC, file_desc);

	if (disk_type == VFD_DISKTYPE_FILE) {
		PrintMessage(MSG_DISKTYPE_FILE);
	}
	else {
		if (media_flags & VFD_FLAG_DATA_MODIFIED) {
			PrintMessage(MSG_DISKTYPE_RAM_DIRTY);
		}
		else {
			PrintMessage(MSG_DISKTYPE_RAM_CLEAN);
		}
	}

	//	print other file info

	PrintMessage(MSG_MEDIA_TYPE, VfdMediaTypeName(media_type));

	if (media_flags & VFD_FLAG_WRITE_PROTECTED) {
		PrintMessage(MSG_MEDIA_PROTECTED);
	}
	else {
		PrintMessage(MSG_MEDIA_WRITABLE);
	}
}

//
//	Print drive letters on a virtual floppy drive
//
void PrintDriveLetter(
	HANDLE			hDevice,
	ULONG			nDrive)
{
	CHAR			letter;

	PrintMessage(MSG_DRIVE_LETTER, nDrive);

	VfdGetGlobalLink(hDevice, &letter);

	if (isalpha(letter)) {
		PrintMessage(MSG_PERSISTENT, toupper(letter));
	}

	while (VfdGetLocalLink(hDevice, &letter) == ERROR_SUCCESS &&
		isalpha(letter)) {
		PrintMessage(MSG_EPHEMERAL, toupper(letter));
	}

	printf("\n");
}

//
//	Prints a text on screen a page a time
//
BOOL ConsolePager(char *pBuffer, BOOL bReset)
{
	static int	rows = 0;
	char		prompt[80];
	int			prompt_len = 0;
	HANDLE		hStdOut;
	HANDLE		hStdIn;

	//
	//	prepare the console input and output handles
	//
	hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	hStdIn	= GetStdHandle(STD_INPUT_HANDLE);

	for (;;) {
		CONSOLE_SCREEN_BUFFER_INFO	info;
		INPUT_RECORD	input;
		DWORD			result;
		DWORD			mode;
		int				cols;
		char			*cur;
		char			save;

		//
		//	Get the current console screen information
		//
		GetConsoleScreenBufferInfo(hStdOut, &info);

		if (bReset || rows <= 0) {
			rows = info.srWindow.Bottom - info.srWindow.Top - 1;
		}

		cols = info.dwSize.X;

		//	console window is too small for paging

		if (rows <= 0) {
			//	print all text and exit
			printf("%s", pBuffer);
			break;
		}

		//
		//	find the tail of the text to be printed this time
		//
		cur = pBuffer;
		save = '\0';

		while (*cur) {
			if (*(cur++) == '\n' || (cols--) == 0) {
				//	reached the end of a line
				if (--rows == 0) {
					//	reached the end of a page
					//	insert a terminating NULL char
					save = *cur;
					*cur = '\0';
					break;
				}

				cols = info.dwSize.X;
			}
		}

		//	print the current page
		printf("%s", pBuffer);

		//	end of the whole text?
		if (save == '\0') {
			break;
		}

		//
		//	prompt for the next page
		//

		//	prepare the prompt text

		if (prompt_len == 0) {

			prompt_len = FormatMessage(
				FORMAT_MESSAGE_FROM_HMODULE |
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL, MSG_PAGER_PROMPT, 0,
				prompt, sizeof(prompt), NULL);

			if (prompt_len == 0) {
				strcpy(prompt, "Press any key to continue...");
				prompt_len = strlen(prompt);
			}
		}

		//	get the current console input mode

		GetConsoleMode(hStdIn, &mode);

		//	change the mode to receive Ctrl+C as a regular input

		SetConsoleMode(hStdIn, (mode & ~ENABLE_PROCESSED_INPUT));

		//	get the current cursor position

		GetConsoleScreenBufferInfo(hStdOut, &info);

		//	print the prompt text

		WriteConsoleOutputCharacter(hStdOut, prompt,
			prompt_len, info.dwCursorPosition, &result);

		//	reverse the text color

		FillConsoleOutputAttribute(hStdOut, 
			(WORD)(info.wAttributes | COMMON_LVB_REVERSE_VIDEO),
			prompt_len, info.dwCursorPosition, &result);

		//	move cursor to the end of the prompt text

		info.dwCursorPosition.X =
			(short)(info.dwCursorPosition.X + prompt_len);

		SetConsoleCursorPosition(hStdOut, info.dwCursorPosition);

		//	wait for a key press event

		FlushConsoleInputBuffer(hStdIn);

		do {
			ReadConsoleInput(hStdIn, &input, sizeof(input), &result);
		}
		while (input.EventType != KEY_EVENT ||
			!input.Event.KeyEvent.bKeyDown ||
			!input.Event.KeyEvent.uChar.AsciiChar);

		//	restore the original cursor position

		info.dwCursorPosition.X =
			(short)(info.dwCursorPosition.X - prompt_len);

		SetConsoleCursorPosition(hStdOut, info.dwCursorPosition);

		//	delete the prompt text

		FillConsoleOutputCharacter(hStdOut, ' ',
			prompt_len, info.dwCursorPosition, &result);

		//	restore the text attribute to norml

		FillConsoleOutputAttribute(hStdOut, info.wAttributes,
			prompt_len, info.dwCursorPosition, &result);

		//	restore the original console mode

		SetConsoleMode(hStdIn, mode);

		//	check if the input was 'q', <esc> or <Ctrl+C> ?

		if (input.Event.KeyEvent.uChar.AsciiChar == VK_CANCEL ||
			input.Event.KeyEvent.uChar.AsciiChar == VK_ESCAPE ||
			tolower(input.Event.KeyEvent.uChar.AsciiChar) == 'q') {

			//	cancelled by the user
			return FALSE;
		}

		//
		//	process the next page
		//
		*cur = save;
		pBuffer = cur;
	}

	return TRUE;
}

//
//	Format and print a message text
//
void PrintMessage(UINT msg, ...)
{
	char *buf = NULL;
	va_list list;

	va_start(list, msg);

	if (FormatMessage(
		FORMAT_MESSAGE_FROM_HMODULE |
		FORMAT_MESSAGE_ALLOCATE_BUFFER,
		NULL, msg, 0, (LPTSTR)&buf, 0, &list)) {

		printf("%s", buf);
	}
	else {
		printf("Unknown Message ID %u\n", msg);
	}

	va_end(list);

	if (buf) {
		LocalFree(buf);
	}
}

//
//	Return a system error message text
//
const char *SystemError(DWORD err)
{
	static char msg[256];

	if (!FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, err, 0, msg, sizeof(msg), NULL)) {
#ifndef __REACTOS__
		sprintf(msg, "Unknown system error %lu (0x%08x)\n", err, err);
#else
		sprintf(msg, "Unknown system error %lu (0x%08lx)\n", err, err);
#endif
	}

	return msg;
}

//
//	Convert a path to match the case of names on the disk
//
void ConvertPathCase(char *src, char *dst)
{
	HANDLE hFind;
	WIN32_FIND_DATA find;
	char *p;

	p = dst;

	if (*src == '\"') {
		src++;
	}

	if (*(src + strlen(src) - 1) == '\"') {
		*(src + strlen(src) - 1) = '\0';
	}

	//
	//	handle drive / remote server name
	//
	if (isalpha(*src) && *(src + 1) == ':') {

		//	drive name

		*(p++) = (char)toupper(*src);
		strcpy(p++, ":\\");

		src += 2;
	}
	else if (*src == '\\' || *src == '/') {

		//	absolute path or remote name

		if ((*(src + 1) == '\\' || *(src + 1) == '/') &&
			*(src + 2) && *(src + 2) != '\\' && *(src + 2) != '/') {

			//	remote path

			*(p++) = '\\';
			*(p++) = '\\';
			src += 2;

			while (*src && *src != '\\' && *src != '/') {
				*(p++) = *(src++);
			}
		}

		strcpy(p, "\\");
	}
	else {
		*p = '\0';
	}

	//	skip redundant '\'

	while (*src == '\\' || *src == '/') {
		src++;
	}

	//	process the path

	while (*src) {

		char *q = src;

		//	separate the next part

		while (*q && *q != '\\' && *q != '/') {
			q++;
		}

		if ((q - src) == 2 && !strncmp(src, "..", 2)) {
			//	parent dir - copy as it is
			if (p != dst) {
				*p++ = '\\';
			}

			strcpy(p, "..");
			p += 2;
		}
		else if ((q - src) > 1 || *src != '.') {
			//	path name other than "."
			if (p != dst) {
				*(p++) = '\\';
			}

			strncpy(p, src, (q - src));
			*(p + (q - src)) = '\0';

			hFind = FindFirstFile(dst, &find);

			if (hFind == INVALID_HANDLE_VALUE) {
				strcpy(p, src);
				break;
			}

			FindClose(hFind);

			strcpy(p, find.cFileName);
			p += strlen(p);
		}

		//	skip trailing '\'s

		while (*q == '\\' || *q == '/') {
			q++;
		}

		src = q;
	}
}