
//////////////////////////////////////////////////////////////////////
// include

#include <windows.h>
#include <windowsx.h>
#include "ruby.h"
#include "win32/win32.h"
#include "exerb.h"
#include "resource.h"

//////////////////////////////////////////////////////////////////////
// pragma

#pragma comment(linker, "/section:.ruby,rw")
#pragma data_seg(".ruby")
	_declspec(dllexport) R2E_ARCHIVE_HEADER ArchiveHeader = {
		0x59425552, // Signature
		0x00000000, // TimeDateStamp
		0x00000000, // OffsetToName
		0x00000000, // NumberOfName
		0x00000000, // OffsetToScript
		0x00000000, // NumberOfScript
		0x00000000, // OffsetToDLL
		0x00000000, // NumberOfDLL
		0x00000000, // Options
	};
#pragma data_seg()

//////////////////////////////////////////////////////////////////////
// prototype

#ifdef _CONSOLE
#define CUI
#endif

#ifdef _WINDOWS
#define GUI
#endif

#ifdef CUI
int main(int argc, char** argv);
#endif

#ifdef GUI
int WINAPI WinMain(HINSTANCE hCurrentInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int iShowCmd);
#endif

static int   ExerbMain(int argc, char** argv);
static void  r2e_init_ruby(int argc, char** argv);
static void  ExerbReplaceRequire(void);
static bool  r2e_mapping(void);
static void  r2e_unmapping(void);
static bool  r2e_setup(void);
static int   r2e_execute(void);
static VALUE r2e_main(void);
static void  r2e_cleanup(void);
static void  r2e_final_ruby(void);
static int   r2e_fail(void);
static void  ExerbPrintStackTrace();
static LRESULT CALLBACK r2e_fail_dialog_proc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
static void  r2e_error_message(LPSTR lpszMessage);
static void  r2e_inspect(VALUE valueObject);
static VALUE r2e_require_with_extension(VALUE valueFileName, char *pszExtension);
static VALUE r2e_require_without_extension(VALUE valueFileName);
static void  r2e_create_temporary_file(char *pszFilePathBuffer);
static bool  ExerbGetImportTable(DWORD dwDosHeader, DWORD *pdwAddress, DWORD *pdwDelta);
static PIMAGE_SECTION_HEADER ExerbGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA);
static PIMAGE_IMPORT_DESCRIPTOR ExerbGetFirstImportDescriptor(DWORD dwDosHeader, DWORD *pdwImportTableDelta);
static void  ExerbPatchImportTable(VALUE valueOffset);
static void  r2e_dll_write_to_file(VALUE valueOffset, VALUE valueSize, char *pszFilePath);
static HMODULE r2e_load_dll(char *pszFilePath, VALUE valueFileName);
static VALUE r2e_init_function_name(VALUE valueFileName);
static VALUE r2e_require_dll(VALUE valueFileName, VALUE valueID);
static VALUE r2e_require_script(VALUE valueFileName, VALUE valueID);
static VALUE r2e_eval(VALUE valueString, VALUE valueFileName);
static void  ExerbGetSelfFilePath(LPSTR lpszBuffer, UINT uiBufferSize);
static void  ExerbGetSelfFileName(LPSTR lpszBuffer, UINT uiBufferSize);

static VALUE exerb_rb_require(VALUE valueObject, VALUE valueFileName);

//////////////////////////////////////////////////////////////////////
// extern

extern "C" VALUE ruby_errinfo;

//////////////////////////////////////////////////////////////////////
// typedef

typedef void (*INITPROC)(void);
typedef VALUE (__cdecl *RUBYFUNC)(...);

//////////////////////////////////////////////////////////////////////
// global variable

static HANDLE g_hFile        = NULL;
static HANDLE g_hFileMapping = NULL;
static DWORD  g_dwFileBase   = 0;
static PR2E_DOS_HEADER     g_pDOSHeader = NULL;
static PR2E_ARCHIVE_HEADER g_pArchive   = NULL;
static DWORD g_dwNameTableBase   = 0;
static DWORD g_dwScriptTableBase = 0;
static DWORD g_dwDllTableBase    = 0;

static const int g_ciDllTableMax  = 32;
static int       g_iDllTableIndex = 0;
static char      **g_ppszDllTable = new char*[g_ciDllTableMax];
static HMODULE   *g_hModuleTable  = new HMODULE[g_ciDllTableMax];

