#include "CustomOpcodeSystem.h"
#include "ScriptEngine.h"
#include "CleoInstance.h"
#include "GameVersionManager.h"
#include "CPed.h"
#include "CVehicle.h"
#include "CObject.h"
#include "CPool.h"
#include "CMarker.h"
#include "CHandling.h"
#include "CModel.h"
#include <type_traits>
#include <cmath>
namespace CLEO
{
	// predeclarations of custom opcode handlers
	OpcodeResult STDCALL opcode_0A8C(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A8D(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A8E(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A8F(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A90(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A91(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A92(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A93(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A94(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A95(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A96(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A97(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A98(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A99(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9A(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9B(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9C(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9D(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9E(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0A9F(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA0(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA1(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA2(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA3(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA4(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA5(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA6(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA7(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA8(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AA9(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAA(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAB(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAC(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAD(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAE(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AAF(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB0(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB1(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB2(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB3(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB4(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB5(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB6(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB7(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB8(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AB9(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABA(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABB(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABC(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABD(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABE(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ABF(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC0(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC1(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC2(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC3(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC4(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC5(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC6(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC7(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC8(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AC9(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACA(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACB(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACC(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACD(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACE(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ACF(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD0(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD1(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD2(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD3(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD4(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD5(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD6(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD7(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD8(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AD9(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADA(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADB(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADC(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADD(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADE(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0ADF(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE0(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE1(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE2(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE3(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE4(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE5(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE6(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE7(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE8(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AE9(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AEA(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AEB(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AEC(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AED(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AEE(CScriptThread *thread);
	OpcodeResult STDCALL opcode_0AEF(CScriptThread *thread);
	CustomOpcodeHandler customOpcodeHandlers[100] = 
	{
		opcode_0A8C, opcode_0A8D, opcode_0A8E, opcode_0A8F, opcode_0A90, 
		opcode_0A91, opcode_0A92, opcode_0A93, opcode_0A94, opcode_0A95,
		opcode_0A96, opcode_0A97, opcode_0A98, opcode_0A99, opcode_0A9A,
		opcode_0A9B, opcode_0A9C, opcode_0A9D, opcode_0A9E, opcode_0A9F,
		opcode_0AA0, opcode_0AA1, opcode_0AA2, opcode_0AA3, opcode_0AA4,
		opcode_0AA5, opcode_0AA6, opcode_0AA7, opcode_0AA8, opcode_0AA9,
		opcode_0AAA, opcode_0AAB, opcode_0AAC, opcode_0AAD, opcode_0AAE,
		opcode_0AAF, opcode_0AB0, opcode_0AB1, opcode_0AB2, opcode_0AB3,
		opcode_0AB4, opcode_0AB5, opcode_0AB6, opcode_0AB7, opcode_0AB8, 
		opcode_0AB9, opcode_0ABA, opcode_0ABB, opcode_0ABC, opcode_0ABD,
		opcode_0ABE, opcode_0ABF, opcode_0AC0, opcode_0AC1, opcode_0AC2,
		opcode_0AC3, opcode_0AC4, opcode_0AC5, opcode_0AC6, opcode_0AC7,
		opcode_0AC8, opcode_0AC9, opcode_0ACA, opcode_0ACB, opcode_0ACC,
		opcode_0ACD, opcode_0ACE, opcode_0ACF, opcode_0AD0, opcode_0AD1,
		opcode_0AD2, opcode_0AD3, opcode_0AD4, opcode_0AD5, opcode_0AD6,
		opcode_0AD7, opcode_0AD8, opcode_0AD9, opcode_0ADA, opcode_0ADB,
		opcode_0ADC, opcode_0ADD, opcode_0ADE, opcode_0ADF, opcode_0AE0,
		opcode_0AE1, opcode_0AE2, opcode_0AE3, opcode_0AE4, opcode_0AE5,
		opcode_0AE6, opcode_0AE7, opcode_0AE8, opcode_0AE9, opcode_0AEA,
		opcode_0AEB, opcode_0AEC, opcode_0AED, opcode_0AEE, opcode_0AEF,
	};

	typedef OpcodeResult THISCALL (*OpcodeHandler)(CScriptThread *thread, unsigned short opcode);
	OpcodeHandler *oldOpcodeHandlerTable;
	OpcodeHandler newOpcodeHandlerTable[329];
	CustomOpcodeHandler extraOpcodeHandlers[100][300];

	// opcode handler for opcodes, defined by user with cleo api
	OpcodeResult THISCALL extraOpcodeHandler(CScriptThread *thread, unsigned short opcode)
	{
		return extraOpcodeHandlers[opcode % 100][opcode / 100 - 28](thread);
	};

	// opcode handler for custom opcodes
	OpcodeResult THISCALL customOpcodeHandler(CScriptThread *thread, unsigned short opcode)
	{
		return customOpcodeHandlers[opcode - 0x0A8C](thread);
	}

	CPedPool		**pedPool		= nullptr;
	CVehiclePool	**vehiclePool	= nullptr;
	CObjectPool		**objectPool	= nullptr;

	inline CPedPool& GetPedPool() { return **pedPool; }
	inline CVehiclePool& GetVehiclePool() { return **vehiclePool; }
	inline CObjectPool& GetObjectPool() { return **objectPool; }

	const char * CDECL (*GetUserDirectory)();

	float CDECL (*FindGroundZ)(float x, float y);
	CMarker *radarBlips;

	CHandling *handling;

	CPed * CDECL (*GetPlayerPed)(unsigned);
	CModel **models;

	void CDECL (*SpawnCar)(unsigned);

	WORD last_opcode = 0;
	char last_thread[8] = "none";
	ptrdiff_t last_off = -1;
	char ScriptExecutionLoop()
	{
		CScriptThread *thread;
		OpcodeResult res;
		asm volatile ("" : "=S"(thread));
		do
		{
			WORD opcode = *(WORD *)thread->ip;
			ptrdiff_t off = thread->IsCustom ? thread->ip - thread->baseIp : thread->ip - scmBlock;
			last_opcode = opcode; last_off = off;
			memcpy(last_thread, thread->threadName, 8);
			thread->notFlag = (opcode & 0x8000) != 0;
			opcode &= 0x7FFF;
			thread->ip += 2;
			res = newOpcodeHandlerTable[opcode / 100](thread, opcode);
		} while (res == OR_CONTINUE);
		return 0;
	}

	CustomOpcodeSystem::~CustomOpcodeSystem()
	{
//		TRACE("Last opcode executed %04X at %s:%d", last_opcode, last_thread, last_off);
	}

	void CustomOpcodeSystem::inject(CodeInjector& inj)
	{
		TRACE("Injecting CustomOpcodeSystem...");
		GameVersionManager& gvm = GetThisInstance().versionManager;
		oldOpcodeHandlerTable = gvm.TranslateMemoryAddress(MA_OPCODE_HANDLER);
		// add handler for custom opcodes
		oldOpcodeHandlerTable[27] = customOpcodeHandler;
		// replace old OpcodeHandlerTable with the new one
		inj.MemoryWrite(gvm.TranslateMemoryAddress(MA_OPCODE_HANDLER_REF), &newOpcodeHandlerTable[0]);
		// copy old table to the new
		std::copy(oldOpcodeHandlerTable, oldOpcodeHandlerTable + 28, newOpcodeHandlerTable);
		// fill the rest with default handler
		std::fill(newOpcodeHandlerTable + 28, newOpcodeHandlerTable + 329, extraOpcodeHandler);
		pedPool = gvm.TranslateMemoryAddress(MA_PED_POOL);
		vehiclePool = gvm.TranslateMemoryAddress(MA_VEHICLE_POOL);
		objectPool = gvm.TranslateMemoryAddress(MA_OBJECT_POOL);
		GetUserDirectory = gvm.TranslateMemoryAddress(MA_GET_USER_DIR_FUNCTION);
		FindGroundZ = gvm.TranslateMemoryAddress(MA_FIND_GROUND_Z_FUNCTION);
		radarBlips = gvm.TranslateMemoryAddress(MA_RADAR_BLIPS);
		handling = gvm.TranslateMemoryAddress(MA_HANDLING);
		GetPlayerPed = gvm.TranslateMemoryAddress(MA_GET_PLAYER_PED_FUNCTION);
		models = gvm.TranslateMemoryAddress(MA_MODELS);
		SpawnCar = gvm.TranslateMemoryAddress(MA_SPAWN_CAR_FUNCTION);
		if (gvm.GetGameVersion() == GV_US10)
		{
//			inj.Nop(0x469FB0, 0x469FFB - 0x469FB0);
//			inj.ReplaceFunction(ScriptExecutionLoop, 0x469FB0);
		}
	}

	inline CScriptThread& operator>>(CScriptThread& thread, unsigned& uval)
	{
		GetScriptParams(&thread, 1);
		uval = opcodeParams[0].dwParam;
		return thread;
	}

	inline CScriptThread& operator<<(CScriptThread& thread, unsigned uval)
	{
		opcodeParams[0].dwParam = uval;
		SetScriptParams(&thread, 1);
		return thread;
	}

	inline CScriptThread& operator>>(CScriptThread& thread, int& nval)
	{
		GetScriptParams(&thread, 1);
		nval = opcodeParams[0].nParam;
		return thread;
	}

	inline CScriptThread& operator<<(CScriptThread& thread, int nval)
	{
		opcodeParams[0].nParam = nval;
		SetScriptParams(&thread, 1);
		return thread;
	}
	
	inline CScriptThread& operator>>(CScriptThread& thread, float& fval)
	{
		GetScriptParams(&thread, 1);
		fval = opcodeParams[0].fParam;
		return thread;
	}
	
	inline CScriptThread& operator<<(CScriptThread& thread, float fval)
	{
		opcodeParams[0].fParam = fval;
		SetScriptParams(&thread, 1);
		return thread;
	}

	inline CScriptThread& operator>>(CScriptThread& thread, RwV3d& vec)
	{
		GetScriptParams(&thread, 3);
		vec.x = opcodeParams[0].fParam;
		vec.y = opcodeParams[1].fParam;
		vec.z = opcodeParams[2].fParam;
		return thread;
	}
	
	inline CScriptThread& operator<<(CScriptThread& thread, const RwV3d& vec)
	{
		opcodeParams[0].fParam = vec.x;
		opcodeParams[1].fParam = vec.y;
		opcodeParams[2].fParam = vec.z;
		SetScriptParams(&thread, 3);
		return thread;
	}

	template<typename T>
	inline CScriptThread& operator>>(CScriptThread& thread, T *& pval)
	{
		GetScriptParams(&thread, 1);
		pval = reinterpret_cast<T *>(opcodeParams[0].pParam);
		return thread;
	}
	
	template<typename T>
	inline CScriptThread& operator<<(CScriptThread& thread, T *pval)
	{
		opcodeParams[0].pParam = (void *)(pval);
		SetScriptParams(&thread, 1);
		return thread;
	}

	inline CScriptThread& operator>>(CScriptThread& thread, memory_pointer& pval)
	{
		GetScriptParams(&thread, 1);
		pval = opcodeParams[0].pParam;
		return thread;
	}

	template<typename T>
	inline CScriptThread& operator<<(CScriptThread& thread, memory_pointer pval)
	{
		opcodeParams[0].pParam = pval;
		SetScriptParams(&thread, 1);
		return thread;
	}

	// read string parameter according to convention on strings
	char *readString(CScriptThread *thread, char* buf = nullptr, BYTE size = 0)
	{
		auto paramType = *thread->ip;
		if (!paramType) return nullptr;
		if (paramType >= 1 && paramType <= 8)
		{
			// process parameter as a pointer to string
			GetScriptParams(thread, 1);
			return opcodeParams[0].szParam;
		}
		else
		{
			// process as scm string
			if (!buf)
			{
				static char result[128];
				std::fill(result, result + 128, '\0');
				GetScriptStringParam(thread, result, 100);	
				return result;
			}
			else
			{
				size = size > 100 || size ? 100 : size;
				std::fill(buf, buf + size, '\0');
				GetScriptStringParam(thread, buf, size);	
				return buf;
			}
		}
	}

	// perform 'sprintf'-operation for parameters, passed through SCM
	int format(CScriptThread *thread, char *str, size_t len, const char *format)
	{
		unsigned int written = 0;
		const char *iter = format;
		char bufa[256], fmtbufa[64], *fmta;

		while (*iter)
		{
			while (*iter && *iter != '%')
			{
				if (written++ >= len)
					return -1;
				*str++ = *iter++;
			}
			if (*iter == '%')
			{
				if (iter[1] == '%')
				{
					if (written++ >= len)
						return -1;
					*str++ = '%'; /* "%%"->'%' */
					iter += 2;
					continue;
				}

				//get flags and width specifier
				fmta = fmtbufa;
				*fmta++ = *iter++;
				while (*iter == '0' ||
					   *iter == '+' ||
					   *iter == '-' ||
					   *iter == ' ' ||
					   *iter == '*' ||
					   *iter == '#')
				{
					if (*iter == '*')
					{
						char *buffiter = bufa;
						//get width
						GetScriptParams(thread, 1);
						_itoa(opcodeParams[0].dwParam, buffiter, 10);
						while (*buffiter)
							*fmta++ = *buffiter++;
					}
					else
						*fmta++ = *iter;
					iter++;
				}

				//get immidiate width value
				while (isdigit(*iter))
					*fmta++ = *iter++;

				//get precision
				if (*iter == '.')
				{
					*fmta++ = *iter++;
					if (*iter == '*')
					{
						char *buffiter = bufa;
						GetScriptParams(thread, 1);
						_itoa(opcodeParams[0].dwParam, buffiter, 10);
						while (*buffiter)
							*fmta++ = *buffiter++;
					}
					else
						while (isdigit(*iter))
							*fmta++ = *iter++;
				}
				//get size
				if (*iter == 'h' || *iter == 'l')
					*fmta++ = *iter++;

				switch (*iter)
				{
				case 's':
				{
					static const char none[] = "(null)";
					const char *astr = readString(thread);
					const char *striter = astr ? astr : none;
					while (*striter)
					{
						if (written++ >= len)
							return -1;
						*str++ = *striter++;
					}
					iter++;
					break;
				}

				case 'c':
					if (written++ >= len)
						return -1;
					GetScriptParams(thread, 1);
					*str++ = (char)opcodeParams[0].nParam;
					iter++;
					break;

				default:
				{
					/* For non wc types, use system sprintf and append to wide char output */
					/* FIXME: for unrecognised types, should ignore % when printing */
					char *bufaiter = bufa;
					if (*iter == 'p' || *iter == 'P')
					{
						GetScriptParams(thread, 1);
						sprintf(bufaiter, "%08X", opcodeParams[0].dwParam);
					}
					else
					{
						*fmta++ = *iter;
						*fmta = '\0';
						if (*iter == 'a' || *iter == 'A' ||
							*iter == 'e' || *iter == 'E' ||
							*iter == 'f' || *iter == 'F' || 
							*iter == 'g' || *iter == 'G')
						{
							GetScriptParams(thread, 1);
							sprintf(bufaiter, fmtbufa, opcodeParams[0].fParam);
						}
						else
						{
							GetScriptParams(thread, 1);
							sprintf(bufaiter, fmtbufa, opcodeParams[0].pParam);
						}
					}
					while (*bufaiter)
					{
						if (written++ >= len)
							return -1;
						*str++ = *bufaiter++;
					}
					iter++;
					break;
				}
				}
			}
		}
		if (written >= len)
			return -1;
		*str++ = 0;
		return (int)written;
	}

	
	inline void __impl_RetrieveScriptParam(SCRIPT_VAR*) { }
	template<typename... Params> inline void __impl_RetrieveScriptParam(SCRIPT_VAR *, unsigned&, Params&...);
	template<typename... Params> inline void __impl_RetrieveScriptParam(SCRIPT_VAR *, int&, Params&...);
	template<typename... Params> inline void __impl_RetrieveScriptParam(SCRIPT_VAR *, float&, Params&...);
	template<typename ThisParam, typename... Params> inline void __impl_RetrieveScriptParam(SCRIPT_VAR *, ThisParam *&, Params&...);

	template<typename... Params>
	inline void __impl_RetrieveScriptParam(SCRIPT_VAR *var, unsigned& thisParam, Params&... restParams)
	{
		thisParam = var->dwParam;
		__impl_RetrieveScriptParam(var + 1, restParams...);
	}

	template<typename... Params>
	inline void __impl_RetrieveScriptParam(SCRIPT_VAR *var, int& thisParam, Params&... restParams)
	{
		thisParam = var->nParam;
		__impl_RetrieveScriptParam(var + 1, restParams...);
	}
	
	template<typename... Params>
	inline void __impl_RetrieveScriptParam(SCRIPT_VAR *var, float& thisParam, Params&... restParams)
	{
		thisParam = var->fParam;
		__impl_RetrieveScriptParam(var + 1, restParams...);
	}
	
	template<typename ThisParam, typename... Params>
	inline void __impl_RetrieveScriptParam(SCRIPT_VAR *var, ThisParam *& thisParam, Params&... restParams)
	{
		thisParam = reinterpret_cast<ThisParam *>(var->pParam);
		__impl_RetrieveScriptParam(var + 1, restParams...);
	}


	template<typename... Params>
	inline void RetrieveScriptParams(CScriptThread *thread, Params&... params)
	{
		GetScriptParams(thread, sizeof...(params));
		__impl_RetrieveScriptParam(opcodeParams, params...);
	}

	inline void ThreadJump(CScriptThread *thread, int off)
	{
		thread->ip = off < 0 ? thread->baseIp - off : scmBlock + off;
	}

	inline void SkipUnusedParameters(CScriptThread *thread)
	{
		while (*thread->ip) GetScriptParams(thread, 1);	// skip parameters
		++thread->ip;
	}

	struct ScmFunction
	{
		unsigned short prevScmFunctionId, thisScmFunctionId;
		BYTE *retnAddress;
		SCRIPT_VAR savedTls[32];
		static const size_t store_size = 0x400;
		static ScmFunction *Store[store_size];
		static size_t allocationPlace;			// contains an index of last allocated object

		void *operator new(size_t size)
		{
			size_t start_search = allocationPlace;
			while (Store[allocationPlace])	// find first unused position in store
			{
				if (++allocationPlace >= store_size) allocationPlace = 0;		// end of store reached
				if (allocationPlace == start_search) throw std::bad_alloc();	// the store is filled up
			}
			ScmFunction *obj = reinterpret_cast<ScmFunction *>(::operator new(size));
			Store[allocationPlace] = obj;
			return obj;
		}

		void operator delete(void *mem)
		{
			Store[reinterpret_cast<ScmFunction *>(mem)->thisScmFunctionId] = nullptr;
			::operator delete(mem);
		}

		ScmFunction(CScriptThread *thread) :
			prevScmFunctionId(thread->scmFunction), retnAddress(thread->ip)
		{
			std::copy(thread->tls, thread->tls + 32, savedTls);
			SCRIPT_VAR fill_val; fill_val.dwParam = 0;
			std::fill(thread->tls, thread->tls + 32, fill_val);	// fill with zeros
			thread->scmFunction = thisScmFunctionId = allocationPlace;
		}

		void Return(CScriptThread *thread)
		{
			std::copy(savedTls, savedTls + 32, thread->tls);
			thread->ip = retnAddress;
			thread->scmFunction = prevScmFunctionId;
		}
	};

	ScmFunction *ScmFunction::Store[store_size] = { /* default initializer - nullptr */ };
	size_t ScmFunction::allocationPlace = 0;

	void ResetScmFunctionStore()
	{
		for (ScmFunction *scmFunc : ScmFunction::Store)
			if (scmFunc) delete scmFunc;
		ScmFunction::allocationPlace = 0;
	}

	/************************************************************************/
	/*						Opcode definitions								*/
	/************************************************************************/

	//0A8C=4,write_memory %1d% size %2d% value %3d% virtual_protect %4d%
	OpcodeResult STDCALL opcode_0A8C(CScriptThread *thread)
	{
		GetScriptParams(thread, 4);	
		void *Address = opcodeParams[0].pParam;
		unsigned size = opcodeParams[1].dwParam;
		unsigned value = opcodeParams[2].dwParam;
		bool vp = opcodeParams[3].dwParam != 0;
		switch (size)
		{
		default:
			GetThisInstance().codeInjector.MemoryWrite(Address, (BYTE)value, vp, size);
			break;
		case 2:
			GetThisInstance().codeInjector.MemoryWrite(Address, (WORD)value, vp);
			break;
		case 4:
			GetThisInstance().codeInjector.MemoryWrite(Address, value, vp);
			break;
		}
		return OR_CONTINUE;
	}

	//0A8D=4,%4d% = read_memory %1d% size %2d% virtual_protect %3d%
	OpcodeResult STDCALL opcode_0A8D(CScriptThread *thread)
	{
		GetScriptParams(thread, 3);
		unsigned& value = opcodeParams[0].dwParam;
		void *Address = opcodeParams[0].pParam;
		unsigned size = opcodeParams[1].dwParam;
		bool vp = opcodeParams[2].dwParam;
		switch (size)
		{
		case 1:
			GetThisInstance().codeInjector.MemoryRead(Address, *(BYTE *)&value, vp);
			break;
		case 2:
			GetThisInstance().codeInjector.MemoryRead(Address, *(WORD *)&value, vp);
			break;
		case 4:
			GetThisInstance().codeInjector.MemoryRead(Address, value, vp);
			break;
		default:
			TRACE("[0A8D] Unallowed size %u", size);
		}
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}

	//0A8E=3,%3d% = %1d% + %2d% ; int
	OpcodeResult STDCALL opcode_0A8E(CScriptThread *thread)
	{
		GetScriptParams(thread, 2);
		opcodeParams[0].nParam += opcodeParams[1].nParam;
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}

	//0A8F=3,%3d% = %1d% - %2d% ; int
	OpcodeResult STDCALL opcode_0A8F(CScriptThread *thread)
	{
		GetScriptParams(thread, 2);
		opcodeParams[0].nParam -= opcodeParams[1].nParam;
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}

	//0A90=3,%3d% = %1d% * %2d% ; int
	OpcodeResult STDCALL opcode_0A90(CScriptThread *thread)
	{
		GetScriptParams(thread, 2);
		opcodeParams[0].nParam *= opcodeParams[1].nParam;
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}

	//0A91=3,%3d% = %1d% / %2d% ; int
	OpcodeResult STDCALL opcode_0A91(CScriptThread *thread)
	{
		GetScriptParams(thread, 2);
		opcodeParams[0].nParam /= opcodeParams[1].nParam;
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}

	//0A92=-1,create_custom_thread %1d%
	OpcodeResult STDCALL opcode_0A92(CScriptThread *thread)
	{
		const char *script_name = readString(thread);
		TRACE("[0A92] Starting new custom script %s from thread named %s",
			script_name, thread->threadName);
		char cwd[MAX_PATH];
		_getcwd(cwd, sizeof(cwd));
		chdir(cleo_dir);
		auto cs = new CCustomScript(script_name);
		SetScriptCondResult(thread, cs->is_ok());
		if (cs->is_ok())
		{
			GetThisInstance().scriptEngine.AddCustomScript(cs);
			TransmitScriptParams(thread, cs);
		}
		else 
		{
			delete cs;
			SkipUnusedParameters(thread);
			TRACE("[0A92] Failed.");
		}
		chdir(cwd);
		return OR_CONTINUE;
	}

	//0A93=0,end_custom_thread
	OpcodeResult STDCALL opcode_0A93(CScriptThread *thread)
	{
		CCustomScript *cs = reinterpret_cast<CCustomScript *>(thread);
		if (thread->missionFlag || !thread->IsCustom) 
		{
			TRACE("[0A93] Incorrect usage of opcode from thread %s",
				thread->threadName);
			return OR_CONTINUE;
		}
		GetThisInstance().scriptEngine.RemoveCustomScript(cs);
		delete cs;
		return OR_INTERRUPT;
	}

	//0A94=-1,create_custom_mission %1d%
	OpcodeResult STDCALL opcode_0A94(CScriptThread *thread)
	{
		char script_name[MAX_PATH];
		readString(thread, script_name);
		strcat(script_name, ".cm");	// add custom mission extension
		TRACE("[0A94] Starting new custom mission %s from thread named %s",
			script_name, thread->threadName);
		char cwd[MAX_PATH];
		_getcwd(cwd, sizeof(cwd));
		chdir(cleo_dir);
		auto cs = new CCustomScript(script_name, true);
		SetScriptCondResult(thread, cs->is_ok());
		if (cs->is_ok())
		{
			GetThisInstance().scriptEngine.AddCustomScript(cs);
			TransmitScriptParams(thread, cs);
		}
		else 
		{
			delete cs;
			SkipUnusedParameters(thread);
			TRACE("[0A94] Failed.");
		}
		chdir(cwd);
		return OR_CONTINUE;
	}

	//0A95=0,enable_thread_saving
	OpcodeResult STDCALL opcode_0A95(CScriptThread *thread)
	{
		reinterpret_cast<CCustomScript *>(thread)->enable_saving();
		return OR_CONTINUE;
	}

	//0A96=2,%2d% = actor %1d% struct
	OpcodeResult opcode_0A96(CScriptThread *thread)
	{
		unsigned handle; 
		*thread >> handle;
		*thread << GetPedPool().atHandle(handle);
		return OR_CONTINUE;
	}

	//0A97=2,%2d% = car %1d% struct
	OpcodeResult opcode_0A97(CScriptThread *thread)
	{
		unsigned handle; 
		*thread >> handle;
		*thread << GetVehiclePool().atHandle(handle);
		return OR_CONTINUE;
	}

	//0A98=2,%2d% = object %1d% struct
	OpcodeResult opcode_0A98(CScriptThread *thread)
	{
		unsigned handle; 
		*thread >> handle;
		*thread << GetObjectPool().atHandle(handle);
		return OR_CONTINUE;
	}

	//0A99=1,chdir %1b:userdir/rootdir%
	OpcodeResult opcode_0A99(CScriptThread *thread)
	{
		auto paramType = *thread->ip;
		if (paramType >= 1 && paramType <= 8)
		{
			// integer param
			unsigned param;
			*thread >> param;
			chdir(param ? GetUserDirectory() : "");
		}
		else
		{
			// string param
			char buf[MAX_PATH];
			std::fill(buf, buf + sizeof(buf), '\0');
			GetScriptStringParam(thread, buf, 100);
			chdir(buf);
		}
		return OR_CONTINUE;
	}

	//0A9A=3,%3d% = openfile %1d% mode %2d% // IF and SET
	OpcodeResult opcode_0A9A(CScriptThread *thread)
	{
		const char *fname = readString(thread);
		auto paramType = *thread->ip;
		char mode[0x10];
		if (paramType >= 1 && paramType <= 8)
		{
			// integer param (for backward compatibility with CLEO 3)
			union
			{
				unsigned uParam;
				char strParam[4];
			}param;
			*thread >> param.uParam;
			strcpy(mode, param.strParam);
		}
		else
			// string param
			GetScriptStringParam(thread, mode, sizeof(mode));
		auto file = fopen(fname, mode);
		*thread << file;
		SetScriptCondResult(thread, file != nullptr);
		if (file) GetThisInstance().opcodeSystem.openedFiles.insert(file);
		return OR_CONTINUE;
	}
		
	//0A9B=1,closefile %1d%
	OpcodeResult opcode_0A9B(CScriptThread *thread)
	{
		FILE *file;
		*thread >> file;
		fclose(file);
		GetThisInstance().opcodeSystem.openedFiles.erase(file);
		return OR_CONTINUE;
	}	

	//0A9C=2,%2d% = file %1d% size
	OpcodeResult opcode_0A9C(CScriptThread *thread)
	{
		FILE *file;
		*thread >> file;
		auto savedPos = ftell(file);
		fseek(file, 0, SEEK_END);
		*thread << static_cast<unsigned>(ftell(file));
		fseek(file, savedPos, SEEK_SET);
		return OR_CONTINUE;
	}

	//0A9D=3,readfile %1d% size %2d% to %3d%
	OpcodeResult opcode_0A9D(CScriptThread *thread)
	{
		FILE *file;
		unsigned size;
		void *buf;
		RetrieveScriptParams(thread, file, size);
		buf = GetScriptParamPointer(thread);
		fread(buf, size, 1, file);
		return OR_CONTINUE;
	}

	//0A9E=3,writefile %1d% size %2d% from %3d%
	OpcodeResult opcode_0A9E(CScriptThread *thread)
	{
		FILE *file;
		unsigned size;
		const void *buf;
		RetrieveScriptParams(thread, file, size);
		buf = GetScriptParamPointer(thread);
		fwrite(buf, size, 1, file);
		fflush(file);
		return OR_CONTINUE;
	}
	
	//0A9F=1,%1d% = current_thread_pointer
	OpcodeResult opcode_0A9F(CScriptThread *thread)
	{
		*thread << thread;
		return OR_CONTINUE;
	}

	//0AA0=1,gosub_if_false %1p%
	OpcodeResult opcode_0AA0(CScriptThread *thread)
	{
		int off;
		*thread >> off;
		if (thread->condResult) return OR_CONTINUE;
		// save pointer to the next instruction on the stack
		thread->gosub_stack[thread->stack_index++] = thread->ip;
		// jump by pointer
		ThreadJump(thread, off);
		return OR_CONTINUE;
	}

	//0AA1=0,return_if_false
	OpcodeResult opcode_0AA1(CScriptThread *thread)
	{
		if (thread->condResult) return OR_CONTINUE;
		// jump back to the location saved on stack
		thread->ip = thread->gosub_stack[--thread->stack_index];
		return OR_CONTINUE;
	}

	//0AA2=2,%2h% = load_library %1d% // IF and SET
	OpcodeResult opcode_0AA2(CScriptThread *thread)
	{
		auto libHandle = LoadLibrary(readString(thread));
		*thread << libHandle;
		SetScriptCondResult(thread, libHandle != nullptr);
		if (libHandle) GetThisInstance().opcodeSystem.nativeLibs.insert(libHandle);

		return OR_CONTINUE;
	}

	//0AA3=1,free_library %1h%
	OpcodeResult opcode_0AA3(CScriptThread *thread)
	{
		HMODULE libHandle;
		*thread >> libHandle;
		FreeLibrary(libHandle);
		GetThisInstance().opcodeSystem.nativeLibs.erase(libHandle);
		return OR_CONTINUE;
	}

	//0AA4=3,%3d% = get_proc_address %1d% library %2d% // IF and SET
	OpcodeResult opcode_0AA4(CScriptThread *thread)
	{
		char *funcName = readString(thread);
		HMODULE libHandle;
		*thread >> libHandle;
		void *funcAddr = (void *)GetProcAddress(libHandle, funcName);
		*thread << funcAddr;
		SetScriptCondResult(thread, funcAddr != nullptr);
		return OR_CONTINUE;
	}	
	
	//0AA5=-1,call %1d% num_params %2h% pop %3h%
	OpcodeResult opcode_0AA5(CScriptThread *thread)
	{
		static char textParams[5][100]; unsigned currTextParam = 0;
		void (*func)();
		unsigned numParams;
		unsigned stackAlign;
		RetrieveScriptParams(thread, func, numParams, stackAlign);
		stackAlign *= 4;
		SCRIPT_VAR *arguments = new SCRIPT_VAR[numParams],
			*arguments_end = arguments + numParams;
		// retrieve parameters
		for (SCRIPT_VAR *arg = arguments; arg != arguments_end; ++arg)
		{
			switch (*thread->ip)
			{
				case imm32f:
				case imm32:
				case imm16:
				case imm8:
				case globalVar:
				case localVar:
				case globalArr:
				case localArr:
					*thread >> (*arg).dwParam;
					break;
				case globalVarVString:
				case localVarVString:
				case globalVarSString:
				case localVarSString:
					(*arg).pParam = GetScriptParamPointer(thread);
					break;
				case vstring:
				case sstring:
					(*arg).szParam = readString(thread, textParams[currTextParam++], 100);
			}
		}
		// call function
		asm volatile (
			"loop_0AA5:\n"
			"cmp %2,%3\n"
			"je loop_end_0AA5\n"
			"pushl (%2)\n"
			"addl $4,%2\n"
			"jmp loop_0AA5\n"
			"loop_end_0AA5:\n"
			"call *%0\n"
			"addl %1,%%esp"
			: // no output parameters 
			: "r"(func), "m"(stackAlign), "r"(arguments), "m"(arguments_end)
			: "%eax", "%edx", "%ecx");
		if (arguments) delete[] arguments;
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	}

	//0AA6=-1,call_method %1d% struct %2d% num_params %3h% pop %4h%
	OpcodeResult opcode_0AA6(CScriptThread *thread)
	{
		static char textParams[5][100]; unsigned currTextParam = 0;
		void (*func)();
		void *struc;
		unsigned numParams;
		unsigned stackAlign;
		RetrieveScriptParams(thread, func, struc, numParams, stackAlign);
		stackAlign *= 4;
		SCRIPT_VAR *arguments = new SCRIPT_VAR[numParams],
			*arguments_end = arguments + numParams;
		// retrieve parameters
		for (SCRIPT_VAR *arg = arguments; arg != arguments_end; ++arg)
		{
			switch (*thread->ip)
			{
				case imm32f:
				case imm32:
				case imm16:
				case imm8:
				case globalVar:
				case localVar:
				case globalArr:
				case localArr:
					*thread >> (*arg).dwParam;
					break;
				case globalVarVString:
				case localVarVString:
				case globalVarSString:
				case localVarSString:
					(*arg).pParam = GetScriptParamPointer(thread);
					break;
				case vstring:
				case sstring:
					(*arg).szParam = readString(thread, textParams[currTextParam++], 100);
			}
		}
		// call function
		asm volatile ("loop_0AA6:\n"
			"cmp %2,%3\n"
			"je loop_end_0AA6\n"
			"pushl (%2)\n"
			"addl $4,%2\n"
			"jmp loop_0AA6\n"
			"loop_end_0AA6:\n"
			"movl %4,%%ecx\n"
			"call *%0\n"
			"addl %1,%%esp" 
			: // no output parameters 
			: "r"(func), "m"(stackAlign), "r"(arguments), "m"(arguments_end), "m"(struc)
			: "%eax", "%edx", "%ecx");
		if (arguments) delete[] arguments;
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	}
	
	//0AA7=-1,call_function %1d% num_params %2h% pop %3h%
	OpcodeResult opcode_0AA7(CScriptThread *thread)
	{
		static char textParams[5][100]; unsigned currTextParam = 0;
		void (*func)();
		unsigned numParams;
		unsigned stackAlign;
		RetrieveScriptParams(thread, func, numParams, stackAlign);
		stackAlign *= 4;
		SCRIPT_VAR *arguments = new SCRIPT_VAR[numParams],
			*arguments_end = arguments + numParams;
		// retrieve parameters
		for (SCRIPT_VAR *arg = arguments; arg != arguments_end; ++arg)
		{
			switch (*thread->ip)
			{
				case imm32f:
				case imm32:
				case imm16:
				case imm8:
				case globalVar:
				case localVar:
				case globalArr:
				case localArr:
					*thread >> (*arg).dwParam;
					break;
				case globalVarVString:
				case localVarVString:
				case globalVarSString:
				case localVarSString:
					(*arg).pParam = GetScriptParamPointer(thread);
					break;
				case vstring:
				case sstring:
					(*arg).szParam = readString(thread, textParams[currTextParam++], 100);
			}
		}
		unsigned result;
		// call function
		asm volatile (
			"loop_0AA7:\n"
			"cmp %3,%4\n"
			"je loop_end_0AA7\n"
			"pushl (%3)\n"
			"addl $4,%3\n"
			"jmp loop_0AA7\n"
			"loop_end_0AA7:\n"
			"call *%1\n"
			"addl %2,%%esp"
			: "=a"(result)
			: "r"(func), "m"(stackAlign), "r"(arguments), "m"(arguments_end)
			: "%edx", "%ecx");
		if (arguments) delete[] arguments;
		*thread << result;
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	}

	//0AA8=-1,call_function_method %1d% struct %2d% num_params %3h% pop %4h%
	OpcodeResult opcode_0AA8(CScriptThread *thread)
	{
		static char textParams[5][100]; unsigned currTextParam = 0;
		void (*func)();
		void *struc;
		unsigned numParams;
		unsigned stackAlign;
		RetrieveScriptParams(thread, func, struc, numParams, stackAlign);
		stackAlign *= 4;
		SCRIPT_VAR *arguments = new SCRIPT_VAR[numParams],
			*arguments_end = arguments + numParams;
		// retrieve parameters
		for (SCRIPT_VAR *arg = arguments; arg != arguments_end; ++arg)
		{
			switch (*thread->ip)
			{
				case imm32f:
				case imm32:
				case imm16:
				case imm8:
				case globalVar:
				case localVar:
				case globalArr:
				case localArr:
					*thread >> (*arg).dwParam;
					break;
				case globalVarVString:
				case localVarVString:
				case globalVarSString:
				case localVarSString:
					(*arg).pParam = GetScriptParamPointer(thread);
					break;
				case vstring:
				case sstring:
					(*arg).szParam = readString(thread, textParams[currTextParam++], 100);
			}
		}
		unsigned result;
		// call function
		asm volatile ("loop_0AA8:\n"
			"cmp %3,%4\n"
			"je loop_end_0AA8\n"
			"pushl (%3)\n"
			"addl $4,%3\n"
			"jmp loop_0AA8\n"
			"loop_end_0AA8:\n"
			"movl %5,%%ecx\n"
			"call *%1\n"
			"addl %2,%%esp" 
			: "=a"(result)
			: "r"(func), "m"(stackAlign), "r"(arguments), "m"(arguments_end), "m"(struc)
			: "%ecx", "%edx");
		if (arguments) delete[] arguments;
		*thread << result;
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	}

	//0AA9=0,  is_game_version_original
	OpcodeResult opcode_0AA9(CScriptThread *thread)
	{
		SetScriptCondResult(thread, GetThisInstance().versionManager.GetGameVersion() == GV_US10);
		return OR_CONTINUE;
	}

	//0AAA=2,%2d% = thread %1d% pointer
	OpcodeResult opcode_0AAA(CScriptThread *thread)
	{
		char *threadName = readString(thread);
		threadName[7] = '\0';
		CScriptThread *cs = GetThisInstance().scriptEngine.FindThreadByName(threadName);
		*thread << cs;
		SetScriptCondResult(thread, cs != nullptr);
		return OR_CONTINUE;
	}

	//0AAB=1,  file_exists %1d%
	OpcodeResult opcode_0AAB(CScriptThread *thread)
	{
		DWORD fAttr = GetFileAttributes(readString(thread));
		SetScriptCondResult(thread, (fAttr != INVALID_FILE_ATTRIBUTES) && 
			!(fAttr & FILE_ATTRIBUTE_DIRECTORY));
		return OR_CONTINUE;
	}
	
	//0AAC=2,%2d% = load_audiostream %1d% //IF and SET
	OpcodeResult opcode_0AAC(CScriptThread *thread)
	{
		auto stream = GetThisInstance().soundSystem.LoadStream(readString(thread));
		*thread << stream;
		SetScriptCondResult(thread,stream != nullptr);
		return OR_CONTINUE;
	}

	//0AAD=2,set_audiostream %1d% perform_action %2d%
	OpcodeResult opcode_0AAD(CScriptThread *thread)
	{
		CAudioStream *stream; int action;
		*thread >> stream >> action;
		switch (action)
		{
		case 0: stream->Stop();   break;
		case 1: stream->Play();   break;
		case 2: stream->Pause();  break;
		case 3: stream->Resume(); break;
		default:
			TRACE("[0AAD] Unknown audiostream's action: %d", action);
		}
		return OR_CONTINUE;
	}
	
	//0AAE=1,release_audiostream %1d%
	OpcodeResult opcode_0AAE(CScriptThread *thread)
	{
		CAudioStream *stream;
		*thread >> stream;
		GetThisInstance().soundSystem.UnloadStream(stream);
		return OR_CONTINUE;
	}

	//0AAF=2,%2d% = get_audiostream_length %1d%
	OpcodeResult opcode_0AAF(CScriptThread *thread)
	{
		CAudioStream *stream;
		*thread >> stream;
		*thread << stream->GetLength();
		return OR_CONTINUE;
	}

	//0AB0=1,  key_pressed %1d%
	OpcodeResult opcode_0AB0(CScriptThread *thread)
	{
		unsigned key;
		*thread >> key;
		SetScriptCondResult(thread, HIBYTE(GetKeyState(key)) == 0xFF);
		return OR_CONTINUE;
	}	
	
	//0AB1=-1,call_scm_func %1p%
	OpcodeResult opcode_0AB1(CScriptThread *thread)
	{
		unsigned label, nParams;
		*thread >> label >> nParams;
		if (nParams) GetScriptParams(thread, nParams);
		new ScmFunction(thread);
		// pass arguments
		std::copy(opcodeParams, opcodeParams + nParams, thread->tls);
		// jump to label
		ThreadJump(thread, label);
		return OR_CONTINUE;
	}

	//0AB2=-1,ret
	OpcodeResult opcode_0AB2(CScriptThread *thread)
	{
		ScmFunction *scmFunc = ScmFunction::Store[thread->scmFunction];
		unsigned nRetParams;
		*thread >> nRetParams;
		if (nRetParams) GetScriptParams(thread, nRetParams);
		scmFunc->Return(thread);
		if (nRetParams) SetScriptParams(thread, nRetParams);
		SkipUnusedParameters(thread);
		delete scmFunc;
		return OR_CONTINUE;
	}

	//0AB3=2,var %1d% = %2d%
	OpcodeResult opcode_0AB3(CScriptThread *thread)
	{
		unsigned varId, value;
		RetrieveScriptParams(thread, varId, value);
		GetThisInstance().scriptEngine.CleoVariables[varId].dwParam = value;
		return OR_CONTINUE;
	}	

	//0AB4=2,%2d% = var %1d%
	OpcodeResult opcode_0AB4(CScriptThread *thread)
	{
		unsigned varId;
		*thread >> varId;
		*thread << GetThisInstance().scriptEngine.CleoVariables[varId].dwParam;
		return OR_CONTINUE;
	}

	//0AB5=3,store_actor %1d% closest_vehicle_to %2d% closest_ped_to %3d%
	OpcodeResult opcode_0AB5(CScriptThread *thread)
	{
		unsigned actor;
		*thread >> actor;
		auto ped = GetPedPool().atHandle(actor);
		auto closVeh = ped->intelligence->vehicleScanner.entities[0];
		auto closPed = ped->intelligence->pedScanner.entities[0];
		*thread << (closVeh ? GetVehiclePool().handleOf(closVeh) : -1)
				<< (closPed ? GetPedPool().handleOf(closPed) : -1);
		return OR_CONTINUE;
	}

	//0AB6=3,store_target_marker_coords_to %1d% %2d% %3d% // IF and SET
	OpcodeResult opcode_0AB6(CScriptThread *thread)
	{
		unsigned hMarker = menuManager->TargetMarkerHandle;
		CMarker *marker;
		bool condResult = hMarker && (marker = &radarBlips[LOWORD(hMarker)]) &&
			marker->_Index == HIWORD(hMarker) && marker->Flag;
		if (condResult)
		{
			RwV3d coords(marker->pos);
			coords.z = FindGroundZ(coords.x, coords.y);
			*thread << coords;
		}
		else
			GetScriptParams(thread, 3);
		SetScriptCondResult(thread, condResult);

		return OR_CONTINUE;
	}

	//0AB7=2,get_vehicle %1d% number_of_gears_to %2d%
	OpcodeResult opcode_0AB7(CScriptThread *thread)
	{
		unsigned hVehicle;
		*thread >> hVehicle;
		auto veh = GetVehiclePool().atHandle(hVehicle);
		*thread << handling->autoHandling[veh->modelIndex - 400].transmissionData.nNumberOfGears;
		return OR_CONTINUE;
	}

	//0AB8=2,get_vehicle %1d% current_gear_to %2d%
	OpcodeResult opcode_0AB8(CScriptThread *thread)
	{
		unsigned hVehicle;
		*thread >> hVehicle;
		*thread << GetVehiclePool().atHandle(hVehicle)->currentGear;
		return OR_CONTINUE;
	}
		
	//0AB9=2,get_audiostream %1d% state_to %2d%
	OpcodeResult opcode_0AB9(CScriptThread *thread)
	{
		CAudioStream *stream;
		*thread >> stream;
		*thread << stream->GetState();
		return OR_CONTINUE;
	}

	//0ABA=1,end_custom_thread_named %1d%
	OpcodeResult opcode_0ABA(CScriptThread *thread)
	{
		char *threadName = readString(thread);
		auto deleted_thread = GetThisInstance().scriptEngine.FindThreadByName(threadName);
		if (deleted_thread)
		{
			GetThisInstance().scriptEngine.RemoveCustomScript(deleted_thread);
			delete deleted_thread;
		}
		return deleted_thread == thread ? OR_INTERRUPT : OR_CONTINUE;
	}	
	
	//0ABB=2,%2d% = audiostream %1d% volume
	OpcodeResult opcode_0ABB(CScriptThread *thread)
	{
		CAudioStream *stream;
		*thread >> stream;
		*thread << stream->GetVolume();
		return OR_CONTINUE;
	}

	//0ABC=2,set_audiostream %1d% volume %2d%
	OpcodeResult opcode_0ABC(CScriptThread *thread)
	{
		CAudioStream *stream; float volume;
		RetrieveScriptParams(thread, stream, volume);
		stream->SetVolume(volume);
		return OR_CONTINUE;
	}

	//0ABD=1,  vehicle %1d% siren_on
	OpcodeResult opcode_0ABD(CScriptThread *thread)
	{
		unsigned hVehicle;
		*thread >> hVehicle;
		// test bit 7
		SetScriptCondResult(thread, ((GetVehiclePool().atHandle(hVehicle)->flags >> 7) & 1) == 1);
		return OR_CONTINUE;
	}

	//0ABE=1,  vehicle %1d% engine_on
	OpcodeResult opcode_0ABE(CScriptThread *thread)
	{
		unsigned hVehicle;
		*thread >> hVehicle;
		// test bit 4
		SetScriptCondResult(thread, ((GetVehiclePool().atHandle(hVehicle)->flags >> 4) & 1) == 1);
		return OR_CONTINUE;
	}

	//0ABF=2,set_vehicle %1d% engine_state_to %2d%
	OpcodeResult opcode_0ABF(CScriptThread *thread)
	{
		unsigned hVehicle, state;
		RetrieveScriptParams(thread, hVehicle, state);
		auto veh = GetVehiclePool().atHandle(hVehicle);
		// clear/set bit 4
		if (state)
			veh->flags |= 0x10;
		else
			veh->flags &= ~0x10;
		return OR_CONTINUE;
	}

	//0AC0=2,loop_audiostream %1d% flag %2d%
	OpcodeResult opcode_0AC0(CScriptThread *thread)
	{
		CAudioStream *stream; unsigned loop;
		RetrieveScriptParams(thread, stream, loop);
		stream->Loop(loop);
		return OR_CONTINUE;
	}

	//0AC1=2,%2d% = load_audiostream_with_3d_support %1d% //IF and SET
	OpcodeResult opcode_0AC1(CScriptThread *thread)
	{
		auto stream = GetThisInstance().soundSystem.LoadStream(readString(thread), true);
		*thread << stream;
		SetScriptCondResult(thread, stream != nullptr);
		return OR_CONTINUE;
	}

	//0AC2=4,set_3d_audiostream %1d% position %2d% %3d% %4d%
	OpcodeResult opcode_0AC2(CScriptThread *thread)
	{
		CAudioStream *stream; RwV3d pos;
		*thread >> stream >> pos;
		stream->Set3dPosition(pos);
		return OR_CONTINUE;
	}

	//0AC3=2,link_3d_audiostream %1d% to_object %2d%
	OpcodeResult opcode_0AC3(CScriptThread *thread)
	{
		CAudioStream *stream; unsigned handle;
		RetrieveScriptParams(thread, stream, handle);
		stream->Link(GetObjectPool().atHandle(handle));
		return OR_CONTINUE;
	}

	//0AC4=2,link_3d_audiostream %1d% to_actor %2d%
	OpcodeResult opcode_0AC4(CScriptThread *thread)
	{
		CAudioStream *stream; unsigned handle;
		RetrieveScriptParams(thread, stream, handle);
		stream->Link(GetPedPool().atHandle(handle));
		return OR_CONTINUE;
	}

	//0AC5=2,link_3d_audiostream %1d% to_vehicle %2d%
	OpcodeResult opcode_0AC5(CScriptThread *thread)
	{
		CAudioStream *stream; unsigned handle;
		RetrieveScriptParams(thread, stream, handle);
		stream->Link(GetVehiclePool().atHandle(handle));
		return OR_CONTINUE;
	}

	//0AC6=2,%2d% = label %1p% offset
	OpcodeResult opcode_0AC6(CScriptThread *thread)
	{
		int label;
		*thread >> label;
		*thread << (label < 0 ? thread->baseIp - label : scmBlock + label);
		return OR_CONTINUE;
	}

	//0AC7=2,%2d% = var %1d% offset
	OpcodeResult opcode_0AC7(CScriptThread *thread)
	{
		*thread << GetScriptParamPointer(thread);
		return OR_CONTINUE;
	};

	//0AC8=2,%2d% = allocate_memory_size %1d%
	OpcodeResult opcode_0AC8(CScriptThread *thread)
	{
		unsigned size;
		*thread >> size;
		void *mem = malloc(size);
		if (mem)
		{
			GetThisInstance().opcodeSystem.allocatedMemory.insert(mem);
			*thread << mem;
			SetScriptCondResult(thread, true);
		}
		else SetScriptCondResult(thread, false);
		return OR_CONTINUE;
	};

	//0AC9=1,free_allocated_memory %1d%
	OpcodeResult opcode_0AC9(CScriptThread *thread)
	{
		void *mem;
		*thread >> mem;
		free(mem);
		GetThisInstance().opcodeSystem.allocatedMemory.erase(mem);
		return OR_CONTINUE;
	};

	//0ACA=1,show_text_box %1d%
	OpcodeResult opcode_0ACA(CScriptThread *thread)
	{
		ShowTextBox(readString(thread));
		return OR_CONTINUE;
	};

	//0ACB=3,show_styled_text %1d% time %2d% style %3d%
	OpcodeResult opcode_0ACB(CScriptThread *thread)
	{
		const char *text = readString(thread);
		unsigned time, style;
		RetrieveScriptParams(thread, time, style);
		ShowStyledText(text, time, style);
		return OR_CONTINUE;
	};

	//0ACC=2,show_text_lowpriority %1d% time %2d%
	OpcodeResult opcode_0ACC(CScriptThread *thread)
	{
		const char *text = readString(thread);
		unsigned time;
		*thread >> time;
		ShowTextLowPriority(text, time);
		return OR_CONTINUE;
	};

	//0ACD=2,show_text_highpriority %1d% time %2d%
	OpcodeResult opcode_0ACD(CScriptThread *thread)
	{
		const char *text = readString(thread);
		unsigned time;
		*thread >> time;
		ShowTextHighPriority(text, time);
		return OR_CONTINUE;
	};
	
	//0ACE=-1,show_formatted_text_box %1d%
	OpcodeResult opcode_0ACE(CScriptThread *thread)
	{
		char fmt[100]; char text[100];
		readString(thread, fmt, sizeof(fmt));
		format(thread, text, sizeof(text), fmt);
		ShowTextBox(text);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};
	
	//0ACF=-1,show_formatted_styled_text %1d% time %2d% style %3d%
	OpcodeResult opcode_0ACF(CScriptThread *thread)
	{
		char fmt[100]; char text[100];
		unsigned time, style;
		readString(thread, fmt, sizeof(fmt));
		RetrieveScriptParams(thread, time, style);
		format(thread, text, sizeof(text), fmt);
		ShowStyledText(text, time, style);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};

	//0AD0=-1,show_formatted_text_lowpriority %1d% time %2d%
	OpcodeResult opcode_0AD0(CScriptThread *thread)
	{
		char fmt[100]; char text[100];
		unsigned time;
		readString(thread, fmt, sizeof(fmt));
		*thread >> time;
		format(thread, text, sizeof(text), fmt);
		ShowTextLowPriority(text, time);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};

	//0AD1=-1,show_formatted_text_highpriority %1d% time %2d%
	OpcodeResult opcode_0AD1(CScriptThread *thread)
	{
		char fmt[100]; char text[100];
		unsigned time;
		readString(thread, fmt, sizeof(fmt));
		*thread >> time;
		format(thread, text, sizeof(text), fmt);
		ShowTextHighPriority(text, time);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};

	//0AD2=2,%2d% = player %1d% targeted_actor //IF and SET
	OpcodeResult opcode_0AD2(CScriptThread *thread)
	{
		unsigned playerId;
		*thread >> playerId;
		CPed *playerPed = GetPlayerPed(playerId);
		CPed *targetedPed = nullptr;
		if (playerPed && ((targetedPed = (CPed *)playerPed->targetedEntity) ||
			(targetedPed = (CPed *)playerPed->targetedObject)) &&
			(targetedPed->type & 7) == 3)		// type is CPed
		{
			*thread << GetPedPool().handleOf(targetedPed);
			SetScriptCondResult(thread, true);
		}
		else
		{
			*thread << 0u;
			SetScriptCondResult(thread, false);
		}
		return OR_CONTINUE;
	};

	//0AD3=-1,string %1d% format %2d%
	OpcodeResult opcode_0AD3(CScriptThread *thread)
	{
		char fmt[100], *dst;
	
		if (*thread->ip >= 1 && *thread->ip <= 8)
			*thread >> dst;
		else
			dst = (char*)GetScriptParamPointer(thread);
		readString(thread, fmt, sizeof(fmt));
		format(thread, dst, -1ul, fmt);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};

	//0AD4=-1,%3d% = scan_string %1d% format %2d%  //IF and SET
	OpcodeResult opcode_0AD4(CScriptThread *thread)
	{
		char fmt[100], *src;
		src = readString(thread);
		readString(thread, fmt, sizeof(fmt));
		
		size_t cExParams = 0;
		int *result = (int *)GetScriptParamPointer(thread);
		SCRIPT_VAR *ExParams[35];
		// read extra params
		while (*thread->ip) ExParams[cExParams++] = GetScriptParamPointer(thread);
		++thread->ip;
		*result = sscanf(src, fmt, 
			/* extra parameters (will be aligned automatically, but the limit of 35 elements maximum exists) */			
			ExParams[0], ExParams[1], ExParams[2], ExParams[3], ExParams[4], ExParams[5],
			ExParams[6], ExParams[7], ExParams[8], ExParams[9], ExParams[10], ExParams[11],
			ExParams[12], ExParams[13], ExParams[14], ExParams[15], ExParams[16], ExParams[17],
			ExParams[18], ExParams[19], ExParams[20], ExParams[21], ExParams[22], ExParams[23],
			ExParams[24], ExParams[25], ExParams[26], ExParams[27], ExParams[28], ExParams[29],
			ExParams[30], ExParams[31], ExParams[32], ExParams[33], ExParams[34]);
		return OR_CONTINUE;
	};

	//0AD5=3,file %1d% seek %2d% from_origin %3d% //IF and SET
	OpcodeResult opcode_0AD5(CScriptThread *thread)
	{
		FILE *file;
		int seek, origin;
		RetrieveScriptParams(thread, file, seek, origin);
		SetScriptCondResult(thread, fseek(file, seek, origin) == 0);
		return OR_CONTINUE;
	};

	//0AD6=1,end_of_file %1d% reached
	OpcodeResult opcode_0AD6(CScriptThread *thread)
	{
		FILE *file;
		*thread >> file;
		SetScriptCondResult(thread, feof(file) != 0);
		return OR_CONTINUE;
	};

	//0AD7=3,read_string_from_file %1d% to %2d% size %3d% //IF and SET
	OpcodeResult opcode_0AD7(CScriptThread *thread)
	{
		FILE *file; char *buf; unsigned size;
		*thread >> file;
		if (*thread->ip >= 1 && *thread->ip <= 8)
			*thread >> buf;
		else
			buf = (char *)GetScriptParamPointer(thread);
		*thread >> size;
		SetScriptCondResult(thread, fgets(buf, size, file) == buf);
		return OR_CONTINUE;
	};

	//0AD8=2,write_string_to_file %1d% from %2d% //IF and SET
	OpcodeResult opcode_0AD8(CScriptThread *thread)
	{
		FILE *file;
		*thread >> file;
		SetScriptCondResult(thread, fputs(readString(thread), file) > 0);
		fflush(file);
		return OR_CONTINUE;
	};	

	//0AD9=-1,write_formated_text %2d% to_file %1d%
	OpcodeResult opcode_0AD9(CScriptThread *thread)
	{
		char fmt[100]; char text[100];
		FILE *file;
		*thread >> file;
		readString(thread, fmt, sizeof(fmt));
		format(thread, text, sizeof(text), fmt);
		fputs(text, file);
		fflush(file);
		SkipUnusedParameters(thread);
		return OR_CONTINUE;
	};

	//0ADA=-1,%3d% = scan_file %1d% format %2d% //IF and SET
	OpcodeResult opcode_0ADA(CScriptThread *thread)
	{
		FILE *file;
		*thread >> file;
		char *fmt = readString(thread);
		
		int *result = (int *)GetScriptParamPointer(thread);
		size_t cExParams = 0;
		SCRIPT_VAR *ExParams[35];
		// read extra params
		while (*thread->ip) ExParams[cExParams++] = GetScriptParamPointer(thread);
		++thread->ip;
		*result = fscanf(file, fmt, 
			/* extra parameters (will be aligned automatically, but the limit of 35 elements maximum exists) */			
			ExParams[0], ExParams[1], ExParams[2], ExParams[3], ExParams[4], ExParams[5],
			ExParams[6], ExParams[7], ExParams[8], ExParams[9], ExParams[10], ExParams[11],
			ExParams[12], ExParams[13], ExParams[14], ExParams[15], ExParams[16], ExParams[17],
			ExParams[18], ExParams[19], ExParams[20], ExParams[21], ExParams[22], ExParams[23],
			ExParams[24], ExParams[25], ExParams[26], ExParams[27], ExParams[28], ExParams[29],
			ExParams[30], ExParams[31], ExParams[32], ExParams[33], ExParams[34]);
		return OR_CONTINUE;
	}

	//0ADB=2,%2d% = car_model %1o% name
	OpcodeResult opcode_0ADB(CScriptThread *thread)
	{
		unsigned mi; char *buf;
		*thread >> mi;
		auto model = (CVehicleModel *)models[mi];
		if (*thread->ip >= 1 && *thread->ip <= 8)
			*thread >> buf;
		else
			buf = (char *)GetScriptParamPointer(thread);
		memcpy(buf, model->gamename, 8);

		return OR_CONTINUE;
	}
	
	//0ADC=1, test_cheat %1d%
	OpcodeResult opcode_0ADC(CScriptThread *thread)
	{
		SetScriptCondResult(thread, TestCheat(readString(thread)));
		return OR_CONTINUE;
	}

	//0ADD=1,spawn_car_with_model %1o% at_player_location 
	OpcodeResult opcode_0ADD(CScriptThread *thread)
	{
		unsigned mi;
		*thread >> mi;
		auto model = (CVehicleModel *)models[mi];
		if (model->type != VT_TRAIN && model->type != VT_UNK)
			SpawnCar(mi);
		return OR_CONTINUE;
	}

	//0ADE=2,%2d% = text_by_GXT_entry %1d%
	OpcodeResult opcode_0ADE(CScriptThread *thread)
	{
		const char *gxt = readString(thread);
		if (*thread->ip >= 1 && *thread->ip <= 8)
			*thread << CText__locate(gameTexts, gxt);
		else
			strcpy((char *)GetScriptParamPointer(thread), CText__locate(gameTexts, gxt));
		return OR_CONTINUE;
	}

	//0ADF=2,add_dynamic_GXT_entry %1d% text %2d%
	OpcodeResult opcode_0ADF(CScriptThread *thread)
	{
		char *entryName; char text[100];
		entryName = readString(thread);
		readString(thread, text, sizeof(text));
		GetThisInstance().textManager.AddFxt(entryName, text);
		return OR_CONTINUE;
	}

	//0AE0=1,remove_dynamic_GXT_entry %1d%
	OpcodeResult opcode_0AE0(CScriptThread *thread)
	{
		GetThisInstance().textManager.RemoveFxt(readString(thread));
		return OR_CONTINUE;
	}

	//0AE1=7,%7d% = find_actor_near_point %1d% %2d% %3d% in_radius %4d% find_next %5h% pass_deads %6h% //IF and SET
	OpcodeResult opcode_0AE1(CScriptThread *thread)
	{
		RwV3d center; float radius; unsigned next, pass_deads;
		static unsigned stat_last_found = 0;
		CPedPool& pool = GetPedPool();
		*thread >> center >> radius >> next >> pass_deads;
		auto IsPedDead = [](CPed& ped) -> bool 
		{
			unsigned f_530 = ped._f530;
			return f_530 >= 54 && f_530 <= 56;
		};
		auto IsPedPlayer = [](CPed& ped) -> bool
		{
			return ped.pedType != 0 && ped.pedType != 1;
		};
		unsigned& last_found = thread->IsCustom ? reinterpret_cast<CCustomScript *>(thread)->
			get_last_found_actor() : stat_last_found;
		if (!next) last_found = 0;
		for (unsigned index = last_found; index < pool.size; ++index)
		{
			if (pool.flags[index] & 0x80) continue;	// empty slot
			CPed& obj = pool[index];
			if (IsPedPlayer(obj)) continue;			// skip player ped
			if (pass_deads && IsPedDead(obj)) continue;
			RwV3d& pos = obj.xyz ? obj.xyz->matrix.pos : obj.placement.pos;
			float dx = pos.x - center.x;
			float dy = pos.y - center.y;
			float dz = pos.z - center.z;
			float distance = sqrt(dx * dx + dy * dy + dz * dz);
			if (distance <= radius)
			{
				last_found = index + 1;	// on next opcode call start search from next index
				// obj.referenceType = 2; // add reference to found actor
				*thread << pool.handleOf(&obj);
				SetScriptCondResult(thread, true);
				return OR_CONTINUE;
			}
		}
		*thread << 0u;
		SetScriptCondResult(thread, false);
		return OR_CONTINUE;
	}

	//0AE2=7,%7d% = find_vehicle_near_point %1d% %2d% %3d% in_radius %4d% find_next %5h% pass_wrecked %6h% //IF and SET
	OpcodeResult opcode_0AE2(CScriptThread *thread)
	{
		RwV3d center; float radius; unsigned next, pass_wrecked;
		static unsigned stat_last_found = 0;
		CVehiclePool& pool = GetVehiclePool();
		*thread >> center >> radius >> next >> pass_wrecked;
		auto IsVehicleWrecked = [](CVehicle& veh) -> bool 
		{	
			if ((veh.type & 0xF8) == 0x28) return 1;
			return (veh._f42B & 0x40) != 0;
		};
		unsigned& last_found = thread->IsCustom ? reinterpret_cast<CCustomScript *>(thread)->
			get_last_found_vehicle() : stat_last_found;
		if (!next) last_found = 0;
		for (unsigned index = last_found; index < pool.size; ++index)
		{
			if (pool.flags[index] & 0x80) continue;	// empty slot
			CVehicle& obj = pool[index];
			if (pass_wrecked && IsVehicleWrecked(obj)) continue;
			RwV3d& pos = obj.xyz ? obj.xyz->matrix.pos : obj.placement.pos;
			float dx = pos.x - center.x;
			float dy = pos.y - center.y;
			float dz = pos.z - center.z;
			float distance = sqrt(dx * dx + dy * dy + dz * dz);
			if (distance <= radius)
			{
				last_found = index + 1;	// on next opcode call start search from next index
				// obj.referenceType = 2; // add reference to found actor
				*thread << pool.handleOf(&obj);
				SetScriptCondResult(thread, true);
				return OR_CONTINUE;
			}
		}
		*thread << 0u;
		SetScriptCondResult(thread, false);
		return OR_CONTINUE;
	}

	//0AE3=6,%6d% = find_object_near_point %1d% %2d% %3d% in_radius %4d% find_next %5h% //IF and SET
	OpcodeResult opcode_0AE3(CScriptThread *thread)
	{
		RwV3d center; float radius; unsigned next;
		static unsigned stat_last_found = 0;
		CObjectPool& pool = GetObjectPool();
		*thread >> center >> radius >> next;
		unsigned& last_found = thread->IsCustom ? reinterpret_cast<CCustomScript *>(thread)->
			get_last_found_object() : stat_last_found;
		if (!next) last_found = 0;
		for (unsigned index = last_found; index < pool.size; ++index)
		{
			if (pool.flags[index] & 0x80) continue;	// empty slot
			CObject& obj = pool[index];
			RwV3d& pos = obj.xyz ? obj.xyz->matrix.pos : obj.placement.pos;
			float dx = pos.x - center.x;
			float dy = pos.y - center.y;
			float dz = pos.z - center.z;
			float distance = sqrt(dx * dx + dy * dy + dz * dz);
			if (distance <= radius)
			{
				last_found = index + 1;	// on next opcode call start search from next index
				// obj.referenceType = 2; // add reference to found actor
				*thread << pool.handleOf(&obj);
				SetScriptCondResult(thread, true);
				return OR_CONTINUE;
			}
		}
		*thread << 0u;
		SetScriptCondResult(thread, false);
		return OR_CONTINUE;
	}

	//0AE4=1,  directory_exist %1d%
	OpcodeResult opcode_0AE4(CScriptThread *thread)
	{
		auto fAttr = GetFileAttributes(readString(thread));
		SetScriptCondResult(thread, (fAttr != INVALID_FILE_ATTRIBUTES) 
			&& (fAttr & FILE_ATTRIBUTE_DIRECTORY));			
		return OR_CONTINUE;
	}

	//0AE5=1,create_directory %1d% //IF and SET
	OpcodeResult opcode_0AE5(CScriptThread *thread)
	{
		bool condResult = CreateDirectory(readString(thread), NULL) != 0;
		SetScriptCondResult(thread, condResult);
		return OR_CONTINUE;
	}

	//0AE6=3,%2d% = find_first_file %1d% get_filename_to %3d% //IF and SET
	OpcodeResult opcode_0AE6(CScriptThread *thread)
	{
		WIN32_FIND_DATA ffd;
		memset(&ffd, 0, sizeof(ffd));

		HANDLE handle = FindFirstFile(readString(thread), &ffd);
		*thread << handle;
		GetThisInstance().opcodeSystem.fileSearches.insert(handle);
		if (handle != INVALID_HANDLE_VALUE)
		{
			auto type = *thread->ip;
			char* str;
			switch (type)
			{
			case globalVarVString:
			case localVarVString:
				str = (char*)GetScriptParamPointer(thread);
				memcpy(str, ffd.cFileName, 16);
				str[15] = '\0';
				break;
			case globalVarSString:
			case localVarSString:
				str = (char*)GetScriptParamPointer(thread);
				memcpy(str, ffd.cFileName, 8);
				str[7] = '\0';
				break;
			default:
				*thread >> str;
				strcpy(str, ffd.cFileName);
			}
			SetScriptCondResult(thread, true);
		}
		else
		{	
			readString(thread);
			SetScriptCondResult(thread, false);
		}
		return OR_CONTINUE;
	}

	//0AE7=2,%2d% = find_next_file %1d% //IF and SET
	OpcodeResult opcode_0AE7(CScriptThread *thread)
	{
		WIN32_FIND_DATA ffd;
		memset(&ffd, 0, sizeof(ffd));

		HANDLE handle;
		*thread >> handle;
		if (FindNextFile(handle, &ffd))
		{
			auto type = *thread->ip;
			char* str;
			switch (type)
			{
			case globalVarVString:
			case localVarVString:
				str = (char*)GetScriptParamPointer(thread);
				memcpy(str, ffd.cFileName, 16);
				str[15] = '\0';
				break;
			case globalVarSString:
			case localVarSString:
				str = (char*)GetScriptParamPointer(thread);
				memcpy(str, ffd.cFileName, 8);
				str[7] = '\0';
				break;
			default:
				*thread >> str;
				strcpy(str, ffd.cFileName);
			}
			SetScriptCondResult(thread, true);
		}
		else
		{	
			readString(thread);
			SetScriptCondResult(thread, false);
		}
		return OR_CONTINUE;
	}

	//0AE8=1,find_close %1d%
	OpcodeResult opcode_0AE8(CScriptThread *thread)
	{
		HANDLE handle;
		*thread >> handle;
		FindClose(handle);
		GetThisInstance().opcodeSystem.fileSearches.erase(handle);
		return OR_CONTINUE;
	}

	//0AE9=0,pop_float
	OpcodeResult opcode_0AE9(CScriptThread *thread)
	{
		asm volatile ("fstp (%0)" : : "r"(&opcodeParams[0].fParam));
		SetScriptParams(thread, 1);
		return OR_CONTINUE;
	}
	
	//0AEA=2,%2d% = actor_struct %1d% handle
	OpcodeResult opcode_0AEA(CScriptThread *thread)
	{
		CPed *struc;
		*thread >> struc;
		*thread << GetPedPool().handleOf(struc);
		return OR_CONTINUE;
	}

	//0AEB=2,%2d% = car_struct %1d% handle
	OpcodeResult opcode_0AEB(CScriptThread *thread)
	{
		CVehicle *struc;
		*thread >> struc;
		*thread << GetVehiclePool().handleOf(struc);
		return OR_CONTINUE;
	}
	
	//0AEC=2,%2d% = object_struct %1d% handle
	OpcodeResult opcode_0AEC(CScriptThread *thread)
	{
		CObject *struc;
		*thread >> struc;
		*thread << GetObjectPool().handleOf(struc);
		return OR_CONTINUE;
	}

	
	//0AED=3,%3d% = float %1d% to_string_format %2d%
	OpcodeResult opcode_0AED(CScriptThread *thread)
	{
		// this opcode is useless now
		float val;
		char *format, *result;
		*thread >> val;
		format = readString(thread);
		if (*thread->ip >= 1 && *thread->ip <= 8)
			*thread >> result;
		else
			result = (char *)GetScriptParamPointer(thread);
		sprintf(result, format, val);
		return OR_CONTINUE;
	}
	
	//0AEE=3,%3d% = %1d% exp %2d% //all floats
	OpcodeResult opcode_0AEE(CScriptThread *thread)
	{
		float base, arg;
		RetrieveScriptParams(thread, base, arg);
		*thread << (float)pow(base, arg);

		return OR_CONTINUE;
	}

	//0AEF=3,%3d% = log %1d% base %2d% //all floats
	OpcodeResult opcode_0AEF(CScriptThread *thread)
	{
		float base, arg;
		RetrieveScriptParams(thread, arg, base);
		*thread << (float)(log(arg) / log(base));

		return OR_CONTINUE;
	}
}

/********************************************************************/

// API
extern "C"
{
	using namespace CLEO;
	
	// Define external symbols with MSVC decorating schemes
	BOOL STDCALL CLEO_RegisterOpcode(WORD opcode, CustomOpcodeHandler callback)
		asm("__CLEO_RegisterOpcode@8");
	DWORD STDCALL CLEO_GetIntOpcodeParam(CScriptThread* thread)
		asm("__CLEO_GetIntOpcodeParam@4");
	float STDCALL WINAPI CLEO_GetFloatOpcodeParam(CScriptThread* thread)
		asm("__CLEO_GetFloatOpcodeParam@4");
	void STDCALL CLEO_SetIntOpcodeParam(CScriptThread* thread, DWORD value)
		asm("__CLEO_SetIntOpcodeParam@8");
	void STDCALL CLEO_SetFloatOpcodeParam(CScriptThread* thread, float value)
		asm("__CLEO_SetFloatOpcodeParam@8");
	LPSTR STDCALL CLEO_ReadStringOpcodeParam(CScriptThread* thread, char *buf, int size)
		asm("__CLEO_ReadStringOpcodeParam@12");
	void STDCALL CLEO_WriteStringOpcodeParam(CScriptThread* thread, LPCSTR str)
		asm("__CLEO_WriteStringOpcodeParam@8");
	void STDCALL CLEO_SetThreadCondResult(CScriptThread* thread, BOOL result)
		asm("__CLEO_SetThreadCondResult@8");
	void STDCALL CLEO_SkipOpcodeParams(CScriptThread* thread, int count)
		asm("__CLEO_SkipOpcodeParams@8");
	void STDCALL CLEO_ThreadJumpAtLabelPtr(CScriptThread* thread, int labelPtr)
		asm("__CLEO_ThreadJumpAtLabelPtr@8");
	int STDCALL CLEO_GetOperandType(CScriptThread* thread)
		asm("__CLEO_GetOperandType@4");
	void STDCALL CLEO_RetrieveOpcodeParams(CScriptThread *thread, int count)
		asm("__CLEO_RetrieveOpcodeParams@8");
	void STDCALL CLEO_RecordOpcodeParams(CScriptThread *thread, int count)
		asm("__CLEO_RecordOpcodeParams@8");

	BOOL STDCALL CLEO_RegisterOpcode(WORD opcode, CustomOpcodeHandler callback)
	{
		if ((opcode > 0x7FFF) || (opcode < 0x0AF0))
			return FALSE;
		CustomOpcodeHandler& dst = extraOpcodeHandlers[opcode % 100][opcode / 100 - 28];
		if (*dst)
		{
			Error("Warning! CLEO couldn't register opcode handler.");
			return FALSE;
		}
		dst = callback;
		return TRUE;
	}

	DWORD STDCALL CLEO_GetIntOpcodeParam(CScriptThread* thread)
	{
		unsigned result;
		*thread >> result;
		return result;
	}

	float STDCALL CLEO_GetFloatOpcodeParam(CScriptThread* thread)
	{
		float result;
		*thread >> result;
		return result;
	}

	void STDCALL CLEO_SetIntOpcodeParam(CScriptThread* thread, DWORD value)
	{
		*thread << (unsigned)value;
	}

	void STDCALL CLEO_SetFloatOpcodeParam(CScriptThread* thread, float value)
	{
		*thread << value;
	}

	LPSTR STDCALL CLEO_ReadStringOpcodeParam(CScriptThread* thread, char *buf, int size)
	{
		static char internal_buf[100];
		if (!buf) { buf = internal_buf; size = 100; }
		if (!size) size = 100;
		std::fill(buf, buf  + size, '\0');
		GetScriptStringParam(thread, buf, size);	
		return buf;
	}

	void STDCALL CLEO_WriteStringOpcodeParam(CScriptThread* thread, LPCSTR str)
	{
		auto dst = (char *)GetScriptParamPointer(thread);
		memcpy(dst, str, 16);
		dst[15] = '\0';
	}

	void STDCALL CLEO_SetThreadCondResult(CScriptThread* thread, BOOL result)
	{
		SetScriptCondResult(thread, result != FALSE);
	}

	void STDCALL CLEO_SkipOpcodeParams(CScriptThread* thread, int count)
	{
		int strlen;
		for (int i = 0; i < count; i++)
		{
			switch (*(thread->ip)++)
			{
			case globalVar:
			case localVar:
			case globalVarVString:
			case localVarVString:
			case globalVarSString:
			case localVarSString:
				thread->ip += 2;
				break;
			case globalArr:
			case localArr:
				thread->ip += 6;
				break;
			case imm8:
				thread->ip += 1;
				break;
			case imm16:
				thread->ip += 2;
				break;
			case imm32:
			case imm32f:
				thread->ip += 4;
				break;
			case vstring:
				strlen = *(thread->ip)++;
				thread->ip += strlen;
			case sstring:
				thread->ip += 8;

			}
		}
	}

	void STDCALL CLEO_ThreadJumpAtLabelPtr(CScriptThread* thread, int labelPtr)
	{
		ThreadJump(thread, labelPtr);
	}

	int STDCALL CLEO_GetOperandType(CScriptThread* thread)
	{
		return *thread->ip;
	}

	void STDCALL CLEO_RetrieveOpcodeParams(CScriptThread *thread, int count)
	{
		GetScriptParams(thread, count);
	}

	void STDCALL CLEO_RecordOpcodeParams(CScriptThread *thread, int count)
	{
		SetScriptParams(thread, count);
	}
}

