// $Id: exerb.cpp,v 1.55 2002/10/28 15:25:52 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"

#ifdef USE_ZLIB
#include "zlib.h"
#endif

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

using namespace std;

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

#ifdef _CONSOLE
#define CUI
#endif

#ifdef _WINDOWS
#define GUI
#endif

#ifdef _DEBUG
#define DEBUGMSG0(msg)          ::fprintf(stderr, msg)
#define DEBUGMSG1(msg, p1)      ::fprintf(stderr, msg, p1)
#define DEBUGMSG2(msg, p1, p2)  ::fprintf(stderr, msg, p1, p2)
#else
#define DEBUGMSG0(msg)
#define DEBUGMSG1(msg, p1)
#define DEBUGMSG2(msg, p1, p2)
#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   ExMain(int argc, char** argv);
static void  ExInitRuby(int argc, char** argv);
static void  ExInitModule();
static void  ExReplaceRequire(void);
static bool  ExMapping(void);
static void  ExUnMapping(void);
static PIMAGE_NT_HEADERS ExGetNtHeader(PIMAGE_DOS_HEADER pDosHeader);
static bool  ExSetup(void);
static void  ExSetupTable(PEXERB_ARCHIVE_HEADER pArchive);
static void  ExSetupKcode(PEXERB_ARCHIVE_HEADER pArchive);
static int   ExExecute(void);
#ifdef RUBY17
static VALUE ExExecuteFirstScript(VALUE data);
#else
static VALUE ExExecuteFirstScript(void);
#endif
static void  ExCleanup(void);
static void  ExFinalRuby(void);
static int   ExFail(void);
static void  ExPrintStackTrace();
static LRESULT CALLBACK ExFailDialogProc(HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
static void  ExErrorMessage(int iResourceID);
static void  ExErrorMessage(LPSTR pszMessage);
static void  ExRaiseLoadError(int iResourceID);
static VALUE ExRequireWithExtension(VALUE vFileName, char *pszExtension);
static VALUE ExRequireWithoutExtension(VALUE vFileName);
static void  ExCreateTemporaryFile(char *pszFilePathBuffer);
static bool  ExGetImportTable(DWORD dwDosHeader, DWORD *pdwAddress, DWORD *pdwDelta);
static PIMAGE_SECTION_HEADER ExGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA);
static PIMAGE_SECTION_HEADER ExFindSection(PIMAGE_NT_HEADERS pNtHeader, char* pszSectionName);
static PIMAGE_IMPORT_DESCRIPTOR ExGetFirstImportDescriptor(DWORD dwDosHeader, DWORD *pdwImportTableDelta);
static void  ExReplaceImportDllName(DWORD dwDosHeader, DWORD dwImportTableDelta, PDWORD pdwNamePoolAddress, PDWORD pdwNamePoolSize, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, char* pszSrc, char* pszDest);
static void  ExReplaceImportFunctionName(DWORD dwOffsetOfName, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, const char* pszDllName, const char* pszSource, const char* pszDestination);
static bool  ExGetSectionUnusedArea(PIMAGE_NT_HEADERS pNtHeader, char* pszName, PDWORD pdwAddress, PDWORD pdwSize);
//static void  ExGetNamePool(PIMAGE_NT_HEADERS pNtHeader, PDWORD pdwAddress, PDWORD pdwSize);
static void  ExReplaceImportTable(DWORD dwOffset);
static void  ExDllWriteToFile(DWORD dwOffset, DWORD dwSize, char *pszFilePath);
static HMODULE ExLoadDll(char *pszFilePath, VALUE vFileName);
static VALUE ExGetInitFunctionName(VALUE vFileName);
static VALUE ExRequireDll(VALUE vFileName, DWORD dwID);
static VALUE ExRequireScript(VALUE vFileName, DWORD dwID);
static void  ExGetSelfFilePath(LPSTR pszBuffer, UINT uiBufferSize);
static void  ExGetSelfFileName(LPSTR pszBuffer, UINT uiBufferSize);
static VALUE ExEvalString(VALUE vString, VALUE vFileName);
static VALUE ExRequire(VALUE valueObject, VALUE valueFileName);

