#include <Windows.h>
#include <string>
#include <vector>
#include <fstream>
#include <strstream>
#include <random>
#include <SESDK.h>
#include <VMProtectSDK.h>
#include "efs.h"
#include "common.h"

using namespace std;

__declspec(noinline)
void Pad(LPBYTE inData, UINT inLen, LPBYTE outData, LPUINT outLen)
{
	SE_PROTECT_START;
	memcpy(outData, inData, inLen);
	*outLen = inLen;
	UINT size = inLen % 16;
	if (size != 0 || inData[inLen - 1] < 16)
	{
		size = 16 - size;
		*outLen += size;
		random_device e;
		uniform_int_distribution<USHORT> u(0, 255);
		for (size_t i = 0; i < size - 1; i++)
			*(outData + inLen + i) = (BYTE)u(e);
		*(outData + *outLen - 1) = size;
	}
	SE_PROTECT_END;
}

void TestEncrypt(LPUINT data, LPUINT key, UINT offset)
{
	SE_PROTECT_START_MUTATION;
	UINT n = *data ^ *key;
	UINT check = 1 << (offset % 32);
	if ((n & check) == 0 && (*data & check) == 0)
		;
	else
		*data = n;
	*key = *data;
	SE_PROTECT_END;
}

__declspec(noinline)
int Encrypt(LPBYTE inData, UINT inLen, const BYTE key[8], LPBYTE outData, LPUINT outLen)
{
	SE_PROTECT_START_MUTATION;
	Pad(inData, inLen, outData, outLen);
	LPUINT arr = (LPUINT)outData;
	LPUINT end = (LPUINT)(outData + *outLen);
	LPUINT rk = (LPUINT)key;
	for (LPUINT begin = arr; begin < end; begin += 2)
	{
		begin[0] ^= rk[0];
		begin[1] ^= rk[1];
	}
	UINT k[2] = { end[-2], end[-1] };
	random_device e;
	uniform_int_distribution<USHORT> u(2, 15);
	int round = u(e);
	for (size_t r = 0; r < round; r++)
	{
		size_t i = 1;
		for (LPUINT begin = arr; begin < end; begin += 2, i += 2)
		{
			TestEncrypt(begin, k, i);
			TestEncrypt(begin + 1, k + 1, i + 1);
		}
	}
	end[-2] ^= rk[0];
	end[-1] ^= rk[1];
	SE_PROTECT_END;
	return round;
}