static VALUE g_hashName2ID           = 0;
static VALUE g_hashID2Name           = 0;
static VALUE g_hashScriptOffsetTable = 0;
static VALUE g_hashScriptSizeTable   = 0;
static VALUE g_hashDllOffsetTable    = 0;
static VALUE g_hashDllSizeTable      = 0;

#ifdef CUI
int
main(int argc, char** argv)
{
	return ExerbMain(argc, argv);
}
#endif

#ifdef GUI
int WINAPI
WinMain(HINSTANCE hCurrentInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int iShowCmd)
{
	return ExerbMain(0, NULL);
}
#endif

static int
ExerbMain(int argc, char** argv)
{
	int iResultCode = 1;

	::r2e_init_ruby(argc, argv); // Rubyを初期化
	::ExerbReplaceRequire();     // requireを置換
	
	if ( ::r2e_mapping() ) {               // 自分自身をメモリにマップ
		if ( ::r2e_setup() ) {             // 実行準備
			iResultCode = ::r2e_execute(); // 実行
			::r2e_final_ruby();            // ファイナライザを呼び出す
			::r2e_cleanup();               // クリーンアップ
		}
		::r2e_unmapping();                 // メモリからアンマップ
	}

	return iResultCode;
}

static void
r2e_init_ruby(int argc, char** argv)
{
	::NtInitialize(&argc, &argv);
	::ruby_init();
	::ruby_set_argv(argc - 1, argv + 1);
}

static void
ExerbReplaceRequire(void)
{
	// requireを置き換える
	::rb_alias(rb_mKernel, ::rb_intern("__old_require__"), ::rb_intern("require"));
	::rb_define_global_function("require", (RUBYFUNC)exerb_rb_require, 1);
}

static bool
r2e_mapping(void)
{
	char szFilePath[MAX_PATH] = "";
	::ExerbGetSelfFilePath(szFilePath, sizeof(szFilePath));

	g_hFile = ::CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if ( g_hFile == INVALID_HANDLE_VALUE ) {
		::r2e_error_message("The file has not been mapped. Fail to call the function CreateFile.");
		//::r2e_error_message("ファイルをマッピングできません。CreateFile関数の呼び出しに失敗しました。");
		return false;
	}

	g_hFileMapping = ::CreateFileMapping(g_hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL);
	if ( !g_hFileMapping ) {
		::CloseHandle(g_hFile);
		::r2e_error_message("The file has not been mapped. Fail to call the function CreateFileMapping.");
		//::r2e_error_message("ファイルをマッピングできません。CreateFileMapping関数の呼び出しに失敗しました。");
		return false;
	}

	g_dwFileBase = (DWORD)::MapViewOfFile(g_hFileMapping, FILE_MAP_COPY, 0, 0, 0);
	if ( !g_dwFileBase ) {
		::CloseHandle(g_hFileMapping);
		::CloseHandle(g_hFile);
		::r2e_error_message("The file has not been mapped. Fail to call the function MapViewOfFile.");
		//::r2e_error_message("ファイルをマッピングできません。MapViewOfFile関数の呼び出しに失敗しました。");
		return false;
	}

	return true;
}

static void
r2e_unmapping(void)
{
	::UnmapViewOfFile((LPVOID)g_dwFileBase);
	::CloseHandle(g_hFileMapping);
	::CloseHandle(g_hFile);
}

