* Update dhooks.inc documentation for consistency - Modifies whitespace (change tabs to spaces for non initial indents, fix alignments, create consistency with rest of SM docs) - Change `/*` to `/**` for consistency and to indicate comment doc - Removes incorrect `@noreturn` doc * Split long comments across multiple lines * Remove `@noreturn` in IGameConfigs.h * Remove `@noreturn` from IGameHelpers.h * Remove `@noreturn` from asm.c * Add `@noreturn` to ThrowError * Add `@noreturn` and `@error` to ThrowNativeError
		
			
				
	
	
		
			479 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			479 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include "asm.h"
 | 
						|
#include "libudis86/udis86.h"
 | 
						|
 | 
						|
#ifndef WIN32
 | 
						|
#define _GNU_SOURCE
 | 
						|
#include <dlfcn.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
 | 
						|
#include "libudis86/udis86.h"
 | 
						|
 | 
						|
#define REG_EAX			0
 | 
						|
#define REG_ECX			1
 | 
						|
#define REG_EDX			2
 | 
						|
#define REG_EBX			3
 | 
						|
 | 
						|
#define IA32_MOV_REG_IMM		0xB8	// encoding is +r <imm32>
 | 
						|
#endif
 | 
						|
 | 
						|
/**
 | 
						|
* Checks if a call to a fpic thunk has just been written into dest.
 | 
						|
* If found replaces it with a direct mov that sets the required register to the value of pc.
 | 
						|
*
 | 
						|
* @param dest		Destination buffer where a call opcode + addr (5 bytes) has just been written.
 | 
						|
* @param pc		The program counter value that needs to be set (usually the next address from the source).
 | 
						|
*/
 | 
						|
