#include "NativeCode.h"

_NATIVE_CODE_LINK::_NATIVE_CODE_LINK()
{
	XedDecodedInstZero(&XedInstruction);
	XedDecodedInstSetMode(&XedInstruction, XED_MACHINE_MODE_LONG_64, XED_ADDRESS_WIDTH_64b);
	Flags = 0UL;
	Next = Prev = NULL;
	Block = NULL;
	Label = 0UL;
	RawData = NULL;
	RawDataSize = 0UL;
	AsmOperations.clear();
}

_NATIVE_CODE_LINK::_NATIVE_CODE_LINK(ULONG LabelId, _NATIVE_CODE_BLOCK* B)
	: _NATIVE_CODE_LINK()
{
	Block = B;
	Label = LabelId;
	Flags = CODE_FLAG_IS_LABEL;
}

_NATIVE_CODE_LINK::_NATIVE_CODE_LINK(ULONG F, PVOID Rd, ULONG Rds, BOOL Decode)
	: _NATIVE_CODE_LINK()
{
	Flags = F;
	RawDataSize = Rds;
	RawData = new UCHAR[Rds];
	if (Rd)
	{
		RtlCopyMemory(RawData, Rd, Rds);
		if (Decode)
			XedDecode(&XedInstruction, RawData, RawDataSize);
	}
}

_NATIVE_CODE_LINK::~_NATIVE_CODE_LINK()
{
	if (RawData)
		delete[] RawData;

	for (STDPAIR<FN_INST_ASM_OP, PVOID> CONST& Op : AsmOperations)
	{
		if (Op.second)
			delete[] Op.second;
	}
}

_NATIVE_CODE_BLOCK::_NATIVE_CODE_BLOCK()
{
	Start = End = NULL;
	LabelIds.clear();
}

VOID NcAppendToBlock(PNATIVE_CODE_BLOCK Block, PNATIVE_CODE_LINK Link)
{
	if (!Link)
		return;

	Link->Block = Block;
	Link->Prev = Block->End;
	Link->Next = NULL;

	if (!Block->End || !Block->Start)
	{
		Block->Start = Block->End = Link;
	}
	else
	{
		Block->End->Next = Link;
		Block->End = Link;
	}
}

VOID NcPrependToBlock(PNATIVE_CODE_BLOCK Block, PNATIVE_CODE_LINK Link)
{
	if (!Link)
		return;

	Link->Block = Block;
	Link->Next = Block->Start;
	Link->Prev = NULL;

	if (!Block->End || !Block->Start)
	{
		Block->Start = Block->End = Link;
	}
	else
	{
		Block->Start->Prev = Link;
		Block->Start = Link;
	}
}

VOID NcInsertLinkAfter(PNATIVE_CODE_LINK Link1, PNATIVE_CODE_LINK Link2)
{
	if (Link1)
	{
		Link2->Prev = Link1;
		Link2->Next = Link1->Next;
		Link1->Next = Link2;
		if (Link2->Next)
			Link2->Next->Prev = Link2;
	}
}

VOID NcInsertLinkBefore(PNATIVE_CODE_LINK Link1, PNATIVE_CODE_LINK Link2)
{
	if (Link1)
	{
		Link2->Next = Link1;
		Link2->Prev = Link1->Prev;
		Link1->Prev = Link2;
		if (Link2->Prev)
			Link2->Prev->Next = Link2;
	}
}

VOID NcUnlink(PNATIVE_CODE_LINK Link)
{
	if (Link)
	{
		if (Link->Next)
			Link->Next->Prev = Link->Prev;
		if (Link->Prev)
			Link->Prev->Next = Link->Next;
	}
}

ULONG NcCountInstructions(PNATIVE_CODE_BLOCK Block, BOOL CountCombinedAsOne)
{
	ULONG InstructionCount = 0;
	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
			continue;
		if (CountCombinedAsOne && (T->Flags & CODE_FLAG_DO_NOT_DIVIDE) && !(T->Flags & CODE_FLAG_GROUP_END))
			continue;
		++InstructionCount;
	}
	return InstructionCount;
}