static bool
r2e_setup(void)
{
	int i = 0;

	// アーカイブヘッダのオフセットとサイズをチェック
	g_pDOSHeader = (PR2E_DOS_HEADER)g_dwFileBase;
	if ( !g_pDOSHeader->OffsetToArchive || !g_pDOSHeader->SizeOfArchive ) {
	    ::r2e_error_message("The archive header was not found. The core that has not been joined with archive was likely to be invoked.");
	    //::r2e_error_message("アーカイブヘッダが見つかりません。アーカイブを結合していないコアを実行しようとしている可能性があります。");
		return false;
	}

	// アーカイブ内のスクリプト数をチェック
	g_pArchive = (PR2E_ARCHIVE_HEADER)(g_dwFileBase + g_pDOSHeader->OffsetToArchive);
	if ( g_pArchive->NumberOfScript == 0 ) {
		::r2e_error_message("The script to excute was not found. It is needed as least one script in an archive.");
		//::r2e_error_message("実行するスクリプトが見つかりません。アーカイブ内には最低1つのスクリプトが必要です。");
		return false;
	}

	// 各テーブルのベースアドレスを計算
	g_dwNameTableBase   = (DWORD)g_pArchive + g_pArchive->OffsetToName;
	g_dwScriptTableBase = (DWORD)g_pArchive + g_pArchive->OffsetToScript;
	g_dwDllTableBase    = (DWORD)g_pArchive + g_pArchive->OffsetToDLL;

	// グローバルハッシュを生成
	g_hashName2ID           = ::rb_hash_new();
	g_hashID2Name           = ::rb_hash_new();
	g_hashScriptOffsetTable = ::rb_hash_new();
	g_hashScriptSizeTable   = ::rb_hash_new();
	g_hashDllOffsetTable    = ::rb_hash_new();
	g_hashDllSizeTable      = ::rb_hash_new();

	// グローバル変数として登録
	::rb_global_variable(&g_hashName2ID);
	::rb_global_variable(&g_hashID2Name);
	::rb_global_variable(&g_hashScriptOffsetTable);
	::rb_global_variable(&g_hashScriptSizeTable);
	::rb_global_variable(&g_hashDllOffsetTable);
	::rb_global_variable(&g_hashDllSizeTable);

	// ネームテーブルの内容をハッシュに格納
	for ( i = 0; i < g_pArchive->NumberOfName; i++ ) {
		PR2E_NAME_HEADER pNameHeader = (PR2E_NAME_HEADER)(g_dwNameTableBase + sizeof(R2E_NAME_HEADER) * i);
		VALUE valueName = ::rb_str_new2((char*)(g_dwNameTableBase + pNameHeader->Offset));
		VALUE valueID   = INT2FIX(pNameHeader->ID);
		::rb_hash_aset(g_hashName2ID, valueName, valueID);
		::rb_hash_aset(g_hashID2Name, valueID, valueName);
	}

	// スクリプトテーブルの内容をハッシュに格納
	for ( i = 0; i < g_pArchive->NumberOfScript; i++ ) {
		PR2E_SCRIPT_HEADER pScriptHeader = (PR2E_SCRIPT_HEADER)(g_dwScriptTableBase + sizeof(R2E_SCRIPT_HEADER) * i);
		VALUE valueID     = INT2FIX(pScriptHeader->NameID);
		VALUE valueOffset = INT2NUM(g_dwScriptTableBase + pScriptHeader->Offset);
		VALUE valueSize   = INT2NUM(pScriptHeader->Size);
		::rb_hash_aset(g_hashScriptOffsetTable, valueID, valueOffset);
		::rb_hash_aset(g_hashScriptSizeTable,   valueID, valueSize);
	}

	// ライブラリテーブルの内容をハッシュに格納
	for ( i = 0; i < g_pArchive->NumberOfDLL; i++ ) {
		PR2E_DLL_HEADER pDllHeader = (PR2E_DLL_HEADER)(g_dwDllTableBase + sizeof(R2E_DLL_HEADER) * i);
		VALUE valueID     = INT2FIX(pDllHeader->NameID);
		VALUE valueOffset = INT2NUM(g_dwDllTableBase + pDllHeader->Offset);
		VALUE valueSize   = INT2NUM(pDllHeader->Size);
		::rb_hash_aset(g_hashDllOffsetTable, valueID, valueOffset);
		::rb_hash_aset(g_hashDllSizeTable,   valueID, valueSize);
	}

	// 漢字コードを設定
	switch ( g_pArchive->Options.Kcode ) {
	case EXERB_OPTIONS_KCODE_NONE: ::rb_set_kcode("n"); break; // NONE
	case EXERB_OPTIONS_KCODE_EUC:  ::rb_set_kcode("e"); break; // EUC
	case EXERB_OPTIONS_KCODE_SJIS: ::rb_set_kcode("s"); break; // Shift-JIS
	case EXERB_OPTIONS_KCODE_UTF8: ::rb_set_kcode("u"); break; // UTF-8
	}

	return true;
}

static int
r2e_execute(void)
{
	int state = 0;

	::rb_protect(r2e_main, 0, &state);

	if ( state ) {
		return ::r2e_fail();
	} else {
		return 0;
	}
}

