#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include "dxwnd.h"
#include "dxwcore.hpp"
#include "syslibs.h"
#include "dxhook.h"
#include "dxhelper.h"

typedef LONG (WINAPI *RegFlushKey_Type)(HKEY);
LONG WINAPI extRegFlushKey(HKEY);
RegFlushKey_Type pRegFlushKey = NULL;
typedef LONG (WINAPI *RegEnumValueA_Type)(HKEY, DWORD, LPTSTR, LPDWORD, LPDWORD, LPDWORD, LPBYTE, LPDWORD);
LONG WINAPI extRegEnumValueA(HKEY, DWORD, LPTSTR, LPDWORD, LPDWORD, LPDWORD, LPBYTE, LPDWORD);
RegEnumValueA_Type pRegEnumValueA = NULL;

static HookEntryEx_Type Hooks[]={
	{HOOK_IAT_CANDIDATE, 0, "RegOpenKeyExA", NULL, (FARPROC *)&pRegOpenKeyEx, (FARPROC)extRegOpenKeyEx},
	{HOOK_IAT_CANDIDATE, 0, "RegCloseKey", NULL, (FARPROC *)&pRegCloseKey, (FARPROC)extRegCloseKey},
	{HOOK_IAT_CANDIDATE, 0, "RegQueryValueA", NULL, (FARPROC *)&pRegQueryValue, (FARPROC)extRegQueryValue},
	{HOOK_IAT_CANDIDATE, 0, "RegQueryValueExA", NULL, (FARPROC *)&pRegQueryValueEx, (FARPROC)extRegQueryValueEx},
	{HOOK_IAT_CANDIDATE, 0, "RegCreateKeyA", NULL, (FARPROC *)&pRegCreateKey, (FARPROC)extRegCreateKey},
	{HOOK_IAT_CANDIDATE, 0, "RegCreateKeyExA", NULL, (FARPROC *)&pRegCreateKeyEx, (FARPROC)extRegCreateKeyEx},
	{HOOK_IAT_CANDIDATE, 0, "RegSetValueExA", NULL, (FARPROC *)&pRegSetValueEx, (FARPROC)extRegSetValueEx},
	{HOOK_IAT_CANDIDATE, 0, "RegFlushKey", NULL, (FARPROC *)&pRegFlushKey, (FARPROC)extRegFlushKey},
	// v2.3.36
	{HOOK_IAT_CANDIDATE, 0, "RegEnumValueA", NULL, (FARPROC *)&pRegEnumValueA, (FARPROC)extRegEnumValueA},
	{HOOK_IAT_CANDIDATE, 0, 0, NULL, 0, 0} // terminator
};
 
void HookAdvApi32(HMODULE module)
{
	if( (dxw.dwFlags3 & EMULATEREGISTRY) || 
		(dxw.dwFlags4 & OVERRIDEREGISTRY) || 
		(dxw.dwFlags6 & (WOW32REGISTRY|WOW64REGISTRY)) || 
		(dxw.dwTFlags & OUTREGISTRY))
	HookLibraryEx(module, Hooks, "ADVAPI32.dll");
}

FARPROC Remap_AdvApi32_ProcAddress(LPCSTR proc, HMODULE hModule)
{
	FARPROC addr;
	if(!(dxw.dwFlags3 & EMULATEREGISTRY) || 
		(dxw.dwFlags4 & OVERRIDEREGISTRY) || 
		(dxw.dwFlags6 & (WOW32REGISTRY|WOW64REGISTRY)) || 
		(dxw.dwTFlags & OUTREGISTRY)) return NULL;
	if (addr=RemapLibraryEx(proc, hModule, Hooks)) return addr;
	return NULL;
}

#define HKEY_FAKE ((HKEY)0x7FFFFFFF)
#define HKEY_MASK 0x7FFFFF00
#define IsFake(hKey) (((DWORD)hKey & HKEY_MASK) == HKEY_MASK)

static FILE *OpenFakeRegistry();
static char *hKey2String(HKEY);
static LONG myRegOpenKeyEx(HKEY, LPCTSTR, PHKEY);

// int ReplaceVar(pData, lplpData, lpcbData): 
// extract the token name from pData beginning up to '}' delimiter
// calculates the value of the token. If the token is unknown, the value is null string.
// if *lplpData, copies the token value string to *lplpData and increments *lplpData
// if lpcbData, increments the key length of the token value length
// returns the length of token label to advance the parsing loop

