#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <string>
#include <windows.h>
//#include <winhttp.h>
#include <iostream>
#include <fstream>
#include <atomic>
//#include <vector>

#include "include/MinHook.h"
#ifdef __clang__
#pragma comment(lib, "lib/libMinHook-MD.x64.Clang.lib")
#else
#pragma comment(lib, "lib/libMinHook-MD.x64.lib")
#endif

//#include "F:/Programmieren/C++/myhelpers/xorstr.h"
#define JM_XORSTR_DISABLE_AVX_INTRINSICS 1
#include "F:/Programmieren/C++/xorstr-master/include/xorstr.hpp"
//#include "F:/Programmieren/C++/myhelpers/SuspendResume.hpp"
#include "include/patternscan.hpp"
//
#include "F:/Programmieren/C++/myhelpers/DebugHelper.hpp"

#include "SparkClasses.h"

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")
#include "F:/Programmieren/C++/json.hpp"
using json = nlohmann::json;

#include "include/easywsclient.hpp"
#include "include/easywsclient.cpp"

using easywsclient::WebSocket;
WebSocket::pointer ws = NULL;

//#include <map>
//#include <fstream>

//typedef BOOL(__stdcall* tWinHttpGetIEProxyConfigForCurrentUser)(IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG*);
//tWinHttpGetIEProxyConfigForCurrentUser oWinHttpGetIEProxyConfigForCurrentUser;

typedef void*(__stdcall* tCurl_Easy_Init)();
tCurl_Easy_Init oCurl_Easy_Init;

typedef void(__stdcall* tCurl_Easy_Cleanup)(void* handle);
tCurl_Easy_Cleanup oCurl_Easy_Cleanup;

typedef DWORD(__stdcall* tCurl_Easy_SetOpt)(void*, long, void*);
tCurl_Easy_SetOpt oCurl_Easy_SetOpt;

typedef DWORD(__stdcall* tCurl_Easy_Perform)(void*);
tCurl_Easy_Perform oCurl_Easy_Perform;

typedef DWORD(__stdcall* tCurl_Multi_Perform)(void* multi_handle, int* running_handles);
tCurl_Multi_Perform oCurl_Multi_Perform;

typedef void*(__stdcall* tCurl_Multi_Init)();
tCurl_Multi_Init oCurl_Multi_Init = NULL;

typedef void* (__fastcall* tFSparkManagerCtor)(void*);
tFSparkManagerCtor oFSparkManagerCtor = NULL;

//const std::wstring proxyURLW = L"https=127.0.0.1:9999";
std::string proxyURLA = "https://127.0.0.1:";
std::string proxyPort = "9999";
auto curl_easy_init_pattern = xorstr("48 83 EC 28 83 3D ?? ?? ?? ?? 00 75 ?? 48 8B 05");
auto curl_easy_cleanup_pattern = xorstr("40 53 48 83 EC 20 48 8B D9 48 85 C9 0F 84 ?? ?? ?? ?? 33 D2");
auto curl_easy_setopt_pattern = xorstr("89 54 24 10 4c 89 44 24 18 4c 89 4c 24 20 48 83 ec 28 48 85 c9 75");
auto curl_easy_perform_pattern = xorstr("33 d2 e9 09 01 00 00 cc cc cc cc cc cc cc");
auto curl_easy_getinfo_pattern = xorstr("89 54 24 ?? 4C 89 44 24 ?? 4C 89 4C 24 ?? 48 83 EC 28 4C 8D 44 24 ?? 4D 8B 00");
auto curl_multi_perform_pattern = xorstr("48 89 5c 24 18 56 57 41 57 48 83 ec 20 4c 8b fa 48 8b f9");
auto curl_multi_init_pattern = xorstr("BA 61 00 00 00 B9 8F 03 00 00");
auto sparkManagerCtorPattern = xorstr("40 53 48 83 EC 20 48 8B D9 E8 ?? ?? ?? ?? 4C 8B C8 48 8D 15 ?? ?? ?? ?? 0F 57 D2 48 8B CB E8 ?? ?? ?? ?? 48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0");
auto generateMicropatchArrayPattern = xorstr("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 54 41 55 41 56 41 57 48 8D 6C 24 ?? 48 81 EC ?? ?? ?? ?? 8B 72 08");
auto generateMicropatchArrayBackupPattern = xorstr("48 8D 54 24 ?? E8 ?? ?? ?? ?? 48 8D 55 60");
auto getNewsItemsPattern = xorstr("48 89 ?? ?? ?? 48 89 ?? ?? ?? 48 89 ?? ?? ?? 48 89 ?? ?? ?? 55 41 ?? 41 ?? 41 ?? 41 ?? 48 8D ?? ?? ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? ?? 8D");
auto FGbxSparkModuleStartupModulePattern = xorstr("40 53 48 83 EC 50 48 8B D9 48 89 0D");
auto onMicropatchesReceivedPattern = xorstr("48 89 5C 24 ?? 48 89 74 24 ?? 55 57 41 55 41 56 41 57 48 8D 6C 24 ?? 48 81 EC ?? ?? ?? ?? 48 63 51 08");
auto areMicropatchesDifferentPattern = xorstr("48 89 5C 24 ?? 55 56 57 48 83 EC 20 C7 02");
// + 0x33
auto parseJSONString = xorstr("40 55 53 56 57 48 8D 6C 24 ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 10 48 8B D9");

// result at +0x88 (E8 ** ** ** **)
auto getNewsServicePattern = xorstr("45 8B C7 48 8D 15 ?? ?? ?? ?? 48 8D 8D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8D 85");

auto mallocPattern = xorstr("48 89 5C 24 ?? 57 48 83 EC 20 48 8B F9 8B DA 48 8B 0D ?? ?? ?? ?? 48 85 C9 75 0C E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01 44 8B C3 48 8B D7 48 8B 5C 24 ?? 48 83 C4 20 5F 48 FF 60 10");
auto reallocPattern = xorstr("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F1 41 8B D8 48 8B 0D ?? ?? ?? ?? 48 8B FA 48 85 C9 75 0C E8 ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01 44 8B CB 4C 8B C7 48 8B D6 48 8B 5C 24 ?? 48 8B 74 24 ?? 48 83 C4 20 5F 48 FF 60 18");

CRITICAL_SECTION critsec;

static bool isInInternalMode = false;

//
//std::string startReceiveHotfixes_Pattern = xorstr("48 63 81 c4 00 00 00 83 f8 06 77 49 4c 8d 05");
////
//std::string beforeInit_Pattern = xorstr("40 53 48 83 ec 40 80 b9 c0 00 00 00 00 48 8b d9 0f 85 41 01 00 00 83 b9 cc 00 00 00 00 0f 84 34 01 00 00 f3 0f 58 89 bc 00 00 00 f3 0f 11 89 bc 00 00 00 8b 89 d0 00 00 00 85 c9 0f 84 fe 00 00 00");
////
//std::string someInterestingFunc_Pattern = xorstr("40 53 48 83 ec 30 48 8b d9 48 85 d2 74 21 45 8b c8 c7 44 24 28 ff ff ff ff");
//
//std::ofstream myfile;

/* long may be 32 or 64 bits, but we should never depend on anything else
   but 32 */
#define CURLOPTTYPE_LONG          0
#define CURLOPTTYPE_OBJECTPOINT   10000
#define CURLOPTTYPE_FUNCTIONPOINT 20000
#define CURLOPTTYPE_OFF_T         30000
#define CURLOPTTYPE_BLOB          40000

#define CURLOPT(na,t,nu) na = t + nu

/* 'char *' argument to a string with a trailing zero */
#define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT

/* 'struct curl_slist *' argument */
#define CURLOPTTYPE_SLISTPOINT  CURLOPTTYPE_OBJECTPOINT

/* 'void *' argument passed untouched to callback */
#define CURLOPTTYPE_CBPOINT     CURLOPTTYPE_OBJECTPOINT

/* 'long' argument with a set of values/bitmask */
#define CURLOPTTYPE_VALUES      CURLOPTTYPE_LONG