static VALUE
r2e_main(void)
{
	PR2E_SCRIPT_HEADER pFirstScriptHeader = (PR2E_SCRIPT_HEADER)g_dwScriptTableBase;
	VALUE valueFirstScirptProgram = ::rb_str_new((char*)(g_dwScriptTableBase + pFirstScriptHeader->Offset), pFirstScriptHeader->Size);
	VALUE valueFirstScritpName    = ::rb_hash_aref(g_hashID2Name, INT2FIX(pFirstScriptHeader->NameID));
	::ruby_script(STR2CSTR(valueFirstScritpName));
	::r2e_eval(valueFirstScirptProgram, valueFirstScritpName);

	return Qnil;
}

static void
r2e_cleanup(void)
{
	// DLLテーブルのDLLを解放し、一時ファイルを削除
	for ( int i = 0; i < g_iDllTableIndex; i++ ) {
		char *pszFilePath = g_ppszDllTable[i];
		HMODULE hModule   = g_hModuleTable[i];
		::FreeLibrary(hModule);
		::DeleteFile(pszFilePath);
		delete pszFilePath;
	}
}

static void
r2e_final_ruby(void)
{
	::ruby_finalize();
}

static int
r2e_fail(void)
{
	// 例外情報が設定されているか調べる
	if ( ruby_errinfo != Qnil ) {
		if ( rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit) == Qtrue ) {
			// SystemExitは無視する
			return 0;
		} else {
#ifdef GUI
			// SystemExit以外の場合は、ダイアログを表示して例外情報を表示する
			::DialogBox(::GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_EXCEPTION), NULL, (DLGPROC)::r2e_fail_dialog_proc);
#endif
#ifdef CUI
			::ExerbPrintStackTrace();
#endif
			return 1;
		}
	} else {
		::r2e_error_message("Can't obtain infrmations about the exception. The exception dialog can't be displayed.");
		//::r2e_error_message("例外情報を取得できません。例外ダイアログを表示することができませんでした。");
		return 1;
	}
}

static void
ExerbPrintStackTrace()
{
	VALUE type       = ::rb_funcall(::rb_funcall(ruby_errinfo, ::rb_intern("type"), 0), ::rb_intern("name"), 0);
	VALUE message    = ::rb_funcall(ruby_errinfo, ::rb_intern("message"), 0);
	VALUE backtrace  = ::rb_funcall(ruby_errinfo, ::rb_intern("backtrace"), 0);
	VALUE backtrace1 = ::rb_ary_shift(backtrace);
	VALUE backtrace2 = ::rb_str_concat(::rb_str_new2("\tfrom "), ::rb_ary_join(backtrace, ::rb_str_new2("\n\tfrom ")));

	::fprintf(stderr, "%s: %s (%s)\n", STR2CSTR(backtrace1), STR2CSTR(message), STR2CSTR(type));
	if ( FIX2INT(::rb_funcall(backtrace, ::rb_intern("size"), 0)) > 0 ) {
		::fprintf(stderr, "%s\n", STR2CSTR(backtrace2));
	}
}

