/*
 * Credits to:
 * spookie (http://spookie.powerpill.co.uk) for the
 *   GTA Vice City speedometer source code.
 * gtaforums.com for some of the memory addresses I use.
 * jacob from gtaforums for figuring out the unique jump stuff.
 *   (it's not used here but I use it in my SAMA application)
 * kyeman from gtaforums for figuring out how to disable the splash screens.
 */

/*
 * Note: this project must be linked
 *   with the /FIXED:NO linker option
 *   It's also recommended that you base
 *   the image at a high address with /BASE
 */

#define NEED_D3D
#include "Common.h"
#include <math.h>
#include "InjectedSection.h"
#include "Custom.h"
#include "FindGTASA.h"
#include "Loader.h"

/*
 * default run options
 */
#ifdef _DEBUG
int bWindow = 1;
#else
int bWindow = 0;
#endif
int bSplashScreens = 0;
static int bInjectScript = 1;

/*
 * other globals
 */
char errorText[256];
PDirect3DCreate9 originalDirect3DCreate9;
HWND hWnd_GTASA;
DWORD dwThreadId_GTASA;

/*
 * Check if we're already injected into
 *   a running game.
 */
static int checkLoaded(HANDLE processHandle)
{
	DWORD bytesRead;
	PBYTE p1, p2;

	/*
	 * Check if the game's PDIRECT3DDEVICE9
	 *   points to something that's got to be
	 *   one of our thunks.
	 * Note: if get an error we assume by
	 *   default we're not loaded
	 */
	if (!ReadProcessMemory(
			processHandle,
			GA(GAME_DIRECT3DDEVICE9),
			&p1,
			sizeof(PBYTE),
			&bytesRead) ||
		bytesRead < sizeof(PBYTE) ||
		!p1)
		return 0;
	if (!ReadProcessMemory(
			processHandle,
			p1,
			&p2,
			sizeof(PVOID),
			&bytesRead) ||
		bytesRead < sizeof(PBYTE) ||
		!p2)
		return 0;
	return abs(p2 - p1) < 20;
}

/*
 * Used for connecting to a running game
 */
static int lateInject(HWND hWnd, PROCESS_INFORMATION& pi)
{
	ptrdiff_t offset;
	HANDLE newTHandle;

	if (!bWindow && !bInjectScript) {
		/*
		 * Nothing to do, no need to hook
		 */
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return 1;
	}
	selectVersion(NULL);
	/*
	 * Check if we're already running, so not
	 *   to mess it up with a double-hook
	 */
	if (checkLoaded(pi.hProcess)) {
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		strcpy(errorText, "Inject: already loaded");
		return 0;
	}
	if (SuspendThread(pi.hThread) == (DWORD) -1) {
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		strcpy(errorText, "Inject: couldn't suspend remote process");
		return 0;
	}
	hWnd_GTASA = hWnd;
	dwThreadId_GTASA = pi.dwThreadId;
	if (!loader(pi.hProcess, offset)) {
		ResumeThread(pi.hThread);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return 0;
	}
	newTHandle = CreateRemoteThread(
		pi.hProcess,
		NULL,
		0,
		(PTHREAD_START_ROUTINE) ((PBYTE) lateHook + offset),
		NULL,
		0,
		NULL);
	if (!newTHandle) {
		unload(pi.hProcess, offset);
		ResumeThread(pi.hThread);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		strcpy(errorText, "Inject: couldn't create remote thread");
		return 0;
	}
	/*
	 * The GTASA window thread will be
	 *   resumed by the injected thread
	 */
	CloseHandle(newTHandle);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	return 1;
}