typedef enum {
	/* The full URL to get/put */
	CURLOPT(CURLOPT_URL, CURLOPTTYPE_STRINGPOINT, 2),
    /* Name of proxy to use. */
    CURLOPT(CURLOPT_PROXY, CURLOPTTYPE_STRINGPOINT, 4),
    /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE
    * bytes big. */
    CURLOPT(CURLOPT_ERRORBUFFER, CURLOPTTYPE_OBJECTPOINT, 10),
	/* Function that will be called to store the output (instead of fwrite). The
	 * parameters will use fwrite() syntax, make sure to follow them. */
	 CURLOPT(CURLOPT_WRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 11),
	 /* Function that will be called to read the input (instead of fread). The
	 * parameters will use fread() syntax, make sure to follow them. */
	 CURLOPT(CURLOPT_READFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 12),
	/* POST static input fields. */
	CURLOPT(CURLOPT_POSTFIELDS, CURLOPTTYPE_OBJECTPOINT, 15),
    /* Set if we should verify the peer in ssl handshake, set 1 to verify. */
    CURLOPT(CURLOPT_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 64),
	/* The CApath or CAfile used to validate the peer certificate
	 this option is used only if SSL_VERIFYPEER is true */
	 CURLOPT(CURLOPT_CAINFO, CURLOPTTYPE_STRINGPOINT, 65),
    /* Set if we should verify the Common name from the peer certificate in ssl
	* handshake, set 1 to check existence, 2 to ensure that it matches the
	* provided hostname. */
	CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81),
	/* The CApath directory used to validate the peer certificate
	this option is used only if SSL_VERIFYPEER is true */
	CURLOPT(CURLOPT_CAPATH, CURLOPTTYPE_STRINGPOINT, 97),
	/* Enable SSL/TLS for FTP, pick one of:
	CURLUSESSL_TRY     - try using SSL, proceed anyway otherwise
	CURLUSESSL_CONTROL - SSL for the control connection or fail
	CURLUSESSL_ALL     - SSL for all communication or fail
	*/
	CURLOPT(CURLOPT_USE_SSL, CURLOPTTYPE_VALUES, 119),
	/* send linked-list of name:port:address sets */
	CURLOPT(CURLOPT_RESOLVE, CURLOPTTYPE_SLISTPOINT, 203),
	/* Set if we should verify the certificate status. */
	CURLOPT(CURLOPT_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 232),
	/* The CApath or CAfile used to validate the proxy certificate
	this option is used only if PROXY_SSL_VERIFYPEER is true */
	CURLOPT(CURLOPT_PROXY_CAINFO, CURLOPTTYPE_STRINGPOINT, 246),
	/* Set if we should verify the proxy in ssl handshake,
	set 1 to verify. */
	CURLOPT(CURLOPT_PROXY_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 248),
	/* Set if we should verify the Common name from the proxy certificate in ssl
	* handshake, set 1 to check existence, 2 to ensure that it matches
	* the provided hostname. */
	CURLOPT(CURLOPT_PROXY_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 249),
	/* The public key in DER form used to validate the proxy public key
	this option is used only if PROXY_SSL_VERIFYPEER is true */
	CURLOPT(CURLOPT_PROXY_PINNEDPUBLICKEY, CURLOPTTYPE_STRINGPOINT, 263),
	/* Same as CURLOPT_SSL_VERIFYPEER but for DoH (DNS-over-HTTPS) servers. */
	CURLOPT(CURLOPT_DOH_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 306),
	/* Same as CURLOPT_SSL_VERIFYHOST but for DoH (DNS-over-HTTPS) servers. */
	CURLOPT(CURLOPT_DOH_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 307),
	/* Same as CURLOPT_SSL_VERIFYSTATUS but for DoH (DNS-over-HTTPS) servers. */
	CURLOPT(CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 308),
	/* The CA certificates as "blob" used to validate the peer certificate
	this option is used only if SSL_VERIFYPEER is true */
	CURLOPT(CURLOPT_CAINFO_BLOB, CURLOPTTYPE_BLOB, 309),
	/* The CA certificates as "blob" used to validate the proxy certificate
	this option is used only if PROXY_SSL_VERIFYPEER is true */
	CURLOPT(CURLOPT_PROXY_CAINFO_BLOB, CURLOPTTYPE_BLOB, 310),
};

#define CURL_ERROR_SIZE 256

typedef enum {
    CURLE_OK = 0
};

typedef enum {
    CURLPROXY_HTTP = 0,   /* added in 7.10, new in 7.19.4 default is to use
                             CONNECT HTTP/1.1 */
    CURLPROXY_HTTP_1_0 = 1,   /* added in 7.19.4, force to use CONNECT
	HTTP/1.0  */
	CURLPROXY_HTTPS = 2, /* added in 7.52.0 */
	CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already in 7.10 */
	CURLPROXY_SOCKS5 = 5, /* added in 7.10 */
	CURLPROXY_SOCKS4A = 6, /* added in 7.18.0 */
	CURLPROXY_SOCKS5_HOSTNAME = 7 /* Use the SOCKS5 protocol but pass along the
	                                 host name rather than the IP address. added
	                                 in 7.18.0 */
} curl_proxytype;  /* this enum was added in 7.10 */

/* parameter for the CURLOPT_USE_SSL option */
typedef enum {
	CURLUSESSL_NONE,    /* do not attempt to use SSL */
	CURLUSESSL_TRY,     /* try using SSL, proceed anyway otherwise */
	CURLUSESSL_CONTROL, /* SSL for the control connection or fail */
	CURLUSESSL_ALL,     /* SSL for all communication or fail */
	CURLUSESSL_LAST     /* not an option, never use */
} curl_usessl;

#define CURLINFO_STRING   0x100000
#define CURLINFO_LONG     0x200000
#define CURLINFO_DOUBLE   0x300000
#define CURLINFO_SLIST    0x400000
#define CURLINFO_PTR      0x400000 /* same as SLIST */
#define CURLINFO_SOCKET   0x500000
#define CURLINFO_OFF_T    0x600000
#define CURLINFO_MASK     0x0fffff
#define CURLINFO_TYPEMASK 0xf00000

typedef enum {
	CURLINFO_NONE, /* first, never use this */
	CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1,
	CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2,
	CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3,
	CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4,
	CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5,
	CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6,
	CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7,
	CURLINFO_SIZE_UPLOAD_T = CURLINFO_OFF_T + 7,
	CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8,
	CURLINFO_SIZE_DOWNLOAD_T = CURLINFO_OFF_T + 8,
	CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9,
	CURLINFO_SPEED_DOWNLOAD_T = CURLINFO_OFF_T + 9,
	CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10,
	CURLINFO_SPEED_UPLOAD_T = CURLINFO_OFF_T + 10,
	CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11,
	CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12,
	CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13,
	CURLINFO_FILETIME = CURLINFO_LONG + 14,
	CURLINFO_FILETIME_T = CURLINFO_OFF_T + 14,
	CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15,
	CURLINFO_CONTENT_LENGTH_DOWNLOAD_T = CURLINFO_OFF_T + 15,
	CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16,
	CURLINFO_CONTENT_LENGTH_UPLOAD_T = CURLINFO_OFF_T + 16,
	CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17,
	CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18,
	CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19,
	CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20,
	CURLINFO_PRIVATE = CURLINFO_STRING + 21,
	CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22,
	CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23,
	CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24,
	CURLINFO_OS_ERRNO = CURLINFO_LONG + 25,
	CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26,
	CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27,
	CURLINFO_COOKIELIST = CURLINFO_SLIST + 28,
	CURLINFO_LASTSOCKET = CURLINFO_LONG + 29,
	CURLINFO_FTP_ENTRY_PATH = CURLINFO_STRING + 30,
	CURLINFO_REDIRECT_URL = CURLINFO_STRING + 31,
	CURLINFO_PRIMARY_IP = CURLINFO_STRING + 32,
	CURLINFO_APPCONNECT_TIME = CURLINFO_DOUBLE + 33,
	CURLINFO_CERTINFO = CURLINFO_PTR + 34,
	CURLINFO_CONDITION_UNMET = CURLINFO_LONG + 35,
	CURLINFO_RTSP_SESSION_ID = CURLINFO_STRING + 36,
	CURLINFO_RTSP_CLIENT_CSEQ = CURLINFO_LONG + 37,
	CURLINFO_RTSP_SERVER_CSEQ = CURLINFO_LONG + 38,
	CURLINFO_RTSP_CSEQ_RECV = CURLINFO_LONG + 39,
	CURLINFO_PRIMARY_PORT = CURLINFO_LONG + 40,
	CURLINFO_LOCAL_IP = CURLINFO_STRING + 41,
	CURLINFO_LOCAL_PORT = CURLINFO_LONG + 42,
	CURLINFO_TLS_SESSION = CURLINFO_PTR + 43,
	CURLINFO_ACTIVESOCKET = CURLINFO_SOCKET + 44,
	CURLINFO_TLS_SSL_PTR = CURLINFO_PTR + 45,
	CURLINFO_HTTP_VERSION = CURLINFO_LONG + 46,
	CURLINFO_PROXY_SSL_VERIFYRESULT = CURLINFO_LONG + 47,
	CURLINFO_PROTOCOL = CURLINFO_LONG + 48,
	CURLINFO_SCHEME = CURLINFO_STRING + 49,
	CURLINFO_TOTAL_TIME_T = CURLINFO_OFF_T + 50,
	CURLINFO_NAMELOOKUP_TIME_T = CURLINFO_OFF_T + 51,
	CURLINFO_CONNECT_TIME_T = CURLINFO_OFF_T + 52,
	CURLINFO_PRETRANSFER_TIME_T = CURLINFO_OFF_T + 53,
	CURLINFO_STARTTRANSFER_TIME_T = CURLINFO_OFF_T + 54,
	CURLINFO_REDIRECT_TIME_T = CURLINFO_OFF_T + 55,
	CURLINFO_APPCONNECT_TIME_T = CURLINFO_OFF_T + 56,
	CURLINFO_RETRY_AFTER = CURLINFO_OFF_T + 57,
	CURLINFO_EFFECTIVE_METHOD = CURLINFO_STRING + 58,
	CURLINFO_PROXY_ERROR = CURLINFO_LONG + 59,
	CURLINFO_REFERER = CURLINFO_STRING + 60,

	CURLINFO_LASTONE = 60
} CURLINFO;

