///////////////////////////////////////////////////////////////////////////////
// setup.cpp


#include "..\nodoka\misc.h"
#include "..\nodoka\registry.h"
#include "..\nodoka\stringtool.h"
#include "..\nodoka\windowstool.h"
#include "..\nodoka\nodoka.h"
#include "setuprc.h"
#include "installer.h"

#include <windowsx.h>
#include <shlobj.h>

#define ID_MENUITEM_quit                40001
#define NODOKA_OLD_REGISTRY_ROOT1		HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\appletkan\\nodoka")
#define NODOKA_OLD_REGISTRY_ROOT2		HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\appletkan")
#define NODOKA_OLD_REGISTRY_ROOT3		HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\nodoka")

using namespace Installer;


///////////////////////////////////////////////////////////////////////////////
// Registry


#define DIR_REGISTRY_ROOT				\
	HKEY_LOCAL_MACHINE,					\
	_T("Software\\appletkan\\nodoka")


///////////////////////////////////////////////////////////////////////////////
// Globals


enum
	{
	Flag_Usb = 1 << 1,
	};
u_int32 g_flags = SetupFile::Normal;


using namespace SetupFile;

// same name
#define SN(i_kind, i_os, i_from, i_destination)			\
		{ i_kind, i_os, Normal|Flag_Usb, _T(i_from), i_destination, _T(i_from) }
// different name
#define DN(i_kind, i_os, i_from, i_destination, i_to)	\
		{ i_kind, i_os, Normal|Flag_Usb, _T(i_from), i_destination, _T(i_to) }