typedef enum {
	LABEL_PATH = 0,
	LABEL_WORKDIR,
	LABEL_VOID,
	LABEL_END
};
static char *sTokenLabels[LABEL_END+1]={
	"path}",
	"work}",
	"}",
	NULL
};

static int ReplaceVar(char *pData, LPBYTE *lplpData, LPDWORD lpcbData)
{
	int iTokenLength;
	int iLabelLength;
	int iTokenIndex;
	char sTokenValue[MAX_PATH];
	// search for a matching token
	for(iTokenIndex=0; sTokenLabels[iTokenIndex]; iTokenIndex++){
		if(!_strnicmp(pData, sTokenLabels[iTokenIndex], strlen(sTokenLabels[iTokenIndex]))) break;
	}
	// set token label length
	iLabelLength = strlen(sTokenLabels[iTokenIndex]);
	// do replacement
	switch(iTokenIndex){
		case LABEL_PATH:
		case LABEL_WORKDIR:
			GetCurrentDirectory(MAX_PATH, sTokenValue);
			break;
		case LABEL_VOID:
		case LABEL_END:
			strcpy(sTokenValue, "");
			break;
	}
	// set output vars if not NULL
	iTokenLength = strlen(sTokenValue);
	OutTraceR("REPLACED token=%d val=\"%s\" len=%d\n", iTokenIndex, sTokenValue, iTokenLength);
	if(*lplpData) {
		strcpy((char *)*lplpData, sTokenValue);
		*lplpData += iTokenLength;
	}
	if(lpcbData) *lpcbData += iTokenLength;
	// return label length to advance parsing
	return iLabelLength;
}

static char *hKey2String(HKEY hKey)
{
	char *skey;
	static char sKey[MAX_PATH+1];
	static char skeybuf[10];
	if(IsFake(hKey)) {
		FILE *regf;
		char RegBuf[MAX_PATH+1];
		regf=OpenFakeRegistry();
		if(regf!=NULL){
			HKEY hLocalKey=HKEY_FAKE;
			fgets(RegBuf, 256, regf);
			while (!feof(regf)){
				if(RegBuf[0]=='['){
					if(hLocalKey == hKey){
						//OutTrace("building fake Key=\"%s\" hKey=%x\n", sKey, hKey);
						fclose(regf);
						strcpy(sKey, &RegBuf[1]);
						//sKey[strlen(sKey)-2]=0; // get rid of "]"
						for(int i=strlen(sKey)-1; i; i--){
							if(sKey[i]==']'){
								sKey[i]=0;
								break;
							}
						}
						return sKey;
					}
					else {
						hLocalKey--;
					}
				}
				fgets(RegBuf, 256, regf);
			}
			fclose(regf);
		}
		return "HKEY_NOT_FOUND";	
	}
	switch((ULONG)hKey){
		case HKEY_CLASSES_ROOT:		skey="HKEY_CLASSES_ROOT"; break;
        case HKEY_CURRENT_CONFIG:	skey="HKEY_CURRENT_CONFIG"; break;
        case HKEY_CURRENT_USER:		skey="HKEY_CURRENT_USER"; break;
        case HKEY_LOCAL_MACHINE:	skey="HKEY_LOCAL_MACHINE"; break;
        case HKEY_USERS:			skey="HKEY_USERS"; break;
		default:					sprintf(skeybuf, "0x%x", hKey); skey=skeybuf; break;
	}
	return skey;
}

static char *Unescape(char *s, char **dest)
{
	if(!*dest)	*dest=(char *)malloc(strlen(s)+100);
	else		*dest=(char *)realloc(*dest, strlen(s)+100); 
	char *t = *dest;
	for(; *s; s++){
		if((*s=='\\') && (*(s+1)=='n')){
			*t++ = '\n';
			s++;
		}
		else{
			*t++ = *s;
		}
	}
	*t=0;
	return *dest;
}

static FILE *OpenFakeRegistry()
{
	DWORD dwAttrib;	
	char sSourcePath[MAX_PATH+1];
	char *p;
	dwAttrib = GetFileAttributes("dxwnd.dll");
	if (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) return NULL;
	GetModuleFileName(GetModuleHandle("dxwnd"), sSourcePath, MAX_PATH);
	p=&sSourcePath[strlen(sSourcePath)-strlen("dxwnd.dll")];
	strcpy(p, "dxwnd.reg");
	return fopen(sSourcePath,"r");
}