//void ProcessCall(LPBYTE& code, LPBYTE begin, LPBYTE end);
//
//void ProcessVar(LPBYTE& code, LPBYTE begin, LPBYTE end)
//{
//	ETAG tag = GetData<ETAG>(code);
//	do
//	{
//		switch (GetData<ECode_Type>(code))
//		{
//		case ECode_Type::ParameterEnd:
//			goto varend;
//		case ECode_Type::ArrayBegin:
//			switch (GetData<ECode_Type>(code))
//			{
//			case ECode_Type::NumberIndex:
//				code += sizeof(int);
//				break;
//			case ECode_Type::ParameterBegin:
//				ProcessVar(code, begin, end);
//				break;
//			case ECode_Type::Method:
//				ProcessCall(code, begin, end);
//				break;
//			}
//			break;
//		case ECode_Type::Field:
//			code += sizeof(EKeyValPair);
//			break;
//		}
//	} while (code < end);
//varend:;
//}
//
//void ProcessCall(LPBYTE& code, LPBYTE begin, LPBYTE end)
//{
//	UINT etag = GetData<UINT>(code);
//	short LibID = GetData<short>(code);
//	UINT len;
//	USHORT tag = GetData<USHORT>(code);
//	GetNoZeroString(code, true);
//	string remark = GetNoZeroString(code, true);
//	ECode_Type head = GetData<ECode_Type>(code);
//	if (head == ECode_Type::ParameterBegin)
//		ProcessVar(code, begin, end);
//	if (head == ECode_Type::ParamBegin || head == ECode_Type::ParameterBegin)
//	{
//		do
//		{
//			switch (GetData<ECode_Type>(code))
//			{
//			case ECode_Type::NullParam:
//				break;
//			case ECode_Type::NumberConst:
//			case ECode_Type::DatetimeConst:
//				code += sizeof(double);
//				break;
//			case ECode_Type::BoolConst:
//				code += sizeof(USHORT);
//				break;
//			case ECode_Type::StringConst:
//				GetString(code, true);
//				break;
//			case ECode_Type::Variable:
//				if (GetData<ECode_Type>(code) == ECode_Type::ParameterBegin)
//					ProcessVar(code, begin, end);
//				break;
//			case ECode_Type::BinConst:
//				do
//				{
//					switch (GetData<ECode_Type>(code))
//					{
//					case ECode_Type::NumberConst:
//						code += sizeof(double);
//						break;
//					case ECode_Type::BinEnd:
//						goto binend;
//					}
//				} while (code < end);
//			binend:
//				break;
//			case ECode_Type::Method:
//				ProcessCall(code, begin, end);
//				break;
//			case ECode_Type::ParamEnd:
//				goto paramend;
//			case ECode_Type::Const:
//			case ECode_Type::n4:
//				code += sizeof(ETAG);
//				break;
//			case ECode_Type::LibConst:
//				code += sizeof(LIBCONST);
//				break;
//			case ECode_Type::LibConstStruct:
//				code += sizeof(LIBCONSTSTRUCT);
//				break;
//			}
//		} while (code < end);
//	paramend:;
//	}
//}
//
//void RepairOffsetTable(vector<BYTE> code, vector<UINT>& row)
//{
//	UINT count = code.size();
//	LPBYTE begin = code.cbegin()._Ptr;
//	LPBYTE end = code.cend()._Ptr;
//	row.clear();
//	LPBYTE ptr = begin;
//	EFlowOffset fo;
//	while (ptr < end)
//	{
//		UINT offset = ptr - begin;
//		switch (GetData<BYTE>(ptr))
//		{
//		case Call:
//			break;
//		case Ife:
//			break;
//		case If:
//			break;
//		case SwitchBegin:
//			continue;
//		case Switch:
//			break;
//		case LoopBegin:
//			break;
//		case LoopEnd:
//			break;
//		case IfeEnd:
//		case IfEnd:
//		case SwitchEnd:
//			continue;
//		default:
//			continue;
//		}
//		ProcessCall(ptr, begin, end);
//		row.push_back(offset);
//	}
//}

void WriteVectorVariable(ostream *stream, vector<ESection_Variable> &list)
{
	SE_PROTECT_START_MUTATION;
	ostrstream tag(NULL, 0, ios::binary);
	ostrstream offset(NULL, 0, ios::binary);
	ostrstream data(NULL, 0, ios::binary);
	for (ESection_Variable &item : list)
	{
		tag.write(C(&item.Tag), sizeof(ETAG));
		UINT len = data.pcount();
		offset.write(C(&len), sizeof(UINT));
		ostrstream buffer(NULL, 0, ios::binary);
		buffer.write(C(&item.DataType), sizeof(DataType));
		buffer.write(C(&item.Attributes), sizeof(EVariableAttr));
		buffer.write(C(&item.ArrayInfo.Dimension), sizeof(BYTE));
		buffer.write(C(item.ArrayInfo.Subscript.begin()._Ptr), item.ArrayInfo.Dimension * sizeof(UINT));
		WriteAutoString(&buffer, item.Name);
		WriteAutoString(&buffer, item.Remark);
		len = buffer.pcount();
		data.write(C(&len), sizeof(UINT));
		data.write(buffer.str(), len);
	}
	UINT len = list.size();
	stream->write(C(&len), sizeof(UINT));
	len = tag.pcount() + offset.pcount() + data.pcount();
	stream->write(C(&len), sizeof(UINT));
	stream->write(tag.str(), tag.pcount());
	stream->write(offset.str(), offset.pcount());
	stream->write(data.str(), data.pcount());
	SE_PROTECT_END;
}