typedef DWORD(__stdcall* tCurl_Easy_GetInfo)(void*, CURLINFO info, void*);
tCurl_Easy_GetInfo oCurl_Easy_GetInfo;

/* CURLINFO_RESPONSE_CODE is the new name for the option previously known as
   CURLINFO_HTTP_CODE */
#define CURLINFO_HTTP_CODE CURLINFO_RESPONSE_CODE

typedef enum {
	CURLCLOSEPOLICY_NONE, /* first, never use this */

	CURLCLOSEPOLICY_OLDEST,
	CURLCLOSEPOLICY_LEAST_RECENTLY_USED,
	CURLCLOSEPOLICY_LEAST_TRAFFIC,
	CURLCLOSEPOLICY_SLOWEST,
	CURLCLOSEPOLICY_CALLBACK,

	CURLCLOSEPOLICY_LAST /* last, never use this */
} curl_closepolicy;

char errbuf[CURL_ERROR_SIZE];

typedef void* (__fastcall* tMalloc)(uint32_t, uint32_t);
tMalloc oMalloc;

typedef void* (__fastcall* tRealloc)(void*, size_t, uint32_t);
tRealloc oRealloc;

typedef void (__fastcall* tGetNewsItems)(void*);
tGetNewsItems oGetNewsItems = NULL;

template<class T>
struct TArray
{
	friend struct FString;

public:
	inline TArray()
	{
		Data = nullptr;
		Count = Max = 0;
	};

	inline int Num() const
	{
		return Count;
	};

	inline T& operator[](int i)
	{
		return Data[i];
	};

	inline const T& operator[](int i) const
	{
		return Data[i];
	};

	inline bool IsValidIndex(int i) const
	{
		return i < Num();
	}

	FORCEINLINE int32_t DefaultCalculateSlackGrow(int32_t NumElements, int32_t NumAllocatedElements, int32_t BytesPerElement, bool bAllowQuantize, uint32_t Alignment = 0)
	{
		int32_t Retval = 0;

		int32_t Grow = 4; // this is the amount for the first alloc
		if (NumAllocatedElements || size_t(NumElements) > Grow)
		{
			// Allocate slack for the array proportional to its size.
			Grow = size_t(NumElements) + 3 * size_t(NumElements) / 8 + 16;
		}
		if (bAllowQuantize)
		{
			//Retval = FMemory::QuantizeSize(Grow * BytesPerElement, Alignment) / BytesPerElement;
			Retval = Retval * BytesPerElement;
		}
		else
		{
			Retval = Grow;
		}
		// NumElements and MaxElements are stored in 32 bit signed integers so we must be careful not to overflow here.
		if (NumElements > Retval)
		{
			Retval = ((int32_t)0x7fffffff);
		}

		return Retval;
	}

	void ResizeGrow(int32_t OldNum, int32_t numElems, size_t sizePElem)
	{
		if (Data || Count) {
			Data = (T*)Realloc(Data, numElems * sizePElem, 0);
		}
	}

	void* Realloc(void* Original, size_t count, uint32_t alignment) {
		if (oRealloc) {
			return oRealloc(Original, count, alignment);
		}
		return NULL;
	}

	void* Malloc(uint32_t count, uint32_t alignment) {
		if (oMalloc) {
			return oMalloc(count, alignment);
		}
		return NULL;
	}

	void ResizeGrow(int32_t OldNum)
	{
		Max = DefaultCalculateSlackGrow(Count + 1, Max, sizeof(T), 0);
		ResizeGrow(Count, Max, sizeof(T));
	}

	/**
	 * Adds a new item to the end of the array, possibly reallocating the whole array to fit.
	 *
	 * @param Item The item to add
	 * @return Index to the new item
	 * @see AddDefaulted, AddUnique, AddZeroed, Append, Insert
	 */
	FORCEINLINE int32_t Add(const T& Item)
	{
		if (Count + 1 > Max) {
			Max = DefaultCalculateSlackGrow(Count + 1, Max, sizeof(T), 0);
			ResizeGrow(Count, Max, sizeof(T));
		}
		if (Data == NULL) {
			Data = (T*)Malloc(Max, sizeof(T));
			//memset(Data, 0, Max * sizeof(T));			
		}
		Data[Count] = Item;
		Count++;
		return Count - 1;
	}

	/**
	 * Inserts a given element into the array at given location.
	 *
	 * @param Item The element to insert.
	 * @param Index Tells where to insert the new elements.
	 * @returns Location at which the insert was done.
	 * @see Add, Remove
	 */
	FORCEINLINE int32_t Insert(const T& Item, int32_t Index)
	{
		if (Index >= 0 && IsValidIndex(Index)) {
			const int32_t OldNum = Count;
			if ((Count += 1) > Max)
			{
				ResizeGrow(OldNum);
			}
			for (int i = Count; i >= Index; i--)
			{
				Data[i + 1] = Data[i];
			}
			Data[Index] = Item;
		}
		return Index;
	}

public:
	T* Data;
	int32_t Count;
	int32_t Max;
};

struct FString : private TArray<wchar_t>
{
	inline FString()
	{
	};

	FString(const wchar_t* other)
	{
		Max = Count = *other ? static_cast<int32_t>(std::wcslen(other) + 1) : 0;

		if (Count)
		{
			this->Data = const_cast<wchar_t*>(other);
		}
	};

	FString(const char* other)
	{
		if (other && *other) {
			size_t len = strlen(other) + 1;
			int32_t destLen = int32_t(MultiByteToWideChar(CP_ACP, 0, other, -1, NULL, 0)) + 1;
			const int32_t oldNum = Count;
			if ((Count += destLen) > Max) {
				this->ResizeGrow(oldNum);
			}
			mbstowcs_s(NULL, this->Data, len, other, len - 1);
		}
		//if (Data != other) {
		//	int nchars = MultiByteToWideChar(CP_ACP, 0, other, -1, NULL, 0);
		//	wchar_t* wc = new wchar_t[nchars];
		//	MultiByteToWideChar(CP_ACP, 0, other, -1, (LPWSTR)wc, nchars);
		//	Max = Count = *wc ? static_cast<int32_t>(std::wcslen(wc) + 1) : 0;

		//}
		///*int nchars = MultiByteToWideChar(CP_ACP, 0, other, -1, NULL, 0);
		//wchar_t* wc = new wchar_t[nchars];
		//MultiByteToWideChar(CP_ACP, 0, other, -1, (LPWSTR)wc, nchars);
		//Max = Count = *wc ? static_cast<int32_t>(std::wcslen(wc) + 1) : 0;*/

		/*if (Count)
		{
			Data = const_cast<wchar_t*>(wc);
		}*/
	};