static LONG SeekFakeKey(FILE *regf, HKEY hKey)
{
	LONG res;
	res = ERROR_FILE_NOT_FOUND;
	char RegBuf[MAX_PATH+1];
	HKEY hCurKey=HKEY_FAKE+1;
	fgets(RegBuf, 256, regf);
	while (!feof(regf)){
		if(RegBuf[0]=='['){
			hCurKey--;
		}
		if(hCurKey==hKey) {
			//OutTraceB("DEBUG: SeekFakeKey fount key at line=%s\n", RegBuf);
			res = ERROR_SUCCESS;
			break;
		}
		fgets(RegBuf, 256, regf);
	}
	return res;
}

static LONG SeekValueName(FILE *regf, LPCTSTR lpValueName)
{
	LONG res;
	char RegBuf[MAX_PATH+1];
	long KeySeekPtr;
	res = ERROR_FILE_NOT_FOUND;
	// v2.04.01: fix to handle the '@' case properly
	if(lpValueName) if(!lpValueName[0]) lpValueName=NULL; 
	KeySeekPtr = ftell(regf);
	fgets(RegBuf, 256, regf);
	while (!feof(regf)){
		if((RegBuf[0]=='"') &&
			!_strnicmp(lpValueName, &RegBuf[1], strlen(lpValueName)) &&
			(RegBuf[strlen(lpValueName)+1]=='"') &&
			(RegBuf[strlen(lpValueName)+2]=='='))
			{
			fseek(regf, KeySeekPtr, SEEK_SET);
			return ERROR_SUCCESS;
		}
		if(!lpValueName && (RegBuf[0]=='@')){
			fseek(regf, KeySeekPtr, SEEK_SET);
			return ERROR_SUCCESS;
		}
		if(RegBuf[0]=='[') return res;
		KeySeekPtr = ftell(regf);
		fgets(RegBuf, 256, regf);
	}
	return res;
}

static LONG SeekValueIndex(FILE *regf, DWORD dwIndex, LPCTSTR lpValueName, LPDWORD lpcchValueName)
{
	LONG res;
	char RegBuf[MAX_PATH+1];
	long KeySeekPtr;
	res = ERROR_NO_MORE_ITEMS;
	KeySeekPtr = ftell(regf);
	fgets(RegBuf, 256, regf);
	dwIndex++;
	while (!feof(regf) && dwIndex){
		if(RegBuf[0]=='"') dwIndex--;
		if(dwIndex == 0){
			fseek(regf, KeySeekPtr, SEEK_SET);
			//sscanf(RegBuf, "\"%s\"=", lpValueName);
			strncpy((char *)lpValueName, strtok(&RegBuf[1], "\""), *lpcchValueName);
			*lpcchValueName = strlen(lpValueName);
			//OutTrace("DEBUG: lpValueName=%s len=%d\n", lpValueName, *lpcchValueName);
			return ERROR_SUCCESS;
		}
		if(RegBuf[0]=='[') return res;
		KeySeekPtr = ftell(regf);
		fgets(RegBuf, 256, regf);
	}
	return res;
}