void WriteVectorMethod(ostream *stream, vector<ESection_Program_Method> &list)
{
	SE_PROTECT_START_MUTATION;
	ostrstream tag(NULL, 0, ios::binary);
	ostrstream offset(NULL, 0, ios::binary);
	ostrstream data(NULL, 0, ios::binary);
	for (ESection_Program_Method &item : list)
	{
		tag.write(C(&item.Tag), sizeof(ETAG));
		UINT len = data.pcount();
		offset.write(C(&len), sizeof(UINT));
		data.write(C(&item.Class), sizeof(ETAG));
		data.write(C(&item.Attributes), sizeof(EMethodAttr));
		data.write(C(&item.ReturnType), sizeof(DataType));
		WriteString(&data, item.Name);
		WriteString(&data, item.Remark);
		WriteVectorVariable(&data, item.Variables);
		WriteVectorVariable(&data, item.Parameters);
		len = item.RowOffset.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.RowOffset.begin()._Ptr), len);
		len = item.FlowOffset.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.FlowOffset.begin()._Ptr), len);
		len = item.MethodOffset.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.MethodOffset.begin()._Ptr), len);
		len = item.VariableOffset.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.VariableOffset.begin()._Ptr), len);
		len = item.ParameterOffset.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.ParameterOffset.begin()._Ptr), len);
		len = item.Code.size() * sizeof(BYTE);
		data.write(C(&len), sizeof(UINT));
		data.write(C(item.Code.begin()._Ptr), len);
	}
	UINT len = tag.pcount() + offset.pcount();
	stream->write(C(&len), sizeof(UINT));
	stream->write(tag.str(), tag.pcount());
	stream->write(offset.str(), offset.pcount());
	stream->write(data.str(), data.pcount());
	SE_PROTECT_END;
}

vector<ESection_Variable> GetVariables(LPBYTE &pointer, bool jump = false)
{
	SE_PROTECT_START_MUTATION;
	vector<ESection_Variable> arr;
	UINT count = GetData<UINT>(pointer);
	UINT length = GetData<UINT>(pointer);
	LPBYTE oldptr = pointer + length;
	if (!jump)
	{
		arr = vector<ESection_Variable>(count);
		ETAG* tags = new ETAG[count];
		LPBYTE* os = new LPBYTE[count];
		for (size_t i = 0; i < count; i++)
			tags[i] = GetData<ETAG>(pointer);
		LPBYTE mptr = pointer + count * sizeof(UINT);
		for (size_t i = 0; i < count; i++)
			os[i] = mptr + GetData<UINT>(pointer);
		for (size_t i = 0; i < count; i++)
		{
			ESection_Variable sv;
			pointer = os[i];
			UINT size = GetData<UINT>(pointer);
			sv.Tag = tags[i];
			sv.DataType = GetData<DataType>(pointer);
			sv.Attributes = GetData<EVariableAttr>(pointer);
			sv.ArrayInfo.Dimension = GetData<BYTE>(pointer);
			for (size_t n = 0; n < sv.ArrayInfo.Dimension; n++)
				sv.ArrayInfo.Subscript.push_back(GetData<UINT>(pointer));
			sv.Name = GetAutoString(pointer);
			sv.Remark = GetAutoString(pointer);
			arr[i] = sv;
		}
		delete tags;
		delete os;
	}
	pointer = oldptr;
	SE_PROTECT_END;
	return arr;
}

vector<ESection_Program_Assembly> GetAssemblies(LPBYTE &pointer, bool isclass = true)
{
	SE_PROTECT_START_MUTATION;
	vector<ESection_Program_Assembly> arr;
	UINT length = GetData<UINT>(pointer) / 2;
	size_t count = length / sizeof(ETAG);
	ETAG* tags = new ETAG[count];
	for (size_t i = 0; i < count; i++)
		tags[i] = GetData<ETAG>(pointer);
	pointer += length;
	for (size_t i = 0; i < count; i++)
	{
		ESection_Program_Assembly spa;
		spa.Tag = tags[i];
		if (spa.Tag.Type2 == ETYPE::WindowClass)
		{
			spa.Base = GetData<ETAG>(pointer);
			pointer += 4;
		}
		else
		{
			spa.Status = GetData<ETagStatus>(pointer);
			if (isclass)
				spa.Base = GetData<ETAG>(pointer);
		}
		spa.Name = GetString(pointer);
		spa.Remark = GetString(pointer);
		if (isclass)
		{
			length = GetData<UINT>(pointer);
			size_t size = length / sizeof(ETAG);
			for (size_t n = 0; n < size; n++)
				spa.Methods.push_back(GetData<ETAG>(pointer));
		}
		spa.Variables = GetVariables(pointer, true);
		arr.push_back(spa);
	}
	delete tags;
	SE_PROTECT_END;
	return arr;
}

