// $Id: exerb.cpp,v 1.43 2002/09/23 15:30:25 yuya Exp $

////////////////////////////////////////////////////////////////////////////////

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

////////////////////////////////////////////////////////////////////////////////

using namespace std;

////////////////////////////////////////////////////////////////////////////////

#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  ExerbInitRuby(int argc, char** argv);
static void  ExerbInitModule();
static void  ExerbReplaceRequire(void);
static bool  ExerbMapping(void);
static void  ExerbUnMapping(void);
static PEXERB_DOS_HEADER     ExerbGetDosHeader(void);
static PIMAGE_NT_HEADERS   ExerbGetNtHeader(void);
static PEXERB_ARCHIVE_HEADER ExerbGetArchiveHeader(void);
static bool  ExerbSetup(void);
static void  ExerbSetupTable(PEXERB_ARCHIVE_HEADER pArchive);
static void  ExerbSetupKcode(PEXERB_ARCHIVE_HEADER pArchive);
static int   ExerbExecute(void);
static VALUE ExerbExecuteFirstScript(void);
static void  ExerbCleanup(void);
static void  ExerbFinalRuby(void);
static int   ExerbFail(void);
static void  ExerbPrintStackTrace();
static LRESULT CALLBACK ExerbFailDialogProc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
static void  ExerbErrorMessage(int iResourceID);
static void  ExerbErrorMessage(LPSTR pszMessage);
static void  ExerbRaiseLoadError(int iResourceID);
static VALUE ExerbRequireWithExtension(VALUE vFileName, char *pszExtension);
static VALUE ExerbRequireWithoutExtension(VALUE vFileName);
static void  ExerbCreateTemporaryFile(char *pszFilePathBuffer);
static bool  ExerbGetImportTable(DWORD dwDosHeader, DWORD *pdwAddress, DWORD *pdwDelta);
static PIMAGE_SECTION_HEADER ExerbGetFirstSectionHeader(PIMAGE_NT_HEADERS pNtHeader);
static PIMAGE_SECTION_HEADER ExerbGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA);
static PIMAGE_IMPORT_DESCRIPTOR ExerbGetFirstImportDescriptor(DWORD dwDosHeader, DWORD *pdwImportTableDelta);
static void  ExerbReplaceImportDllName(DWORD dwOffsetOfName, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, char* pszSource, char* pszDestination);
static void  ExerbReplaceImportFunctionName(DWORD dwOffsetOfName, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, const char* pszDllName, const char* pszSource, const char* pszDestination);
static void  ExerbReplaceImportTable(DWORD dwOffset);
static void  ExerbDllWriteToFile(DWORD dwOffset, DWORD dwSize, char *pszFilePath);
static HMODULE ExerbLoadDll(char *pszFilePath, VALUE vFileName);
static VALUE ExerbGetInitFunctionName(VALUE vFileName);
static VALUE ExerbRequireDll(VALUE vFileName, DWORD dwID);
static VALUE ExerbRequireScript(VALUE vFileName, DWORD dwID);
static void  ExerbGetSelfFilePath(LPSTR pszBuffer, UINT uiBufferSize);
static void  ExerbGetSelfFileName(LPSTR pszBuffer, UINT uiBufferSize);
static VALUE ExerbEvalString(VALUE vString, VALUE vFileName);

static VALUE ExerbRequire(VALUE valueObject, VALUE valueFileName);
static VALUE rb_exerb_s_runtime_p(VALUE self);

VALUE WINAPI rbxrequire(const char *fname);

////////////////////////////////////////////////////////////////////////////////

extern "C" VALUE ruby_errinfo;

////////////////////////////////////////////////////////////////////////////////

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

typedef struct {
	char*   pszFilePath;
	HMODULE hModule;
} DLL_ENTRY;

////////////////////////////////////////////////////////////////////////////////

static void* g_pArchive = NULL;

static DWORD g_dwBaseOfNameTable   = 0;
static DWORD g_dwBaseOfScriptTable = 0;
static DWORD g_dwBaseOfDllTable    = 0;