static int inject()
{
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	DWORD bytesDone;
	ptrdiff_t addressOffset;
	HWND hWnd;

	if (bInjectScript && !buildScript())
		return 0;

	/*
	 * Check for a running GTASA
	 */
	if (findGTASAWindow(hWnd, pi, NULL))
		return lateInject(hWnd, pi);

#if 0
	/*
	 * Not used, does window search instead
	 */
	pi.hProcess = findGTASAProcess(NULL);
	if (!pi.hProcess) {
#ifdef STDIO_SUPPORT
		fprintf(stderr, "Couldn't find a running GTA San Andreas\n");
#endif
		strcpy(errorText, "Inject: can't find a running GTA San Andreas");
		return 0;
	}
#endif

	memset(&si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	if (!findGTASA())
		return 0;	// can't really happen
	BOOL rc = CreateProcess(
		gta_saFullPath,
		NULL,
		NULL,
		NULL,
		FALSE,
		CREATE_SUSPENDED,
		NULL,
		gta_saFolder[0] ? gta_saFolder : NULL,
		&si,
		&pi);
	if (!rc) {
#ifdef STDIO_SUPPORT
		fprintf(stderr, "Couldn't run GTA San Andreas, error = %u\n", GetLastError());
#endif
		strcpy(errorText, "Inject: can't launch GTA San Andreas");
		return 0;
	}
	if (!bWindow && !bSplashScreens && !bInjectScript) {
		/*
		 * Nothing else to do, no need to hook
		 */
		ResumeThread(pi.hThread);	// Up, Up and Away!
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return 1;
	}
	selectVersion(remoteDetectVersion(pi.hProcess));
	if (!ReadProcessMemory(
			pi.hProcess,
			GA(IAT_ENTRY_Direct3DCreate9),
			&originalDirect3DCreate9,
			sizeof(PVOID),
			&bytesDone) ||
		bytesDone < sizeof(PVOID)) {
		TerminateProcess(pi.hProcess, 0);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		strcpy(errorText, "Inject: can't read remote process memory");
		return 0;
	}
	if (!loader(pi.hProcess, addressOffset)) {
		TerminateProcess(pi.hProcess, 0);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		return 0;
	}
	PBYTE hookHead = (PBYTE) hook + addressOffset;
	if (!WriteProcessMemory(
			pi.hProcess,
			GA(IAT_ENTRY_Direct3DCreate9),
			&hookHead,
			sizeof(PVOID),
			&bytesDone) ||
		bytesDone < sizeof(PVOID)) {
		TerminateProcess(pi.hProcess, 0);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		strcpy(errorText, "Inject: can't write remote process memory");
		return 0;
	}

	ResumeThread(pi.hThread);	// Up, Up and Away!

	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	return 1;
}

static void processCommandLine(char* cl)
{
	char* token = strtok(cl, " \t");
	while (token) {
		if (!stricmp(token, "-window"))
			bWindow = 1;
		else if (!stricmp(token, "-fullscreen"))
			bWindow = 0;
		else if (!stricmp(token, "-splash"))
			bSplashScreens = 1;
		else if (!stricmp(token, "-nosplash"))
			bSplashScreens = 0;
		else if (!stricmp(token, "-script"))
			bInjectScript = 1;
		else if (!stricmp(token, "-noscript"))
			bInjectScript = 0;
		token = strtok(NULL, " \t");
	}
}

int __stdcall WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	/*
	 * Note: we can use malloc() while we're still in the
	 *   original process, but once we get transplanted to
	 *   the gta_sa process, malloc() won't work, since _crtheap
	 *   is initialized by CRT startup code before WinMain,
	 *   and in the remote process it will point to an invalid
	 *   region of memory.  Instead, you can use Win32 heap
	 *   API, and all other system allocation functions.
	 *   While we can still use malloc() here, this is an example.
	 */
	char* s = (char*) HeapAlloc(GetProcessHeap(), 0, strlen(lpCmdLine) + 1);
	if (s) {
		strcpy(s, lpCmdLine);
		processCommandLine(s);
		HeapFree(GetProcessHeap(), 0, s);
	}
	int rc = inject();
	if (!rc)
		MessageBox(NULL, errorText, "GTA: San Andreas Loader", MB_OK | MB_ICONERROR);
	return rc - 1;
}