	inline bool IsValid() const
	{
		return this->Data != nullptr;
	}

	inline const wchar_t* c_str() const
	{
		return this->Data;
	}

	std::string ToString() const
	{
		if (!this->Data)
			return "NULL";
		auto length = std::wcslen(this->Data);

		std::string str(length, '\0');

		std::use_facet<std::ctype<wchar_t>>(std::locale()).narrow(this->Data, this->Data + length, '?', &str[0]);

		return str;
	}
};

struct FArticleTag {
	FString Tag;
	FString TagName;
	FString TagValue;
};

struct FNewsItem {
	FString NewsText;
	char Unknown1[0x8];
	char Unknown2[0x8];
	char Unknown3[0x8];
	char Unknown4[0x8];
	char Unknown5[0x8];
	char Unknown6[0x8];
	FString IconName;
	FString TargetLink;
	char Unknown7[0x8];
	char Unknown8[0x8];
	char Unknown9[0x8];
	char Unknown10[0x8];
	char Unknown11[0x8];
	char Unknown12[0x8];
	TArray<FArticleTag> ArticleTags;
};

class FSparkNewsService
{
public:
	char pad_0000[64]; //0x0000
	class TArray<FNewsItem> NewsItems; //0x0040
	char pad_0050[64]; //0x0050
}; //Size: 0x0090
static_assert(sizeof(FSparkNewsService) == 0x90);

class FSparkModule
{
public:
	wchar_t* ConfigFile; //0x0008
	char pad_0010[8]; //0x0010
	class FSparkManager* SparkManager; //0x0018
	char pad_0020[240]; //0x0020
	class FSparkNewsService* SparkNewsService; //0x0110
	char pad_0118[1192]; //0x0118

	virtual void Function0();
	virtual void Function1();
	virtual void Function2();
	virtual void Function3();
	virtual void Function4();
	virtual void Function5();
	virtual void Function6();
	virtual void Function7();
}; //Size: 0x05C0
static_assert(sizeof(FSparkModule) == 0x5C0);

enum HotfixType : uint32_t {
	SparkPatchEntry = 1,
	SparkLevelPatchEntry = 2,
	SparkStreamedPackageEntry = 3,
	SparkSaveGameEntry = 4,
	SparkPostLoadedEntry = 5,
	SparkCharacterLoadedEntry = 6,
	SparkEarlyLevelPatchEntry = 7
};

class FMicropatch
{
public:
	HotfixType PatchType; //0x0000
	uint32_t N00001B5E; //0x0004
	FString Patchname; //0x0008
	FString Patchdata; //0x0018
}; //Size: 0x0028
static_assert(sizeof(FMicropatch) == 0x28);

class UserWriteData
{
public:
	char pad_0000[16]; //0x0000
	TArray<char> Content; //0x0010
	//char* Content; 
	//uint32_t WriteSize; //0x0018
	//uint32_t MaxSize; //0x001C
	uint32_t DataSize; //0x0020
	char pad_0024[4]; //0x0024
}; //Size: 0x0028
static_assert(sizeof(UserWriteData) == 0x28);

class Curl_Write_Data
{
public:
	char pad_0000[88]; //0x0000
	class FString RequestURL; //0x0058
	char pad_0068[32]; //0x0068
	class UserWriteData* Body; //0x0088
	char pad_0090[32]; //0x0090
	class TArray<FString>* Headers; //0x00B0
}; //Size: 0x00B8
static_assert(sizeof(Curl_Write_Data) == 0xB8);

typedef void(__fastcall* tGenerateMicropatchArray)(void* someTMap, TArray<FMicropatch>* outMicropatches);
tGenerateMicropatchArray oGenerateMicropatchArray = NULL;

typedef void* (__fastcall* tParseJSONString)(void** jsonReader, const FString* text);
tParseJSONString oParseJSONString = NULL;

typedef bool(__fastcall* tAreMicropatchesDifferent)(TArray<FMicropatch>* micropatches, void* severity);
tAreMicropatchesDifferent oAreMicropatchesDifferent = NULL;

FSparkModule** pSparkModule = NULL;


//std::vector<SDK::UObject*> GetObjectNamesWith(const std::string nameToFind) {
//    std::vector<SDK::UObject*> entries;
//    for (int i = 0; i < SDK::UObject::GetGlobalObjects().Num(); i++) {
//        SDK::UObject* current = SDK::UObject::GetGlobalObjects().GetByIndex(i).Object;
//        if (current && strstr(current->GetFullName().c_str(), nameToFind.c_str()) != NULL) {
//            entries.push_back(const_cast<SDK::UObject*>(current));
//        }
//    }
//    return entries;
//}

//class HotfixInitStuff
//{
//public:
//	char pad_0000[196]; //0x0000
//	uint32_t CurrentInitStep; //0x00C4
//	char pad_00C8[952]; //0x00C8
//}; //Size: 0x0480
//
//
//HotfixInitStuff* someObject;
//typedef void(*tstartReceiveHotfixes)(HotfixInitStuff* obj);
//tstartReceiveHotfixes ostartReceiveHotfixes;
//
//void hkstartReceiveHotfixes(HotfixInitStuff* obj) {
//    someObject = obj;
//    return ostartReceiveHotfixes(obj);
//}
//
//typedef void* (__fastcall *thkBeforeInit)(HotfixInitStuff* pHFX, void* someConst, void* unknown);
//thkBeforeInit oBeforeInit;
//
//static void* sConst = NULL;
//void* __fastcall hkBeforeInit(HotfixInitStuff* pHFX, void* someConst, void* unknown) {
//    if (sConst == NULL) {
//        sConst = someConst;
//    }
//    return oBeforeInit(pHFX, sConst, unknown);
//}

uint64_t TotalHotfixes = 0;
uint64_t ModdedHotfixes = 0;

