// Copyright 2005-2016 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

/* stdint.h */
typedef int uint32_t;
typedef long long uint64_t;

#if _DEBUG
#endif

#include "../mumble_plugin_win32.h"

struct guid {
	uint64_t first;
	uint64_t second;
};

uint32_t p_playerBase;
guid g_playerGUID;

/*
 * To update visit http://www.ownedcore.com/forums/world-of-warcraft/world-of-warcraft-bots-programs/wow-memory-editing
 * and look for a thread called [WoW][TheVersion] Info Dump Thread.
 *
 * Where possible, I have included in the comments the different names some posters
 * call each value, to ease in upgrading. "[_]" means the value name may or may not
 * have an underscore in it depending on who's posting the offset.
 */
static uint32_t ptr_ClientConnection=0xFF0248; // ClientConnection or CurMgrPointer
static uint32_t off_ObjectManager=0x62C; // objectManager or CurMgrOffset
static uint32_t ptr_WorldFrame=0xEAF1F0; // Camera[_]Pointer, CameraStruct
static uint32_t off_CameraOffset=0x7610; // Camera[_]Offset
static uint32_t ptr_PlayerName=0xFF0288; // PlayerName
static uint32_t ptr_RealmName=0xFF0436; // RealmName

static uint32_t off_localGUID = 0xF8; // localGUID
static uint32_t off_firstObject = 0xD8; // firstObject
static uint32_t off_nextObject = 0x3C; // nextObject
static uint32_t off_objectGUID = 0x28;

static uint32_t off_unitpos = 0xAC0; // UnitOrigin
static uint32_t off_unitheading = 0xAD0; // UnitAngle
static uint32_t off_unitpitch = 0xAE0; // Not tracked, but probably off_unitheading + 0x10

uint32_t getInt32(uint32_t ptr) {
	uint32_t result;
	SIZE_T r;
	BOOL ok=ReadProcessMemory(hProcess, (void *)ptr, &result, sizeof(uint32_t), &r);
	if (ok && (r == sizeof(uint32_t))) {
		return result;
	} else {
		return 0xffffffff;
	}
}

uint64_t getInt64(uint32_t ptr) {
	uint64_t result;
	SIZE_T r;
	BOOL ok=ReadProcessMemory(hProcess, (void *)ptr, &result, sizeof(uint64_t), &r);
	if (ok && (r == sizeof(uint64_t))) {
		return result;
	} else {
		return 0xffffffffffffffff;
	}
}

float getFloat(uint32_t ptr) {
	float result;
	SIZE_T r;
	BOOL ok=ReadProcessMemory(hProcess, (void *)ptr, &result, sizeof(float), &r);
	if (ok && (r == sizeof(float))) {
		return result;
	} else {
		return (float)0xffffffff;
	}
}

int getCStringN(uint32_t ptr, char *buffer, size_t buffersize) {
	SIZE_T r;
	BOOL ok = ReadProcessMemory(hProcess, (void *)ptr, buffer, buffersize, &r);

	/* safety net, just in case we didn't get a string back at all */
	buffer[buffersize-1] = '\0';

	if (ok && (r == buffersize)) {
		return static_cast<int>(strlen(buffer));
	} else {
		return 0;
	}
}

int getString(uint32_t ptr, std::string &buffer) {
	char buf[1024];
	int bufLength;

	bufLength = getCStringN(ptr, buf, sizeof(buf));
	buffer = buf;

	return bufLength;
}

int getWString(uint32_t ptr, std::wstring &buffer) {
	char buf[1024];
	int bufLength;
	wchar_t wbuf[1024];
	int wbufLength;

	bufLength = getCStringN(ptr, buf, sizeof(buf));
	wbufLength = MultiByteToWideChar(CP_UTF8, 0,
	                                 buf, bufLength,
	                                 wbuf, 1024);
	buffer.assign(wbuf, wbufLength);

	return wbufLength;
}

#ifdef _DEBUG
void getDebug16(uint32_t ptr) {
	unsigned char buf[16];
	SIZE_T r;
	BOOL ok=ReadProcessMemory(hProcess, (void *)ptr, &buf, sizeof(buf), &r);
	if (ok && (r == sizeof(buf))) {
		printf("%08x: %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x\n",
		       ptr,
		       buf[0], buf[1], buf[2], buf[3],
		       buf[4], buf[5], buf[6], buf[7],
		       buf[8], buf[9], buf[10], buf[11],
		       buf[12], buf[13], buf[14], buf[15]
		      );
	}
}

void stringDebug(std::string &theString) {
	std::cout << "String length=" << theString.length() << " content=\"" << theString << "\" debug=";
	for (size_t i=0; i<theString.length(); i++) {
		if (i>0) {
			std::cout << " ";
		}
		std::cout << (unsigned int)theString[i];
	}
	std::cout << std::endl;
}
#endif