ULONG NcCalcBlockSizeInBytes(PNATIVE_CODE_BLOCK Block)
{
	ULONG TotalSize = 0;
	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
			continue;
		TotalSize += T->RawDataSize;
	}
	return TotalSize;
}

ULONG NcGenUnusedLabelId(PNATIVE_CODE_BLOCK Block)
{
	ULONG ReturnLabelId = rand();
	while (StdFind(Block->LabelIds.begin(), Block->LabelIds.end(), ReturnLabelId) != Block->LabelIds.end())
		ReturnLabelId = rand();
	Block->LabelIds.push_back(ReturnLabelId);
	return ReturnLabelId;
}

VOID NcChangeLabelId(PNATIVE_CODE_BLOCK Block, ULONG Original, ULONG New)
{
	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (((T->Flags & CODE_FLAG_IS_LABEL) || (T->Flags & CODE_FLAG_IS_REL_JMP)) && T->Label == Original)
			T->Label = New;
	}
}

VOID NcFixLabelsForBlocks(PNATIVE_CODE_BLOCK Block1, PNATIVE_CODE_BLOCK Block2)
{
	for (PNATIVE_CODE_LINK T = Block2->Start; T && T != Block2->End->Next; T = T->Next)
	{
		if ((T->Flags & CODE_FLAG_IS_LABEL) && StdFind(Block1->LabelIds.begin(), Block1->LabelIds.end(), T->Label) != Block1->LabelIds.end())
		{
			NcChangeLabelId(Block2, T->Label, NcGenUnusedLabelId(Block1));
		}
	}
}

BOOL NcInsertBlockAfter(PNATIVE_CODE_LINK Link, PNATIVE_CODE_BLOCK Block, BOOL FixLabels)
{
	if (!Link || !Link->Block || !Block || !Block->Start || !Block->End || Link->Block == Block)
		return FALSE;

	if (FixLabels && Block->LabelIds.size() && Link->Block->LabelIds.size())
		NcFixLabelsForBlocks(Link->Block, Block);

	if (Link->Next)
		Link->Next->Prev = Block->End;
	Block->End->Next = Link->Next;
	Block->Start->Prev = Link;
	Link->Next = Block->Start;

	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
		T->Block = Link->Block;

	return TRUE;
}

BOOL NcInsertBlockBefore(PNATIVE_CODE_LINK Link, PNATIVE_CODE_BLOCK Block, BOOL FixLabels)
{
	if (!Link || !Link->Block || !Block || !Block->Start || !Block->End)
		return FALSE;

	if (FixLabels && Block->LabelIds.size() && Link->Block->LabelIds.size())
		NcFixLabelsForBlocks(Link->Block, Block);
	
	if (Link->Prev)
		Link->Prev->Next = Block->Start;
	Block->Start->Prev = Link->Prev;
	Block->End->Next = Link;
	Link->Prev = Block->End;

	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
		T->Block = Link->Block;

	return TRUE;
}

BOOL NcCreateLabels(PNATIVE_CODE_BLOCK Block)
{
	ULONG CurrentLabelId = 0;
	for (PNATIVE_CODE_LINK T = Block->Start; T; T = T->Next)
	{
		if (!(T->Flags & CODE_FLAG_IS_INST))
			continue;

		XED_CATEGORY_ENUM Category = XedDecodedInstGetCategory(&T->XedInstruction);
		if (Category != XED_CATEGORY_COND_BR && Category != XED_CATEGORY_UNCOND_BR)
			continue;

		ULONG OperandCount = XedDecodedInstNumOperands(&T->XedInstruction);
		if (OperandCount < 1)
			continue;

		CONST XED_INST* Inst = XedDecodedInstInst(&T->XedInstruction);
		if (!Inst)
			continue;
		
		CONST XED_OPERAND* Operand = XedInstOperand(Inst, 0);
		if (!Operand)
			continue;

		XED_OPERAND_TYPE_ENUM OperandType = XedOperandType(Operand);
		if (OperandType != XED_OPERAND_TYPE_IMM && OperandType != XED_OPERAND_TYPE_IMM_CONST)
			continue;

		INT32 BranchDisplacement = XedDecodedInstGetBranchDisplacement(&T->XedInstruction);
		PNATIVE_CODE_LINK JmpPos = NcValidateJmp(T, BranchDisplacement);
		if (!JmpPos)
		{
			printf("Failed to validate jump. Type: %s, Displacement: %d\n", XedCategoryEnumToString(Category), BranchDisplacement);
			return FALSE;
		}

		if (JmpPos->Prev && (JmpPos->Prev->Flags & CODE_FLAG_IS_LABEL))
		{
			T->Label = JmpPos->Prev->Label;
		}
		else
		{
			NcInsertLinkBefore(JmpPos, new NATIVE_CODE_LINK(CurrentLabelId, Block));
			Block->LabelIds.push_back(CurrentLabelId);
			T->Label = CurrentLabelId;
			++CurrentLabelId;
		}
		T->Flags |= CODE_FLAG_IS_REL_JMP;
	}
	return TRUE;
}