void check_thunks(unsigned char *dest, unsigned char *pc)
 | 
						|
{
 | 
						|
#if defined(_WIN32) || defined(__x86_64__)
 | 
						|
	return;
 | 
						|
#else
 | 
						|
	/* Step write address back 4 to the start of the function address */
 | 
						|
	unsigned char *writeaddr = dest - 4;
 | 
						|
	unsigned char *calloffset = *(unsigned char **)writeaddr;
 | 
						|
	unsigned char *calladdr = (unsigned char *)(dest + (unsigned int)calloffset);
 | 
						|
 | 
						|
	/* Lookup name of function being called */
 | 
						|
	if ((*calladdr == 0x8B) && (*(calladdr+2) == 0x24) && (*(calladdr+3) == 0xC3))
 | 
						|
	{
 | 
						|
		//a thunk maybe?
 | 
						|
		char movByte = IA32_MOV_REG_IMM;
 | 
						|
 | 
						|
		/* Calculate the correct mov opcode */
 | 
						|
		switch (*(calladdr+1))
 | 
						|
		{
 | 
						|
		case 0x04:
 | 
						|
			{
 | 
						|
				movByte += REG_EAX;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		case 0x1C:
 | 
						|
			{
 | 
						|
				movByte += REG_EBX;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		case 0x0C:
 | 
						|
			{
 | 
						|
				movByte += REG_ECX;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		case 0x14:
 | 
						|
			{
 | 
						|
				movByte += REG_EDX;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			{
 | 
						|
				printf("Unknown thunk: %c\n", *(calladdr+1));
 | 
						|
#ifndef NDEBUG
 | 
						|
				abort();
 | 
						|
#endif
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* Move our write address back one to where the call opcode was */
 | 
						|
		writeaddr--;
 | 
						|
 | 
						|
 | 
						|
		/* Write our mov */
 | 
						|
		*writeaddr = movByte;
 | 
						|
		writeaddr++;
 | 
						|
 | 
						|
		/* Write the value - The provided program counter value */
 | 
						|
		*(void **)writeaddr = (void *)pc;
 | 
						|
		writeaddr += 4;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
int copy_bytes(unsigned char *func, unsigned char *dest, int required_len)
 | 
						|
{
 | 
						|
	ud_t ud_obj;
 | 
						|
	ud_init(&ud_obj);
 | 
						|
 | 
						|
#if defined(_WIN64) || defined(__x86_64__)
 | 
						|
	ud_set_mode(&ud_obj, 64);
 | 
						|
#else
 | 
						|
	ud_set_mode(&ud_obj, 32);
 | 
						|
#endif
 | 
						|
 | 
						|
	ud_set_input_buffer(&ud_obj, func, 20);
 | 
						|
	unsigned int bytecount = 0;
 | 
						|
	
 | 
						|
	while (bytecount < required_len && ud_disassemble(&ud_obj))
 | 
						|
	{
 | 
						|
		unsigned int insn_len = ud_insn_len(&ud_obj);
 | 
						|
		bytecount += insn_len;
 | 
						|
		
 | 
						|
		if (dest)
 | 
						|
		{	
 | 
						|
			const uint8_t *opcode = ud_insn_ptr(&ud_obj);
 | 
						|
			if ((opcode[0] & 0xFE) == 0xE8)	// Fix CALL/JMP offset
 | 
						|
			{
 | 
						|
				dest[0] = func[0];
 | 
						|
				dest++; func++;
 | 
						|
				if (ud_insn_opr(&ud_obj, 0)->size == 32)
 | 
						|
				{
 | 
						|
					*(int32_t *)dest = func + *(int32_t *)func - dest;
 | 
						|
					check_thunks(dest+4, func+4);
 | 
						|
					dest += sizeof(int32_t);
 | 
						|
				}
 | 
						|
				else
 | 
						|
				{
 | 
						|
					*(int16_t *)dest = func + *(int16_t *)func - dest;
 | 
						|
					dest += sizeof(int16_t);
 | 
						|
				}
 | 
						|
				func--;
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				memcpy(dest, func, insn_len);
 | 
						|
				dest += insn_len;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		func += insn_len;
 | 
						|
	}
 | 
						|
	
 | 
						|
	return bytecount;
 | 
						|
}
 | 
						|
 | 
						|
#if 0
 | 
						|
//if dest is NULL, returns minimum number of bytes needed to be copied
 | 
						|
//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs
 | 
						|
//http://www.devmaster.net/forums/showthread.php?t=2311
 | 
						|
int copy_bytes(unsigned char *func, unsigned char* dest, int required_len) {
 | 
						|
	int bytecount = 0;
 | 
						|
 | 
						|
	while(bytecount < required_len && *func != 0xCC)
 | 
						|
	{
 | 
						|
		// prefixes F0h, F2h, F3h, 66h, 67h, D8h-DFh, 2Eh, 36h, 3Eh, 26h, 64h and 65h
 | 
						|
		int operandSize = 4;
 | 
						|
		int FPU = 0;
 | 
						|
		int twoByte = 0;
 | 
						|
		unsigned char opcode = 0x90;
 | 
						|
		unsigned char modRM = 0xFF;
 | 
						|
		while(*func == 0xF0 ||
 | 
						|
			  *func == 0xF2 ||
 | 
						|
			  *func == 0xF3 ||
 | 
						|
			 (*func & 0xFC) == 0x64 ||
 | 
						|
			 (*func & 0xF8) == 0xD8 ||
 | 
						|
			 (*func & 0x7E) == 0x62)
 | 
						|
		{
 | 
						|
			if(*func == 0x66)
 | 
						|
			{
 | 
						|
				operandSize = 2;
 | 
						|
			}
 | 
						|
			else if((*func & 0xF8) == 0xD8)
 | 
						|
			{
 | 
						|
				FPU = *func;
 | 
						|
				if (dest)
 | 
						|
						*dest++ = *func++;
 | 
						|
				else
 | 
						|
						func++;
 | 
						|
				bytecount++;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			if (dest)
 | 
						|
				*dest++ = *func++;
 | 
						|
			else
 | 
						|
				func++;
 | 
						|
			bytecount++;
 | 
						|
		}
 | 
						|
 | 
						|
		// two-byte opcode byte
 | 
						|
		if(*func == 0x0F)
 | 
						|
		{
 | 
						|
			twoByte = 1;
 | 
						|
			if (dest)
 | 
						|
				*dest++ = *func++;
 | 
						|
			else
 | 
						|
				func++;
 | 
						|
			bytecount++;
 | 
						|
		}
 | 
						|
 | 
						|
		// opcode byte
 | 
						|
		opcode = *func++;
 | 
						|
		if (dest) *dest++ = opcode;
 | 
						|
		bytecount++;
 | 
						|
 | 
						|
		// mod R/M byte
 | 
						|
		modRM = 0xFF;
 | 
						|
		if(FPU)
 | 
						|
		{
 | 
						|
			if((opcode & 0xC0) != 0xC0)
 | 
						|
			{
 | 
						|
				modRM = opcode;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else if(!twoByte)
 | 
						|
		{
 | 
						|
			if((opcode & 0xC4) == 0x00 ||
 | 
						|
			   ((opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x09)) ||
 | 
						|
			   (opcode & 0xF0) == 0x80 ||
 | 
						|
			   ((opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02) ||
 | 
						|
			   (opcode & 0xFC) == 0xD0 ||
 | 
						|
			   (opcode & 0xF6) == 0xF6)
 | 
						|
			{
 | 
						|
				modRM = *func++;
 | 
						|
				if (dest) *dest++ = modRM;
 | 
						|
				bytecount++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			if(((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D) ||
 | 
						|
			   (opcode & 0xF0) == 0x30 ||
 | 
						|
			   opcode == 0x77 ||
 | 
						|
			   (opcode & 0xF0) == 0x80 ||
 | 
						|
			   ((opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02) ||
 | 
						|
			   (opcode & 0xF8) == 0xC8)
 | 
						|
			{
 | 
						|
				// No mod R/M byte
 | 
						|
			}
 | 
						|
			else
 | 
						|
			{
 | 
						|
				modRM = *func++;
 | 
						|
				if (dest) *dest++ = modRM;
 | 
						|
				bytecount++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// SIB
 | 
						|
		if((modRM & 0x07) == 0x04 &&
 | 
						|
		   (modRM & 0xC0) != 0xC0)
 | 
						|
		{
 | 
						|
			if (dest)
 | 
						|
				*dest++ = *func++;   //SIB
 | 
						|
			else
 | 
						|
				func++;
 | 
						|
			bytecount++;
 | 
						|
		}
 | 
						|
 | 
						|
		// mod R/M displacement
 | 
						|
 | 
						|
		// Dword displacement, no base
 | 
						|
	if((modRM & 0xC5) == 0x05) {
 | 
						|
		if (dest) {
 | 
						|
			*(unsigned int*)dest = *(unsigned int*)func;
 | 
						|
			dest += 4;
 | 
						|
		}
 | 
						|
		func += 4;
 | 
						|
		bytecount += 4;
 | 
						|
	}
 | 
						|
 | 
						|
		// Byte displacement
 | 
						|
	if((modRM & 0xC0) == 0x40) {
 | 
						|
		if (dest)
 | 
						|
			*dest++ = *func++;
 | 
						|
		else
 | 
						|
			func++;
 | 
						|
		bytecount++;
 | 
						|
	}
 | 
						|
 | 
						|
		// Dword displacement
 | 
						|
	if((modRM & 0xC0) == 0x80) {
 | 
						|
		if (dest) {
 | 
						|
			*(unsigned int*)dest = *(unsigned int*)func;
 | 
						|
			dest += 4;
 | 
						|
		}
 | 
						|
		func += 4;
 | 
						|
		bytecount += 4;
 | 
						|
	}
 | 
						|
 | 
						|
		// immediate
 | 
						|
		if(FPU)
 | 
						|
		{
 | 
						|
			// Can't have immediate operand
 | 
						|
		}
 | 
						|
		else if(!twoByte)
 | 
						|
		{
 | 
						|
			if((opcode & 0xC7) == 0x04 ||
 | 
						|
			   (opcode & 0xFE) == 0x6A ||   // PUSH/POP/IMUL
 | 
						|
			   (opcode & 0xF0) == 0x70 ||   // Jcc
 | 
						|
			   opcode == 0x80 ||
 | 
						|
			   opcode == 0x83 ||
 | 
						|
			   (opcode & 0xFD) == 0xA0 ||   // MOV
 | 
						|
			   opcode == 0xA8 ||			// TEST
 | 
						|
			   (opcode & 0xF8) == 0xB0 ||   // MOV
 | 
						|
			   (opcode & 0xFE) == 0xC0 ||   // RCL
 | 
						|
			   opcode == 0xC6 ||			// MOV
 | 
						|
			   opcode == 0xCD ||			// INT
 | 
						|
			   (opcode & 0xFE) == 0xD4 ||   // AAD/AAM
 | 
						|
			   (opcode & 0xF8) == 0xE0 ||   // LOOP/JCXZ
 | 
						|
			   opcode == 0xEB ||
 | 
						|
			   (opcode == 0xF6 && (modRM & 0x30) == 0x00))   // TEST
 | 
						|
			{
 | 
						|
				if (dest)
 | 
						|
					*dest++ = *func++;
 | 
						|
				else
 | 
						|
					func++;
 | 
						|
				bytecount++;
 | 
						|
			}
 | 
						|
		else if((opcode & 0xF7) == 0xC2) // RET
 | 
						|
			{
 | 
						|
				if (dest) {
 | 
						|
					*(unsigned short*)dest = *(unsigned short*)func;
 | 
						|
					dest += 2;
 | 
						|
				}
 | 
						|
				func += 2;
 | 
						|
				bytecount += 2;
 | 
						|
			}
 | 
						|
			else if((opcode & 0xFC) == 0x80 ||
 | 
						|
					(opcode & 0xC7) == 0x05 ||
 | 
						|
					(opcode & 0xF8) == 0xB8 ||
 | 
						|
					(opcode & 0xFE) == 0xE8 ||	  // CALL/Jcc
 | 
						|
					(opcode & 0xFE) == 0x68 ||
 | 
						|
					(opcode & 0xFC) == 0xA0 ||
 | 
						|
					(opcode & 0xEE) == 0xA8 ||
 | 
						|
					opcode == 0xC7 ||
 | 
						|
					(opcode == 0xF7 && (modRM & 0x30) == 0x00))
 | 
						|
			{
 | 
						|
				if (dest) {
 | 
						|
					//Fix CALL/JMP offset
 | 
						|
					if ((opcode & 0xFE) == 0xE8) {
 | 
						|
						if (operandSize == 4)
 | 
						|
						{
 | 
						|
							*(long*)dest = ((func + *(long*)func) - dest);
 | 
						|
 | 
						|
							//pRED* edit. func is the current address of the call address, +4 is the next instruction, so the value of $pc
 | 
						|
							check_thunks(dest+4, func+4);
 | 
						|
						}
 | 
						|
						else
 | 
						|
							*(short*)dest = ((func + *(short*)func) - dest);
 | 
						|
 | 
						|
					} else {
 | 
						|
						if (operandSize == 4)
 | 
						|
							*(unsigned long*)dest = *(unsigned long*)func;
 | 
						|
						else
 | 
						|
							*(unsigned short*)dest = *(unsigned short*)func;
 | 
						|
					}
 | 
						|
					dest += operandSize;
 | 
						|
				}
 | 
						|
				func += operandSize;
 | 
						|
				bytecount += operandSize;
 | 
						|
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			if(opcode == 0xBA ||			// BT
 | 
						|
			   opcode == 0x0F ||			// 3DNow!
 | 
						|
			   (opcode & 0xFC) == 0x70 ||   // PSLLW
 | 
						|
			   (opcode & 0xF7) == 0xA4 ||   // SHLD
 | 
						|
			   opcode == 0xC2 ||
 | 
						|
			   opcode == 0xC4 ||
 | 
						|
			   opcode == 0xC5 ||
 | 
						|
			   opcode == 0xC6)
 | 
						|
			{
 | 
						|
				if (dest)
 | 
						|
					*dest++ = *func++;
 | 
						|
				else
 | 
						|
					func++;
 | 
						|
			}
 | 
						|
			else if((opcode & 0xF0) == 0x80) // Jcc -i
 | 
						|
			{
 | 
						|
				if (dest) {
 | 
						|
					if (operandSize == 4)
 | 
						|
						*(unsigned long*)dest = *(unsigned long*)func;
 | 
						|
					else
 | 
						|
						*(unsigned short*)dest = *(unsigned short*)func;
 | 
						|
 | 
						|
					dest += operandSize;
 | 
						|
				}
 | 
						|
				func += operandSize;
 | 
						|
				bytecount += operandSize;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return bytecount;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
//insert a specific JMP instruction at the given location
 | 
						|
void inject_jmp(void* src, void* dest) {
 | 
						|
	*(unsigned char*)src = OP_JMP;
 | 
						|
	*(long*)((unsigned char*)src+1) = (long)((unsigned char*)dest - ((unsigned char*)src + OP_JMP_SIZE));
 | 
						|
}
 | 
						|
 | 
						|
//fill a given block with NOPs
 | 
						|
void fill_nop(void* src, unsigned int len) {
 | 
						|
	unsigned char* src2 = (unsigned char*)src;
 | 
						|
	while (len) {
 | 
						|
		*src2++ = OP_NOP;
 | 
						|
		--len;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void* eval_jump(void* src) {
 | 
						|
	unsigned char* addr = (unsigned char*)src;
 | 
						|
 | 
						|
	if (!addr) return 0;
 | 
						|
 | 
						|
	//import table jump
 | 
						|
	if (addr[0] == OP_PREFIX && addr[1] == OP_JMP_SEG) {
 | 
						|
		addr += 2;
 | 
						|
		addr = *(unsigned char**)addr;
 | 
						|
		//TODO: if addr points into the IAT
 | 
						|
		return *(void**)addr;
 | 
						|
	}
 | 
						|
 | 
						|
	//8bit offset
 | 
						|
	else if (addr[0] == OP_JMP_BYTE) {
 | 
						|
		addr = &addr[OP_JMP_BYTE_SIZE] + *(char*)&addr[1];
 | 
						|
		//mangled 32bit jump?
 | 
						|
		if (addr[0] == OP_JMP) {
 | 
						|
			addr = addr + *(int*)&addr[1];
 | 
						|
		}
 | 
						|
		return addr;
 | 
						|
	}
 | 
						|
	/*
 | 
						|
	//32bit offset
 | 
						|
	else if (addr[0] == OP_JMP) {
 | 
						|
		addr = &addr[OP_JMP_SIZE] + *(int*)&addr[1];
 | 
						|
	}
 | 
						|
	*/
 | 
						|
 | 
						|
	return addr;
 | 
						|
}
 | 
						|
/*
 | 
						|
from ms detours package
 | 
						|
static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress)
 | 
						|
{
 | 
						|
	MEMORY_BASIC_INFORMATION mbi;
 | 
						|
	VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi));
 | 
						|
	__try {
 | 
						|
		PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase;
 | 
						|
		if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader +
 | 
						|
														  pDosHeader->e_lfanew);
 | 
						|
		if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
 | 
						|
		if (pbAddress >= ((PBYTE)pDosHeader +
 | 
						|
						  pNtHeader->OptionalHeader
 | 
						|
						  .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) &&
 | 
						|
			pbAddress < ((PBYTE)pDosHeader +
 | 
						|
						 pNtHeader->OptionalHeader
 | 
						|
						 .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress +
 | 
						|
						 pNtHeader->OptionalHeader
 | 
						|
						 .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) {
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	__except(EXCEPTION_EXECUTE_HANDLER) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
}
 | 
						|
*/
 |