static DWORD GetKeyValue(
				FILE *regf,
				char *ApiName, 
				LPCTSTR lpValueName, 
				LPDWORD lpType, // beware: could be NULL
				LPBYTE lpData,  // beware: could be NULL
				LPDWORD lpcbData)
{
	LONG res;
	LPBYTE lpb;
	char *pData;
	char RegBuf[MAX_PATH+1];
	DWORD cbData=0;

	//OutTrace("GetKeyValue: ValueName=%s\n", lpValueName);
	fgets(RegBuf, 256, regf);
	if(RegBuf[0]=='@')
		pData=&RegBuf[2];
	else
		pData=&RegBuf[strlen(lpValueName)+3];
	lpb = lpData;
	if(lpcbData) {
		cbData = *lpcbData;
		*lpcbData=0;
	}
	do {
		if(*pData=='"'){ // string value
			if(lpType) *lpType=REG_SZ;
			pData++;
			while(*pData && (*pData != '"')){
				if(*pData=='\\') {
					pData++;
					switch(*pData){
						case '{':{
							pData++; // skip '{'
							pData += ReplaceVar(pData, &lpb, lpcbData);
							continue; // v2.04.13 fix
							}
							break;
						default: 
							break; // skip first '\'
					}
				}
				if(lpData && lpcbData) if(*lpcbData < cbData) *lpb++=*pData;
				pData++;
				if(lpcbData) (*lpcbData)++;
			}
			if(lpcbData) (*lpcbData)++; // extra space for string terminator ?
			if(lpData && lpcbData) if(*lpcbData < cbData) *lpb = 0; // string terminator
			OutTraceR("%s: type=REG_SZ cbData=%d Data=\"%s\"\n", 
				ApiName, lpcbData ? *lpcbData : 0, lpData ? (char *)lpData : "(NULL)");
			// v2.04.14 fix: ERROR_MORE_DATA should be returned only in case lpData is not NULL
			res=ERROR_SUCCESS;
			if(lpData && lpcbData)
				if (*lpcbData > cbData) res = ERROR_MORE_DATA;
			break;
		}
		if(!strncmp(pData,"dword:",strlen("dword:"))){ //dword value
			DWORD val;
			if(lpType) *lpType=REG_DWORD;
			pData+=strlen("dword:");
			sscanf(pData, "%x", &val);
			if(lpData) {
				if (cbData >= sizeof(DWORD)) {
					memcpy(lpData, &val, sizeof(DWORD));
					res=ERROR_SUCCESS;
				}
				else
					res=ERROR_MORE_DATA;
			}
			else 
				res=ERROR_SUCCESS; // data not needed
			if (lpcbData) *lpcbData=sizeof(DWORD);
			OutTraceR("%s: type=REG_DWORD cbData=%x Data=0x%x\n", 
				ApiName, lpcbData ? *lpcbData : 0, val);
			break;
		}
		if(!strncmp(pData,"hex:",strlen("hex:"))){ //hex value
			BYTE *p;
			if(lpType) *lpType=REG_BINARY;
			p = (BYTE *)pData;
			p+=strlen("hex:");
			while(TRUE){
				p[strlen((char *)p)-1]=0; // eliminates \n at the end of line
				while(strlen((char *)p)>1){
					if((*lpcbData < cbData) && lpData){
						sscanf((char *)p, "%x,", (char *)lpb);
						lpb++;
					}
					p+=3;
					if(lpcbData) (*lpcbData)++;
				}
				if(p[strlen((char *)p)-1]=='\\'){
					fgets(RegBuf, 256, regf);
					pData = RegBuf;
					p = (BYTE *)pData;
				}
				else break;
			}
			OutTraceR("%s: type=REG_BINARY cbData=%d Data=%s\n", 
				ApiName,
				lpcbData ? *lpcbData : 0, 
				lpData ? "(NULL)" : hexdump(lpData, *lpcbData));
			res=(*lpcbData > cbData) ? ERROR_MORE_DATA : ERROR_SUCCESS;
			break;
		}
	} while(FALSE);
	return res;
} 