vector<ESection_Program_Method> GetMethods(LPBYTE &pointer)
{
	SE_PROTECT_START_MUTATION;
	vector<ESection_Program_Method> arr;
	UINT length = GetData<UINT>(pointer) / 2;
	size_t count = length / sizeof(ETAG);
	ETAG* tags = new ETAG[count];
	for (size_t i = 0; i < count; i++)
		tags[i] = GetData<ETAG>(pointer);
	pointer += length;
	for (size_t i = 0; i < count; i++)
	{
		ESection_Program_Method spm;
		spm.Tag = tags[i];
		spm.Class = GetData<ETAG>(pointer);
		spm.Attributes = GetData<EMethodAttr>(pointer);
		spm.ReturnType = GetData<DataType>(pointer);
		spm.Name = GetString(pointer);
		spm.Remark = GetString(pointer);
		spm.Variables = GetVariables(pointer);
		spm.Parameters = GetVariables(pointer);
		spm.RowOffset = GetBytes(pointer);
		spm.FlowOffset = GetBytes(pointer);
		spm.MethodOffset = GetBytes(pointer);
		spm.VariableOffset = GetBytes(pointer);
		spm.ParameterOffset = GetBytes(pointer);
		spm.Code = GetBytes(pointer);
		arr.push_back(spm);
	}
	delete tags;
	SE_PROTECT_END;
	return arr;
}

void AppendPackInfo(ostream *outStream, ostrstream *pack, UINT index)
{
	SE_PROTECT_START;
	const BYTE PackKey[4] = { 0x19, 0x73, 0x00, 0xff };
	const BYTE EncKey[8] = { 72, 167, 105, 96, 133, 251, 168, 5 };
	ESection_Info si = { 0 };
	memcpy(si.Key, PackKey, 4);
	memcpy(si.Name, "程序加固段", 11);
	Decode_Str(si.Name, si.Key);
	si.Index = index;
	UINT size = pack->pcount();
	LPBYTE data = new BYTE[size * 2];
	si.CodeType = Encrypt(D(pack->str()), size, EncKey, data, &size);
	si.DataLength = size;
	si.Data_CheckSum = GetCheckValue(data, size);
	ESection_Header sh;
	memcpy(sh.Magic, Magic_Section, 4);
	sh.Info_CheckSum = GetCheckValue(D(&si), sizeof(ESection_Info));
	outStream->write(C(&sh), sizeof(ESection_Header));
	outStream->write(C(&si), sizeof(ESection_Info));
	outStream->write(C(data), size);
	delete[] data;
	SE_PROTECT_END;
}