static VALUE rb_exerb_s_runtime_p(VALUE self);

VALUE WINAPI ex_require(const char *fname);
VALUE WINAPI ex_f_require(VALUE obj, VALUE 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;

static char g_szPhiSoFileName[MAX_PATH] = "";

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

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

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

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

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

	::ExInitRuby(argc, argv);
	::ExInitModule();
	::ExReplaceRequire();
	
	if ( ::ExMapping() ) {
		if ( ::ExSetup() ) {
			iResultCode = ::ExExecute();
			::ExFinalRuby();
			::ExCleanup();
		}
		::ExUnMapping();
	}

	return iResultCode;
}

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

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

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

static bool
ExMapping(void)
{
	PEXERB_DOS_HEADER pDosHeader = (PEXERB_DOS_HEADER)::GetModuleHandle(NULL);
	if ( !pDosHeader->OffsetToArchive || !pDosHeader->SizeOfArchive ) {
	    ::ExErrorMessage(IDS_HEADER_NOT_FOUND);
	    return false;
	}

	int nArchiveSize = (pDosHeader->SizeOfCompressedArchive > 0 ? pDosHeader->SizeOfCompressedArchive : pDosHeader->SizeOfArchive);
	g_pArchive = new char[nArchiveSize];

	char szFilePath[MAX_PATH] = "";
	::ExGetSelfFilePath(szFilePath, sizeof(szFilePath));

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

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

	::CloseHandle(hFile);

	if ( pDosHeader->SizeOfCompressedArchive > 0 ) {
#ifdef USE_ZLIB
		DWORD dwOriginalSize   = pDosHeader->SizeOfArchive;
		DWORD dwCompressedSize = pDosHeader->SizeOfCompressedArchive;
		void* pUncompressedArchive = new char[dwOriginalSize];
		::uncompress((BYTE*)pUncompressedArchive, &dwOriginalSize, (BYTE*)g_pArchive, dwCompressedSize);
		delete[] g_pArchive;
		g_pArchive = pUncompressedArchive;
#else
	    ::ExErrorMessage("The compressed archive isn't supported by the core.");
	    return false;
#endif
	}

	return true;
}

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

static PIMAGE_NT_HEADERS
ExGetNtHeader(PIMAGE_DOS_HEADER pDosHeader)
{
	return (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
}

static bool
ExSetup(void)
{
	PEXERB_ARCHIVE_HEADER pArchive = (PEXERB_ARCHIVE_HEADER)g_pArchive;

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

	::ExSetupTable(pArchive);
	::ExSetupKcode(pArchive);

	return true;
}

static void
ExSetupTable(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
ExSetupKcode(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
ExExecute(void)
{
	int state = 0;

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

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

static VALUE
#ifdef RUBY17
ExExecuteFirstScript(VALUE data)
#else
ExExecuteFirstScript(void)
#endif
{
	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));
	::ExEvalString(vScirptSource, vScritpName);

	return Qnil;
}

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

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

	g_DllTable.clear();
}

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

static int
ExFail(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)::ExFailDialogProc);
#endif
#ifdef CUI
			::ExPrintStackTrace();
#endif
			return 1;
		}
	} else {
		::ExErrorMessage(IDS_CANNOT_OBTAIN_EXCEPTION);
		return 1;
	}
}

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

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

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

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

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