static LRESULT CALLBACK
r2e_fail_dialog_proc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam)
{
	switch ( uiMessage ) {
		case WM_INITDIALOG:
			// 例外の内容を表示
			if ( ruby_errinfo ) {
				VALUE valueType            = ::rb_funcall(ruby_errinfo, ::rb_intern("type"), 0);
				VALUE valueTypeName        = ::rb_funcall(valueType,    ::rb_intern("name"), 0);
				VALUE valueMessage         = ::rb_funcall(ruby_errinfo, ::rb_intern("message"), 0);
				VALUE valueMessageString   = ::rb_funcall(valueMessage, ::rb_intern("gsub"), 2, ::rb_str_new2("\n"), ::rb_str_new2("\r\n"));
				VALUE valueBacktrace       = ::rb_funcall(ruby_errinfo, ::rb_intern("backtrace"), 0);
				VALUE valueBacktraceString = ::rb_str_concat(::rb_ary_join(valueBacktrace, ::rb_str_new2("\r\n")), rb_str_new2("\r\n"));

				::SetDlgItemText(hWnd, IDC_EDIT_TYPE,      STR2CSTR(valueTypeName));
				::SetDlgItemText(hWnd, IDC_EDIT_MESSAGE,   STR2CSTR(valueMessageString));
				::SetDlgItemText(hWnd, IDC_EDIT_BACKTRACE, STR2CSTR(valueBacktraceString));
			}

			{
				// ウィンドウタイトルを設定
				char szSelfFileName[MAX_PATH]      = "";
				char szWindowTitleFormat[MAX_PATH] = "";
				char szWindowTitle[MAX_PATH]       = "";
				::ExerbGetSelfFileName(szSelfFileName, sizeof(szSelfFileName));
				::GetWindowText(hWnd, szWindowTitleFormat, sizeof(szWindowTitleFormat));
				::wsprintf(szWindowTitle, szWindowTitleFormat, szSelfFileName);
				::SetWindowText(hWnd, szWindowTitle);
			}

			{
				// テキストボックスのフォントを固定幅フォントにする
				HFONT hFont = ::CreateFont(14, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FIXED_PITCH | FF_MODERN, "Terminal");
				SetWindowFont(::GetDlgItem(hWnd, IDC_EDIT_TYPE),      hFont, false);
				SetWindowFont(::GetDlgItem(hWnd, IDC_EDIT_MESSAGE),   hFont, false);
				SetWindowFont(::GetDlgItem(hWnd, IDC_EDIT_BACKTRACE), hFont, false);
			}

			// 警告音を鳴らす
			::MessageBeep(MB_ICONHAND);

			return TRUE;
		case WM_CLOSE:
			// ダイアログを閉じる
			::EndDialog(hWnd, ID_CLOSE);
			return TRUE;
		case WM_COMMAND:
			switch ( LOWORD(wParam) ) {
				case ID_CLOSE:
					// ダイアログを閉じる
					::EndDialog(hWnd, ID_CLOSE);
					return TRUE;
			}
			break;
	}

	return FALSE;
}

static void
r2e_error_message(LPSTR lpszMessage)
{
#ifdef GUI
	char szSelfFileName[MAX_PATH] = "";
	::ExerbGetSelfFileName(szSelfFileName, sizeof(szSelfFileName));
	::MessageBox(NULL, lpszMessage, szSelfFileName, MB_ICONERROR);
#endif
#ifdef CUI
	::fprintf(stderr, "%s\n", lpszMessage);
#endif
}

// for debug
static void
r2e_inspect(VALUE valueObject)
{
	VALUE valueInspect = ::rb_inspect(valueObject);
	::OutputDebugString("inspect: ");
	::OutputDebugString(STR2CSTR(valueInspect));
	::OutputDebugString("\n");
}

static VALUE
r2e_require_with_extension(VALUE valueFileName, char *pszExtension)
{
	// ファイル名から名前IDを取得
	VALUE valueID = ::rb_hash_aref(g_hashName2ID, valueFileName);

	if ( valueID != Qnil ) {
		if ( ::strcmp(".rb", pszExtension) == 0 ) {
			// 拡張子が.rbの場合
			return ::r2e_require_script(valueFileName, valueID);
		} else if ( ::strcmp(".so", pszExtension) == 0 || ::strcmp(".dll", pszExtension) == 0 ) {
			// 拡張子が.soまたは.dllの場合
			return ::r2e_require_dll(valueFileName, valueID);
		} else {
			// 不明な拡張子の場合
			return Qfalse;
		}
	} else {
		// 名前IDが取得できなかった場合
		return Qfalse;
	}
}

static VALUE
r2e_require_without_extension(VALUE valueFileName)
{
	VALUE valueID = Qnil;
	VALUE valueFileNameSO  = ::rb_str_concat(::rb_str_dup(valueFileName), ::rb_str_new2(".so"));
	VALUE valueFileNameDLL = ::rb_str_concat(::rb_str_dup(valueFileName), ::rb_str_new2(".dll"));
	VALUE valueFileNameRB  = ::rb_str_concat(::rb_str_dup(valueFileName), ::rb_str_new2(".rb"));

	if ( (valueID = ::rb_hash_aref(g_hashName2ID, valueFileNameSO)) != Qnil ) {
		// .soを探す
		return ::r2e_require_dll(valueFileNameSO, valueID);
	} else if ( (valueID = ::rb_hash_aref(g_hashName2ID, valueFileNameDLL)) != Qnil ) {
		// .dllを探す
		return ::r2e_require_dll(valueFileNameDLL, valueID);
	} else if ( (valueID = ::rb_hash_aref(g_hashName2ID, valueFileNameRB)) != Qnil ) {
		// .rbを探す
		return ::r2e_require_script(valueFileNameRB, valueID);
	} else {
		// 見つからなかった
		return Qfalse;
	}
}