void ParseProgram(LPBYTE pointer, LPBYTE end, ostream *outStream, ostream *pack)
{
	SE_PROTECT_START_MUTATION;
	LPBYTE begin = pointer;
	pointer += sizeof(UINT);
	pointer += sizeof(UINT);
	GetBytes(pointer, true);
	GetBytes(pointer, true);
	GetBytes(pointer, true);
	USHORT len = GetData<USHORT>(pointer);
	for (size_t i = 0; i < len; i++)
		GetString(pointer, true);
	int flag = GetData<int>(pointer);
	pointer += sizeof(UINT);
	if ((flag & 1) != 0) pointer += 16;
	GetBytes(pointer, true);
	GetBytes(pointer, true);
	GetAssemblies(pointer);
	outStream->write(C(begin), pointer - begin);
	vector<ESection_Program_Method> methods = GetMethods(pointer);
	UINT size = methods.size();
	pack->write(C(&size), sizeof(UINT));
	bool first = true;
	for (ESection_Program_Method &item : methods)
	{
		size = item.MethodOffset.size();
		pack->write(C(&size), sizeof(UINT));
		LPBYTE ptr = item.MethodOffset.begin()._Ptr;
		pack->write(C(ptr), size);
		item.MethodOffset.clear();

		size = item.VariableOffset.size();
		pack->write(C(&size), sizeof(UINT));
		ptr = item.VariableOffset.begin()._Ptr;
		pack->write(C(ptr), size);
		item.VariableOffset.clear();

		size = item.ParameterOffset.size();
		pack->write(C(&size), sizeof(UINT));
		ptr = item.ParameterOffset.begin()._Ptr;
		pack->write(C(ptr), size);
		item.ParameterOffset.clear();

		size = item.Code.size();
		pack->write(C(&size), sizeof(UINT));
		ptr = item.Code.begin()._Ptr;
		pack->write(C(ptr), size);

		const char prompt[] = "本模块使用模块加固保护,安装易语言助手后即可正常使用";
		string output("\x6a\xc5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36", 18);
		output.push_back((char)ECode_Type::StringConst);
		size = sizeof(prompt);
		output.append(C(&size), 4);
		output.append(prompt, size);
		output.append("\x01", 1);
		if (item.ReturnType != DataType::EDT_VOID)
		{
			string returnCode("\x6a\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36", 18);
			switch (item.ReturnType)
			{
			case DataType::EDT_BYTE:
			case DataType::EDT_SHORT:
			case DataType::EDT_INT:
			case DataType::EDT_LONG:
			case DataType::EDT_FLOAT:
			case DataType::EDT_DOUBLE:
				returnCode.push_back((char)ECode_Type::NumberConst);
				returnCode.append("\x00\x00\x00\x00\x00\x00\x00\x00", 8);
				break;
			case DataType::EDT_BOOL:
				returnCode.push_back((char)ECode_Type::BoolConst);
				returnCode.append("\x00\x00", 2);
				break;
			case DataType::EDT_TEXT:
				returnCode.push_back((char)ECode_Type::StringConst);
				returnCode.append("\x01\x00\x00\x00\x00", 5);
				break;
			case DataType::EDT_BIN:
				returnCode.push_back((char)ECode_Type::BinConst);
				returnCode.push_back((char)ECode_Type::BinEnd);
				break;
			case DataType::EDT_DATETIME:
				returnCode.push_back((char)ECode_Type::DatetimeConst);
				returnCode.append("\x00\x00\x00\x00\x00\x00\x00\x00", 8);
				break;
			case DataType::EDT_ALL:
				break;
			default:
			{
				size = output.size() + returnCode.size();
				item.VariableOffset.resize(sizeof(UINT));
				ptr = item.VariableOffset.begin()._Ptr;
				*(LPUINT)ptr = size;
				returnCode.push_back((char)ECode_Type::Variable);
				returnCode.push_back((char)ECode_Type::ParameterBegin);
				bool ss = false;
				for (ESection_Variable& var : item.Variables)
				{
					if (var.DataType == item.ReturnType)
					{
						returnCode.append(C(&var.Tag), sizeof(ETAG));
						ss = true;
						break;
					}
				}
				if (ss == false)
				{
					for (ESection_Variable& var : item.Parameters)
					{
						if (var.DataType == item.ReturnType)
						{
							returnCode.append(C(&var.Tag), sizeof(ETAG));
							ss = true;
							break;
						}
					}
				}
				if (ss == false)
				{
					ESection_Variable var;
					var.DataType = item.ReturnType;
					var.Tag = item.Tag;
					var.Tag.Type2 = ETYPE::Variable;
					var.ArrayInfo.Dimension = 0;
					item.Variables.push_back(var);
					returnCode.append(C(&var.Tag), sizeof(ETAG));
				}
				returnCode.push_back((char)ECode_Type::ParameterEnd);
				break;
			}
			}
			returnCode.push_back((char)ECode_Type::ParamEnd);
		other:
			output.append(returnCode);
		}
		item.Code.assign(output.begin(), output.end());
		if ((item.Attributes & EMethodAttr::M_Public) == 0)
			item.Remark.clear();
	}
	WriteVectorMethod(outStream, methods);
	outStream->write(C(pointer), end - pointer);
	SE_PROTECT_END;
}