static VALUE
ExRequireWithoutExtension(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 ::ExRequireScript(vFileNameRB, idRB);
	} else if ( idSO ) {
		return ::ExRequireDll(vFileNameSO, idSO);
	} else if ( idDLL ) {
		return ::ExRequireDll(vFileNameDLL, idDLL);
	} else {
		return Qfalse;
	}
}

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

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

	PIMAGE_SECTION_HEADER pSection = ::ExGetEnclosingSectionHeader(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
ExGetEnclosingSectionHeader(PIMAGE_NT_HEADERS pNtHeader, DWORD dwRVA)
{
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(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_SECTION_HEADER
ExFindSection(PIMAGE_NT_HEADERS pNtHeader, char* pszSectionName)
{
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeader);

	for ( int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++, pSection++ ) {
		if ( ::strnicmp(pszSectionName, (char*)pSection->Name, 8) == 0 ) {
			return pSection;
		}
	}

	return NULL;
}

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

static void
ExReplaceImportDllName(DWORD dwDosHeader, DWORD dwImportTableDelta, PDWORD pdwNamePoolAddress, PDWORD pdwNamePoolSize, PIMAGE_IMPORT_DESCRIPTOR pFirstDescriptor, char* pszSrc, char* pszDest)
{
	DWORD dwOffsetOfName = dwDosHeader - dwImportTableDelta;
	DWORD dwDestLength   = ::strlen(pszDest);

	for ( PIMAGE_IMPORT_DESCRIPTOR pDescriptor = pFirstDescriptor; pDescriptor->Name; pDescriptor++ ) {
		char *pszName = (char*)(dwOffsetOfName + pDescriptor->Name);

		if ( ::stricmp(pszName, pszSrc) == 0 ) {
			if ( dwDestLength <= ::strlen(pszName) ) {
				::strcpy(pszName, pszDest);
			} else if ( dwDestLength + 1 <= *pdwNamePoolSize ) {
				DWORD dwAddress = *pdwNamePoolAddress - dwDestLength - 1;
				pDescriptor->Name = dwAddress + dwImportTableDelta;

				::memcpy((void*)(dwDosHeader + dwAddress), pszDest, dwDestLength);

				*pdwNamePoolAddress -= dwDestLength + 1;
				*pdwNamePoolSize    -= dwDestLength + 1;
			} else {
				::rb_raise(rb_eLoadError, "Fail to modify the import table. exe/dll file name is too long.");
			}
		}
	}
}

static void
ExReplaceImportFunctionName(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 bool
ExGetSectionUnusedArea(PIMAGE_NT_HEADERS pNtHeader, char* pszName, PDWORD pdwAddress, PDWORD pdwSize)
{
	PIMAGE_SECTION_HEADER pSection = ::ExFindSection(pNtHeader, pszName);

	if ( pSection ) {
		*pdwAddress = pSection->PointerToRawData + pSection->SizeOfRawData;
		*pdwSize    = pSection->SizeOfRawData    - pSection->Misc.VirtualSize;
		return true;
	} else {
		*pdwAddress = 0;
		*pdwSize    = 0;
		return false;
	}
}

/*
static void
ExGetNamePool(PIMAGE_NT_HEADERS pNtHeader, PDWORD pdwAddress, PDWORD pdwSize)
{
	char* ppszSections[] = {".idata", ".edata", ".rsrc"};
	int   nSections      = 3;
	DWORD dwAddress = 0;
	DWORD dwSize    = 0;

	for ( int i = 0; i < nSections; i++ ) {
		if ( ::ExGetSectionUnusedArea(pNtHeader, ppszSections[i], &dwAddress, &dwSize) ) {
			if ( *pdwSize < dwSize ) {
				*pdwAddress = dwAddress;
				*pdwSize    = dwSize;
			}
		}
	}
}
*/

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

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

	DWORD dwNamePoolAddress = 0;
	DWORD dwNamePoolSize    = 0;
	PIMAGE_NT_HEADERS     pNtHeader      = ::ExGetNtHeader((PIMAGE_DOS_HEADER)dwDosHeader);

	::ExGetSectionUnusedArea(pNtHeader, ".idata", &dwNamePoolAddress, &dwNamePoolSize);
	//::ExGetNamePool(pNtHeader, &dwNamePoolAddress, &dwNamePoolSize);

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

#ifdef RUBY17
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "msvcrt-ruby17.dll",  szSelfFileName);
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "cygwin-ruby17.dll",  szSelfFileName);
#else
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "mswin32-ruby16.dll", szSelfFileName);
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "mingw32-ruby16.dll", szSelfFileName);
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "cygwin-ruby16.dll",  szSelfFileName);
#endif
	::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "ruby.exe",           szSelfFileName);

	if ( ::strlen(g_szPhiSoFileName) > 0 ) {
		::ExReplaceImportDllName(dwDosHeader, dwImportTableDelta, &dwNamePoolAddress, &dwNamePoolSize, pDescriptor, "phi.so", g_szPhiSoFileName);
	}

	DWORD dwOffsetOfName = dwDosHeader - dwImportTableDelta;
	::ExReplaceImportFunctionName(dwOffsetOfName, pDescriptor, szSelfFileName, "rb_require",   "ex_require");
	::ExReplaceImportFunctionName(dwOffsetOfName, pDescriptor, szSelfFileName, "rb_f_require", "ex_f_require");
}