static vector<DLL_ENTRY*> g_DllTable;
static map<string, DWORD> g_Name2IdTable;
static map<DWORD, string> g_Id2NameTable;
static map<DWORD, DWORD> g_ScriptOffsetTable;
static map<DWORD, DWORD> g_ScriptSizeTable;
static map<DWORD, DWORD> g_DllOffsetTable;
static map<DWORD, DWORD> g_DllSizeTable;

////////////////////////////////////////////////////////////////////////////////

#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;

	::ExerbInitRuby(argc, argv);
	::ExerbInitModule();
	::ExerbReplaceRequire();
	
	if ( ::ExerbMapping() ) {
		if ( ::ExerbSetup() ) {
			iResultCode = ::ExerbExecute();
			::ExerbFinalRuby();
			::ExerbCleanup();
		}
		::ExerbUnMapping();
	}

	return iResultCode;
}

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

static void
ExerbInitModule()
{
	VALUE mExerb = ::rb_define_module("Exerb");
	::rb_define_singleton_method(mExerb, "runtime?", (RUBYFUNC)rb_exerb_s_runtime_p, 0);
}

static void
ExerbReplaceRequire(void)
{
	::rb_alias(rb_mKernel, ::rb_intern("__old_require__"), ::rb_intern("require"));
	::rb_define_global_function("require", (RUBYFUNC)ExerbRequire, 1);
}

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

	PEXERB_DOS_HEADER pDosHeader = ::ExerbGetDosHeader();
	if ( !pDosHeader->OffsetToArchive || !pDosHeader->SizeOfArchive ) {
	    ::ExerbErrorMessage(IDS_HEADER_NOT_FOUND);
	    return false;
	}

	g_pArchive = new char[pDosHeader->SizeOfArchive];

	HANDLE hFile = ::CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if ( hFile == INVALID_HANDLE_VALUE ) {
		::ExerbErrorMessage(IDS_FAIL_CREATEFILE);
		return false;
	}

	DWORD dwRead = 0;
	::SetFilePointer(hFile, pDosHeader->OffsetToArchive, 0, FILE_BEGIN);
	::ReadFile(hFile, g_pArchive, pDosHeader->SizeOfArchive, &dwRead, NULL);

	::CloseHandle(hFile);

	return true;
}

static void
ExerbUnMapping(void)
{
	delete[] g_pArchive;
}

static PEXERB_DOS_HEADER
ExerbGetDosHeader(void)
{
	return (PEXERB_DOS_HEADER)::GetModuleHandle(NULL);
}

static PIMAGE_NT_HEADERS
ExerbGetNtHeader(void)
{
	PEXERB_DOS_HEADER pDosHeader = ::ExerbGetDosHeader();
	return (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->OffsetToNewHeader);
}

static PEXERB_ARCHIVE_HEADER
ExerbGetArchiveHeader(void)
{
	return (PEXERB_ARCHIVE_HEADER)g_pArchive;
}

static bool
ExerbSetup(void)
{
	PEXERB_ARCHIVE_HEADER pArchive = ::ExerbGetArchiveHeader();

	if ( pArchive->NumberOfScript == 0 ) {
		::ExerbErrorMessage(IDS_SCRIPT_NOT_FOUND);
		return false;
	}

	ExerbSetupTable(pArchive);
	ExerbSetupKcode(pArchive);

	return true;
}