static void LogKeyValue(char *ApiName, LONG res, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
{
	char sInfo[1024];
	if(res) {
		OutTrace("%s: ERROR res=%d\n", ApiName, res);
		return;
	}
	sprintf(sInfo, "%s: res=ERROR_SUCCESS size=%d type=%x(%s)", 
		ApiName, lpcbData?*lpcbData:0, lpType?*lpType:0, lpType?ExplainRegType(*lpType):"none");
	if(lpType && lpData && lpcbData) {
		DWORD cbData = *lpcbData;
		switch(*lpType){
			case REG_SZ: sprintf(sInfo, "%s Data=\"%*.*s\"\n", sInfo, cbData-1, cbData-1, lpData); break; 
			case REG_DWORD: sprintf(sInfo, "%s Data=0x%x\n", sInfo, *(DWORD *)lpData); break;
			case REG_BINARY: sprintf(sInfo, "%s Data=%s\n", sInfo, hexdump((BYTE *)lpData, cbData)); break;
			case REG_NONE: sprintf(sInfo, "%s Data=\"%s\"\n", sInfo, lpData); break;
			default: sprintf(sInfo, "%s Data=???\n", sInfo); break;
		}
	}
	else 
		sprintf(sInfo, "%s\n", sInfo);
	OutTrace(sInfo);
} 

static LONG myRegOpenKeyEx(
			    LPCSTR label,
				HKEY hKey,
				LPCTSTR lpSubKey,
				PHKEY phkResult)
{
	FILE *regf;
	char sKey[MAX_PATH+1];
	char RegBuf[MAX_PATH+1];

	sprintf(sKey,"%s\\%s", hKey2String(hKey), lpSubKey);
	OutTraceR("%s: searching for key=\"%s\"\n", label, sKey);

	regf=OpenFakeRegistry();
	if(regf!=NULL){
		if(phkResult) *phkResult=HKEY_FAKE;
		fgets(RegBuf, 256, regf);
		while (!feof(regf)){
			if(RegBuf[0]=='['){
				// beware: registry keys are case insensitive. Must use _strnicmp instead of strncmp
				if((!_strnicmp(&RegBuf[1],sKey,strlen(sKey))) && (RegBuf[strlen(sKey)+1]==']')){
					OutTraceR("%s: found fake Key=\"%s\" hkResult=%x\n", label, sKey, phkResult ? *phkResult : 0);
					fclose(regf);
					return ERROR_SUCCESS;
				}
				else {
					if(phkResult) (*phkResult)--;
				}
			}
			fgets(RegBuf, 256, regf);
		}
		fclose(regf);
	}
	return ERROR_FILE_NOT_FOUND;
}

// ---------------------------------------------------------------------------------

LONG WINAPI extRegOpenKeyEx(
				HKEY hKey,
				LPCTSTR lpSubKey,
				DWORD ulOptions,
				REGSAM samDesired,
				PHKEY phkResult)
{
	LONG res;

	OutTraceR("RegOpenKeyEx: hKey=%x(%s) SubKey=\"%s\" Options=%x\n", 
		hKey, hKey2String(hKey), lpSubKey, ulOptions);

	if(dxw.dwFlags4 & OVERRIDEREGISTRY){
		res = myRegOpenKeyEx("RegOpenKeyEx", hKey, lpSubKey, phkResult);
		if(res == ERROR_SUCCESS) return res;
	}

	if(dxw.dwFlags6 & WOW64REGISTRY){
		ulOptions &= ~KEY_WOW64_32KEY;
		ulOptions |= KEY_WOW64_64KEY;
	}
	if(dxw.dwFlags6 & WOW32REGISTRY){
		ulOptions &= ~KEY_WOW64_64KEY;
		ulOptions |= KEY_WOW64_32KEY;
	}

	res=(*pRegOpenKeyEx)(hKey, lpSubKey, ulOptions, samDesired, phkResult);
	OutTraceR("RegOpenKeyEx: res=%x phkResult=%x\n", res, phkResult ? *phkResult : 0); 

	if((res==ERROR_SUCCESS) || !(dxw.dwFlags3 & EMULATEREGISTRY) || (dxw.dwFlags4 & OVERRIDEREGISTRY)) return res;
	
	return myRegOpenKeyEx("RegOpenKeyEx", hKey, lpSubKey, phkResult);
}

// extRegQueryValue: legacy API, almost always replaced by extRegQueryValueEx but referenced
// in "Warhammer 40.000 Shadow of the Horned Rat"

LONG WINAPI extRegQueryValue(
				HKEY hKey, 
				LPCTSTR lpSubKey, 
				LPTSTR lpValue, 
				PLONG lpcbValue)
{
	LONG res;
	FILE *regf;

	OutTraceR("RegQueryValue: hKey=%x(%s) SubKey=\"%s\"\n", hKey, hKey2String(hKey), lpSubKey);
	
	if (!IsFake(hKey)){
		res=(*pRegQueryValue)(hKey, lpSubKey, lpValue, lpcbValue);
		if(IsTraceR) LogKeyValue("RegQueryValue", res, 0, (LPBYTE)lpValue, (LPDWORD)lpcbValue);
		return res;
	}

	regf=OpenFakeRegistry();
	if(regf==NULL) {
		OutTraceR("RegQueryValue: error in OpenFakeRegistry err=%s\n", GetLastError());	
		return ERROR_FILE_NOT_FOUND;
	}
	res = SeekFakeKey(regf, hKey);
	if(res != ERROR_SUCCESS) {
		OutTraceR("RegQueryValue: error in SeekFakeKey res=%x hKey=%x\n", res, hKey);	
		return res;
	}
	res = SeekValueName(regf, lpSubKey);
	if(res != ERROR_SUCCESS) {
		OutTraceR("RegQueryValue: error in SeekValueName res=%x ValueName=%s\n", res, lpSubKey);	
		return res;
	}
	res = GetKeyValue(regf, "RegQueryValue", lpSubKey, NULL, (LPBYTE)lpValue, (LPDWORD)lpcbValue);
	if(IsTraceR) LogKeyValue("RegQueryValue", res, NULL, (LPBYTE)lpValue, (LPDWORD)lpcbValue);
	fclose(regf);
	return res;
}

LONG WINAPI extRegQueryValueEx(
				HKEY hKey, 
				LPCTSTR lpValueName, 
				LPDWORD lpReserved, 
				LPDWORD lpType, // beware: could be NULL
				LPBYTE lpData,  // beware: could be NULL
				LPDWORD lpcbData)
{
	LONG res;
	FILE *regf;
	DWORD cbData=0;

	OutTraceR("RegQueryValueEx: hKey=%x(\"%s\") ValueName=\"%s\" Reserved=%x lpType=%x lpData=%x lpcbData=%x\n", 
		hKey, hKey2String(hKey), lpValueName, lpReserved, lpType, lpData, lpcbData);
	if (!IsFake(hKey)){
		res=(*pRegQueryValueEx)(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
		if(IsTraceR) LogKeyValue("RegQueryValueEx", res, lpType, lpData, lpcbData);
		return res;
	}

	regf=OpenFakeRegistry();
	if(regf==NULL) {
		OutTraceR("RegQueryValueEx: error in OpenFakeRegistry err=%s\n", GetLastError());	
		return ERROR_FILE_NOT_FOUND;
	}
	res = SeekFakeKey(regf, hKey);
	if(res != ERROR_SUCCESS) {
		OutTraceR("RegQueryValueEx: error in SeekFakeKey res=%x hKey=%x\n", res, hKey);	
		return res;
	}
	res = SeekValueName(regf, lpValueName);
	if(res != ERROR_SUCCESS) {
		OutTraceR("RegQueryValueEx: error in SeekValueName res=%x ValueName=%s\n", res, lpValueName);	
		return res;
	}
	res = GetKeyValue(regf, "RegQueryValueEx", lpValueName, lpType, lpData, lpcbData);
	if(IsTraceR) LogKeyValue("RegQueryValueEx", res, lpType, lpData, lpcbData);
	fclose(regf);
	return res;
}

LONG WINAPI extRegCloseKey(HKEY hKey)
{
	OutTraceR("RegCloseKey: hKey=%x\n", hKey);
	if (IsFake(hKey)) return ERROR_SUCCESS;
	return (*pRegCloseKey)(hKey);
}

LONG WINAPI extRegFlushKey(HKEY hKey)
{
	OutTraceR("RegFlushKey: hKey=%x\n", hKey);
	if (IsFake(hKey)) return ERROR_SUCCESS;
	return (*pRegFlushKey)(hKey);
}

LONG WINAPI extRegSetValueEx(HKEY hKey, LPCTSTR lpValueName, DWORD Reserved, DWORD dwType, const BYTE *lpData, DWORD cbData)
{
	if (IsTraceR){
		char sInfo[1024];
		sprintf(sInfo, "RegSetValueEx: hKey=%x ValueName=\"%s\" Type=%x(%s) cbData=%d", hKey, lpValueName, dwType, ExplainRegType(dwType), cbData);
		switch(dwType){
			case REG_DWORD: OutTrace("%s Data=%x\n", sInfo, *(DWORD *)lpData); break;
			case REG_NONE: OutTrace("%s Data=\"%s\"\n", sInfo, lpData); break;
			case REG_BINARY: OutTrace("%s Data=%s\n", sInfo, hexdump((BYTE *)lpData, cbData)); break;
			case REG_SZ: OutTrace("%s Data=\"%*.*s\"\n", sInfo, cbData-1, cbData-1, lpData); break;
			default: OutTrace("%s\n", sInfo);
		}
	}
	if(IsFake(hKey) && (dxw.dwFlags3 & EMULATEREGISTRY)) {
		OutTraceR("RegSetValueEx: SUPPRESS registry key set\n");
		return ERROR_SUCCESS;
	}
	return (*pRegSetValueEx)(hKey, lpValueName, Reserved, dwType, lpData, cbData);
}

LONG WINAPI extRegCreateKeyEx(HKEY hKey, LPCTSTR lpSubKey, DWORD Reserved, LPTSTR lpClass, DWORD dwOptions, REGSAM samDesired,
				LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition)
{
	OutTraceR("RegCreateKeyEx: hKey=%x(%s) SubKey=\"%s\" Class=%x\n", hKey, hKey2String(hKey), lpSubKey, lpClass);
	if (dxw.dwFlags3 & EMULATEREGISTRY){
		*phkResult = HKEY_FAKE;
        // V2.3.12: return existing fake key if any ....
        if(dxw.dwFlags4 & OVERRIDEREGISTRY) myRegOpenKeyEx("RegCreateKeyEx", hKey, lpSubKey, phkResult);
		if(lpdwDisposition) *lpdwDisposition=REG_OPENED_EXISTING_KEY;
		return ERROR_SUCCESS;
	}
	else
		return (*pRegCreateKeyEx)(hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired,
				lpSecurityAttributes, phkResult, lpdwDisposition);
}

LONG WINAPI extRegCreateKey(HKEY hKey, LPCTSTR lpSubKey, PHKEY phkResult)
{
	OutTraceR("RegCreateKey: hKey=%x(%s) SubKey=\"%s\"\n", hKey, hKey2String(hKey), lpSubKey);
	if (dxw.dwFlags3 & EMULATEREGISTRY){
		*phkResult = HKEY_FAKE;
        // V2.3.12: return existing fake key if any ....
        if(dxw.dwFlags4 & OVERRIDEREGISTRY) myRegOpenKeyEx("RegCreateKey", hKey, lpSubKey, phkResult);
		return ERROR_SUCCESS;
	}
	else
		return (*pRegCreateKey)(hKey, lpSubKey, phkResult);
}

LONG WINAPI extRegEnumValueA(HKEY hKey, DWORD dwIndex, LPTSTR lpValueName, LPDWORD lpcchValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData)
{
	LONG res;

	OutTraceR("RegEnumValue: hKey=%x(\"%s\") index=%d cchValueName=%d Reserved=%x lpType=%x lpData=%x lpcbData=%x\n", 
		hKey, hKey2String(hKey), dwIndex, *lpcchValueName, lpReserved, lpType, lpData, lpcbData);
	if (!IsFake(hKey)){
		res=(*pRegEnumValueA)(hKey, dwIndex, lpValueName, lpcchValueName, lpReserved, lpType, lpData, lpcbData);
		if(IsTraceR) LogKeyValue("RegQueryValueEx", res, lpType, lpData, lpcbData);
		return res;
	}

	// try emulated registry
	FILE *regf;
	regf=OpenFakeRegistry();
	if(regf==NULL) return ERROR_FILE_NOT_FOUND;
	res = SeekFakeKey(regf, hKey);
	if(res != ERROR_SUCCESS) return res;
	res = SeekValueIndex(regf, dwIndex, lpValueName, lpcchValueName);
	if(res != ERROR_SUCCESS) return res;
	res = GetKeyValue(regf, "RegEnumValue", lpValueName, lpType, lpData, lpcbData);
	if(IsTraceR) LogKeyValue("RegEnumValue", res, lpType, lpData, lpcbData);
	fclose(regf);
	return res;
}

#ifdef TOBEDONE
LONG WINAPI RegQueryInfoKey(
  _In_        HKEY      hKey,
  _Out_opt_   LPTSTR    lpClass,
  _Inout_opt_ LPDWORD   lpcClass,
  _Reserved_  LPDWORD   lpReserved,
  _Out_opt_   LPDWORD   lpcSubKeys,
  _Out_opt_   LPDWORD   lpcMaxSubKeyLen,
  _Out_opt_   LPDWORD   lpcMaxClassLen,
  _Out_opt_   LPDWORD   lpcValues,
  _Out_opt_   LPDWORD   lpcMaxValueNameLen,
  _Out_opt_   LPDWORD   lpcMaxValueLen,
  _Out_opt_   LPDWORD   lpcbSecurityDescriptor,
  _Out_opt_   PFILETIME lpftLastWriteTime
);
#endif