const SetupFile::Data g_setupFiles[] =
	{
	// executables
	SN(Dll , ALL, "nodoka.dll"				, ToDest),
	SN(File, ALL, "nodoka.exe"				, ToDest),
	SN(File, ALL, "nodoka_limit.exe"		, ToDest),
	SN(File, ALL, "nodoka_hil.exe"			, ToDest),
	SN(File, ALL, "setup.exe"				, ToDest),
	SN(File, ALL, "nshell.exe"				, ToDest),
	SN(Dll , ALL, "gamepad.dll"				, ToDest),
	SN(File, ALL, "GuiEdit.exe"				, ToDest),
	SN(File, ALL, "dotnet_starter.exe"		, ToDest),
	SN(Dll , ALL, "sirius_hook_x86.dll"		, ToDest),
	SN(Dll , AMD64, "gamepad64.dll"			, ToDest),
	SN(Dll , AMD64, "nodoka64.dll"			, ToDest),
	SN(File, AMD64, "nodoka64.exe"			, ToDest),
	SN(File, AMD64, "nodoka64_limit.exe"	, ToDest),
	SN(File, AMD64, "nodoka64_hil.exe"		, ToDest),
	SN(File, AMD64, "nodoka_helper.exe"		, ToDest),
	SN(File, AMD64, "setup64.exe"			, ToDest),
	SN(File, AMD64, "nshell64.exe"			, ToDest),
	SN(Dll , AMD64, "sirius_hook_x64.dll"	, ToDest),

	// drivers
#if defined(_WINNT)
	SN(File, AMD64, "nodokadx64.sys"		, ToDest),
	SN(File, W2kx86, "nodokad.sys"			, ToDest),
	SN(File, AMD64, "nodokadx64rsc.sys"		, ToDest),
	SN(File, W2kx86, "nodokadrsc.sys"		, ToDest),

	DN(File, AMD64, "nodokadx64.sys"		, ToDriver, "nodokad.sys"),

	SN(File, W2kx86, "nodokad.sys"			, ToDriver),
	SN(File, AMD64, "nodokadx64rsc.sys"		, ToDriver),
	SN(File, W2kx86, "nodokadrsc.sys"		, ToDriver),
#else
#  error
#endif

	// setting files		    
	SN(File, ALL, "104.nodoka"	     , ToDest),
	SN(File, ALL, "104on109.nodoka"	     , ToDest),
	SN(File, ALL, "109.nodoka"	     , ToDest),
	SN(File, ALL, "109on104.nodoka"	     , ToDest),
	SN(File, ALL, "default.nodoka"	     , ToDest),
	SN(File, ALL, "default2.nodoka"	     , ToDest),
	SN(File, ALL, "dot.nodoka"	     , ToDest),
	SN(File, ALL, "doten.nodoka"	     , ToDest),
	SN(File, ALL, "dotjp.nodoka"	     , ToDest),
	SN(File, ALL, "emacsedit.nodoka"     , ToDest),
	SN(File, ALL, "gamepad.nodoka"     , ToDest),
	SN(File, ALL, "gamepad-mouse.nodoka"     , ToDest),
	SN(File, ALL, "gamepad2-mouse.nodoka"     , ToDest),
	SN(File, ALL, "add-mouse-gamepad.nodoka"     , ToDest),

	DN(File, ALL, "104.nodoka"	     , ToDest, "104.mayu"	 ),
	DN(File, ALL, "104on109.nodoka"	 , ToDest, "104on109.mayu"	 ),
	DN(File, ALL, "109.nodoka"	     , ToDest, "109.mayu"	 ),
	DN(File, ALL, "109on104.nodoka"	 , ToDest, "109on104.mayu"	 ),
	DN(File, ALL, "default.nodoka"	 , ToDest, "default.mayu"	 ),
	DN(File, ALL, "emacsedit.nodoka" , ToDest, "emacsedit.mayu"	 ),

	// documents				    
	SN(Dir , ALL, "doc"	 	     , ToDest), // mkdir
	DN(File, ALL, "banner-ja.gif"	     , ToDest, "doc\\banner-ja.gif"	 ),
	DN(File, ALL, "edit-setting-ja.png", ToDest, "doc\\edit-setting-ja.png"),
	DN(File, ALL, "investigate-ja.png" , ToDest, "doc\\investigate-ja.png" ),
	DN(File, ALL, "log-ja.png"	     , ToDest, "doc\\log-ja.png"	 ),
	DN(File, ALL, "menu-ja.png"	     , ToDest, "doc\\menu-ja.png"	 ),
	DN(File, ALL, "pause-ja.png"	     , ToDest, "doc\\pause-ja.png"	 ),
	DN(File, ALL, "setting-ja.png"     , ToDest, "doc\\setting-ja.png"	 ),
	DN(File, ALL, "target.png"	     , ToDest, "doc\\target.png"	 ),
	DN(File, ALL, "version-ja.png"     , ToDest, "doc\\version-ja.png"	 ),
	DN(File, ALL, "tasktray-icon.png"     , ToDest, "doc\\tasktray-icon.png"	 ),
	DN(File, ALL, "copy-ja.png"     , ToDest, "doc\\copy-ja.png"	 ),
	DN(File, ALL, "virtualstore-ja.png"     , ToDest, "doc\\virtualstore-ja.png"	 ),
	DN(File, ALL, "icon0.png"     , ToDest, "doc\\icon0.png"	 ),
	DN(File, ALL, "icon1.png"     , ToDest, "doc\\icon1.png"	 ),
	DN(File, ALL, "icon2.png"     , ToDest, "doc\\icon2.png"	 ),
	DN(File, ALL, "icon3.png"     , ToDest, "doc\\icon3.png"	 ),
	DN(File, ALL, "icon4.png"     , ToDest, "doc\\icon4.png"	 ),
	DN(File, ALL, "icon5.png"     , ToDest, "doc\\icon5.png"	 ),
	DN(File, ALL, "icon6.png"     , ToDest, "doc\\icon6.png"	 ),
	DN(File, ALL, "icon7.png"     , ToDest, "doc\\icon7.png"	 ),
	DN(File, ALL, "regedit.png"     , ToDest, "doc\\regedit.png"	 ),
	DN(File, ALL, "version7-ja.png"     , ToDest, "doc\\version7-ja.png"	 ),
	DN(File, ALL, "tasktray-icon7.png"     , ToDest, "doc\\tasktray-icon7.png"	 ),
	DN(File, ALL, "tasktray-icon7help.png"     , ToDest, "doc\\tasktray-icon7help.png"	 ),
	DN(File, ALL, "tasktray-icon7help2.png"     , ToDest, "doc\\tasktray-icon7help2.png"	 ),
	DN(File, ALL, "tasktray-icon7help3.png"     , ToDest, "doc\\tasktray-icon7help3.png"	 ),
	DN(File, ALL, "GuiEdit.png"     , ToDest, "doc\\GuiEdit.png"	 ),
	DN(File, ALL, "setup.png"     , ToDest, "doc\\setup.png"	 ),
	DN(File, ALL, "CONTENTS-ja.html"   , ToDest, "doc\\CONTENTS-ja.html"	 ),
	DN(File, ALL, "CONTENTS-en.html"   , ToDest, "doc\\CONTENTS-en.html"	 ),
	DN(File, ALL, "CUSTOMIZE-ja.html"  , ToDest, "doc\\CUSTOMIZE-ja.html"	 ),
	DN(File, ALL, "CUSTOMIZE-en.html"  , ToDest, "doc\\CUSTOMIZE-en.html"	 ),
	DN(File, ALL, "MANUAL-ja.html"     , ToDest, "doc\\MANUAL-ja.html"	 ),
	DN(File, ALL, "MANUAL-en.html"     , ToDest, "doc\\MANUAL-en.html"	 ),
	DN(File, ALL, "README-ja.html"     , ToDest, "doc\\README-ja.html"	 ),
	DN(File, ALL, "README-en.html"     , ToDest, "doc\\README-en.html"	 ),
	DN(File, ALL, "README.css"	     , ToDest, "doc\\README.css"	 ),
	DN(File, ALL, "syntax.txt"	     , ToDest, "doc\\syntax.txt"	 ),
	DN(File, ALL, "104.nodoka"	     , ToDest, "doc\\104.nodoka.txt"	 ),
	DN(File, ALL, "104on109.nodoka"	     , ToDest, "doc\\104on109.nodoka.txt"	 ),
	DN(File, ALL, "109.nodoka"	     , ToDest, "doc\\109.nodoka.txt"	 ),
	DN(File, ALL, "109on104.nodoka"	     , ToDest, "doc\\109on104.nodoka.txt"	 ),
	DN(File, ALL, "default.nodoka"	     , ToDest, "doc\\default.nodoka.txt"	 ),
	DN(File, ALL, "default2.nodoka"	     , ToDest, "doc\\default2.nodoka.txt"	 ),
	DN(File, ALL, "dot.nodoka"	     , ToDest, "doc\\dot.nodoka.txt"	 ),
	DN(File, ALL, "doten.nodoka"	     , ToDest, "doc\\doten.nodoka.txt"	 ),
	DN(File, ALL, "dotjp.nodoka"	     , ToDest, "doc\\dotjp.nodoka.txt"	 ),
	DN(File, ALL, "emacsedit.nodoka"	     , ToDest, "doc\\emacsedit.nodoka.txt"	 ),
	DN(File, ALL, "gamepad.nodoka"	     , ToDest, "doc\\gamepad.nodoka.txt"	 ),
	DN(File, ALL, "gamepad-mouse.nodoka"	     , ToDest, "doc\\gamepad-mouse.nodoka.txt"	 ),
	DN(File, ALL, "gamepad2-mouse.nodoka"	     , ToDest, "doc\\gamepad2-mouse.nodoka.txt"	 ),
	DN(File, ALL, "add-mouse-gamepad.nodoka"	     , ToDest, "doc\\add-mouse-gamepad.nodoka.txt"	 ),
	DN(File, ALL, "thumbsense.nodoka"	     , ToDest, "doc\\thumbsense.nodoka.txt"	 ),
	DN(File, ALL, "nodoka-mode.el"	     , ToDest, "doc\\nodoka-mode.el.txt"	 ),
	DN(File, ALL, "109onAX.nodoka"	     , ToDest, "doc\\109onAX.nodoka.txt"	 ),
	DN(File, ALL, "98x1.nodoka"	     , ToDest, "doc\\98x1.nodoka.txt"	 ),
	DN(File, ALL, "ax.nodoka"	     , ToDest, "doc\\ax.nodoka.txt"	 ),
	DN(File, ALL, "dvorak.nodoka"	     , ToDest, "doc\\dvorak.nodoka.txt"	 ),
	DN(File, ALL, "dvorak109.nodoka"	     , ToDest, "doc\\dvorak109.nodoka.txt"	 ),
	DN(File, ALL, "DVORAKon109.nodoka"	     , ToDest, "doc\\DVORAKon109.nodoka.txt"	 ),
	DN(File, ALL, "keitai.nodoka"	     , ToDest, "doc\\keitai.nodoka.txt"	 ),
	DN(File, ALL, "keyboard.html"	     , ToDest, "doc\\keyboard.html"	 ),
	DN(File, ALL, "104.gif"	     , ToDest, "doc\\104.gif"	 ),
	DN(File, ALL, "109.gif"	     , ToDest, "doc\\109.gif"	 ),
	DN(File, ALL, "keyboard_vsd.pdf"	     , ToDest, "doc\\keyboard_vsd.pdf"	 ),

	DN(File, ALL, "GUIEdit-ja.html"	     , ToDest, "doc\\GUIEdit-ja.html"	 ),
	DN(File, ALL, "gui-edit-main-describe.png"	     , ToDest, "doc\\gui-edit-main-describe.png"	 ),
	DN(File, ALL, "gui-edit-command-main-edited.png"	     , ToDest, "doc\\gui-edit-command-main-edited.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-other3.png"	     , ToDest, "doc\\gui-edit-command-wizard-other3.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-other2.png"	     , ToDest, "doc\\gui-edit-command-wizard-other2.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-other1.png"	     , ToDest, "doc\\gui-edit-command-wizard-other1.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-mod3.png"	     , ToDest, "doc\\gui-edit-command-wizard-mod3.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-mod2.png"	     , ToDest, "doc\\gui-edit-command-wizard-mod2.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-mod1.png"	     , ToDest, "doc\\gui-edit-command-wizard-mod1.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-include2.png"	     , ToDest, "doc\\gui-edit-command-wizard-include2.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-include1.png"	     , ToDest, "doc\\gui-edit-command-wizard-include1.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-keymap3.png"	     , ToDest, "doc\\gui-edit-command-wizard-keymap3.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-keymap2.png"	     , ToDest, "doc\\gui-edit-command-wizard-keymap2.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-keymap1.png"	     , ToDest, "doc\\gui-edit-command-wizard-keymap1.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-3.png"	     , ToDest, "doc\\gui-edit-command-wizard-3.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-2.png"	     , ToDest, "doc\\gui-edit-command-wizard-2.png"	 ),
	DN(File, ALL, "gui-edit-command-wizard-1.png"	     , ToDest, "doc\\gui-edit-command-wizard-1.png"	 ),
	DN(File, ALL, "gui-edit-start-new.png"	     , ToDest, "doc\\gui-edit-start-new.png"	 ),
	DN(File, ALL, "gui-edit-main-loaded.png"	     , ToDest, "doc\\gui-edit-main-loaded.png"	 ),

	SN(File, ALL, "readme.txt"	     , ToDest),
	SN(File, ALL, "readme-en.txt"	   , ToDest),
	SN(File, ALL, "nshell.txt"	     , ToDest),
	SN(File, ALL, "Common_Public_License_1_0.txt"	     , ToDest),
	SN(File, ALL, "Common_Public_License_1_0_JP.txt"	     , ToDest),
	SN(File, ALL, "nodoka-mode.el"	     , ToDest),

	SN(Dir , ALL, "contrib"	     , ToDest), // mkdir
	DN(File, ALL, "nodoka-settings.txt"  , ToDest, "contrib\\nodoka-settings.txt"),
	DN(File, ALL, "dvorak.nodoka"	     , ToDest, "contrib\\dvorak.nodoka"      ),
	DN(File, ALL, "DVORAKon109.nodoka"   , ToDest, "contrib\\DVORAKon109.nodoka" ),
	DN(File, ALL, "keitai.nodoka"	     , ToDest, "contrib\\keitai.nodoka"      ),
	DN(File, ALL, "ax.nodoka"	     , ToDest, "contrib\\ax.nodoka"          ),
	DN(File, ALL, "98x1.nodoka"	     , ToDest, "contrib\\98x1.nodoka"        ),
	DN(File, ALL, "109onAX.nodoka"	     , ToDest, "contrib\\109onAX.nodoka"     ),

	SN(Dir , ALL, "Plugins"	     , ToDest), // mkdir

	SN(Dir , ALL, "ts4nodoka"			, ToDest), // mkdir
	DN(File, ALL, "thumbsense.nodoka"	, ToDest, "ts4nodoka\\thumbsense.nodoka"   ),
	DN(File, ALL, "cts4nodoka.dll"		, ToDest, "ts4nodoka\\cts4nodoka.dll"      ),
	DN(File, ALL, "sts4nodoka.dll"		, ToDest, "ts4nodoka\\sts4nodoka.dll"      ),
	DN(File, ALL, "ats4nodoka.dll"		, ToDest, "ts4nodoka\\ats4nodoka.dll"      ),
	DN(File, AMD64, "ats4nodoka64.dll"	, ToDest, "ts4nodoka\\ats4nodoka64.dll"    ),
	DN(File, AMD64, "sts4nodoka64.dll"	, ToDest, "ts4nodoka\\sts4nodoka64.dll"    ),
	};


