#include <windows.h>
#include <stdio.h>

typedef struct keylist
{
	HKEY		mainKey;
	HKEY		subKey;
	char		*keyname;
	struct keylist	*next;
} KEYLIST,	*LPKEYLIST;

#define CLSID_STRING_LEN				100
#define MAX_PATH_LEN					360

#define DEV_ERR_SELFREG					-100
#define ERRSREG_MODULE_NOT_FOUND		DEV_ERR_SELFREG-1
#define ERRSREG_MODPATH_NOT_FOUND		DEV_ERR_SELFREG-2
#define ERRSREG_STRING_FROM_CLSID		DEV_ERR_SELFREG-3
#define ERRSREG_CHAR_TO_MULTIBYTE		DEV_ERR_SELFREG-4

#define SZREGSTR_DESCRIPTION			"Description"
#define SZREGSTR_CLSID					"CLSID"
#define SZREGSTR_INPROCSERVER32			"InprocServer32"
#define SZREGSTR_THREADINGMODEL			"ThreadingModel"
#define SZREGSTR_SOFTWARE				"SOFTWARE"
#define SZREGSTR_ASIO					"ASIO"

LONG RegisterAsioDriver (CLSID,char *,char *,char *,char *);
LONG UnregisterAsioDriver (CLSID,char *,char *);

static LONG findRegPath (HKEY,char *);
static LONG createRegPath (HKEY,char *,char *);
static LONG createRegStringValue (HKEY,char *,char *,char *);
static LONG getRegString (HKEY,char *,char *,LPVOID,DWORD);
static LPKEYLIST findAllSubKeys (HKEY,HKEY,DWORD,char *,LPKEYLIST);
static LONG deleteRegPath (HKEY,char *,char *);

static char	subkeybuf[MAX_PATH_LEN];


// ------------------------------------------
// Global Functions 
// ------------------------------------------
LONG RegisterAsioDriver (CLSID clsid,char *szdllname,char *szregname,char *szasiodesc,char *szthreadmodel)
{
	HMODULE			hMod;
	char			szDLLPath[MAX_PATH_LEN];
	char			szModuleName[MAX_PATH_LEN];
	char			szregpath[MAX_PATH_LEN];
	char			szclsid[CLSID_STRING_LEN];
	LPOLESTR		wszCLSID = NULL;
	BOOL			newregentry = FALSE;
	LONG			rc;

	hMod = GetModuleHandle(szdllname);
	if (!hMod) return ERRSREG_MODULE_NOT_FOUND;
	szModuleName[0] = 0;
	GetModuleFileName(hMod,szModuleName,MAX_PATH_LEN);
	if (!szModuleName[0]) return ERRSREG_MODPATH_NOT_FOUND;
	CharLower((LPTSTR)szModuleName);

	rc = (LONG)StringFromCLSID(clsid,&wszCLSID);
	if (rc != S_OK) return ERRSREG_STRING_FROM_CLSID;
	rc = (LONG)WideCharToMultiByte(CP_ACP,0,(LPWSTR)wszCLSID,-1,szclsid,CLSID_STRING_LEN,0,0);
	if (!rc) return ERRSREG_CHAR_TO_MULTIBYTE;

	sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid);
	rc = findRegPath(HKEY_CLASSES_ROOT,szregpath);
	if (rc) {
		sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_CLSID,szclsid,SZREGSTR_INPROCSERVER32);
		getRegString (HKEY_CLASSES_ROOT,szregpath,0,(LPVOID)szDLLPath,MAX_PATH_LEN);
		CharLower((LPTSTR)szDLLPath);
		rc = (LONG)strcmp(szDLLPath,szModuleName);
		if (rc) {
			// delete old regentry
			sprintf(szregpath,"%s",SZREGSTR_CLSID);
			deleteRegPath(HKEY_CLASSES_ROOT,szregpath,szclsid);
			newregentry = TRUE;
		}
	}
	else newregentry = TRUE;

	if (newregentry) {
		// HKEY_CLASSES_ROOT\CLSID\{...}
		sprintf(szregpath,"%s",SZREGSTR_CLSID);
		createRegPath (HKEY_CLASSES_ROOT,szregpath,szclsid);
		// HKEY_CLASSES_ROOT\CLSID\{...} -> Description
		sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid);
		createRegStringValue(HKEY_CLASSES_ROOT,szregpath,0,szasiodesc);
		// HKEY_CLASSES_ROOT\CLSID\InProcServer32
		sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid);
		createRegPath (HKEY_CLASSES_ROOT,szregpath,SZREGSTR_INPROCSERVER32);
		// HKEY_CLASSES_ROOT\CLSID\InProcServer32 -> DLL path
		sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_CLSID,szclsid,SZREGSTR_INPROCSERVER32);
		createRegStringValue(HKEY_CLASSES_ROOT,szregpath,0,szModuleName);
		// HKEY_CLASSES_ROOT\CLSID\InProcServer32 -> ThreadingModel
		createRegStringValue(HKEY_CLASSES_ROOT,szregpath,SZREGSTR_THREADINGMODEL,szthreadmodel);
	}

	// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO
	sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO);
	rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath);
	if (rc) {
		sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname);
		rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath);
		if (rc) {
			sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO);
			deleteRegPath(HKEY_LOCAL_MACHINE,szregpath,szregname);
		}
	}
	else {
		// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO
		sprintf(szregpath,"%s",SZREGSTR_SOFTWARE);
		createRegPath (HKEY_CLASSES_ROOT,szregpath,SZREGSTR_ASIO);
	}

	// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\<szregname>
	sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO);
	createRegPath (HKEY_LOCAL_MACHINE,szregpath,szregname);

	// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\<szregname> -> CLSID 
	// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO\<szregname> -> Description
	sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname);
	createRegStringValue(HKEY_LOCAL_MACHINE,szregpath,SZREGSTR_CLSID,szclsid);
	createRegStringValue(HKEY_LOCAL_MACHINE,szregpath,SZREGSTR_DESCRIPTION,szasiodesc);

	return 0;
}