uint32_t getPlayerBase() {
	uint32_t gClientConnection;
	uint32_t sCurMgr;
	uint32_t curObj;
	guid playerGUID;
	uint32_t playerBase;

	uint32_t nextObj;
	guid GUID;

	playerBase=0;

	gClientConnection=getInt32((uint32_t)pModule + ptr_ClientConnection);
	sCurMgr=getInt32(gClientConnection + off_ObjectManager);
	if (sCurMgr != 0) {
		playerGUID.first=getInt64(sCurMgr+off_localGUID);
		playerGUID.second=getInt64(sCurMgr+off_localGUID + 0x8);
		if (playerGUID.second != 0) {
			g_playerGUID.first = playerGUID.first;
			g_playerGUID.second = playerGUID.second;

			curObj=getInt32(sCurMgr+off_firstObject); // firstObject
			while (curObj != 0) {
				nextObj=getInt32(curObj + off_nextObject); // nextObject
				GUID.first=getInt64(curObj + off_objectGUID);
				GUID.second=getInt64(curObj + off_objectGUID + 0x8);
				if (playerGUID.first == GUID.first && playerGUID.second == GUID.second) {
					playerBase = curObj;
					break;
				} else if (curObj == nextObj) {
					break;
				} else {
					curObj = nextObj;
				}
			}
		}
	}

	return playerBase;
}

void getPlayerName(std::wstring &identity) {
	std::wstring playerName, realmName;
	
	getWString((uint32_t)pModule + ptr_PlayerName, playerName);
	getWString((uint32_t)pModule + ptr_RealmName, realmName);
	
	identity = playerName + L"-" + realmName;
	//printf("Name: %ls\n", identity.data());
	return;
}

void getCamera(float camera_pos[3], float camera_front[3], float camera_top[3]) {
	uint32_t ptr1, ptr2;
	float buf[4][3];

	ptr1 = getInt32((uint32_t)pModule + ptr_WorldFrame);
	ptr2 = getInt32(ptr1+off_CameraOffset);

	peekProc((BYTE *) ptr2+0x08, buf, sizeof(buf));

	/* camera postition */
	camera_pos[0] = -buf[0][1];
	camera_pos[1] =  buf[0][2];
	camera_pos[2] =  buf[0][0];

	/* camera front vector */
	camera_front[0] = -buf[1][1];
	camera_front[1] =  buf[1][2];
	camera_front[2] =  buf[1][0];

	/* camera right vector */
#if 0 /* gets calculated in mumble client */
	camera_right[0] = -buf[2][1];
	camera_right[1] =  buf[2][2];
	camera_right[2] =  buf[2][0];
#endif

	/* camera top vector */
	camera_top[0] = -buf[3][1];
	camera_top[1] =  buf[3][2];
	camera_top[2] =  buf[3][0];
}

typedef class WowData {
		std::wstring nameAvatar;
		bool nameAvatarValid;

		guid playerGUID;
		uint32_t pointerPlayerObject;

	public:
		WowData::WowData() {
			refresh();
		}

		void WowData::updateAvatarName() {
			getPlayerName(nameAvatar);
			if (!nameAvatar.empty()) {
				nameAvatarValid = true;
			} else {
				nameAvatarValid = false;
			}
		}

		std::wstring getNameAvatar() {
			if (!nameAvatarValid) {
				updateAvatarName();
			}

			return nameAvatar;
		}

		void refresh() {
			nameAvatarValid = false;
		}
} WowData_t;

WowData_t wow;