enum KeyboardKind
	{
	KEYBOARD_KIND_109,
	KEYBOARD_KIND_104,
	} g_keyboardKind;


static const StringResource g_strres[] =
	{
#include "strres.h"
	};


bool g_wasExecutedBySFX = false;	// Was setup executed by cab32 SFX ?
Resource *g_resource;			// resource information
tstringi g_destDir;			// destination directory


///////////////////////////////////////////////////////////////////////////////
// functions


// show message
int message(int i_id, int i_flag, HWND i_hwnd = NULL)
	{
	return MessageBox(i_hwnd, g_resource->loadString(i_id),
		g_resource->loadString(IDS_nodokaSetup), i_flag);
	}


// driver service error
void driverServiceError(DWORD i_err)
	{
	switch (i_err)
		{
		case ERROR_ACCESS_DENIED:
			message(IDS_notAdministrator, MB_OK | MB_ICONSTOP);
			break;
		case ERROR_SERVICE_MARKED_FOR_DELETE:
			message(IDS_alreadyUninstalled, MB_OK | MB_ICONSTOP);
			break;
		default:
			{
			TCHAR *errmsg;
			int err = int(i_err);
			if (err < 0) {
				i_err = -err;
				}
			if (FormatMessage(
				FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
				NULL, i_err, 0, (LPTSTR)&errmsg, 0, NULL)) {
					TCHAR buf[1024];
					_sntprintf_s(buf, NUMBER_OF(buf), _TRUNCATE, _T("%s: %d: %s\n"),
						g_resource->loadString(IDS_error),
						err, errmsg);
					LocalFree(errmsg);
					MessageBox(NULL, buf, g_resource->loadString(IDS_nodokaSetup),
						MB_OK | MB_ICONSTOP);
				} else {
					message(IDS_error, MB_OK | MB_ICONSTOP);
				}
			break;
			}
		}
	}