LONG UnregisterAsioDriver (CLSID clsid,char *szdllname,char *szregname)
{
	LONG			rc;
	HMODULE			hMod;
	char			szregpath[MAX_PATH_LEN];
	char			szModuleName[MAX_PATH_LEN];
	char			szclsid[CLSID_STRING_LEN];
	LPOLESTR		wszCLSID = NULL;


	hMod = GetModuleHandle(szdllname);
	if (!hMod) return ERRSREG_MODULE_NOT_FOUND;
	szModuleName[0] = 0;
	GetModuleFileName(hMod,szModuleName,MAX_PATH_LEN);
	if (!szModuleName[0]) return ERRSREG_MODPATH_NOT_FOUND;
	CharLower((LPTSTR)szModuleName);

	rc = (LONG)StringFromCLSID(clsid,&wszCLSID) ;
	if (rc != S_OK) return ERRSREG_STRING_FROM_CLSID;
	rc = (LONG)WideCharToMultiByte(CP_ACP,0,(LPWSTR)wszCLSID,-1,szclsid,CLSID_STRING_LEN,0,0);
	if (!rc) return ERRSREG_CHAR_TO_MULTIBYTE;


	sprintf(szregpath,"%s\\%s",SZREGSTR_CLSID,szclsid);
	rc = findRegPath(HKEY_CLASSES_ROOT,szregpath);
	if (rc) {
		// delete old regentry
		sprintf(szregpath,"%s",SZREGSTR_CLSID);
		deleteRegPath(HKEY_CLASSES_ROOT,szregpath,szclsid);
	}


	// HKEY_LOCAL_MACHINE\SOFTWARE\ASIO
	sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO);
	rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath);
	if (rc) {
		sprintf(szregpath,"%s\\%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO,szregname);
		rc = findRegPath(HKEY_LOCAL_MACHINE,szregpath);
		if (rc) {
			sprintf(szregpath,"%s\\%s",SZREGSTR_SOFTWARE,SZREGSTR_ASIO);
			deleteRegPath(HKEY_LOCAL_MACHINE,szregpath,szregname);
		}
	}

	return 0;
}


// ------------------------------------------
// Local Functions 
// ------------------------------------------
static LONG findRegPath (HKEY mainkey,char *szregpath)
{
	HKEY	hkey;
	LONG 	cr,rc = -1;
	
	if (szregpath) {
		if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) {
			RegCloseKey(hkey);
			rc = 1;
		}
		else rc = 0;
	}

	return rc;
}