static FString modified;
static FString modifiedMailResponse;
void* hkParseJSONString(void** reader, FString* text) {
	if (text && text->IsValid()) {
		try{			
			auto received = json::parse(text->ToString());
			// make sure we are using the "News"-data
			if (received.contains("version") && received.contains("status") && received.contains("message") && received.contains("data")) {

				INT rc;
				WSADATA wsaData;

				rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
				if (rc)
				{
					printf("WSAStartup Failed.\n");
					if (oParseJSONString)
						return oParseJSONString(reader, text);
					return NULL;
				}

				ws = WebSocket::from_url("ws://localhost:9998/ws", "", true);
				if (!ws)
				{
					if (oParseJSONString)
						return oParseJSONString(reader, text);
					return NULL;
				}
				json j;
				char buffer[256] = { 0 };
				if (ModdedHotfixes == 0 && TotalHotfixes == 0) {
					delete ws;
#ifdef _WIN32
					WSACleanup();
#endif
					if (oParseJSONString)
						return oParseJSONString(reader, text);
					return NULL;
				}
				
				sprintf(buffer, "%I64u/%I64u", ModdedHotfixes, TotalHotfixes);
				
				j["eventName"] = "getNews";
				j["content"] = buffer;
				j["error"] = "ok";

				ws->send(j.dump());
				std::string response;
				bool gotNews = false;
				while (ws->getReadyState() != WebSocket::CLOSED) {
					ws->poll();
					ws->dispatch([&](const std::string& incMsg) {
						try {
							json j = json::from_msgpack(incMsg);
							auto eventName = j["EventName"];
							auto content = j["Content"];
							auto bindata = content.get_binary();
							auto output = std::string(bindata.begin(), bindata.end());
							auto data = json::parse(output);

							json fin;
							fin["EventName"] = j["EventName"];
							fin["Content"] = output;
							fin["Error"] = j["Error"];
							if (eventName == "news") {
								auto newsData = data["data"].items();
								auto throwaway = received;

								//throwaway["data"].push_back(newsData);

								
								for (auto& [paramKey, paramValue] : newsData) {
									try {
										throwaway["data"].push_back(paramValue);
									}
									catch (...) { continue; }
								}
								std::string dumped = throwaway.dump();
								static std::wstring widened = std::wstring(dumped.begin(), dumped.end());
								modified = FString(widened.c_str());

								text = &modified;
								gotNews = true;
							}
						}
						catch (...) {}
						});
					if (gotNews)
						break;
				}
				delete ws;
#ifdef _WIN32
				WSACleanup();
#endif
				//std::cout << modified.ToString() << std::endl;
				if (oParseJSONString) {
					void* result = oParseJSONString(reader, text);
					return result;
				}
			}
			// MailItems
		/*	else if (received.contains("messages") && received.contains("status")) {
				INT rc;
				WSADATA wsaData;

				rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
				if (rc)
				{
					printf("WSAStartup Failed.\n");
					if (oParseJSONString)
						return oParseJSONString(reader, text);
					return NULL;
				}

				ws = WebSocket::from_url("ws://localhost:9998/ws", "", true);
				if (!ws)
				{
					if (oParseJSONString)
						return oParseJSONString(reader, text);
					return NULL;
				}
				json j;
				j["eventName"] = "getCustomMailItems";
				j["content"] = "";
				j["error"] = "ok";

				ws->send(j.dump());
				std::string response;
				bool gotMail = false;
				while (ws->getReadyState() != WebSocket::CLOSED) {
					ws->poll();
					ws->dispatch([&](const std::string& incMsg) {
						try {
							json j = json::from_msgpack(incMsg);
							auto eventName = j["EventName"];
							auto content = j["Content"];
							auto bindata = content.get_binary();
							auto output = std::string(bindata.begin(), bindata.end());
							auto data = json::parse(output);

							json fin;
							fin["EventName"] = j["EventName"];
							fin["Content"] = output;
							fin["Error"] = j["Error"];
							if (eventName == "mailItems") {
								auto mailData = data["messages"].items();
								auto throwaway = received;

								for (auto& [paramKey, paramValue] : mailData) {
									try {
										throwaway["messages"].push_back(paramValue);
									}
									catch (...) { continue; }
								}
								std::string dumped = throwaway.dump();
								static std::wstring widenedMail = std::wstring(dumped.begin(), dumped.end());
								modifiedMailResponse = FString(widenedMail.c_str());

								text = &modifiedMailResponse;
								gotMail = true;
							}
						}
						catch (...) {}
						});
					if (gotMail)
						break;
				}
				delete ws;
#ifdef _WIN32
				WSACleanup();
#endif
				std::cout << modifiedMailResponse.ToString() << std::endl;
				if (oParseJSONString) {
					void* result = oParseJSONString(reader, text);
					return result;
				}
			}
			else if (text->ToString().find("\"internal_code\":11007") != std::string::npos) {
				json response;
				response["result"] = "message rejected";
				response["status"] = "success";
				std::string fakeResponse = response.dump();
				static std::wstring fakeWideResp = std::wstring(fakeResponse.begin(), fakeResponse.end());
				text = &FString(fakeWideResp.c_str());
			}*/
		}catch(...){}
	}
	if (oParseJSONString) {
		return oParseJSONString(reader, text);
	}		
	return NULL;
}

std::string getMicropatchString() {
	INT rc;
	WSADATA wsaData;
	std::string response;
	rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (rc)
	{
		printf("WSAStartup Failed.\n");
		return response;
	}

	ws = WebSocket::from_url("ws://localhost:9998/ws", "", true);
	if (!ws)
		return response;
	json j;
	j["eventName"] = "getMicropatches";
	j["content"] = "";
	j["error"] = "ok";

	ws->send(j.dump());
	
	bool gotMicropatches = false;
	while (ws->getReadyState() != WebSocket::CLOSED) {
		ws->poll();
		ws->dispatch([&](const std::string& incMsg) {
			try {
				json j = json::from_msgpack(incMsg);
				auto eventName = j["EventName"];
				auto content = j["Content"];
				auto bindata = content.get_binary();
				response = std::string(bindata.begin(), bindata.end());
				/*auto data = json::parse(output);*/

				//json fin;
				//fin["EventName"] = j["EventName"];
				//fin["Content"] = output;
				//fin["Error"] = j["Error"];
				if (eventName == "micropatches" || eventName == "micropatchesR") {
					//	
					//	auto params = data["parameters"].items();
					//	for (auto& [paramKey, paramValue] : params) {
					//		//std::cout << "Key: " << paramValue["key"] << " Value: " << paramValue["value"] << std::endl;
					//		FMicropatch patch;
					//		std::string patchType = paramValue["key"].get<std::string>();
					//		if (patchType.find("SparkPatchEntry") != std::string::npos)
					//			patch.PatchType = SparkPatchEntry;
					//		else if (patchType.find("SparkLevelPatchEntry") != std::string::npos)
					//			patch.PatchType = SparkLevelPatchEntry;
					//		else if (patchType.find("SparkStreamedPackageEntry") != std::string::npos)
					//			patch.PatchType = SparkStreamedPackageEntry;
					//		else if (patchType.find("SparkSaveGameEntry") != std::string::npos)
					//			patch.PatchType = SparkSaveGameEntry;
					//		else if (patchType.find("SparkPostLoadedEntry") != std::string::npos)
					//			patch.PatchType = SparkPostLoadedEntry;
					//		else if (patchType.find("SparkCharacterLoadedEntry") != std::string::npos)
					//			patch.PatchType = SparkCharacterLoadedEntry;
					//		else if (patchType.find("SparkEarlyLevelPatchEntry") != std::string::npos)
					//			patch.PatchType = SparkEarlyLevelPatchEntry;

					//		patch.N00001B5E = 1;
					//		patch.Patchname = FString(paramValue["key"].get<std::string>().c_str());
					//		patch.Patchdata = FString(paramValue["value"].get<std::string>().c_str());

					//		outPatches->Add(patch);
					//		++ModdedHotfixes;
					//		++TotalHotfixes;
					//}
					gotMicropatches = true;
				}
			}
			catch (...) {}
			});
		if (gotMicropatches)
			break;
	}
	delete ws;
#ifdef _WIN32
	WSACleanup();
#endif
	return response;
}

static FSparkManager* fsparkManager = NULL;
void* hkFSparkManagerCtor(void* outBuf) {
    fsparkManager = reinterpret_cast<FSparkManager*>(oFSparkManagerCtor(outBuf));
    return fsparkManager;
}

void* __stdcall hkCurl_Easy_Init() {
    return oCurl_Easy_Init();
}

void* __stdcall hkCurl_Multi_Init() {
    void* multihandle = oCurl_Multi_Init();
    return multihandle;
}

void __stdcall hkCurl_Easy_Cleanup(void* handle) {
    if (handle)
        oCurl_Easy_Cleanup(handle);
}

bool hookedWriteCallback = false;
typedef size_t (__stdcall* tWrite_Callback)(char* ptr, size_t size, size_t nmemb, void* userdata);
tWrite_Callback oWrite_Callback = NULL;

DWORD __stdcall hkCurl_Easy_Perform(void* easy_handle) {
    //hkCurl_Easy_SetOpt(easy_handle, CURLOPT_SSL_VERIFYPEER, 0L);
    ZeroMemory(errbuf, CURL_ERROR_SIZE);

    //std::cout << "Inside Curl_Easy_perform" << std::endl;
    auto result = oCurl_Easy_Perform(easy_handle);
    if (result != CURLE_OK) {
        std::cout << "Error: " << result << std::endl;
        std::cout << errbuf << std::endl;
    }
    return result;
}

bool isHotfixRepsonse = false;
static Curl_Write_Data* currentCurlWriteData = NULL;