#if defined(_WINNT)
// Check keyboard filter Entry  TURE: OK,   FALSE: NG
BOOL checkDriverEntry()
	{
	BOOL bOK = TRUE;
	Registry reg(HKEY_LOCAL_MACHINE, NODOKAD_FILTER_KEY);
	typedef std::list<tstring> Filters;
	Filters filters;
	tstringi filtername = _T("Drivers Name: ");


	if(!reg.read(_T("UpperFilters"), &filters))
		return TRUE;											// Entryが無いが、通常ありえないのでTRUE

	for(Filters::iterator i = filters.begin(); i != filters.end(); )
		{
		Filters::iterator next = i;
		++ next;
		if ((*i != _T("kbdclass")) && (*i != _T("nodokad")))
			{
			// ex. AltIME:altime, Nekomaneki:nmkcore, VMware:vmkbd, PGPi:pgpsdk
			bOK = FALSE;
			filtername += *i + _T(" ");
			}
		i = next;
		}
	// kbdclass, nodokad 以外のフィルタドライバを見つけたのでダイアログを出してインストールを終了させる。
	if(!bOK)
		{
		TCHAR buf[1024];
		_sntprintf_s(buf, NUMBER_OF(buf), _TRUNCATE, _T("%s\n\n%s\n"), g_resource->loadString(IDS_detectfilterdriver),	filtername.c_str());

		int iYES = MessageBox(NULL, buf, g_resource->loadString(IDS_nodokaSetup), MB_YESNO | MB_ICONWARNING);

		if(iYES == IDYES)
			bOK = TRUE;
		}
	return bOK;
	}

bool ExitNodoka(void)
	{
	HWND	hwnd;										// nodokaのウィンドウハンドル
	bool	bMayu = false;								// nodokaではなくmayuが居た?
	bool	bNodoka = false;							// nodokaを終了させた?

	// nodokaを探す
	hwnd = FindWindow(_T("nodokaTasktray"), NULL);

	// nodokaが居なければ、mayuを探す。
	if(!hwnd)
		{
		hwnd = FindWindow(_T("mayuTasktray"), NULL);
		if(hwnd)
			bMayu = true;
		}
	else
		bNodoka = true;

	if (hwnd)		// 居たら、正常終了させる。
		if(bMayu)
			SendMessage(hwnd, WM_COMMAND, MAKELONG(ID_MENUITEM_quit, 0), 0);
		else
			SendMessage(hwnd, WM_CLOSE, 0, 0);
		
		Sleep(3000);	// 3秒待つ。

		return bNodoka;
	}

#endif // _WINNT

///////////////////////////////////////////////////////////////////////////////
// dialogue