static int fetch(float *avatar_pos, float *avatar_front, float *avatar_top, float *camera_pos, float *camera_front, float *camera_top, std::string &context, std::wstring &identity) {
	/* clear position */
	for (int i=0; i<3; i++) {
		avatar_pos[i]=avatar_front[i]=avatar_top[i]=camera_pos[i]=camera_front[i]=camera_top[i]=0.0;
	}

	/* are we still looking at the right object? */
	guid peekGUID, tempGUID;
	peekGUID.first=getInt64(p_playerBase+0x30);
	peekGUID.second=getInt64(p_playerBase+0x30+0x8);
	if (g_playerGUID.first != peekGUID.first || g_playerGUID.second != peekGUID.second) {
		/* no? Try to resynch to the new address. Happens when walking through portals quickly (aka no or short loading screen) */
		tempGUID.first = g_playerGUID.first;
		tempGUID.second = g_playerGUID.second;
		p_playerBase=getPlayerBase();
		if (tempGUID.first != g_playerGUID.first || tempGUID.first != g_playerGUID.first) {
			/* GUID of actor changed, likely a character and/or realm change */
			wow.refresh();
		}
		peekGUID.first=getInt64(p_playerBase+0x28);
		peekGUID.second=getInt64(p_playerBase+0x28+0x8);
		if (g_playerGUID.first != peekGUID.first || g_playerGUID.second != peekGUID.second) {
			/* no? we are still getting the expected GUID for our avatar, but we don't have it's current position */
			return true;
		}
	}

	context.clear();
	std::wstringstream identityStream;
	identityStream << wow.getNameAvatar();
	identity = identityStream.str();

	BOOL ok = true;

	// Wow stores as
	// North/South (+ North)
	// East/West (+ West)
	// Up/Down (+Up)
	// ... which isn't a right-hand coordinate system.

	float pos[3];
	ok = ok && peekProc((BYTE *) p_playerBase + off_unitpos, pos, sizeof(float)*3);
	if (! ok) {
		if (g_playerGUID.second == 0xffffffffffffffff) {
			return false;
		} else if (g_playerGUID.second == 0) {
			return true;
		} else {
			/* FIXME need a better way to mark PlayerBase invalid */
			g_playerGUID.first=0;
			g_playerGUID.second=0;
			return true; /* we got a good reference for an avatar, but no good position */
		}
	}

	/* convert wow -> right hand coordinate system */
	avatar_pos[0] = -pos[1];
	avatar_pos[1] = pos[2];
	avatar_pos[2] = pos[0];

	float heading=0.0;
	ok = ok && peekProc((BYTE *) p_playerBase + off_unitheading, &heading, sizeof(heading));
	if (! ok)
		return false;

	float pitch=0.0;
	ok = ok && peekProc((BYTE *) p_playerBase + off_unitpitch, &pitch, sizeof(pitch));
	if (! ok)
		return false;

	/* TODO use yaw (heading) and pitch angles */
	/* FIXME sin/cos (heading) is right from the numbers, but (-heading) is right from the sound position */
	avatar_front[0]=-sin(heading);
	avatar_front[1]=0.0;
	avatar_front[2]=cos(heading);

	// Dummy top vector, can't tilt your head sideways in wow.
	avatar_top[0]= 0.0;
	avatar_top[1]= 1.0;
	avatar_top[2]= 0.0;

	getCamera(camera_pos, camera_front, camera_top);

	if (! ok)
		return false;

	//printf("P %f %f %f -- %f %f %f \n", avatar_pos[0], avatar_pos[1], avatar_pos[2], avatar_front[0], avatar_front[1], avatar_front[2]);
	//printf("C %f %f %f -- %f %f %f \n", camera_pos[0], camera_pos[1], camera_pos[2], camera_front[0], camera_front[1], camera_front[2]);

	// is it a unit length vector?
	if (fabs((avatar_front[0]*avatar_front[0]+avatar_front[1]*avatar_front[1]+avatar_front[2]*avatar_front[2])-1.0)>0.5) {
		//		printf("F %f %f %f\n", front[0], front[1], front[2]);
		return false;
	}

	// are we around 0/0/0
	if ((fabs(avatar_pos[0])<0.1) && (fabs(avatar_pos[1])<0.1) && (fabs(avatar_pos[2])<0.1)) {
		//		printf("P %f %f %f\n", avatar_pos[0], avatar_pos[1], avatar_pos[2]);
		return false;
	}

	return true;
}

static int trylock(const std::multimap<std::wstring, unsigned long long int> &pids) {
	if (! initialize(pids, L"Wow.exe"))
		return false;

	p_playerBase=getPlayerBase();
	if (p_playerBase != 0) {
		float apos[3], afront[3], atop[3], cpos[3], cfront[3], ctop[3];
		std::string context;
		std::wstring identity;
		if (fetch(apos, afront, atop, cpos, cfront, ctop, context, identity))
			return true;
	}

	generic_unlock();
	return false;
}

static const std::wstring longdesc() {
	return std::wstring(L"Supports World of Warcraft 32-bit 6.2.2 (20490), with identity support.");
}

static std::wstring description(L"World of Warcraft 32-bit 6.2.2 (20490)");

static std::wstring shortname(L"World of Warcraft");

static int trylock1() {
	return trylock(std::multimap<std::wstring, unsigned long long int>());
}

static MumblePlugin wowplug = {
	MUMBLE_PLUGIN_MAGIC,
	description,
	shortname,
	NULL,
	NULL,
	trylock1,
	generic_unlock,
	longdesc,
	fetch
};

static MumblePlugin2 wowplug2 = {
	MUMBLE_PLUGIN_MAGIC_2,
	MUMBLE_PLUGIN_VERSION,
	trylock
};

extern "C" __declspec(dllexport) MumblePlugin *getMumblePlugin() {
	return &wowplug;
}

extern "C" __declspec(dllexport) MumblePlugin2 *getMumblePlugin2() {
	return &wowplug2;
}
