#define WIN32_LEAN_AND_MEAN
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501

#include <windows.h>
#include <stdio.h>
#include <Shlwapi.h>
#include <ShlObj.h>
#include "MemoryMgr.h"
#include "Patterns.h"

#include "Common_ddraw.h"

#pragma comment(lib, "shlwapi.lib")

extern "C" HRESULT WINAPI DirectDrawCreateEx(GUID FAR *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown FAR *pUnkOuter)
{
	static HRESULT	(WINAPI *pDirectDrawCreateEx)(GUID FAR*, LPVOID*, REFIID, IUnknown FAR*);
	if ( pDirectDrawCreateEx == nullptr )
	{
		wchar_t		wcSystemPath[MAX_PATH];
		GetSystemDirectoryW(wcSystemPath, MAX_PATH);
		PathAppendW(wcSystemPath, L"ddraw.dll");

		HMODULE		hLib = LoadLibraryW(wcSystemPath);
		pDirectDrawCreateEx = (HRESULT(WINAPI*)(GUID FAR*, LPVOID*, REFIID, IUnknown FAR*))GetProcAddress(hLib, "DirectDrawCreateEx");
	}
	return pDirectDrawCreateEx(lpGUID, lplpDD, iid, pUnkOuter);
}

char** ppUserFilesDir;

char* GetMyDocumentsPath()
{
	static char	cUserFilesPath[MAX_PATH];

	if ( cUserFilesPath[0] == '\0' )
	{	
		SHGetFolderPathA(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, cUserFilesPath);
		PathAppendA(cUserFilesPath, *ppUserFilesDir);

		CreateDirectoryA(cUserFilesPath, nullptr);
	}
	return cUserFilesPath;
}


void InjectHooks()
{
	static char		aNoDesktopMode[64];

	RECT			desktop;
	GetWindowRect(GetDesktopWindow(), &desktop);
	sprintf_s(aNoDesktopMode, "Cannot find %dx%dx32 video mode", desktop.right, desktop.bottom);

	std::unique_ptr<ScopedUnprotect::Unprotect> Protect = ScopedUnprotect::UnprotectSectionOrFullModule( GetModuleHandle( nullptr ), ".text" );

	if (*(DWORD*)0x5C1E75 == 0xB85548EC)
	{
		// III 1.0
		ppUserFilesDir = (char**)0x580C16;
		Common::Patches::DDraw_III_10( desktop, aNoDesktopMode );
	}
	else if (*(DWORD*)0x5C2135 == 0xB85548EC)
	{
		// III 1.1
		ppUserFilesDir = (char**)0x580F66;
		Common::Patches::DDraw_III_11( desktop, aNoDesktopMode );
	}
	else if (*(DWORD*)0x5C6FD5 == 0xB85548EC)
	{
		// III Steam
		ppUserFilesDir = (char**)0x580E66;
		Common::Patches::DDraw_III_Steam( desktop, aNoDesktopMode );
	}

	else if (*(DWORD*)0x667BF5 == 0xB85548EC)
	{
		// VC 1.0
		ppUserFilesDir = (char**)0x6022AA;
		Common::Patches::DDraw_VC_10( desktop, aNoDesktopMode );
	}
	else if (*(DWORD*)0x667C45 == 0xB85548EC)
	{
		// VC 1.1
		ppUserFilesDir = (char**)0x60228A;
		Common::Patches::DDraw_VC_11( desktop, aNoDesktopMode );
	}
	else if (*(DWORD*)0x666BA5 == 0xB85548EC)
	{
		// VC Steam
		ppUserFilesDir = (char**)0x601ECA;
		Common::Patches::DDraw_VC_Steam( desktop, aNoDesktopMode );
	}

	Common::Patches::DDraw_Common();
}

static bool rwcsegUnprotected = false;

static void ProcHook()
{
	static bool		bPatched = false;
	if ( !bPatched )
	{
		bPatched = true;

		InjectHooks();

		if ( !rwcsegUnprotected )
		{
			rwcsegUnprotected = Common::Patches::FixRwcseg_Patterns();
		}
	}
}

static VOID (WINAPI* pOrgGetStartupInfoA)(LPSTARTUPINFOA);
VOID WINAPI GetStartupInfoA_Hook(LPSTARTUPINFOA lpStartupInfo)
{
	ProcHook();
	pOrgGetStartupInfoA(lpStartupInfo);
}

static uint8_t orgCode[5];
static decltype(SystemParametersInfoA)* pOrgSystemParametersInfoA;
BOOL WINAPI SystemParametersInfoA_OverwritingHook( UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni )
{
	ProcHook();
	Memory::VP::Patch( pOrgSystemParametersInfoA, { orgCode[0], orgCode[1], orgCode[2], orgCode[3], orgCode[4] } );
	return pOrgSystemParametersInfoA( uiAction, uiParam, pvParam, fWinIni );
}