// dialog box
class DlgMain
	{
	HWND m_hwnd;
	bool m_doRegisterToStartMenu;	// if register to the start menu
	bool m_doRegisterToStartUp;		// if register to the start up
	bool m_doRegisterToStartUp2;	// if do ngen.exe, and dotnet_starter.exe register to the start up
	bool m_doRegisterMouseHook;		// -mの登録
	bool m_doRegisterKeyboardHook;	// -kの登録
	bool m_doRegisterDeviceDriver;	// デバイスドライバの登録
	bool m_doRegisterLimitVersion;	// _limit.exe登録
	bool m_doRegisterHILVersion;	// _hil.exe登録
	bool m_doRegisterToStartUp3;	// if register to the desktop


	private:
		// install
		int install(bool m_doRegisterDeviceDriver)
			{
			Registry reg(DIR_REGISTRY_ROOT);
			CHECK_TRUE( reg.write(_T("dir"), g_destDir) );
			tstringi srcDir = getModuleDirectory();
			DWORD err;
			BOOL bError = FALSE;
			PVOID oldValue;

#ifdef _WIN64
			// remove old Registry
			Registry::remove(NODOKA_OLD_REGISTRY_ROOT1);
			Registry::remove(NODOKA_OLD_REGISTRY_ROOT2);
			Registry::remove(NODOKA_OLD_REGISTRY_ROOT3);
#endif

			// ファイルコピー
			if (!installFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, srcDir, g_destDir, m_doRegisterDeviceDriver))
				{
				// ファイルコピーに失敗したので、ドライバの登録を解除し、インストールしたファイルを削除する。
				// 本来デバイスドライバーのインストール不要の場合には、登録解除を呼ぶべきではないが、危険性が残るので、あえて残す。 
				err = removeDriverService(_T("nodokad"));
				removeFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, g_destDir);

				// リトライ
				if (!installFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, srcDir, g_destDir, m_doRegisterDeviceDriver))
					{
					bError = TRUE;
					// やはりファイルコピーに失敗したので、ドライバの登録を解除し、インストールしたファイルを削除する。
					err = removeDriverService(_T("nodokad"));
					removeFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, g_destDir);
					}
				}

			if (g_wasExecutedBySFX){
				removeSrcFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, srcDir);
			}
			if(bError)	// ショートカットの削除、アンインストール情報の削除。拡張子の設定は残っているがあるが、そのまま
				{
				disableWow64FsRedir(&oldValue);
				DeleteFile(getStartMenuName(g_resource->loadString(IDS_shortcutName)).c_str());
				DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName)).c_str());
				DeleteFile(getDeskTopName(g_resource->loadString(IDS_shortcutName)).c_str());
				revertWow64FsRedir(oldValue);
				removeUninstallInformation(_T("nodoka"));
				message(IDS_installerror, MB_OK | MB_ICONSTOP);
				return 1;
				}
			else	// doten/jp.nodoka -> dot.nodoka, doten/jp.nodoka.txt -> dot.nodoka.txt
				{
				disableWow64FsRedir(&oldValue);
				//MessageBox(NULL, (g_destDir + _T("\\") + g_resource->loadString(IDS_dotnodoka)).c_str(), _T("Test"), MB_OK);
				CopyFile((g_destDir + _T("\\") + g_resource->loadString(IDS_dotnodoka)).c_str(), (g_destDir + _T("\\dot.nodoka")).c_str(), false);
				CopyFile((g_destDir + _T("\\doc\\") + g_resource->loadString(IDS_dotnodoka) + _T(".txt")).c_str(), (g_destDir + _T("\\doc\\dot.nodoka.txt")).c_str(), false);
				revertWow64FsRedir(oldValue);
				}

			// ドライバインストール

			if (m_doRegisterDeviceDriver)
			{
				err = createDriverService(_T("nodokad"),
					g_resource->loadString(IDS_nodokad),
					getDriverDirectory() + _T("\\nodokad.sys"),
					_T("+Keyboard Class\0"));

				if (err != ERROR_SUCCESS)
				{
					// ドライバの登録に失敗したので、ドライバの登録を解除し、インストールしたファイルを削除する。
					driverServiceError(err);
					err = removeDriverService(_T("nodokad"));
					removeFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, g_destDir);

					// ショートカットの削除、アンインストール情報の削除。拡張子は残っているがあるが、そのまま
					disableWow64FsRedir(&oldValue);
					DeleteFile(getStartMenuName(g_resource->loadString(IDS_shortcutName)).c_str());
					DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName)).c_str());
					DeleteFile(getDeskTopName(g_resource->loadString(IDS_shortcutName)).c_str());
					revertWow64FsRedir(oldValue);
					removeUninstallInformation(_T("nodoka"));
					return 1;
				}

				if (g_flags == Flag_Usb)
					CHECK_TRUE( reg.write(_T("isUsbDriver"), DWORD(1)) );
			} else {
				// device driver不要なので、登録だけ削除する。
				err = removeDriverService(_T("nodokad"));
			}

			// create shortcut
			tstringi GuiExe, ExeName2, ExeName, FileName;
			int argMK = 0;

			if (checkOs(SetupFile::AMD64))
			{
				FileName = L"nodoka64.exe";
				if(m_doRegisterLimitVersion)
					FileName = L"nodoka64_limit.exe";
				if(m_doRegisterHILVersion)
					FileName = L"nodoka64_hil.exe";
			}
			else
			{
				FileName = L"nodoka.exe";
				if(m_doRegisterLimitVersion)
					FileName = L"nodoka_limit.exe";
				if(m_doRegisterHILVersion)
					FileName = L"nodoka_hil.exe";
			}
			ExeName = g_destDir + L"\\" + FileName;
			ExeName2 = g_destDir + L"\\dotnet_starter.exe";
			GuiExe = g_destDir + L"\\GuiEdit.exe";

			// 0: none, 1: -m, 2: -k, 3: -m -k
			if (m_doRegisterMouseHook)
				argMK += 1;
			if (m_doRegisterKeyboardHook)
				argMK += 2;

			if (m_doRegisterToStartMenu)
				{
				tstringi shortcut = getStartMenuName(loadString(IDS_shortcutName));
				if (!shortcut.empty())
					createLink(ExeName.c_str(), shortcut.c_str(), g_resource->loadString(IDS_shortcutName), g_destDir.c_str(), argMK);
				}
			if (m_doRegisterToStartUp)
				{
				tstringi shortcut = getStartUpName(loadString(IDS_shortcutName));
				if (!shortcut.empty())
					createLink(ExeName.c_str(), shortcut.c_str(), g_resource->loadString(IDS_shortcutName), g_destDir.c_str(), argMK);
				}
			if (m_doRegisterToStartUp2)
				{
				tstringi shortcut = getStartUpName(loadString(IDS_shortcutName2));

				// dotnet_starter.exe のショートカットがあるなら削除する。
				// DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName2)).c_str());

				// dotnet_starter.exe のショートカットを設定する。
				if (!shortcut.empty())
					createLink(ExeName2.c_str(), shortcut.c_str(), g_resource->loadString(IDS_shortcutName2), g_destDir.c_str(), 0);

				// ngen.exeをdotnet_starter.exe, GuiEdit.exeに対して実行する。
				dongen(ExeName2.c_str());
				dongen(GuiExe.c_str());

				} else {
					tstringi shortcut = getStartUpName(loadString(IDS_shortcutName2));

					// dotnet_starter.exe のショートカットがあるなら削除する。
					DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName2)).c_str());

				}
			if (m_doRegisterToStartUp3)
				{
				tstringi shortcut = getDeskTopName(loadString(IDS_shortcutName));
				if (!shortcut.empty())
					createLink(ExeName.c_str(), shortcut.c_str(), g_resource->loadString(IDS_shortcutName), g_destDir.c_str(), argMK);
				}

			// set registry
			reg.write(_T("layout"),
				(g_keyboardKind == KEYBOARD_KIND_109) ? _T("109") : _T("104"));
			reg.write(_T("FileName"), FileName);

			// file extension
			createFileExtension(_T(".nodoka"), _T("text/plain"),
				_T("nodokafile"), g_resource->loadString(IDS_nodokaFile),
				g_destDir + _T("\\nodoka.exe,1"),
				g_resource->loadString(IDS_nodokaShellOpen));

			// uninstall
			createUninstallInformation(_T("nodoka"), g_resource->loadString(IDS_nodoka),
				g_destDir + _T("\\setup.exe -u"));

			if (g_flags == Flag_Usb)
				{
				if (message(IDS_copyFinishUsb, MB_YESNO | MB_ICONQUESTION, m_hwnd)
					== IDYES)
					{
					// reboot ...
					HANDLE hToken; 
					// Get a token for this process. 
					if (!OpenProcessToken(GetCurrentProcess(), 
						TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
						{
						message(IDS_failedToReboot, MB_OK | MB_ICONSTOP);
						return 0;
						}
					// Get the LUID for the shutdown privilege.
					TOKEN_PRIVILEGES tkp;
					LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
					tkp.PrivilegeCount = 1;  // one privilege to set
					tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
					// Get the shutdown privilege for this process. 
					AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
						(PTOKEN_PRIVILEGES)NULL, 0);
					// Cannot test the return value of AdjustTokenPrivileges. 
					if (GetLastError() != ERROR_SUCCESS)
						{
						message(IDS_failedToReboot, MB_OK | MB_ICONSTOP);
						return 0;
						}
					// Shut down the system and force all applications to close. 
					if (!ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0))
						{
						message(IDS_failedToReboot, MB_OK | MB_ICONSTOP);
						return 0;
						}
					}
				}
			else
				{
				if (message(IDS_copyFinish, MB_YESNO | MB_ICONQUESTION, m_hwnd)
					== IDYES)
					ExitWindows(0, 0);			// logoff
				}
			return 0;
			}

	private:
		// WM_INITDIALOG
		BOOL wmInitDialog(HWND /* focus */, LPARAM /* lParam */)
			{
			setSmallIcon(m_hwnd, IDI_ICON_nodoka);
			setBigIcon(m_hwnd, IDI_ICON_nodoka);

			CheckDlgButton(m_hwnd, IDC_CHECK_registerStartMenu, BST_CHECKED);
			CheckDlgButton(m_hwnd, IDC_CHECK_registerStartUp, BST_CHECKED);
			CheckDlgButton(m_hwnd, IDC_CHECK_registerStartUp3, BST_CHECKED);
			if (checkOs(SetupFile::W2k) || !checkDotNet()){
				CheckDlgButton(m_hwnd, IDC_CHECK_registerStartUp2, BST_UNCHECKED);
				EnableWindow(GetDlgItem(m_hwnd, IDC_CHECK_registerStartUp2), FALSE);
			} else {
				CheckDlgButton(m_hwnd, IDC_CHECK_registerStartUp2, BST_CHECKED);
			}
			//CheckDlgButton(m_hwnd, IDC_CHECK_mouse, BST_CHECKED);
			CheckDlgButton(m_hwnd, IDC_CHECK_devicedriver, BST_CHECKED);

			Edit_SetText(GetDlgItem(m_hwnd, IDC_EDIT_path), g_destDir.c_str());
			HWND hwndCombo = GetDlgItem(m_hwnd, IDC_COMBO_keyboard);

			ComboBox_AddString(hwndCombo,
					g_resource->loadString(IDS_keyboard109usb));
			ComboBox_AddString(hwndCombo,
					g_resource->loadString(IDS_keyboard104usb));

				ComboBox_SetCurSel(hwndCombo,
				(g_keyboardKind == KEYBOARD_KIND_109) ? 0 : 1);
			tstring note;
			for (int i = IDS_note01; i <= IDS_note19; ++ i) {
				note += g_resource->loadString(i);
				}
			Edit_SetText(GetDlgItem(m_hwnd, IDC_EDIT_note), note.c_str());
			return TRUE;
			}

		// WM_CLOSE
		BOOL wmClose()
			{
			EndDialog(m_hwnd, 0);
			return TRUE;
			}

		// WM_COMMAND
		BOOL wmCommand(int /* notify_code */, int i_id, HWND /* hwnd_control */)
			{
			switch (i_id)
				{
				case IDC_BUTTON_browse:				// 参照ボタン
					{
					_TCHAR folder[GANA_MAX_PATH];

					BROWSEINFO bi;
					ZeroMemory(&bi, sizeof(bi));
					bi.hwndOwner      = m_hwnd;
					bi.pidlRoot       = NULL;
					bi.pszDisplayName = folder;
					bi.lpszTitle      = g_resource->loadString(IDS_selectDir);
					ITEMIDLIST *browse = SHBrowseForFolder(&bi);
					if (browse != NULL)
						{
						if (SHGetPathFromIDList(browse, folder))
							{
							if (createDirectories(folder))
								Edit_SetText(GetDlgItem(m_hwnd, IDC_EDIT_path), folder);
							}
						IMalloc *imalloc = NULL;
						if (SHGetMalloc(&imalloc) == NOERROR)
							imalloc->Free((void *)browse);
						}
					return TRUE;
					}

				case IDOK:
					{
					_TCHAR buf[GANA_MAX_PATH];
					Edit_GetText(GetDlgItem(m_hwnd, IDC_EDIT_path), buf, NUMBER_OF(buf));
					if (buf[0])
						{
						g_destDir = normalizePath(buf);
						m_doRegisterToStartMenu =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_registerStartMenu) ==
							BST_CHECKED);
						m_doRegisterToStartUp =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_registerStartUp) ==
							BST_CHECKED);
						m_doRegisterToStartUp2 =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_registerStartUp2) ==
							BST_CHECKED);
						m_doRegisterToStartUp3 =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_registerStartUp3) ==
							BST_CHECKED);
						m_doRegisterMouseHook =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_mouse) ==
							BST_CHECKED);
						m_doRegisterKeyboardHook =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_keyboard) ==
							BST_CHECKED);
						m_doRegisterDeviceDriver = true;