PNATIVE_CODE_LINK NcValidateJmp(PNATIVE_CODE_LINK Jmp, INT32 Delta)
{
	PNATIVE_CODE_LINK T;
	if (Delta > 0)
	{
		T = Jmp->Next;
		while (Delta > 0 && T)
		{
			if (T->Flags & CODE_FLAG_IS_LABEL)
				continue;
			Delta -= XedDecodedInstGetLength(&T->XedInstruction);
			T = T->Next;
		}
		if (Delta != 0 || !T)
			return NULL;
		while (T && (T->Flags & CODE_FLAG_IS_LABEL))
			T = T->Next;
		return T;
	}
	else if (Delta < 0)
	{
		T = Jmp;
		while (T)
		{
			if (T->Flags & CODE_FLAG_IS_LABEL)
				continue;
			Delta += XedDecodedInstGetLength(&T->XedInstruction);
			if (Delta >= 0)
				break;
			T = T->Prev;
		}
		if (Delta != 0 || !T)
			return NULL;
		while (T && (T->Flags & CODE_FLAG_IS_LABEL))
			T = T->Next;
		return T;
	}
	return Jmp;
}

PNATIVE_CODE_LINK NcDeepCopyLink(PNATIVE_CODE_LINK Link)
{
	if (Link->Flags & CODE_FLAG_IS_LABEL)
	{
		return new NATIVE_CODE_LINK(Link->Label, NULL);
	}
	else
	{		
		PNATIVE_CODE_LINK NewLink = new NATIVE_CODE_LINK(Link->Flags, Link->RawData, Link->RawDataSize);
		NewLink->Label = Link->Label;
		XED_ERROR_ENUM DecodeError = XedDecode(&NewLink->XedInstruction, Link->RawData, Link->RawDataSize);
		if (DecodeError != XED_ERROR_NONE)
		{
			printf("XedDecode failed in NcDeepCopyLink: %s %u\n", XedErrorEnumToString(DecodeError), Link->RawDataSize);
			delete NewLink;
			return NULL;
		}
		if (Link->Flags & CODE_FLAG_HAS_ASM_OP)
		{
			for (STDPAIR<FN_INST_ASM_OP, PVOID> CONST& Op : Link->AsmOperations)
			{
				NewLink->AsmOperations.emplace_back(Op.first, Op.second);
			}
		}
		return NewLink;
	}
}

BOOL NcDeepCopyPartialBlock(PNATIVE_CODE_LINK Start, PNATIVE_CODE_LINK End, PNATIVE_CODE_BLOCK Block)
{
	if (!Start || !End || !Start->Block || Start->Block != End->Block || !Block)
		return FALSE;

	Block->LabelIds.clear();
	Block->Start = Block->End = NULL;

	for (ULONG L : Start->Block->LabelIds)
		Block->LabelIds.push_back(L);

	for (PNATIVE_CODE_LINK CurLink = Start; CurLink && CurLink != End->Next; CurLink = CurLink->Next)
	{
		PNATIVE_CODE_LINK Temp = NcDeepCopyLink(CurLink);
		if (!Temp)
		{
			NcDeleteBlock(Block);
			return FALSE;
		}
		NcAppendToBlock(Block, Temp);
	}

	return TRUE;
}

BOOL NcDeepCopyBlock(PNATIVE_CODE_BLOCK Block, PNATIVE_CODE_BLOCK BlockCopy)
{
	return NcDeepCopyPartialBlock(Block->Start, Block->End, BlockCopy);
}