size_t __stdcall hkWriteCallback(char* ptr, size_t size, size_t dataSize, void* userdata) {
	size_t writeSize = oWrite_Callback(ptr, size, dataSize, userdata);
	//currentCurlWriteData = (Curl_Write_Data*)userdata;
	//std::string currentURL = currentCurlWriteData->RequestURL.ToString();
	//if (currentURL.find("verification") != std::string::npos && currentURL.find("discovery") != std::string::npos) {
	//	std::cout << "Need to get and alter response here" << std::endl;
	//	/*static std::string moddedHotfixes = getMicropatchString();
	//	currentCurlWriteData->Body->Content.ResizeGrow(currentCurlWriteData->Body->Content.Count, moddedHotfixes.size(), 1);
	//	strcpy(currentCurlWriteData->Body->Content.Data, &moddedHotfixes.data()[0]);
	//	currentCurlWriteData->Body->Content.Max = moddedHotfixes.size();
	//	currentCurlWriteData->Body->Content.Count = moddedHotfixes.size();*/
	//	std::cout << "" << std::endl;
	//}
	return writeSize;
}

DWORD __stdcall hkCurl_Multi_Perform(void* multi_handle, int* running_handles) {
	auto result = oCurl_Multi_Perform(multi_handle, running_handles);
	if (!currentCurlWriteData)
		return result;
	//if (currentCurlWriteData->RequestURL.IsValid()) {
	//	std::string currentURL = currentCurlWriteData->RequestURL.ToString();
	//	if (currentURL.find("verification") != std::string::npos && currentURL.find("discovery") != std::string::npos) {
	//		std::cout << "Need to get and alter response here" << std::endl;
	//		/*static std::string moddedHotfixes = getMicropatchString();
	//		currentCurlWriteData->Body->Content.ResizeGrow(currentCurlWriteData->Body->Content.Count, moddedHotfixes.size(), 1);
	//		strcpy(currentCurlWriteData->Body->Content.Data, &moddedHotfixes.data()[0]);
	//		currentCurlWriteData->Body->Content.Max = moddedHotfixes.size();
	//		currentCurlWriteData->Body->Content.Count = moddedHotfixes.size();*/
	//		std::cout << "" << std::endl;
	//	}
	//}	
	return result;
}

DWORD __stdcall hkCurl_Easy_GetInfo(void* handle, CURLINFO info, void* dataPtr) {
	return oCurl_Easy_GetInfo(handle, info, dataPtr);
}

bool startLogging = false;
DWORD __stdcall hkCurl_Easy_SetOpt(void* curlHandle, long option, void* parameter)
{
	DWORD result;
	result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
	if (!isInInternalMode) {
		EnterCriticalSection(&critsec);

		result = oCurl_Easy_SetOpt(curlHandle, CURLOPT_PROXY, const_cast<char*>((proxyURLA + proxyPort).c_str()));
		if (result != CURLE_OK) {
			std::cout << "Failed to set proxy. Errorcode: " << result << std::endl;
		}

		result = oCurl_Easy_SetOpt(curlHandle, CURLOPT_ERRORBUFFER, errbuf);
		if (result != CURLE_OK) {
			std::cout << "Failed to set errorbuffer. Errorcode: " << result << std::endl;
		}

		if (option == CURLOPT_SSL_VERIFYHOST) {
			parameter = (void*)0L;
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}
		else if (option == CURLOPT_SSL_VERIFYPEER) {
			parameter = (void*)0L;
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}
		else if (option == CURLOPT_USE_SSL) {
			parameter = (void*)CURLUSESSL_TRY;
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}
		else if (option == CURLOPT_WRITEFUNCTION) {
			//std::cout << "Write data func at: 0x" << std::hex << parameter << std::endl;

			oWrite_Callback = (tWrite_Callback)parameter;
			parameter = &hkWriteCallback;
			hookedWriteCallback = true;
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}
		else if (option == CURLOPT_READFUNCTION) {
			//std::cout << "Read data func at: 0x" << std::hex << parameter << std::endl;
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}
		else if (option == CURLOPT_CAINFO) {
			result = CURLE_OK;
		}
		else {
			result = oCurl_Easy_SetOpt(curlHandle, option, parameter);
		}

		LeaveCriticalSection(&critsec);
	}	

	return result;
}

bool __fastcall hkAreMicropatchesDifferent(TArray<FMicropatch>* micropatches, void* severity) {
	return true;
}

void hkGenerateMicropatchArray(void* someTMap, TArray<FMicropatch>* outPatches) {
	if (oGenerateMicropatchArray) {
		oGenerateMicropatchArray(someTMap, outPatches);
	}
	TotalHotfixes = outPatches->Count;
	//for (int i = 0; i < outPatches->Count; ++i) {
	//	/*if (outPatches->Data[i].PatchType != 1 || outPatches->Data[i].N00001B5E != 1) {*/
	//		std::cout << outPatches->Data[i].Patchname.ToString() << " " << outPatches->Data[i].PatchType << ":" << outPatches->Data[i].N00001B5E << std::endl;
	//	//}
	//}
	if (outPatches) {
		INT rc;
		WSADATA wsaData;

		rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (rc)
		{
			printf("WSAStartup Failed.\n");
			return;
		}

		ws = WebSocket::from_url("ws://localhost:9998/ws", "", true);
		if (!ws)
			return;
		json j;
		j["eventName"] = "getMicropatches";
		j["content"] = "";
		j["error"] = "ok";

		ws->send(j.dump());
		std::string response;
		bool gotMicropatches = false;
		while (ws->getReadyState() != WebSocket::CLOSED) {
			ws->poll();
			ws->dispatch([&](const std::string& incMsg) {
				try {
					json j = json::from_msgpack(incMsg);
					auto eventName = j["EventName"];
					auto content = j["Content"];
					auto bindata = content.get_binary();
					auto output = std::string(bindata.begin(), bindata.end());
					auto data = json::parse(output);
					
					json fin;
					fin["EventName"] = j["EventName"];
					fin["Content"] = output;
					fin["Error"] = j["Error"];
					if (eventName == "micropatches" || eventName == "micropatchesR") {
						bool replaceMode = false;
						if (eventName == "micropatchesR")
						{
							ZeroMemory(outPatches->Data, outPatches->Count * sizeof(FMicropatch));							
							outPatches->Max = 0;
							outPatches->Count = 0;
							TotalHotfixes = 0;
						}
						ModdedHotfixes = 0;
						auto params = data["parameters"].items();
						for (auto& [paramKey, paramValue] : params) {
							//std::cout << "Key: " << paramValue["key"] << " Value: " << paramValue["value"] << std::endl;
							FMicropatch patch;
							std::string patchType = paramValue["key"].get<std::string>();
							if (patchType.find("SparkPatchEntry") != std::string::npos)
								patch.PatchType = SparkPatchEntry;
							else if (patchType.find("SparkLevelPatchEntry") != std::string::npos)
								patch.PatchType = SparkLevelPatchEntry;
							else if (patchType.find("SparkStreamedPackageEntry") != std::string::npos)
								patch.PatchType = SparkStreamedPackageEntry; 
							else if (patchType.find("SparkSaveGameEntry") != std::string::npos)
								patch.PatchType = SparkSaveGameEntry; 
							else if (patchType.find("SparkPostLoadedEntry") != std::string::npos)
								patch.PatchType = SparkPostLoadedEntry; 
							else if (patchType.find("SparkCharacterLoadedEntry") != std::string::npos)
								patch.PatchType = SparkCharacterLoadedEntry; 
							else if (patchType.find("SparkEarlyLevelPatchEntry") != std::string::npos)
								patch.PatchType = SparkEarlyLevelPatchEntry;

							patch.N00001B5E = 1;
							patch.Patchname = FString(paramValue["key"].get<std::string>().c_str());
							patch.Patchdata = FString(paramValue["value"].get<std::string>().c_str());

							outPatches->Add(patch);
							++ModdedHotfixes;
							++TotalHotfixes;
						}
						gotMicropatches = true;
					}						
				}
				catch (...) {}
				});
			if (gotMicropatches)
				break;
		}
		delete ws;
#ifdef _WIN32
		WSACleanup();
#endif
		//std::cout << "Number of Patches: " << outPatches->Count << std::endl;
	}
	return;
}

void hkGetNewsItems(void* newsWidget) {
	//void* result = NULL;
	if (*pSparkModule) {
		if ((*pSparkModule)->SparkNewsService && (*pSparkModule)->SparkNewsService->NewsItems.Data != NULL) {			
			
			/*for (int i = 0; i < (*pSparkModule)->SparkNewsService->NewsItems.Count; ++i) {
				std::cout << (*pSparkModule)->SparkNewsService->NewsItems.Data[i].NewsText.ToString() << std::endl;
			}*/
		}
	}
	if (oGetNewsItems) {
		return oGetNewsItems(newsWidget);
	}
	return;
}