#if 0
						m_doRegisterDeviceDriver =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_devicedriver) ==
							BST_CHECKED);
#endif
						m_doRegisterLimitVersion =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_limit) ==
							BST_CHECKED);
						m_doRegisterHILVersion =
							(IsDlgButtonChecked(m_hwnd, IDC_CHECK_HIL) ==
							BST_CHECKED);
						int curSel =
							ComboBox_GetCurSel(GetDlgItem(m_hwnd, IDC_COMBO_keyboard));
						g_flags = SetupFile::Normal;

						switch (curSel)
							{
							case 0:
								g_keyboardKind = KEYBOARD_KIND_109;
								g_flags = Flag_Usb;
								break;
							case 1:
								g_keyboardKind = KEYBOARD_KIND_104;
								g_flags = Flag_Usb;
								break;
							}

						if (createDirectories(g_destDir.c_str()))
							EndDialog(m_hwnd, install(m_doRegisterDeviceDriver));
						else
							message(IDS_invalidDirectory, MB_OK | MB_ICONSTOP, m_hwnd);
						}
					else
						message(IDS_nodokaEmpty, MB_OK, m_hwnd);
					return TRUE;
					}

				case IDCANCEL:
					{
					CHECK_TRUE( EndDialog(m_hwnd, 0) );
					return TRUE;
					}
				case IDC_CHECK_limit:
					{
						if(IsDlgButtonChecked(m_hwnd, IDC_CHECK_limit) == BST_CHECKED)
						{
							CheckDlgButton(m_hwnd, IDC_CHECK_limit, BST_UNCHECKED);		// clear
						} else {
							CheckDlgButton(m_hwnd, IDC_CHECK_limit, BST_CHECKED);		// set
							CheckDlgButton(m_hwnd, IDC_CHECK_HIL, BST_UNCHECKED);		// clear
						}
						return TRUE;
					}
				case IDC_CHECK_HIL:
					{
						if(IsDlgButtonChecked(m_hwnd, IDC_CHECK_HIL) == BST_CHECKED)
						{
							CheckDlgButton(m_hwnd, IDC_CHECK_HIL, BST_UNCHECKED);		// clear
						} else {
							CheckDlgButton(m_hwnd, IDC_CHECK_HIL, BST_CHECKED);			// set
							CheckDlgButton(m_hwnd, IDC_CHECK_limit, BST_UNCHECKED);		// clear
						}
						return TRUE;
					}
				}
			return FALSE;
			}

	public:
		DlgMain(HWND i_hwnd)
			: m_hwnd(i_hwnd),
			m_doRegisterToStartMenu(false),
			m_doRegisterToStartUp(false),
			m_doRegisterToStartUp2(false),
			m_doRegisterToStartUp3(false),
			m_doRegisterMouseHook(false),
			m_doRegisterKeyboardHook(false),
			m_doRegisterDeviceDriver(false),
			m_doRegisterLimitVersion(false),
			m_doRegisterHILVersion(false)
			{
			}

		static BOOL CALLBACK dlgProc(HWND i_hwnd, UINT i_message,
			WPARAM i_wParam, LPARAM i_lParam)
			{
			DlgMain *wc;
			getUserData(i_hwnd, &wc);
			if (!wc)
				switch (i_message)
				{
					case WM_INITDIALOG:
						wc = setUserData(i_hwnd, new DlgMain(i_hwnd));
						return wc->wmInitDialog(reinterpret_cast<HWND>(i_wParam), i_lParam);
				}
			else
				switch (i_message)
				{
					case WM_COMMAND:
						return wc->wmCommand(HIWORD(i_wParam), LOWORD(i_wParam),
							reinterpret_cast<HWND>(i_lParam));
					case WM_CLOSE:
						return wc->wmClose();
					case WM_NCDESTROY:
						delete wc;
						return TRUE;
				}
			return FALSE;
			}
	};