static bool FixRwcseg_Header()
{
	HINSTANCE					hInstance = GetModuleHandle(nullptr);
	PIMAGE_NT_HEADERS			ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)hInstance + ((PIMAGE_DOS_HEADER)hInstance)->e_lfanew);

	// Give _rwcseg proper access rights
	PIMAGE_SECTION_HEADER	pSection = IMAGE_FIRST_SECTION(ntHeader);

	for ( SIZE_T i = 0, j = ntHeader->FileHeader.NumberOfSections; i < j; i++, pSection++ )
	{
		if ( *(uint64_t*)(pSection->Name) == 0x006765736377725F )	// _rwcseg
		{
			DWORD	dwProtect;
			VirtualProtect((LPVOID)((DWORD_PTR)hInstance + pSection->VirtualAddress), pSection->Misc.VirtualSize, PAGE_EXECUTE_READ, &dwProtect);

			DWORD Characteristics = pSection->Characteristics;
			if ( (Characteristics & IMAGE_SCN_CNT_CODE) == 0 )
			{
				Characteristics |= IMAGE_SCN_CNT_CODE;
				Memory::VP::Patch( &ntHeader->OptionalHeader.SizeOfCode, ntHeader->OptionalHeader.SizeOfCode + pSection->Misc.VirtualSize );
			}
			if ( (Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) != 0 )
			{
				Characteristics &= ~(IMAGE_SCN_CNT_INITIALIZED_DATA);
				Memory::VP::Patch( &ntHeader->OptionalHeader.SizeOfInitializedData, ntHeader->OptionalHeader.SizeOfInitializedData - pSection->Misc.VirtualSize );
			}
			if ( (Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 )
			{
				Characteristics &= ~(IMAGE_SCN_CNT_UNINITIALIZED_DATA);
				Memory::VP::Patch( &ntHeader->OptionalHeader.SizeOfUninitializedData, ntHeader->OptionalHeader.SizeOfUninitializedData - pSection->Misc.VirtualSize );
			}
			Memory::VP::Patch( &pSection->Characteristics, Characteristics );
			return true;
		}
	}
	return false;
}

static bool PatchIAT()
{
	HINSTANCE					hInstance = GetModuleHandle(nullptr);
	PIMAGE_NT_HEADERS			ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)hInstance + ((PIMAGE_DOS_HEADER)hInstance)->e_lfanew);

	// Find IAT	
	PIMAGE_IMPORT_DESCRIPTOR	pImports = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)hInstance + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

	// Find kernel32.dll
	for ( ; pImports->Name != 0; pImports++ )
	{
		if ( !_stricmp((const char*)((DWORD_PTR)hInstance + pImports->Name), "KERNEL32.DLL") )
		{
			if ( pImports->OriginalFirstThunk != 0 )
			{
				PIMAGE_IMPORT_BY_NAME*		pFunctions = (PIMAGE_IMPORT_BY_NAME*)((DWORD_PTR)hInstance + pImports->OriginalFirstThunk);

				// kernel32.dll found, find GetStartupInfoA
				for ( ptrdiff_t j = 0; pFunctions[j] != nullptr; j++ )
				{
					if ( !strcmp((const char*)((DWORD_PTR)hInstance + pFunctions[j]->Name), "GetStartupInfoA") )
					{
						// Overwrite the address with the address to a custom GetStartupInfoA
						DWORD			dwProtect[2];
						DWORD_PTR*		pAddress = &((DWORD_PTR*)((DWORD_PTR)hInstance + pImports->FirstThunk))[j];

						VirtualProtect(pAddress, sizeof(DWORD_PTR), PAGE_EXECUTE_READWRITE, &dwProtect[0]);
						pOrgGetStartupInfoA = **(VOID(WINAPI**)(LPSTARTUPINFOA))pAddress;
						*pAddress = (DWORD_PTR)GetStartupInfoA_Hook;
						VirtualProtect(pAddress, sizeof(DWORD_PTR), dwProtect[0], &dwProtect[1]);

						return true;
					}
				}
			}
		}
	}
	return false;
}

static bool PatchIAT_ByPointers()
{
	pOrgSystemParametersInfoA = SystemParametersInfoA;
	memcpy( orgCode, pOrgSystemParametersInfoA, sizeof(orgCode) );
	Memory::VP::InjectHook( pOrgSystemParametersInfoA, SystemParametersInfoA_OverwritingHook, PATCH_JUMP );
	return true;
}

static void ApplyDDrawHooks()
{
	rwcsegUnprotected = FixRwcseg_Header();

	bool getStartupInfoHooked = PatchIAT();
	if ( !getStartupInfoHooked )
	{
		PatchIAT_ByPointers();
	}
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	UNREFERENCED_PARAMETER(hinstDLL);
	UNREFERENCED_PARAMETER(lpvReserved);

	if ( fdwReason == DLL_PROCESS_ATTACH )
	{
		ApplyDDrawHooks();
	}

	return TRUE;
}