static void
ExerbSetupTable(PEXERB_ARCHIVE_HEADER pArchive)
{
	DWORD i = 0;

	g_dwBaseOfNameTable   = (DWORD)pArchive + pArchive->OffsetToName;
	g_dwBaseOfScriptTable = (DWORD)pArchive + pArchive->OffsetToScript;
	g_dwBaseOfDllTable    = (DWORD)pArchive + pArchive->OffsetToDLL;

	PEXERB_NAME_HEADER pNameHeader = (PEXERB_NAME_HEADER)g_dwBaseOfNameTable;
	for ( i = 0; i < pArchive->NumberOfName; i++, pNameHeader++ ) {
		const char* name = (char*)(pNameHeader->Offset + g_dwBaseOfNameTable);
		const DWORD id   = pNameHeader->ID;
		g_Name2IdTable[name] = id;
		g_Id2NameTable[id]   = name;	
	}

	PEXERB_SCRIPT_HEADER pScriptHeader = (PEXERB_SCRIPT_HEADER)g_dwBaseOfScriptTable;
	for ( i = 0; i < pArchive->NumberOfScript; i++, pScriptHeader++ ) {
		g_ScriptOffsetTable[pScriptHeader->NameID] = pScriptHeader->Offset + g_dwBaseOfScriptTable;
		g_ScriptSizeTable[pScriptHeader->NameID]   = pScriptHeader->Size;
	}

	PEXERB_DLL_HEADER pDllHeader = (PEXERB_DLL_HEADER)g_dwBaseOfDllTable;
	for ( i = 0; i < pArchive->NumberOfDLL; i++, pDllHeader++ ) {
		g_DllOffsetTable[pDllHeader->NameID] = pDllHeader->Offset + g_dwBaseOfDllTable;
		g_DllSizeTable[pDllHeader->NameID]   = pDllHeader->Size;
	}
}

static void
ExerbSetupKcode(PEXERB_ARCHIVE_HEADER pArchive)
{
	switch ( 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
	}
}

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

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

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

static VALUE
ExerbExecuteFirstScript(void)
{
	PEXERB_SCRIPT_HEADER pScriptHeader = (PEXERB_SCRIPT_HEADER)g_dwBaseOfScriptTable;
	VALUE vScirptSource = ::rb_str_new((char*)(g_dwBaseOfScriptTable + pScriptHeader->Offset), pScriptHeader->Size);
	VALUE vScritpName   = ::rb_str_new2(g_Id2NameTable[pScriptHeader->NameID].c_str());
	::ruby_script(STR2CSTR(vScritpName));
	::ExerbEvalString(vScirptSource, vScritpName);

	return Qnil;
}

static void
ExerbCleanup(void)
{
	g_Name2IdTable.clear();
	g_Id2NameTable.clear();
	g_ScriptOffsetTable.clear();
	g_ScriptSizeTable.clear();
	g_DllOffsetTable.clear();
	g_DllSizeTable.clear();

	for ( int i = 0; i < g_DllTable.size(); i++ ) {
		DLL_ENTRY* entry = g_DllTable[i];
		::FreeLibrary(entry->hModule);
		::DeleteFile(entry->pszFilePath);
		delete[] entry->pszFilePath;
		delete entry;
	}

	g_DllTable.clear();
}

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