// uninstall
// (in this function, we cannot use any resource, so we use strres[])
int uninstall()
	{
	if (IDYES != message(IDS_removeOk, MB_YESNO | MB_ICONQUESTION))
		return 1;

#if defined(_WINNT)
	DWORD err = removeDriverService(_T("nodokad"));
	if (err != ERROR_SUCCESS)
		{
		driverServiceError(err);
		return 1;
		}
#endif // _WINNT

	PVOID oldValue;
	disableWow64FsRedir(&oldValue);

	BOOL bFlag = DeleteFile(getStartMenuName(g_resource->loadString(IDS_shortcutName)).c_str());

#if 0
	if(!bFlag)
		{
		LPVOID lpMsgBuf;
		FormatMessage( 
			FORMAT_MESSAGE_ALLOCATE_BUFFER | 
			FORMAT_MESSAGE_FROM_SYSTEM | 
			FORMAT_MESSAGE_IGNORE_INSERTS,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
			(LPTSTR) &lpMsgBuf,
			0,
			NULL 
			);

		MessageBox(NULL, (LPCTSTR)lpMsgBuf, L"debug", MB_OK);
		LocalFree( lpMsgBuf );
		}

	MessageBox(NULL, getStartMenuName(g_resource->loadString(IDS_shortcutName)).c_str(), L"debug", MB_OK);
#endif

	DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName)).c_str());
	DeleteFile(getStartUpName(g_resource->loadString(IDS_shortcutName2)).c_str());
	DeleteFile(getDeskTopName(g_resource->loadString(IDS_shortcutName)).c_str());

	removeFiles(g_setupFiles, NUMBER_OF(g_setupFiles), g_flags, g_destDir);
	removeFileExtension(_T(".nodoka"), _T("nodokafile"));
	removeUninstallInformation(_T("nodoka"));

	Registry::remove(DIR_REGISTRY_ROOT);
	Registry::remove(HKEY_CURRENT_USER, _T("Software\\appletkan\\nodoka"));

	revertWow64FsRedir(oldValue);

	message(IDS_removeFinish, MB_OK | MB_ICONINFORMATION);
	return 0;
	}