BOOL NcGetDeltaToLabel(PNATIVE_CODE_LINK Link, PINT32 DeltaOut)
{
	INT32 Delta = 0;
	//First checking backwards because I feel like thats the direction most jmps are in
	for (PNATIVE_CODE_LINK T = Link; T; T = T->Prev)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
		{
			if (T->Label == Link->Label)
			{
				*DeltaOut = Delta;
				return TRUE;
			}
			continue;
		}
		Delta -= T->RawDataSize;
	}

	//Now check forwards
	Delta = 0;
	for (PNATIVE_CODE_LINK T = Link->Next; T; T = T->Next)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
		{
			if (T->Label == Link->Label)
			{
				*DeltaOut = Delta;
				return TRUE;
			}
			continue;
		}
		Delta += T->RawDataSize;
	}
	return FALSE;
}

BOOL NcFixRelJmps(PNATIVE_CODE_BLOCK Block)
{
	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next;)
	{
		if (T->Flags & CODE_FLAG_IS_REL_JMP)
		{
			INT32 BranchDisp = 0;
			if (!NcGetDeltaToLabel(T, &BranchDisp))
				return FALSE;

			ULONG DispWidth = XedDecodedInstGetBranchDisplacementWidthBits(&T->XedInstruction);
			if (log2(abs(BranchDisp)) + 1 > DispWidth)
			{
				//duh oh
				if (DispWidth == 32)
					return FALSE;

				////Grow displacement width to required size
				//DispWidth *= 2;

				////Check again
				//if (log2(abs(BranchDisp)) + 1 > DispWidth)
				//{
				//	if (DispWidth == 32)
				//		return FALSE;

				//	//Grow once more if not already at 32
				//	DispWidth *= 2;
				//}

				DispWidth = 32;

				//Encode new instruction 
				XED_STATE MachineState;
				MachineState.mmode = XED_MACHINE_MODE_LONG_64;
				MachineState.stack_addr_width = XED_ADDRESS_WIDTH_64b;
				XED_ENCODER_INSTRUCTION EncoderInstruction;
				XED_ENCODER_REQUEST EncoderRequest;
				UCHAR EncodeBuffer[15];
				UINT ReturnedSize;
				XED_ICLASS_ENUM IClass = XedDecodedInstGetIClass(&T->XedInstruction);

				//Do the encoding
				XedInst1(&EncoderInstruction, MachineState, IClass, DispWidth, XedRelBr(0, DispWidth));
				XedEncoderRequestZeroSetMode(&EncoderRequest, &MachineState);
				if (!XedConvertToEncoderRequest(&EncoderRequest, &EncoderInstruction))
					return FALSE;
				XED_ERROR_ENUM Err = XedEncode(&EncoderRequest, EncodeBuffer, 15, &ReturnedSize);
				if (XED_ERROR_NONE != Err)
					return FALSE;

				//fixup T->RawData
				delete[] T->RawData;
				T->RawDataSize = ReturnedSize;
				T->RawData = new UCHAR[ReturnedSize];
				RtlCopyMemory(T->RawData, EncodeBuffer, ReturnedSize);

				//Decode instruction so its proper and all that
				XedDecodedInstZeroSetMode(&T->XedInstruction, &MachineState);
				if (XED_ERROR_NONE != XedDecode(&T->XedInstruction, T->RawData, T->RawDataSize))
					return FALSE;

				//Go back to the start and loop through all labels again because now this instruction is larger :))))
				T = Block->Start;
				continue;
			}
			else
			{
				DispWidth = XedDecodedInstGetBranchDisplacementWidth(&T->XedInstruction);
				switch (DispWidth)
				{
				case 1: *(PINT8)&T->RawData[T->RawDataSize - DispWidth] = (INT8)BranchDisp; break;
				case 2: *(PINT16)&T->RawData[T->RawDataSize - DispWidth] = (INT16)BranchDisp; break;
				case 4: *(PINT32)&T->RawData[T->RawDataSize - DispWidth] = (INT32)BranchDisp; break;
				}
			}
		}

		T = T->Next;
	}
	return TRUE;
}