static void
r2e_create_temporary_file(char *pszFilePathBuffer)
{
	char szTemporaryDirectoryPath[MAX_PATH];
	::GetTempPath(sizeof(szTemporaryDirectoryPath), szTemporaryDirectoryPath);
	::GetTempFileName(szTemporaryDirectoryPath, "ruby", 0, pszFilePathBuffer);
}

static bool
ExerbGetImportTable(DWORD dwDosHeader, DWORD *pdwAddress, DWORD *pdwDelta)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwDosHeader;
	PIMAGE_NT_HEADERS pNtHeader  = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	DWORD dwImportTableRVA = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

	PIMAGE_SECTION_HEADER pSection = ::ExerbGetEnclosingSectionHeader(pNtHeader, dwImportTableRVA);
	if ( pSection ) {
		*pdwDelta   = pSection->VirtualAddress - pSection->PointerToRawData;
		*pdwAddress = dwImportTableRVA - *pdwDelta;
		return true;
	} else {
		*pdwDelta   = 0;
		*pdwAddress = 0;
		return false;
	}
}

static PIMAGE_SECTION_HEADER
ExerbGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA)
{
	PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS));

	for ( int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, pSection++ ) {
		if ( (dwRVA >= pSection->VirtualAddress) && (dwRVA < (pSection->VirtualAddress + pSection->Misc.VirtualSize)) ) {
			return pSection;
		}
	}

	return NULL;
}

static PIMAGE_IMPORT_DESCRIPTOR
ExerbGetFirstImportDescriptor(DWORD dwDosHeader, DWORD *pdwImportTableDelta)
{
	DWORD dwImportTableAddress = 0;
	if ( ::ExerbGetImportTable(dwDosHeader, &dwImportTableAddress, pdwImportTableDelta) ) {
		DWORD dwImportTableBase = dwDosHeader + dwImportTableAddress;
		return (PIMAGE_IMPORT_DESCRIPTOR)dwImportTableBase;
	} else {
		return NULL;
	}
}

static void
ExerbPatchImportTable(VALUE valueOffset)
{
	// DOSヘッダのアドレスを取得
	DWORD dwDosHeader = NUM2INT(valueOffset);

	// インポートテーブルの最初の要素を取得
	DWORD dwImportTableDelta = 0;
	PIMAGE_IMPORT_DESCRIPTOR pDescriptor = ExerbGetFirstImportDescriptor(dwDosHeader, &dwImportTableDelta);
	if ( !pDescriptor ) {
		::rb_raise(rb_eLoadError, "Fail to modify the import table.\nAny import descriptor could not be obtained.");
		//::rb_raise(rb_eLoadError, "インポートテーブルの修正に失敗しました。\nインポートディスクリプタが取得できません。");
	}

	// 自分自身のファイル名を取得
	char szSelfFileName[MAX_PATH] = "";
	::ExerbGetSelfFileName(szSelfFileName, sizeof(szSelfFileName));

	// 書き換え対象のファイル名
	const char *MSWIN_DLL  = "mswin32-ruby16.dll";
	const char *MINGW_DLL  = "mingw32-ruby16.dll";
	const char *CYGWIN_DLL = "cygwin-ruby16.dll";

	// 自分自身のファイル名の長さをチェック
	// 書き換え対象より長いファイル名だとパッチを当てることができない
	if ( ::strlen(szSelfFileName) > ::strlen(MSWIN_DLL) ||
		 ::strlen(szSelfFileName) > ::strlen(MINGW_DLL) ||
		 ::strlen(szSelfFileName) > ::strlen(CYGWIN_DLL) ) {
		::rb_raise(rb_eLoadError, "Fail to modify the import table.\nThe excutable file name is too long.");
		//::rb_raise(rb_eLoadError, "インポートテーブルの修正に失敗しました。\n実行ファイルのファイル名が長すぎます。");
	}

	// インポートアドレステーブルのDLL名を書き換える
	while ( pDescriptor->Name ) {
		char *pszName = (char*)(dwDosHeader + pDescriptor->Name - dwImportTableDelta);

		if ( ::strcmp(pszName, MSWIN_DLL) == 0 ||
			 ::strcmp(pszName, MINGW_DLL) == 0 ||
			 ::strcmp(pszName, CYGWIN_DLL) == 0 ) {
			::strcpy(pszName, szSelfFileName);
		}

		pDescriptor++;
	}
}