//typedef DNS_STATUS(WINAPI* DNSQUERYEX)(PDNS_QUERY_REQUEST pQueryRequest, PDNS_QUERY_RESULT pQueryResults, PDNS_QUERY_CANCEL pCancelHandle);
//DNSQUERYEX fpDnsQueryEx = NULL;
//
//DNS_STATUS hkDnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest, PDNS_QUERY_RESULT pQueryResults, PDNS_QUERY_CANCEL pCancelHandle) {
//    DNS_STATUS result = fpDnsQueryEx(pQueryRequest, pQueryResults, pCancelHandle);
//    return result;
//}
//
//typedef DNS_STATUS (WINAPI *DNSQUERYCONFIG)(DNS_CONFIG_TYPE Config,	DWORD Flag, PCWSTR pwsAdapterName, PVOID pReserved,PVOID pBuffer,PDWORD pBufLen);
//DNSQUERYCONFIG fpDnsQueryConfig = NULL;
//DNS_STATUS hkDnsQueryConfig(DNS_CONFIG_TYPE Config, DWORD Flag, PCWSTR pwsAdapterName, PVOID pReserved, PVOID pBuffer, PDWORD pBufLen) {
//    DNS_STATUS rv = fpDnsQueryConfig(Config, Flag, pwsAdapterName, pReserved, pBuffer, pBufLen);
//    return rv;
//}
//
//typedef int (WINAPI* SOCKRCV)(SOCKET s, char* buf, int len, int flags);
//SOCKRCV fpnSockRcv = NULL;
//
//typedef int (WSAAPI* SOCKCONNECT)(SOCKET s, const sockaddr* name, int namelen);
//SOCKCONNECT fpnSockConnect = NULL;
//
//int hkSockRcv(SOCKET s, char* buf, int len, int flags) {
//    int result = fpnSockRcv(s, buf, len, flags);
//    return result;
//}
//
//int hkSockConnect(SOCKET s, const sockaddr* name, int namelen) {
//	int result = fpnSockConnect(s, name, namelen);
//	return result;
//}

//typedef int (WSAAPI* tconnect)(SOCKET s, const sockaddr* name, int namelen);
//tconnect oconnect = NULL;
//
//int WSAAPI hkconnect(SOCKET s, const sockaddr* name, int namelen) {
//    int retval = oconnect(s, name, namelen);
//    return retval;
//}
//
//typedef SOCKET (WSAAPI* tWSASocketW)(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g, DWORD dwFlags);
//tWSASocketW owsaSocketW = NULL;
//
//SOCKET WSAAPI hkWSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g, DWORD dwFlags) {
//    SOCKET result = owsaSocketW(af, type, protocol, lpProtocolInfo, g, dwFlags);
//    return result;
//}
//
//typedef INT (WSAAPI* tgetaddrinfo)(PCSTR pNodeName, PCSTR pServiceName, const ADDRINFOA* pHints, PADDRINFOA* ppResult);
//tgetaddrinfo ogetaddrinfo = NULL;
//
//INT WSAAPI hkgetaddrinfo(PCSTR pNodeName, PCSTR pServiceName, const ADDRINFOA* pHints, PADDRINFOA* ppResult) {
//    int status = ogetaddrinfo(pNodeName, pServiceName, pHints, ppResult);
//    return status;
//}

//
//typedef void* (*tSomeInterestingFunc)(void* out, char* name, DWORD someNumber);
//tSomeInterestingFunc oSomeInterestingFunc;

//std::map<std::string, void*> someFuncInfo;
//const char* test = "DataTable_Mayhem_CoreMods_Easy";

//void* hkSomeInterestingFunc(void* out, char* name, DWORD someNumber) {
//	//someFuncInfo[std::string(name)] = NULL;
//	//std::cout << "[HOOK] SomeInterestingFunc: " << name << std::endl;
//	return oSomeInterestingFunc(out, name, someNumber);
//}

HMODULE thisDLL;
static HMODULE forRefCount;

void callAtExit() {
    MH_DisableHook(MH_ALL_HOOKS);
    MH_Uninitialize();
    FreeLibrary(thisDLL);
}

const auto doThingPattern = xorstr("40 53 48 83 EC 40 80 B9 ?? ?? ?? ?? ?? 48 8B D9 0F 85 ?? ?? ?? ?? 83 B9");
//const std::string doThingPattern = xorstr("4C 8B ?? 55 56 41 ?? 49 8D ?? ?? 48 81 EC ?? ?? ?? ?? 4C 8D");
typedef bool(__fastcall* tDoThing)(FSparkInitProcess* fsip);
tDoThing oDoThing = NULL;

bool doSomething(FSparkInitProcess* fsip) {
    return oDoThing(fsip);
}