BOOL NcDisassemble(PNATIVE_CODE_BLOCK Block, PVOID Buffer, ULONG BufferSize)
{
	PUCHAR Buf = (PUCHAR)Buffer;
	ULONG Offset = 0;

	while (Offset < BufferSize)
	{
		PNATIVE_CODE_LINK Link = new NATIVE_CODE_LINK;
		Link->Flags = CODE_FLAG_IS_INST;
		ULONG PossibleSize = min(15, BufferSize - Offset);
		XED_ERROR_ENUM DecodeError = XedDecode(&Link->XedInstruction, (Buf + Offset), PossibleSize);
		if (DecodeError != XED_ERROR_NONE)
		{
			printf("XedDecode failed with error %s\n", XedErrorEnumToString(DecodeError));
			NcDeleteBlock(Block);
			delete Link;
			return FALSE;
		}
		Link->RawDataSize = XedDecodedInstGetLength(&Link->XedInstruction);
		Link->RawData = new UCHAR[Link->RawDataSize];
		RtlCopyMemory(Link->RawData, (Buf + Offset), Link->RawDataSize);

		NcAppendToBlock(Block, Link);

		Offset += Link->RawDataSize;
	}

	NcCreateLabels(Block);

	return TRUE;
}

PVOID NcAssemble(PNATIVE_CODE_BLOCK Block, PULONG OutSize)
{
	if (!NcFixRelJmps(Block))
		return NULL;

	*OutSize = NcCalcBlockSizeInBytes(Block);

	PUCHAR Buffer = (PUCHAR)malloc(*OutSize);
	if (!Buffer)
		return NULL;

	PUCHAR BufferOffset = Buffer;

	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
			continue;
		RtlCopyMemory(BufferOffset, T->RawData, T->RawDataSize);
		if (T->Flags & CODE_FLAG_HAS_ASM_OP)
		{
			for (STDPAIR<FN_INST_ASM_OP, PVOID> CONST& Op : T->AsmOperations)
				Op.first(T, BufferOffset, Op.second);
		}
		BufferOffset += T->RawDataSize;
	}

	return Buffer;
}

PVOID NcAssembleEx(PNATIVE_CODE_BLOCK Block, PULONG OutSize, BOOL ChooseRandomDuplicateLabel)
{
	return NULL;
}

VOID NcDeleteBlock(PNATIVE_CODE_BLOCK Block)
{
	if (!Block->Start || !Block->End)
		return;

	PNATIVE_CODE_LINK BlockEnding = Block->End->Next;

	for (PNATIVE_CODE_LINK T = Block->Start; T && T != BlockEnding;)
	{
		PNATIVE_CODE_LINK Next = T->Next;
		delete T;
		T = Next;
	}	
}

VOID NcDebugPrint(PNATIVE_CODE_BLOCK Block)
{
	HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
	if (!ConsoleHandle)
		return;

	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (T->Flags & CODE_FLAG_IS_LABEL)
		{
			SetConsoleTextAttribute(ConsoleHandle, FOREGROUND_GREEN | FOREGROUND_RED);
			printf("Label: %u\n", T->Label);
		}
		else
		{
			XED_ICLASS_ENUM IClass = XedDecodedInstGetIClass(&T->XedInstruction);
			if (T->Flags & CODE_FLAG_IS_REL_JMP)
			{
				SetConsoleTextAttribute(ConsoleHandle, FOREGROUND_GREEN | FOREGROUND_RED);
				printf("%s: %u\n", XedIClassEnumToString(IClass), T->Label);
			}
			else
			{
				SetConsoleTextAttribute(ConsoleHandle, FOREGROUND_GREEN | FOREGROUND_BLUE);
				printf("%s\n", XedIClassEnumToString(IClass));
			}
		}
	}
}

VOID NcPrintBlockCode(PNATIVE_CODE_BLOCK Block)
{
	for (PNATIVE_CODE_LINK T = Block->Start; T && T != Block->End->Next; T = T->Next)
	{
		if (!(T->Flags & CODE_FLAG_IS_LABEL))
		{
			for (uint32_t i = 0; i < T->RawDataSize; i++)
			{
				std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)T->RawData[i] << ' ';
			}
		}
	}
}