static void
r2e_dll_write_to_file(VALUE valueOffset, VALUE valueSize, char *pszFilePath)
{
	void  *pvBuffer = (void*)NUM2INT(valueOffset);
	DWORD dwSize    = NUM2INT(valueSize);
	DWORD dwWritten = 0;

	HANDLE hFile = ::CreateFile(pszFilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	::WriteFile(hFile, pvBuffer, dwSize, &dwWritten, NULL);
	::CloseHandle(hFile);
}

static HMODULE
r2e_load_dll(char *pszFilePath, VALUE valueFileName)
{
	// DLLを読み込む
	HMODULE hModule = ::LoadLibraryEx(pszFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
	DWORD   dwError = ::GetLastError();

	if ( hModule ) {
		// 読み込み成功

		// 初期化関数のアドレスを取得
		VALUE valueInitFunctionName = ::r2e_init_function_name(valueFileName);
		DWORD dwInitProc = (DWORD)::GetProcAddress(hModule, STR2CSTR(valueInitFunctionName));

		if ( dwInitProc ) {
			// 取得成功
			INITPROC fpInitProc = (INITPROC)dwInitProc;
			(*fpInitProc)();
			return hModule;
		} else {
			// 取得失敗
			::FreeLibrary(hModule);
			::DeleteFile(pszFilePath);
			::rb_raise(rb_eLoadError, "Fail to read an extension library.\nThe address of initialization function in the extension library could not obtained.");
			//::rb_raise(rb_eLoadError, "拡張ライブラリの読み込みに失敗しました。\n拡張ライブラリの初期化関数の関数アドレスを取得することができません。");
			return NULL;
		}
	} else {
		// 読み込み失敗

		::DeleteFile(pszFilePath);
		::rb_raise(rb_eLoadError, "Fail to read an extension library.\nLoadLibraryEx failed. \n The error No. was [%i].", dwError);
		//::rb_raise(rb_eLoadError, "拡張ライブラリの読み込みに失敗しました。\nLoadLibraryEx関数の実行に失敗しました。\nエラー番号は[%i]です。", dwError);
		return NULL;
	}
}

static VALUE
r2e_init_function_name(VALUE valueFileName)
{
	static VALUE id_basename = ::rb_intern("basename");
	static VALUE id_sub      = ::rb_intern("sub");

	// ファイル名から初期化関数名を取得
	VALUE valueBaseName     = ::rb_funcall(rb_cFile, id_basename, 1, valueFileName);
	VALUE valueRegexpString = ::rb_str_new2("(\\.so|\\.dll)$");
	VALUE valueRegexp       = ::rb_reg_new(RSTRING(valueRegexpString)->ptr, RSTRING(valueRegexpString)->len, 0);
	VALUE valueFeatureName  = ::rb_funcall(valueBaseName, id_sub, 2, valueRegexp, ::rb_str_new2(""));
	VALUE valueFunctionName = ::rb_str_concat(::rb_str_new2("Init_"), valueFeatureName);

	return valueFunctionName;
}

static VALUE
r2e_require_dll(VALUE valueFileName, VALUE valueID)
{
	// DLLへのオフセットとサイズを取得
	VALUE valueOffset = ::rb_hash_aref(g_hashDllOffsetTable, valueID);
	VALUE valueSize   = ::rb_hash_aref(g_hashDllSizeTable,   valueID);

	if ( valueOffset != Qnil && valueSize != Qnil ) {
		// DLL名を書き換える
		::ExerbPatchImportTable(valueOffset);

		// 一時ファイルを作成
		char szTemporaryFilePath[MAX_PATH];
		::r2e_create_temporary_file(szTemporaryFilePath);

		// DLLをファイルに書き込み
		::r2e_dll_write_to_file(valueOffset, valueSize, szTemporaryFilePath);

		// DLLを読み込む
		HMODULE hModule = ::r2e_load_dll(szTemporaryFilePath, valueFileName);
		if ( hModule ) {
			// DLLテーブルに登録
			g_hModuleTable[g_iDllTableIndex] = hModule;
			g_ppszDllTable[g_iDllTableIndex] = new char[::strlen(szTemporaryFilePath) + 1];
			::strcpy(g_ppszDllTable[g_iDllTableIndex], szTemporaryFilePath);
			g_iDllTableIndex++;
			if ( g_iDllTableIndex >= g_ciDllTableMax ) {
				::rb_raise(rb_eLoadError, "The dll table exceeded it's limit.\nThe limit is [%i].", g_ciDllTableMax);
				//::rb_raise(rb_eLoadError, "DLLテーブルの最大サイズを超えました。\nDLLテーブルの最大サイズは[%i]です。", g_ciDllTableMax);
			}

			return Qtrue;
		} else {
			return Qfalse;
		}
	} else {
		return Qfalse;
	}
}

static VALUE
r2e_require_script(VALUE valueFileName, VALUE valueID)
{
	VALUE valueOffset = ::rb_hash_aref(g_hashScriptOffsetTable, valueID);
	VALUE valueSize   = ::rb_hash_aref(g_hashScriptSizeTable,   valueID);

	if ( valueOffset != Qnil && valueSize != Qnil ) {
		VALUE valueScript = ::rb_str_new((char*)(NUM2INT(valueOffset)), NUM2INT(valueSize));
		::r2e_eval(valueScript, valueFileName);
		return Qtrue;
	} else {
		::rb_raise(rb_eLoadError, "Can't excute the script. The ID is impropriety.");
		//::rb_raise(rb_eLoadError, "スクリプトが実行できません。IDが不正です。");
		return Qfalse;
	}
}

static void
ExerbGetSelfFilePath(LPSTR lpszBuffer, UINT uiBufferSize)
{
	::GetModuleFileName(NULL, lpszBuffer, uiBufferSize);
}

static void
ExerbGetSelfFileName(LPSTR lpszBuffer, UINT uiBufferSize)
{
	char szSelfFilePath[MAX_PATH]     = "";
	char szSelfFilePathTemp[MAX_PATH] = "";
	char *lpszSelfFileName            = NULL;

	::ExerbGetSelfFilePath(szSelfFilePath, sizeof(szSelfFilePath));
	::GetFullPathName(szSelfFilePath, sizeof(szSelfFilePathTemp), szSelfFilePathTemp, &lpszSelfFileName);
	::strncpy(lpszBuffer, lpszSelfFileName, uiBufferSize);
}

static VALUE
r2e_eval(VALUE valueString, VALUE valueFileName)
{
	static VALUE id_eval      = ::rb_intern("eval");
	static VALUE valueBinding = ::rb_const_get(rb_mKernel, ::rb_intern("TOPLEVEL_BINDING"));
	static VALUE valueLine    = INT2FIX(1);
	return ::rb_funcall(rb_mKernel, id_eval, 4, valueString, valueBinding, valueFileName, valueLine);
}

static VALUE
exerb_rb_require(VALUE valueObject, VALUE valueFileName)
{
	// 既に読み込まれていたら、trueを返して終了
	VALUE valueFeatures = ::rb_gv_get("$\"");
	if ( ::rb_ary_includes(valueFeatures, valueFileName) == Qtrue ) {
		return Qfalse;
	}

	// 拡張子を取得
	char *pszFileName  = STR2CSTR(valueFileName);
	char *pszExtension = ::strrchr(pszFileName, '.');

	if ( pszExtension ) {
		// 拡張子が指定されている場合
		if ( ::r2e_require_with_extension(valueFileName, pszExtension) == Qtrue ) {
			::rb_ary_push(valueFeatures, valueFileName);
			return Qtrue;
		}
	} else {
		// 拡張子が指定されていない場合
		if ( ::r2e_require_without_extension(valueFileName) == Qtrue ) {
			::rb_ary_push(valueFeatures, valueFileName);
			return Qtrue;
		}
	}

	static VALUE id_require = ::rb_intern("__old_require__");
	return ::rb_funcall(rb_mKernel, id_require, 1, valueFileName);
}