static int
ExerbFail(void)
{
	if ( ruby_errinfo ) {
		if ( ::rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit) == Qtrue ) {
			return 0;
		} else {
#ifdef GUI
			::DialogBox(::GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_EXCEPTION), NULL, (DLGPROC)::ExerbFailDialogProc);
#endif
#ifdef CUI
			::ExerbPrintStackTrace();
#endif
			return 1;
		}
	} else {
		::ExerbErrorMessage(IDS_CANNOT_OBTAIN_EXCEPTION);
		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
ExerbFailDialogProc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam)
{
	switch ( uiMessage ) {
		case WM_INITDIALOG:
			if ( ruby_errinfo ) {
				VALUE vType            = ::rb_funcall(ruby_errinfo, ::rb_intern("type"), 0);
				VALUE vTypeName        = ::rb_funcall(vType, ::rb_intern("name"), 0);
				VALUE vMessage         = ::rb_funcall(ruby_errinfo, ::rb_intern("message"), 0);
				VALUE vMessageString   = ::rb_funcall(vMessage, ::rb_intern("gsub"), 2, ::rb_str_new2("\n"), ::rb_str_new2("\r\n"));
				VALUE vBacktrace       = ::rb_funcall(ruby_errinfo, ::rb_intern("backtrace"), 0);
				VALUE vBacktraceString = ::rb_str_concat(::rb_ary_join(vBacktrace, ::rb_str_new2("\r\n")), rb_str_new2("\r\n"));

				::SetDlgItemText(hWnd, IDC_EDIT_TYPE,      STR2CSTR(vTypeName));
				::SetDlgItemText(hWnd, IDC_EDIT_MESSAGE,   STR2CSTR(vMessageString));
				::SetDlgItemText(hWnd, IDC_EDIT_BACKTRACE, STR2CSTR(vBacktraceString));
			}

			{
				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
ExerbErrorMessage(int iResourceID)
{
	char message[1024] = "";
	::LoadString(::GetModuleHandle(NULL), iResourceID, message, sizeof(message));
	::ExerbErrorMessage(message);
}

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

static void
ExerbRaiseLoadError(int iResourceID)
{
	char message[1024] = "";
	::LoadString(::GetModuleHandle(NULL), iResourceID, message, sizeof(message));
	::rb_raise(rb_eLoadError, message);
}

static VALUE
ExerbRequireWithExtension(VALUE vFileName, char *pszExtension)
{
	DWORD dwID = g_Name2IdTable[STR2CSTR(vFileName)];

	if ( dwID ) {
		if ( ::stricmp(".rb", pszExtension) == 0 ) {
			return ::ExerbRequireScript(vFileName, dwID);
		} else if ( ::stricmp(".so", pszExtension) == 0 || ::stricmp(".dll", pszExtension) == 0 ) {
			return ::ExerbRequireDll(vFileName, dwID);
		} else {
			return Qfalse;
		}
	} else {
		return Qfalse;
	}
}

static VALUE
ExerbRequireWithoutExtension(VALUE vFileName)
{
	VALUE vFileNameRB  = ::rb_str_concat(::rb_str_dup(vFileName), ::rb_str_new2(".rb"));
	VALUE vFileNameSO  = ::rb_str_concat(::rb_str_dup(vFileName), ::rb_str_new2(".so"));
	VALUE vFileNameDLL = ::rb_str_concat(::rb_str_dup(vFileName), ::rb_str_new2(".dll"));

	const DWORD idRB  = g_Name2IdTable[STR2CSTR(vFileNameRB)];
	const DWORD idSO  = g_Name2IdTable[STR2CSTR(vFileNameSO)];
	const DWORD idDLL = g_Name2IdTable[STR2CSTR(vFileNameDLL)];

/*
	const char* filename = STR2CSTR(vFileName);
	const DWORD idRB  = g_Name2IdTable[string(filename).append(".rb")];
	const DWORD idSO  = g_Name2IdTable[string(filename).append(".so")];
	const DWORD idDLL = g_Name2IdTable[string(filename).append(".dll")];
*/

	if ( idRB ) {
		return ::ExerbRequireScript(vFileNameRB, idRB);
	} else if ( idSO ) {
		return ::ExerbRequireDll(vFileNameSO, idSO);
	} else if ( idDLL ) {
		return ::ExerbRequireDll(vFileNameDLL, idDLL);
	} else {
		return Qfalse;
	}
}

static void
ExerbCreateTemporaryFile(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
ExerbGetFirstSectionHeader(PIMAGE_NT_HEADERS pNtHeader)
{
	return (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + sizeof(IMAGE_NT_HEADERS));
}

static PIMAGE_SECTION_HEADER
ExerbGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA)
{
	PIMAGE_SECTION_HEADER pSection = ExerbGetFirstSectionHeader(pNtHeader);

	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
ExerbReplaceImportDllName(DWORD dwOffsetOfName, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, char* pszSource, char* pszDestination)
{
	for ( PIMAGE_IMPORT_DESCRIPTOR pDescriptor = pFirstDescriptor; pDescriptor->Name; pDescriptor++ ) {
		char *pszName = (char*)(dwOffsetOfName + pDescriptor->Name);

		if ( ::stricmp(pszName, pszSource) == 0 ) {
			if ( ::strlen(pszDestination) <= ::strlen(pszName) ) {
				::strcpy(pszName, pszDestination);
			} else {
				::rb_raise(rb_eLoadError, "Fail to modify the import table.\nThe excutable file name is too long.");
			}
		}
	}
}

static void
ExerbReplaceImportFunctionName(DWORD dwOffsetOfName, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, const char* pszDllName, const char* pszSource, const char* pszDestination)
{
	for ( PIMAGE_IMPORT_DESCRIPTOR pDescriptor = pFirstDescriptor; pDescriptor->Name; pDescriptor++ ) {
		char *pszName = (char*)(dwOffsetOfName + pDescriptor->Name);

		if ( ::strcmp(pszName, pszDllName) == 0 ) {
			PIMAGE_THUNK_DATA thunk    = (PIMAGE_THUNK_DATA)pDescriptor->Characteristics;
			PIMAGE_THUNK_DATA thunkIAT = (PIMAGE_THUNK_DATA)pDescriptor->FirstThunk;

			if ( !thunk ) {
				if ( !thunkIAT ) {
					continue;
				}
				thunk = thunkIAT;
			}

			thunk    = (PIMAGE_THUNK_DATA)((DWORD)thunk    + dwOffsetOfName);
			thunkIAT = (PIMAGE_THUNK_DATA)((DWORD)thunkIAT + dwOffsetOfName);

			while ( thunk->u1.AddressOfData ) {
				if ( !(thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) ) {
					PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)(thunk->u1.AddressOfData) + dwOffsetOfName);
					LPSTR pszFunctionName = (LPSTR)pImportByName->Name;
					if ( ::strcmp(pszFunctionName, pszSource) == 0 ) {
						::strcpy(pszFunctionName, pszDestination);
					}
				}

				thunk++;
				thunkIAT++;
			}
		}
	}
}

static void
ExerbReplaceImportTable(DWORD dwOffset)
{
	DWORD dwDosHeader = dwOffset;

	DWORD dwImportTableDelta = 0;
	PIMAGE_IMPORT_DESCRIPTOR pDescriptor = ExerbGetFirstImportDescriptor(dwDosHeader, &dwImportTableDelta);
	if ( !pDescriptor ) {
		::ExerbRaiseLoadError(IDS_FAIL_MODIFY_IMPORT_TABLE);
	}

	char szSelfFileName[MAX_PATH] = "";
	::ExerbGetSelfFileName(szSelfFileName, sizeof(szSelfFileName));

	DWORD dwOffsetOfName = dwDosHeader - dwImportTableDelta;
	ExerbReplaceImportDllName(dwOffsetOfName, pDescriptor, "mswin32-ruby16.dll", szSelfFileName);
	ExerbReplaceImportDllName(dwOffsetOfName, pDescriptor, "mingw32-ruby16.dll", szSelfFileName);
	ExerbReplaceImportDllName(dwOffsetOfName, pDescriptor, "cygwin-ruby16.dll",  szSelfFileName);

	ExerbReplaceImportFunctionName(dwOffsetOfName, pDescriptor, szSelfFileName, "rb_require", "rbxrequire");
}

static void
ExerbDllWriteToFile(DWORD dwOffset, DWORD dwSize, char *pszFilePath)
{
	void  *pvBuffer = (void*)dwOffset;
	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
ExerbLoadDll(char *pszFilePath, VALUE vFileName)
{
	HMODULE hModule = ::LoadLibraryEx(pszFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
	DWORD   dwError = ::GetLastError();

	if ( hModule ) {
		VALUE vInitFunctionName = ::ExerbGetInitFunctionName(vFileName);
		DWORD dwInitProc = (DWORD)::GetProcAddress(hModule, STR2CSTR(vInitFunctionName));

		if ( dwInitProc ) {
			INITPROC fpInitProc = (INITPROC)dwInitProc;
			(*fpInitProc)();
			return hModule;
		} else {
			::FreeLibrary(hModule);
			::DeleteFile(pszFilePath);
			::ExerbRaiseLoadError(IDS_FAIL_GETPROCADDRESS);
			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
ExerbGetInitFunctionName(VALUE vFileName)
{
	static VALUE id_basename = ::rb_intern("basename");
	static VALUE id_sub      = ::rb_intern("sub");

	VALUE vBaseName     = ::rb_funcall(rb_cFile, id_basename, 1, vFileName);
	VALUE vRegexpString = ::rb_str_new2("(\\.so|\\.dll)$");
	VALUE vRegexp       = ::rb_reg_new(RSTRING(vRegexpString)->ptr, RSTRING(vRegexpString)->len, 0);
	VALUE vFeatureName  = ::rb_funcall(vBaseName, id_sub, 2, vRegexp, ::rb_str_new2(""));
	VALUE vFunctionName = ::rb_str_concat(::rb_str_new2("Init_"), vFeatureName);

	return vFunctionName;
}

static VALUE
ExerbRequireDll(VALUE vFileName, DWORD dwID)
{
	DWORD dwOffset = g_DllOffsetTable[dwID];
	DWORD dwSize   = g_DllSizeTable[dwID];

	if ( dwOffset && dwSize ) {
		::ExerbReplaceImportTable(dwOffset);

		char szTemporaryFilePath[MAX_PATH] = "";
		::ExerbCreateTemporaryFile(szTemporaryFilePath);

		::ExerbDllWriteToFile(dwOffset, dwSize, szTemporaryFilePath);

		HMODULE hModule = ::ExerbLoadDll(szTemporaryFilePath, vFileName);
		if ( hModule ) {
			DLL_ENTRY* entry = new DLL_ENTRY;
			entry->hModule     = hModule;
			entry->pszFilePath = new char[::strlen(szTemporaryFilePath) + 1];
			::strcpy(entry->pszFilePath, szTemporaryFilePath);
			g_DllTable.push_back(entry);

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

static VALUE
ExerbRequireScript(VALUE vFileName, DWORD dwID)
{
	DWORD dwOffset = g_ScriptOffsetTable[dwID];
	DWORD dwSize   = g_ScriptSizeTable[dwID];

	if ( dwOffset && dwSize ) {
		VALUE vScript = ::rb_str_new((char*)dwOffset, dwSize);
		::ExerbEvalString(vScript, vFileName);
		return Qtrue;
	} else {
		::ExerbRaiseLoadError(IDS_CANNOT_EXECUTE_SCRIPT);
		return Qfalse;
	}
}

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

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

	::ExerbGetSelfFilePath(szSelfFilePath, sizeof(szSelfFilePath));
	::GetFullPathName(szSelfFilePath, sizeof(szSelfFilePathTemp), szSelfFilePathTemp, &pszSelfFileName);
	::strncpy(pszBuffer, pszSelfFileName, uiBufferSize);
}

static VALUE
ExerbEvalString(VALUE vString, VALUE vFileName)
{
	static VALUE id_eval  = ::rb_intern("eval");
	static VALUE vBinding = ::rb_const_get(rb_mKernel, ::rb_intern("TOPLEVEL_BINDING"));
	static VALUE vLine    = INT2FIX(1);
	return ::rb_funcall(rb_mKernel, id_eval, 4, vString, vBinding, vFileName, vLine);
}

static VALUE
ExerbRequire(VALUE vObject, VALUE vFileName)
{
	VALUE vFeatures = ::rb_gv_get("$\"");
	if ( ::rb_ary_includes(vFeatures, vFileName) == Qtrue ) {
		return Qfalse;
	}

	char *pszFileName  = STR2CSTR(vFileName);
	char *pszExtension = ::strrchr(pszFileName, '.');

	if ( pszExtension ) {
		if ( ::ExerbRequireWithExtension(vFileName, pszExtension) == Qtrue ) {
			::rb_ary_push(vFeatures, vFileName);
			return Qtrue;
		}
	} else {
		if ( ::ExerbRequireWithoutExtension(vFileName) == Qtrue ) {
			::rb_ary_push(vFeatures, vFileName);
			return Qtrue;
		}
	}

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

static VALUE
rb_exerb_s_runtime_p(VALUE self)
{
	return Qtrue;
}

VALUE WINAPI
rbxrequire(const char *fname)
{
	return ::ExerbRequire(Qnil, ::rb_str_new2(fname));
}

////////////////////////////////////////////////////////////////////////////////