static void
ExDllWriteToFile(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
ExLoadDll(char *pszFilePath, VALUE vFileName)
{
	DEBUGMSG2("ExLoadDll('%s', '%s')\n", pszFilePath, STR2CSTR(vFileName));

	HMODULE hModule = ::LoadLibraryEx(pszFilePath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
	DWORD   dwError = ::GetLastError();

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

		if ( dwInitProc ) {
			DEBUGMSG0("  module info\n");
			DEBUGMSG1("    handle = 0x%08X\n", hModule);
			DEBUGMSG0("  initialize function info\n");
			DEBUGMSG1("    name    = '%s'\n", STR2CSTR(vInitFunctionName));
			DEBUGMSG1("    address = 0x%08X\n", dwInitProc);
			DEBUGMSG0("  call initialize function...\n");
			INITPROC fpInitProc = (INITPROC)dwInitProc;
			(*fpInitProc)();
			DEBUGMSG0("  succeeded\n");
			return hModule;
		} else {
			::FreeLibrary(hModule);
			::DeleteFile(pszFilePath);
			::ExRaiseLoadError(IDS_FAIL_GETPROCADDRESS);
			return NULL;
		}
	} else {
		::DeleteFile(pszFilePath);
		::rb_raise(rb_eLoadError, "LoadLibraryEx() failed. [Error:%i].", dwError);
		return NULL;
	}
}

static VALUE
ExGetInitFunctionName(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
ExRequireDll(VALUE vFileName, DWORD dwID)
{
	DWORD dwOffset = g_DllOffsetTable[dwID];
	DWORD dwSize   = g_DllSizeTable[dwID];

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

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

		::ExDllWriteToFile(dwOffset, dwSize, szTemporaryFilePath);

		HMODULE hModule = ::ExLoadDll(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);

			VALUE basename = ::rb_funcall(rb_cFile, ::rb_intern("basename"), 1, vFileName);
			if ( ::stricmp(STR2CSTR(basename), "phi.so") == 0 ) {
				char szFullPath[MAX_PATH] = "";
				char* pszFileName = NULL;
				::GetFullPathName(szTemporaryFilePath, sizeof(szFullPath), szFullPath, &pszFileName);
				::strcpy(g_szPhiSoFileName, pszFileName);
			}

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

static VALUE
ExRequireScript(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);
		::ExEvalString(vScript, vFileName);
		return Qtrue;
	} else {
		::ExRaiseLoadError(IDS_CANNOT_EXECUTE_SCRIPT);
		return Qfalse;
	}
}

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

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

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

static VALUE
ExEvalString(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
ExRequire(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 ( ::ExRequireWithExtension(vFileName, pszExtension) == Qtrue ) {
			::rb_ary_push(vFeatures, vFileName);
			return Qtrue;
		}
	} else {
		if ( ::ExRequireWithoutExtension(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
ex_require(const char *fname)
{
	DEBUGMSG1("ex_require('%s')\n", fname);
	return ::ExRequire(Qnil, ::rb_str_new2(fname));
}

VALUE WINAPI
ex_f_require(VALUE obj, VALUE fname)
{
	DEBUGMSG1("ex_f_require('%s')\n", STR2CSTR(fname));
    Check_SafeStr(fname);
	return ::ExRequire(Qnil, fname);
}

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