bool Parse(LPBYTE data, size_t size, ostream *outStream)
{
	SE_PROTECT_START;
	bool result = false;
	EFile_Header header;
	memcpy(&header, data, sizeof(EFile_Header));
	if (arrcmp(header.Magic1, Magic1, 4) && arrcmp(header.Magic2, Magic2, 4))
	{
		outStream->write(C(&header), sizeof(EFile_Header));
		long long offset = sizeof(EFile_Header);
		UINT add = 0;
		while (offset < size)
		{
			ESection_Header sh = GetData<ESection_Header>(data, offset);
			if (!arrcmp(sh.Magic, Magic_Section, 4))
				goto end;
			ESection_Info si = GetData<ESection_Info>(data, offset);
			if (!arrcmp(si.Key, KEY, 4))
				Decode_Str(si.Name, si.Key);
			LPSTR Block_Name = (LPSTR)si.Name;
			if (strcmp(Block_Name, "") == 0)
			{
				if (add)
				{
					if (!arrcmp(si.Key, KEY, 4))
						Decode_Str(si.Name, si.Key);
					si.Index += add;
					sh.Info_CheckSum = GetCheckValue(D(&si), sizeof(ESection_Info));
					outStream->write(C(&sh), sizeof(ESection_Header));
					outStream->write(C(&si), sizeof(ESection_Info));
					outStream->write(C(data + offset), si.DataLength);
				}
				else
					outStream->write(C(data + offset - sizeof(ESection_Header) - sizeof(ESection_Info)), sizeof(ESection_Header) + sizeof(ESection_Info) + si.DataLength);
				break;
			}
			else if (strcmp(Block_Name, "程序段") == 0)
			{
				ostrstream stream(NULL, 0, ios::binary);
				ostrstream pack(NULL, 0, ios::binary);
				ParseProgram(data + offset, data + offset + si.DataLength, &stream, &pack);
				UINT oldlen = si.DataLength;
				if (!arrcmp(si.Key, KEY, 4))
					Decode_Str(si.Name, si.Key);
				si.DataLength = stream.pcount();
				si.Data_CheckSum = GetCheckValue(D(stream.str()), stream.pcount());
				sh.Info_CheckSum = GetCheckValue(D(&si), sizeof(ESection_Info));
				outStream->write(C(&sh), sizeof(ESection_Header));
				outStream->write(C(&si), sizeof(ESection_Info));
				outStream->write(stream.str(), stream.pcount());
				si.DataLength = oldlen;
				AppendPackInfo(outStream, &pack, si.Index + 1);
				add = 1;
			}
			else if (strcmp(Block_Name, "程序加固段") == 0)
				goto end;
			else
			{
				if (add)
				{
					if (!arrcmp(si.Key, KEY, 4))
						Decode_Str(si.Name, si.Key);
					si.Index += add;
					sh.Info_CheckSum = GetCheckValue(D(&si), sizeof(ESection_Info));
					outStream->write(C(&sh), sizeof(ESection_Header));
					outStream->write(C(&si), sizeof(ESection_Info));
					outStream->write(C(data + offset), si.DataLength);
				}
				else
					outStream->write(C(data + offset - sizeof(ESection_Header) - sizeof(ESection_Info)), sizeof(ESection_Header) + sizeof(ESection_Info) + si.DataLength);
			}
			offset += si.DataLength;
		}
		result = true;
	end:;
	}
	SE_PROTECT_END;
	return result;
}

int main(int argc, LPSTR argv[])
{
	string path = argv[1];
	ifstream inFile(path, ios::binary);
	ostrstream stream(NULL, 0, ios::binary);
	stream << inFile.rdbuf();
	inFile.close();
	strstream outStream(NULL, 0, ios::binary | ios::in | ios::out);
	if (Parse(D(stream.str()), stream.pcount(), &outStream))
	{
		ofstream outFile(path.substr(0, path.length() - 3) + "_p.ec", ios::binary);
		outStream >> outFile.rdbuf();
		outFile.close();
		return 1;
	}
	return 0;
}