static LONG createRegPath (HKEY mainkey,char *szregpath,char *sznewpath)
{
	HKEY	hkey,hksub;
	LONG 	cr,rc = -1;
	char	newregpath[MAX_PATH_LEN];
	
	sprintf(newregpath,"%s\\%s",szregpath,sznewpath);
	if (!(cr = findRegPath(mainkey,newregpath))) {
		if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) {
			if ((cr = RegCreateKey(hkey,sznewpath,&hksub)) == ERROR_SUCCESS) {
				RegCloseKey(hksub);
				rc = 0;
			}
			RegCloseKey(hkey);
		}
	}
	else if (cr > 0) rc = 0;

	return rc;
}

static LONG createRegStringValue (HKEY mainkey,char *szregpath,char *valname,char *szvalstr)
{
	LONG	cr,rc = -1;
	HKEY	hkey;

	if (szregpath) {
		if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) {
			cr = RegSetValueEx(hkey,(LPCTSTR)valname,0,REG_SZ,(const BYTE *)szvalstr,(DWORD)strlen(szvalstr));
			RegCloseKey(hkey);
			if (cr == ERROR_SUCCESS) rc = 0;
		}
	} 

	return rc;
}


static LONG getRegString (HKEY mainkey,char *szregpath,char *valname,LPVOID pval,DWORD vsize)
{
	HKEY	hkey;
	LONG 	cr,rc = -1;
	DWORD	hsize,htype;

	if (szregpath) {
		if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) {
			cr = RegQueryValueEx(hkey,valname,0,&htype,0,&hsize);
			if (cr == ERROR_SUCCESS) {
				if (hsize <= vsize) {
					cr = RegQueryValueEx(hkey,valname,0,&htype,(LPBYTE)pval,&hsize);
					rc = 0;
				}
			}
			RegCloseKey(hkey);
		}
	}
	return rc;
}

static LPKEYLIST findAllSubKeys (HKEY hkey,HKEY hksub,DWORD index,char *keyname,LPKEYLIST kl)
{
	HKEY	hknew = 0;
	char	*newkey;
	LONG 	cr;

	if (!hksub) {
		cr = RegOpenKeyEx(hkey,(LPCTSTR)keyname,0,KEY_ALL_ACCESS,&hknew);
		if (cr != ERROR_SUCCESS) return kl;
	}
	else hknew = hksub;
		
	cr = RegEnumKey(hknew,index,(LPTSTR)subkeybuf,MAX_PATH_LEN);
	if (cr == ERROR_SUCCESS) {
		newkey = new char[strlen(subkeybuf)+1];
		strcpy(newkey,subkeybuf);

		kl = findAllSubKeys(hknew,0,0,newkey,kl);
		kl = findAllSubKeys(hkey,hknew,index+1,keyname,kl);
		
		return kl;

	}	
	
	if (!kl->next) {
		kl->next = new KEYLIST[1];
		kl = kl->next;
		kl->mainKey = hkey;
		kl->subKey = hknew;
		kl->keyname = keyname;
		kl->next = 0;
	}

	return kl;
}

static LONG deleteRegPath (HKEY mainkey,char *szregpath,char *szdelpath)
{
	HKEY		hkey;
	LONG 		cr,rc = -1;
	KEYLIST		klist;
	LPKEYLIST	pkl,k;
	char		*keyname = 0;
	
	if ((cr = RegOpenKey(mainkey,szregpath,&hkey)) == ERROR_SUCCESS) {

		keyname = new char[strlen(szdelpath)+1];
		if (!keyname) {
			RegCloseKey(hkey);
			return rc;
		}
		strcpy(keyname,szdelpath);
		klist.next = 0;

		findAllSubKeys(hkey,0,0,keyname,&klist);
	
		if (klist.next) {
			pkl = klist.next;
	
			while (pkl) {
				RegCloseKey(pkl->subKey);
				cr = RegDeleteKey(pkl->mainKey,pkl->keyname);
				delete pkl->keyname;
				k = pkl;
				pkl = pkl->next;
				delete k;
			}
			rc = 0;
		}

		RegCloseKey(hkey);
	}

	return rc;
}