int WINAPI _tWinMain(HINSTANCE i_hInstance, HINSTANCE /* hPrevInstance */,
										 LPTSTR /* lpszCmdLine */, int /* nCmdShow */)
	{
	CoInitialize(NULL);

	g_hInst = i_hInstance;
	Resource resource(g_strres);
	g_resource = &resource;
	bool bToGo = false;
	HANDLE mutex = NULL;
	HANDLE mutexPrevVer = NULL;
	HWND hwnd;

	// check OS
	if (!checkOs(SetupFile::NT))
	{
		message(IDS_invalidOS, MB_OK | MB_ICONSTOP);
		return 1;
	}
#ifndef _WIN64			// is setup.exe, Not setup64.exe
	if (checkOs(SetupFile::AMD64))
	{
		SHELLEXECUTEINFO shExecInfo;
		tstringi curDir = getModuleDirectory();

		shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);

		shExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
		shExecInfo.hwnd = NULL;
		shExecInfo.lpVerb = L"open";
		shExecInfo.lpFile = L"setup64.exe";
		shExecInfo.lpDirectory = curDir.c_str();
		shExecInfo.nShow = SW_SHOWNORMAL;
		shExecInfo.hInstApp = NULL;

		if (__argc == 3 && _tcsicmp(__targv[1], _T("-u")) == 0) {
			shExecInfo.lpParameters = _T("-u");
		} else if (__argc == 2 && _tcsicmp(__targv[1], _T("-u")) == 0) {
			shExecInfo.lpParameters = _T("-u");
		} else if (__argc == 2 && _tcsicmp(__targv[1], _T("-s")) == 0) {
			shExecInfo.lpParameters = _T("-s");
		} else if (__argc == 1) {
			shExecInfo.lpParameters = NULL;
		}

		ShellExecuteEx(&shExecInfo);

		WaitForSingleObject(shExecInfo.hProcess, INFINITE);	// wait exit

		return 0;		// exit setup.exe
	}
#endif

	// keyboard kind
	g_keyboardKind = (resource.getLocale() == LOCALE_Japanese_Japan_932) ? KEYBOARD_KIND_109 : KEYBOARD_KIND_104;

	// read registry
	tstring programFiles;			// "Program Files" directory

	// OS既定の ProgramFilesを取得
	if(GetEnv(_T("ProgramW6432")) == NULL)
	{
		// ProgramW6432が無い場合は、ProgramFilesを参照。
		programFiles = GetEnv(_T("ProgramFiles"));
	} else {
		// ProgramW6432 を参照。WoW64環境で、(x86)がつかない方。
		programFiles = GetEnv(_T("ProgramW6432"));
	}

	// 前回のインストール先のフォルダ名を取得。もしなかったら、OS規定+\nodoka にする。
	Registry::read(DIR_REGISTRY_ROOT, _T("dir"), &g_destDir, programFiles + _T("\\nodoka"));
	int retval = 1;

	if (__argc == 2 && _tcsicmp(__targv[1], _T("-u")) == 0)
		retval = uninstallStep1(_T("-u"));
	else
	{
		bToGo = true;

		mutexPrevVer = CreateMutex((SECURITY_ATTRIBUTES *)NULL, TRUE, MUTEX_NODOKA_EXCLUSIVE_RUNNING);
		if (GetLastError() == ERROR_ALREADY_EXISTS) { // nodoka is running
			if(IDOK == (message(IDS_nodokaRunning, MB_OKCANCEL | MB_ICONQUESTION)))
			{
				// のどか,窓使いの憂鬱の終了
				ExitNodoka();

				// 再度確認
				hwnd = FindWindow(_T("nodokaTasktray"), NULL);
				if (hwnd)
					bToGo = false;
				else
					bToGo = true;
			} else {
				bToGo = false;
			}
		} else {
			bToGo = true;
			// is nodoka running ?
			mutex = CreateMutex((SECURITY_ATTRIBUTES *)NULL, TRUE, addSessionId(MUTEX_NODOKA_EXCLUSIVE_RUNNING).c_str());
			if (GetLastError() == ERROR_ALREADY_EXISTS) { // nodoka is running
				if(IDOK == (message(IDS_nodokaRunning, MB_OKCANCEL | MB_ICONQUESTION)))
				{
					// のどか,窓使いの憂鬱の終了
					ExitNodoka();

					// 再度確認
					hwnd = FindWindow(_T("nodokaTasktray"), NULL);
					if (hwnd)
						bToGo = false;
					else
						bToGo = true;
				} else {
					bToGo = false;
				}
			}
		}

		if(bToGo)
		{
			if (__argc == 3 && _tcsicmp(__targv[1], _T("-u")) == 0) {
				uninstallStep2(__targv[2]);
				retval = uninstall();
			} else if (__argc == 2 && _tcsicmp(__targv[1], _T("-s")) == 0) {
				g_wasExecutedBySFX = true;
				if(checkDriverEntry())
					retval = DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG_main), NULL,	&(DLGPROC)(DlgMain::dlgProc));
			} else if (__argc == 1) {
				if(checkDriverEntry())
					retval = DialogBox(g_hInst, MAKEINTRESOURCE(IDD_DIALOG_main), NULL,	&(DLGPROC)(DlgMain::dlgProc));
			}

		}
	}
		if(mutex != NULL)
			CloseHandle(mutex);
		if(mutexPrevVer != NULL)
			CloseHandle(mutexPrevVer);

	return retval;
	}