DWORD WINAPI MainThread(LPVOID param)
{
#ifdef _DEBUG
    DebugHelper::CreateConsole();
#endif
    
    atexit(callAtExit);
    GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN, L"BL3ProxySettings.dll", &forRefCount);

	while (!oMalloc)
		oMalloc = (tMalloc)PatternScan::FindSignature(NULL, mallocPattern.crypt_get());
	while (!oRealloc)
		oRealloc = (tRealloc)PatternScan::FindSignature(NULL, reallocPattern.crypt_get());

	uintptr_t curl_easy_init = PatternScan::FindSignature(NULL, curl_easy_init_pattern.crypt_get());
	while (curl_easy_init == NULL)
	{
        curl_easy_init = PatternScan::FindSignature(NULL, curl_easy_init_pattern.crypt_get());
		Sleep(50);
	}
    std::cout << "Found 1/7 Patterns" << std::endl;
    uintptr_t curl_easy_setopt = PatternScan::FindSignature(NULL, curl_easy_setopt_pattern.crypt_get());
    while (curl_easy_setopt == NULL)
    {
        curl_easy_setopt = PatternScan::FindSignature(NULL, curl_easy_setopt_pattern.crypt_get());
        Sleep(50);
    }
    std::cout << "Found 2/7 Patterns" << std::endl;
    uintptr_t curl_easy_perform = PatternScan::FindSignature(NULL, curl_easy_perform_pattern.crypt_get());
    while (curl_easy_perform == NULL) {
        curl_easy_perform = PatternScan::FindSignature(NULL, curl_easy_perform_pattern.crypt_get());
        Sleep(50);
    }
    std::cout << "Found 3/7 Patterns" << std::endl;
	uintptr_t curl_easy_cleanup = PatternScan::FindSignature(NULL, curl_easy_cleanup_pattern.crypt_get());
	while (curl_easy_cleanup == NULL) {
        curl_easy_cleanup = PatternScan::FindSignature(NULL, curl_easy_cleanup_pattern.crypt_get());
		Sleep(50);
	}
    std::cout << "Found 4/7 Patterns" << std::endl;
	uintptr_t curl_multi_perform = PatternScan::FindSignature(NULL, curl_multi_perform_pattern.crypt_get());
	while (curl_multi_perform == NULL) {
		curl_multi_perform = PatternScan::FindSignature(NULL, curl_multi_perform_pattern.crypt_get());
		Sleep(50);
	}
	uintptr_t curl_easy_getinfo = PatternScan::FindSignature(NULL, curl_easy_getinfo_pattern.crypt_get());
	while (curl_easy_getinfo == NULL) {
		curl_easy_getinfo = PatternScan::FindSignature(NULL, curl_easy_getinfo_pattern.crypt_get());
		Sleep(50);
	}
    std::cout << "Found 5/7 Patterns" << std::endl;

	uintptr_t areMicropatchesDifferentAddress = PatternScan::FindSignature(NULL, areMicropatchesDifferentPattern.crypt_get());
	while (areMicropatchesDifferentAddress == NULL) {
		areMicropatchesDifferentAddress = PatternScan::FindSignature(NULL, areMicropatchesDifferentPattern.crypt_get());
		Sleep(100);
	}

	uintptr_t sparkModuleAddress = PatternScan::FindSignature(NULL, FGbxSparkModuleStartupModulePattern.crypt_get());
	while (sparkModuleAddress == NULL) {
		sparkModuleAddress = PatternScan::FindSignature(NULL, FGbxSparkModuleStartupModulePattern.crypt_get());
		Sleep(50);
	}
	sparkModuleAddress += 9;
	DWORD sparkSingletonOffset = *(DWORD*)(sparkModuleAddress + 3);

	pSparkModule = reinterpret_cast<FSparkModule**>(sparkModuleAddress + sparkSingletonOffset + 7);

	

	std::cout << "Found 7/7 Patterns" << std::endl;
	/*uintptr_t fsparkmanagerctor = PatternScan::FindSignature(NULL, sparkManagerCtorPattern.c_str());
	while (fsparkmanagerctor == NULL) {
        fsparkmanagerctor = PatternScan::FindSignature(NULL, sparkManagerCtorPattern.c_str());
		Sleep(50);
	}
    uintptr_t dothingAddress = PatternScan::FindSignature(NULL, doThingPattern.c_str());
	while (dothingAddress == NULL) {
        dothingAddress = PatternScan::FindSignature(NULL, doThingPattern.c_str());
		Sleep(50);
	}
    oDoThing = (tDoThing)dothingAddress;*/

    MH_STATUS status;
    status = MH_Initialize();
    while (status == MH_ERROR_NOT_INITIALIZED) {
        Sleep(200);
        status = MH_Initialize();
        if (status == MH_ERROR_ALREADY_INITIALIZED)
            break;
    }
	/*while (MH_CreateHookEx((LPVOID)fsparkmanagerctor, &hkFSparkManagerCtor, &oFSparkManagerCtor) != MH_OK)
		Sleep(200);*/
	while (MH_CreateHookEx((LPVOID)curl_easy_init, &hkCurl_Easy_Init, &oCurl_Easy_Init) != MH_OK)
		Sleep(200);
    std::cout << "Placed 1/7 hooks" << std::endl;
    while (MH_CreateHookEx((LPVOID)curl_easy_setopt, &hkCurl_Easy_SetOpt, &oCurl_Easy_SetOpt) != MH_OK)
        Sleep(200);    
    std::cout << "Placed 2/7 hooks" << std::endl;
    while (MH_CreateHookEx((LPVOID)curl_easy_perform, &hkCurl_Easy_Perform, &oCurl_Easy_Perform) != MH_OK)
        Sleep(200);
    std::cout << "Placed 3/7 hooks" << std::endl;
	while (MH_CreateHookEx((LPVOID)curl_easy_cleanup, &hkCurl_Easy_Cleanup, &oCurl_Easy_Cleanup) != MH_OK)
		Sleep(200);
    std::cout << "Placed 4/7 hooks" << std::endl;
	while (MH_CreateHookEx((LPVOID)curl_multi_perform, &hkCurl_Multi_Perform, &oCurl_Multi_Perform) != MH_OK)
		Sleep(200);
	while (MH_CreateHookEx((LPVOID)curl_easy_getinfo, &hkCurl_Easy_GetInfo, &oCurl_Easy_GetInfo) != MH_OK)
		Sleep(200);
	while (MH_CreateHookEx((LPVOID)areMicropatchesDifferentAddress, &hkAreMicropatchesDifferent, &oAreMicropatchesDifferent) != MH_OK)
		Sleep(200);
    std::cout << "Placed 5/7 hooks" << std::endl;
	
	std::cout << "Placed 7/7 hooks" << std::endl;
    while (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
        Sleep(200);
    }

    std::cout << "Enabled all hooks" << std::endl;
    return TRUE;
}

struct ArgStruct {
    const char dllPath[MAX_PATH];
    int portNumber;
	bool boolValue;
};

// TODO: Send log messages on success/errors
extern "C" __declspec(dllexport) DWORD __stdcall ToggleOperationalMode(ArgStruct* argStruct)
{
	EnterCriticalSection(&critsec);
	static uintptr_t generateMicropatchArray;
	while (generateMicropatchArray == NULL) {
		generateMicropatchArray = PatternScan::FindSignature(NULL, generateMicropatchArrayPattern.crypt_get());
		Sleep(50);
	}
	static uintptr_t getNewsItems;
	while (getNewsItems == NULL) {
		getNewsItems = PatternScan::FindSignature(NULL, getNewsItemsPattern.crypt_get());
		Sleep(50);
	}
	static uintptr_t parseJSONStringAddress;
	while (parseJSONStringAddress == NULL) {
		parseJSONStringAddress = PatternScan::FindSignature(NULL, parseJSONString.crypt_get());
		if (parseJSONStringAddress != NULL) {
			parseJSONStringAddress += 0x33;

			int32_t parseJSONStringOffset = *(int32_t*)(parseJSONStringAddress + 1);
			parseJSONStringAddress = parseJSONStringAddress + parseJSONStringOffset + 5;
		}
		Sleep(50);
	}
	// Hook or unhook
	if (argStruct->boolValue && !isInInternalMode) {
		MH_STATUS hookStatus = MH_CreateHookEx((LPVOID)generateMicropatchArray, &hkGenerateMicropatchArray, &oGenerateMicropatchArray);
		if (hookStatus == MH_ERROR_ALREADY_CREATED || hookStatus == MH_OK) {
			hookStatus = MH_EnableHook((LPVOID)generateMicropatchArray);
		}
		hookStatus = MH_CreateHookEx((LPVOID)getNewsItems, &hkGetNewsItems, &oGetNewsItems);
		if (hookStatus == MH_ERROR_ALREADY_CREATED || hookStatus == MH_OK) {
			hookStatus = MH_EnableHook((LPVOID)getNewsItems);
		}
		hookStatus = MH_CreateHookEx((LPVOID)parseJSONStringAddress, &hkParseJSONString, &oParseJSONString);
		if (hookStatus == MH_ERROR_ALREADY_CREATED || hookStatus == MH_OK) {
			hookStatus = MH_EnableHook((LPVOID)parseJSONStringAddress);
		}

		isInInternalMode = true;
	}
	else if (!argStruct->boolValue && isInInternalMode) {
		while (MH_DisableHook((LPVOID)generateMicropatchArray) != MH_OK)
			Sleep(200);
		while (MH_DisableHook((LPVOID)getNewsItems) != MH_OK)
			Sleep(200);
		while (MH_DisableHook((LPVOID)parseJSONStringAddress) != MH_OK)
			Sleep(200);
		while (MH_ApplyQueued() != MH_OK)
			Sleep(200);
		isInInternalMode = false;
	}

	LeaveCriticalSection(&critsec);
	return TRUE;
}

extern "C" __declspec(dllexport) DWORD __stdcall InitFunc(ArgStruct * argstruct) 
{
    EnterCriticalSection(&critsec);
    proxyPort = std::to_string(argstruct->portNumber);
    std::cout << "Set Proxy to: " << proxyURLA << proxyPort << std::endl;
    LeaveCriticalSection(&critsec);
    return TRUE;
}

extern "C" __declspec(dllexport) LRESULT CALLBACK WndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    //std::cout << "Looks like this is working!" << std::endl;
    return CallNextHookEx(0, nCode, wParam, lParam);
};

//extern "C" __declspec(dllexport) LRESULT RefreshHotfixes() {
//    EnterCriticalSection(&critsec);
//    if (fsparkManager && fsparkManager->FSparkInitProcessPtr) {
//        if (fsparkManager->FSparkInitProcessPtr->FSparkinitProcess)
//            fsparkManager->FSparkInitProcessPtr->FSparkinitProcess->InitStep = 0;
//            fsparkManager->FSparkInitProcessPtr->FSparkinitProcess->hasBeenRequested = false;
//            //fsparkManager->FSparkInitProcessPtr->FSparkinitProcess->N0000160A = 5000.0;
//            doSomething(fsparkManager->FSparkInitProcessPtr->FSparkinitProcess);
//    }
//    LeaveCriticalSection(&critsec);
//    return false;
//}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        InitializeCriticalSection(&critsec);
        DisableThreadLibraryCalls(hModule);
        thisDLL = hModule;
        CreateThread(nullptr, NULL, (LPTHREAD_START_ROUTINE)MainThread, hModule, NULL, nullptr);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:       	
        break;
    }
    return TRUE;
}