/*
***************************************************************************
wamulti - provide multiuser support for Winamp 5.x
Copyright (C) 2004-2005 Christoph Langguth

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
***************************************************************************
*/

#include <windows.h>
// define as 1 if you want the OutputDebugString messages
// (for easy debugging using DebugView from Systernals, for example)
#define DEBUG 0


#include "msacm32.h"
#include <stdio.h>
#include <imagehlp.h>
// link with imagehlp.lib when compiling

#define STARTUP 1
#define SHUTDOWN 0

#define RUNMODE_UNKNOWN 0
#define RUNMODE_WINAMP 1
#define RUNMODE_RUNDLL 2

int runmode = RUNMODE_UNKNOWN;

#define ERROR1 "Winamp installation directory setting not found or empty!"
#define ERROR2 "Winamp reference directory setting not found or empty!"
#define ERROR3 "Unable to get user data directory!"
#define ERROR4 "Unable to access/create user data directory!"
#define ERROR5 "Unable to access main Winamp directory!"
#define ERROR6 "Unable to access Winamp reference configuration directory!"

// What class name does the main Winamp window have?
#define MAINWINDOWCLASS "Winamp v1.x"

#ifdef DEBUG
char dbuf[1024];
#endif

char refDir[MAX_PATH] = {0};
char userDir[MAX_PATH] = {0};
char mainDir[MAX_PATH] = {0};
char* refPrefix;
char* userPrefix;
char* mainPrefix;
WIN32_FIND_DATA refFind,mainFind,userFind;

PSECURITY_DESCRIPTOR securityDescriptor = NULL;
DWORD securityDescriptorSize = 0;
TOKEN_USER *currentUser = NULL;
SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
BYTE sidBuffer[100];
PSID adminGroupSID = (PSID)&sidBuffer;
BOOL userIsAdmin = FALSE;

HHOOK hHook;
WNDPROC wndProc;

bool noCleanup = 0;
DWORD administrativeRun = 0;
bool administrativeRunCopyBack = 0;
bool isLogOff = 0;

void SynchronizeRefMain(char*,char*,char*,bool);
void MergeUserPrefs(char*,char*);
BOOL DeleteDirectory(char* d);
BOOL CopyDirectory(char *src, char *dest);
BOOL EnsurePathExists(char *path, bool isFile);
void VirtualDelete(char* userDir);
void SetStartupStatusOK();
void acmInit();
LRESULT CALLBACK CallWndProc(int, WPARAM,LPARAM);
BOOLEAN SetUserSettingsDir();

HMODULE acmhReal = NULL;
HMODULE acmhSelf = NULL;
void PatchIAT(HMODULE hModule, char* moduleName, PROC orgAddress, PROC newAddress);


void Error(char* errorString) {
	MessageBox(NULL, errorString, FULLAPPNAME, MB_ICONSTOP);
	ExitProcess(1);
}


BOOL IsOnlyInstance () {
	return (NULL == FindWindow(MAINWINDOWCLASS,NULL));
}

extern "C" __declspec( dllexport ) int BookmarkCopy() {
	char winampDir[MAX_PATH] = {0};
	char refDir[MAX_PATH] = {0};
	char userDir[MAX_PATH] = {0};
	char *fileToCopy = NULL;
	HKEY hKey;
	DWORD dummy,administrativeRun;

	// read directory information
	RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGKEY,0,KEY_READ,&hKey);
	dummy = sizeof(administrativeRun);
	RegQueryValueEx(hKey,"AdministrativeRun",0,NULL,(LPBYTE) &administrativeRun,&dummy);
	if (administrativeRun) return 0;

	dummy = sizeof(winampDir);
	RegQueryValueEx(hKey,"InstallationDir",0,NULL,(LPBYTE)winampDir,&dummy);
	dummy = sizeof(refDir);
	RegQueryValueEx(hKey,"ReferenceDir",0,NULL,(LPBYTE)refDir,&dummy);
	RegCloseKey(hKey);

	SetUserSettingsDir();

	if (*userDir == NULL || *winampDir == NULL || *refDir == NULL) return 0;

	if(userDir[strlen(userDir)-1] != '\\') strcat(userDir,"\\");
	if(winampDir[strlen(winampDir)-1] != '\\') strcat(winampDir,"\\");
	if(refDir[strlen(refDir)-1] != '\\') strcat(refDir,"\\");

	strcat(userDir,BMFILE);
	strcat(refDir,BMFILE);
	strcat(winampDir,BMFILE);

	if (0xFFFFFFFF != GetFileAttributes(winampDir)) DeleteFile(winampDir);
	if (0xFFFFFFFF != GetFileAttributes(refDir)) fileToCopy = refDir;
	if (0xFFFFFFFF != GetFileAttributes(userDir)) fileToCopy = userDir;

	if (fileToCopy == NULL) return 0;
	CopyFile(fileToCopy,winampDir,0);
	return 0;
}

void SynchronizeDirs(int mode) {
	*refPrefix = '\0';
	*mainPrefix = '\0';
	if (mode == STARTUP) {
		if (!administrativeRun) {
			if (IsOnlyInstance()) {
				SynchronizeRefMain(refPrefix, mainPrefix, NULL, true);//NULL
				MergeUserPrefs(userPrefix,mainPrefix);
			}
		}
		else {
			int rc = MessageBox(NULL,"Winamp is about to run in administrative mode.\n(HKLM\\"
				REGKEY "\\AdministrativeRun is set to something non-zero)\n\n"
				"You are currently working with the reference copy of the program; per-user settings are ignored.\n\n"
				"Press YES if you want to copy the reference directory to the Winamp program directory now.\n"
				"Press NO if you do not want to copy the reference directory to the program directory.\n"
				"Press CANCEL if you want to abort program startup.\n\n"
				"Restore reference configuration?"
				,FULLAPPNAME,MB_ICONWARNING | MB_YESNOCANCEL);
			if (rc == IDCANCEL) ExitProcess(1);
			if (rc == IDYES) {
				SynchronizeRefMain(refPrefix, mainPrefix, NULL, true);
			}
		}
	}
	else {
		if (!administrativeRun) {
			if (isLogOff || IsOnlyInstance()) {
				*(userPrefix-1) = '\0';
				DeleteFile(userDir); // in case such a file exists
				DeleteDirectory(userDir);
				EnsurePathExists(userDir,false);
				*(userPrefix-1) = '\\';
				SynchronizeRefMain(refPrefix, mainPrefix, userPrefix, true);
				BookmarkCopy();
			}
		}
		else {
			if (administrativeRunCopyBack) {
				DeleteDirectory(refDir);
				CopyDirectory(mainDir,refDir);
				Beep(500,100);
				Beep(750,100);
				Beep(1000,100);
			}
		}
	}
}

BOOL IsOwnedByUser(char* filename) {
	DWORD neededLength = 0;

	// administrative runs aren't user-dependent
	if (administrativeRun) return FALSE;

	if (currentUser == NULL) {
		// this gets called the first time, get the sid for the current user
		HANDLE token;
		PTOKEN_GROUPS pGroupInfo;

		AllocateAndInitializeSid( &SIDAuth, 2,
			SECURITY_BUILTIN_DOMAIN_RID,
			DOMAIN_ALIAS_RID_ADMINS,
			0, 0, 0, 0, 0, 0,
			&adminGroupSID);
		OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY, &token);
		if (!token) return FALSE;
		GetTokenInformation(token,TokenUser,NULL,0,&neededLength);
		currentUser = (TOKEN_USER*) LocalAlloc(0,neededLength);
		if (currentUser == NULL) return FALSE;
		if (!GetTokenInformation(token,TokenUser,currentUser,neededLength,&neededLength)) {
			LocalFree(currentUser);
			currentUser = NULL;
			return FALSE;
		}

		GetTokenInformation(token,TokenGroups,NULL,0,&neededLength);
		pGroupInfo = (TOKEN_GROUPS*) LocalAlloc(0,neededLength);
		if (pGroupInfo == NULL) return FALSE;
		if (!GetTokenInformation(token,TokenGroups,pGroupInfo,neededLength,&neededLength)) {
			LocalFree(pGroupInfo);
			return FALSE;
		}
		for(DWORD i=0; i<pGroupInfo->GroupCount; i++) {
			if ( EqualSid(adminGroupSID, pGroupInfo->Groups[i].Sid) ) {
				userIsAdmin = TRUE;
				break;
			}
		}
#ifdef DEBUG
		if (userIsAdmin) OutputDebugString("IOU: user is in Administrators group");
		else OutputDebugString("IOU: user is NOT in Administrators group");
#endif

	}
	
	if (!GetFileSecurity(filename,OWNER_SECURITY_INFORMATION,securityDescriptor,securityDescriptorSize,&neededLength)) {
		if (securityDescriptor != NULL) LocalFree(securityDescriptor);
		securityDescriptor = LocalAlloc(0,neededLength);
		if (securityDescriptor != NULL) securityDescriptorSize = neededLength;
		neededLength = 0;
		if (!GetFileSecurity(filename,OWNER_SECURITY_INFORMATION,securityDescriptor,securityDescriptorSize,&neededLength)) {
			// unable to get security descriptor
			return FALSE;
		}
	}
	SID *owner;
	int defaulted;
	if (!GetSecurityDescriptorOwner(securityDescriptor,(void**)&owner,&defaulted)) {
		return FALSE;
	}
	if (EqualSid(currentUser->User.Sid, owner)) return TRUE;
	if (userIsAdmin && EqualSid(adminGroupSID, owner)) {
#ifdef DEBUG
		OutputDebugString("IOU: file is owned by admin group");
#endif
		return TRUE;
	}
	return FALSE;
}

void SynchronizeRefMain(char *rP, char *mP, char *uP, bool isMainDir) {
	HANDLE refHandle = NULL;
	HANDLE mainHandle = NULL;
	char *refPre = rP;
	char *mainPre = mP;
	char *userPre = uP; // userPre will be NULL on startup
	int refOk = 1;
	int mainOk = 1;
	int refWait = 0;
	int mainWait = 0;
	int count=0;

#ifdef DEBUG
			sprintf(dbuf,"SRM: !!! Working %s ### %s",refDir,mainDir);
			OutputDebugString(dbuf);
#endif
	strcpy(refPre,"*");
	strcpy(mainPre,"*");

	while (true) {
		if (!refOk && !mainOk) break;
		if (refHandle == NULL) {
			refHandle = FindFirstFile(refDir,&refFind);
			mainHandle = FindFirstFile(mainDir,&mainFind);
			refOk = (refHandle != INVALID_HANDLE_VALUE);
			mainOk = (mainHandle != INVALID_HANDLE_VALUE);
		}
		else {
			if (!refWait) refOk = FindNextFile(refHandle, &refFind);
			if (!mainWait) mainOk = FindNextFile(mainHandle, &mainFind);
		}
		refWait = mainWait = 0;
		// both handles finished simultaneously?
		if (!refOk && !mainOk) break;
		strcpy(refPre,refFind.cFileName);
		strcpy(mainPre,mainFind.cFileName);
		if ((!strcmp(refPre,".") || !strcmp(refPre,".."))) continue;
		if (userPre != NULL) strcpy(userPre,mainFind.cFileName);

		if (refOk && mainOk) {
#ifdef DEBUG
			sprintf(dbuf,"SRM: %s ### %s",refPre,mainPre);
			OutputDebugString(dbuf);
#endif
			// both handles not finished yet
			if (!strcmpi(refPre,mainPre)) {
				// both files have the same name
				// check if the attributes differ
				if (((refFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) & 
					(mainFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))) {
					// both directories -> recurse
					strcat(refPre,"\\");
					strcat(mainPre,"\\");
					if (userPre != NULL) {
						strcat(userPre,"\\");
						SynchronizeRefMain(refPre+strlen(refPre),mainPre+strlen(mainPre),userPre+strlen(userPre),false);
					}
					else
						SynchronizeRefMain(refPre+strlen(refPre),mainPre+strlen(mainPre),NULL,false);
					continue;
				}
				else if ((!(refFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &
					!(mainFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))) {
					// both files.

					// Check size and file times
					if (refFind.ftLastWriteTime.dwHighDateTime == mainFind.ftLastWriteTime.dwHighDateTime
						&& refFind.ftLastWriteTime.dwLowDateTime == mainFind.ftLastWriteTime.dwLowDateTime
						&& refFind.nFileSizeHigh == mainFind.nFileSizeHigh
						&& refFind.nFileSizeLow == mainFind.nFileSizeLow) {
						// oh well, we did our best. seems the files are the same.
						continue;
					}
					// both are files, but they differ in something
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! files differ");
							OutputDebugString(dbuf);
#endif
					if (userPre != 0) {
						if (EnsurePathExists(userDir,true)) { 
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! copy %s to userdir",mainPre);
							OutputDebugString(dbuf);
#endif
							CopyFile(mainDir,userDir,0);
						}
					}
					// if the file is already the user's one, no need to overwrite
					if (IsOwnedByUser(mainDir)) {
#ifdef DEBUG
						sprintf(dbuf,"SRM: !!! skip file %s (belongs to user)",mainPre);
						OutputDebugString(dbuf);
#endif
						continue;
					}
#ifdef DEBUG
					sprintf(dbuf,"SRM: !!! copy %s to maindir",refPre);
					OutputDebugString(dbuf);
#endif

					CopyFile(refDir,mainDir,0);
					continue;
				}
				// these cases should be really uncommon. but, well...
				else if (refFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
					// ref is a dir and main a file
					if (userPre != NULL) {
						if (EnsurePathExists(userDir,true)) {
							CopyFile(mainDir,userDir,0);
						}

					}
					if (DeleteFile(mainDir)) {
						CopyDirectory(refDir,mainDir);
						continue;
					}
				}
				else {
					// ref is a file and main a dir
					if (userPre != NULL) {
						if (EnsurePathExists(userDir,false)) {
							CopyDirectory(mainDir,userDir);
						}

					}
					if (DeleteDirectory(mainDir)) {
						CopyFile(refDir,mainDir,0);
						continue;
					}
				} // dir/file type check
			} // names match check
			else {
				// names do NOT match in here.
				// find out which one of the searches goes "faster"
				// the gotos are really ugly, but they avoid duplicating code
				if (strcmpi(refPre, mainPre) < 0) {
					goto missfile;
				} // ref < main
				else {
					goto newfile;
				} // ref > main
			} // name match check done
		} // finds successful
		else {
			// not both file lists are ending. This is a special case of name "mismatch"
			if (refOk && !mainOk) {
missfile:
				// main was thru faster - so it must be missing something
				mainWait = 1;
				strcpy(mainPre,refFind.cFileName);
				if (userPre != NULL) {
					strcpy(userPre,refFind.cFileName);
#ifdef DEBUG
					sprintf(dbuf,"SRM: !!! VirtualDelete %s",userPre);
					OutputDebugString(dbuf);
#endif
					VirtualDelete(userDir);
					continue;
#ifdef DEBUG
				sprintf(dbuf,"SRM: !!! Copy %s to mainDir",refPre);
				OutputDebugString(dbuf);
#endif
				}
				if (refFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
					CopyDirectory(refDir,mainDir);
				}
				else {
					CopyFile(refDir,mainDir,0);
				}
				continue;
			} // ref < main
			else {
newfile:
				// ref has gone through faster -> that means there's "garbage" in main
				refWait = 1;
				strcpy(refPre,mainFind.cFileName);
				if (mainFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
					if (userPre != NULL) {
						if (EnsurePathExists(userDir,false)) {
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! CopyDirectory %s to userDir",mainPre);
							OutputDebugString(dbuf);
#endif
							CopyDirectory(mainDir,userDir);
						}
					}
					if (!IsOwnedByUser(mainDir)) {
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! DeleteDirectory %s in mainDir",mainPre);
							OutputDebugString(dbuf);
#endif
						DeleteDirectory(mainDir);
					}
					else {
#ifdef DEBUG
						sprintf(dbuf,"SRM: !!! skipped dir. %s in main (belongs to user)",mainPre);
						OutputDebugString(dbuf);
#endif
					}
				}
				else {
					if (userPre != NULL) {
						if (EnsurePathExists(userDir,true)) {
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! CopyFile %s to userDir",mainPre);
							OutputDebugString(dbuf);
#endif
							CopyFile(mainDir,userDir,0);
						}
					}

					if (!IsOwnedByUser(mainDir)) {
#ifdef DEBUG
							sprintf(dbuf,"SRM: !!! DeleteFile %s in mainDir",mainPre);
							OutputDebugString(dbuf);
#endif
						DeleteFile(mainDir);
					}
					else {
#ifdef DEBUG
						sprintf(dbuf,"SRM: !!! skipped file %s in main (belongs to user)",mainPre);
						OutputDebugString(dbuf);
#endif
					}
				}
				continue;
			} // ref > main
		}
	} // filename loop
#ifdef DEBUG
			sprintf(dbuf,"SRM: !!! Finished directory, chdir ..");
			OutputDebugString(dbuf);
#endif
	FindClose(refHandle);
	FindClose(mainHandle);
}

void MergeUserPrefs(char *uP, char *mP) {
	HANDLE userHandle = NULL;
	char *mainPre = mP;
	char *userPre = uP;
#ifdef DEBUG
	sprintf(dbuf,"MUP: !!! Working %s",userDir);
	OutputDebugString(dbuf);
#endif
	strcpy(userPre,"*");


	while (true) {
		if (userHandle == NULL) {
			userHandle = FindFirstFile(userDir,&userFind);
			if (userHandle == INVALID_HANDLE_VALUE) break;
		}
		else {
			if (!FindNextFile(userHandle, &userFind)) break;
		}
		strcpy(mainPre,userFind.cFileName);
		strcpy(userPre,userFind.cFileName);

#ifdef DEBUG
		sprintf(dbuf,"MUP: main %s ### user %s",mainPre,userPre);
		OutputDebugString(dbuf);
#endif
		if ((!strcmp(userPre,".") || !strcmp(userPre,".."))) continue;
		if (userPre[strlen(userPre)-1] == '-') {
			// virtually deleted file
			if (GetFileAttributes(mainDir) == 0xFFFFFFFF) {
				mainPre[strlen(mainPre)-1] = '\0';
				// could be either a file or a directory
#ifdef DEBUG
				sprintf(dbuf,"MUP: !!! Delete %s",mainDir);
				OutputDebugString(dbuf);
#endif
				DeleteFile(mainDir);
				DeleteDirectory(mainDir);
				continue;
			}
		}
		if (userFind.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			EnsurePathExists(mainDir,false);
			// recurse into subdirs
			strcat(userDir,"\\");
			strcat(mainDir,"\\");
			MergeUserPrefs(userPre+strlen(userPre),mainPre+strlen(mainPre));
			continue;
		}
		EnsurePathExists(mainDir,true);
/*		if (GetFileAttributes(mainDir) != 0xFFFFFFFF) {
			// we only copy if the user's file is actually newer
			HANDLE hMain = CreateFile(mainDir,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
			if (hMain != INVALID_HANDLE_VALUE) {
				FILETIME ftMain;
				GetFileTime(hMain,NULL,NULL,&ftMain);
				CloseHandle(hMain);
				if (ftMain.dwHighDateTime > userFind.ftLastWriteTime.dwHighDateTime) continue;
				if (ftMain.dwHighDateTime == userFind.ftLastWriteTime.dwHighDateTime) {
					if (ftMain.dwLowDateTime > userFind.ftLastWriteTime.dwLowDateTime) continue;
				}
			}
		}*/
#ifdef DEBUG
		sprintf(dbuf,"MUP: !!! Copy %s -> %s",userDir,mainDir);
		OutputDebugString(dbuf);
#endif
		CopyFile(userDir,mainDir,false);
	}
#ifdef DEBUG
	sprintf(dbuf,"MUP: !!! Finished directory, chdir ..");
	OutputDebugString(dbuf);
#endif
	if (userHandle != INVALID_HANDLE_VALUE && userHandle != NULL) FindClose(userHandle);
}

void VirtualDelete(char* userDir) {
	// we do a "virtual delete" in the user's home dir
	// we also don't care if it's been a file or a dir,
	// we just delete anything called that way and create a
	// "deleted" file
	EnsurePathExists(userDir,true);
	DeleteFile(userDir);
	DeleteDirectory(userDir);
	strcat(userDir,"-"); // "virtual" delete
	HANDLE hf = CreateFile(userDir,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,0,NULL);
	if (hf != INVALID_HANDLE_VALUE) CloseHandle(hf);

}


BOOL DeleteDirectory(char* d) {
	char dir[MAX_PATH];
	strncpy(dir,d,sizeof(dir));
	if (dir[strlen(dir)-1] != '\\') strcat(dir,"\\");
	char *c = dir + strlen(dir);
	strcat(dir,"*");
	WIN32_FIND_DATA find;
	HANDLE handle = NULL;

	while (true) {
		int ok;
		if (handle == NULL) {
			handle = FindFirstFile(dir,&find);
			ok = (handle != INVALID_HANDLE_VALUE);
		}
		else ok = FindNextFile(handle,&find);
		if (!ok) {
			if (handle != INVALID_HANDLE_VALUE) FindClose(handle);
			break;
		}
		if (!strcmp(find.cFileName,".") || !strcmp(find.cFileName,"..")) continue;
		strcpy(c,find.cFileName);
		if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			if (!DeleteDirectory(dir)) return false;
		}
		else {
			if (!DeleteFile(dir)) return false;
		}
	}
	return RemoveDirectory(d) != 0;
}

BOOL CopyDirectory(char* src, char* dest) {
	char ddir[MAX_PATH];
	char sdir[MAX_PATH];
	strncpy(ddir,dest,sizeof(ddir));
	strncpy(sdir,src,sizeof(sdir));

	if (GetFileAttributes(dest) == 0xFFFFFFFF) {
		if (!CreateDirectory(dest,NULL)) return false;
	}

	if (sdir[strlen(sdir)-1] != '\\') strcat(sdir,"\\");
	if (ddir[strlen(ddir)-1] != '\\') strcat(ddir,"\\");
	char *sc = sdir + strlen(sdir);
	char *dc = ddir + strlen(ddir);
	strcat(sdir,"*");
	WIN32_FIND_DATA find;
	HANDLE handle = NULL;

	while (true) {
		int ok;
		if (handle == NULL) {
			handle = FindFirstFile(sdir,&find);
			ok = (handle != INVALID_HANDLE_VALUE);
		}
		else ok = FindNextFile(handle,&find);
		if (!ok) {
			if (handle != INVALID_HANDLE_VALUE) FindClose(handle);
			break;
		}
		if (!strcmp(find.cFileName,".") || !strcmp(find.cFileName,"..")) continue;
		strcpy(sc,find.cFileName);
		strcpy(dc,find.cFileName);
		if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
			if (!CopyDirectory(sdir,ddir)) return false;
		}
		else {
			if (!CopyFile(sdir,ddir,false)) return false;
		}
	}
	return true;
}


BOOL EnsurePathExists(char* path, bool isFile) {
	char* end = path+strlen(path);
	if (0xFFFFFFFF != GetFileAttributes(path)) return true;
	while (*end != '\\' && end >= path) --end;
	if (end == path) return false;
	*end = '\0';
	if (!EnsurePathExists(path,false)) return false;
	*end = '\\';
	if (isFile) return true;
	return CreateDirectory(path,NULL);
}

BOOLEAN SetUserSettingsDir() {
	HKEY hKey;
	DWORD dummy;

	RegOpenKeyEx(HKEY_CURRENT_USER,REGKEY_APPDATA,0,KEY_READ,&hKey);

	dummy = sizeof(userDir);
	RegQueryValueEx(hKey,"AppData",0,NULL,(LPBYTE) userDir,&dummy);
	if (*userDir == NULL) return FALSE;
	if (userDir[strlen(userDir)-1] != '\\') strcat(userDir ,"\\");
	strcat(userDir,"Winamp");
	return TRUE;
}

int Initialize() {
	HKEY hKey;
	DWORD dummy;


	RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGKEY,0,KEY_READ,&hKey);

	dummy = sizeof(mainDir);
	RegQueryValueEx(hKey,"InstallationDir",0,NULL,(LPBYTE) mainDir,&dummy);
	if (*mainDir == NULL) Error(ERROR1);
	if (mainDir[strlen(mainDir)-1] != '\\') strcat(mainDir ,"\\");
	dummy = sizeof(refDir);
	RegQueryValueEx(hKey,"ReferenceDir",0,NULL,(LPBYTE) refDir,&dummy);
	if (*refDir == NULL) Error(ERROR2);
	if (refDir[strlen(refDir)-1] != '\\') strcat(refDir ,"\\");

	dummy = sizeof(administrativeRun);
	RegQueryValueEx(hKey,"AdministrativeRun",0,NULL,(LPBYTE) &administrativeRun,&dummy);
	RegCloseKey(hKey);


	if (!SetUserSettingsDir()) Error(ERROR3);
	if (0xFFFFFFFF == GetFileAttributes(userDir)) {
		if (!CreateDirectory(userDir,NULL)) {
			Error(ERROR4);
		}
	}
	if (userDir[strlen(userDir)-1] != '\\') strcat(userDir,"\\");

	if (0xFFFFFFFF == GetFileAttributes(mainDir)) Error(ERROR5);
	if (0xFFFFFFFF == GetFileAttributes(refDir)) Error(ERROR6);

	
	refPrefix = refDir + strlen(refDir);
	userPrefix = userDir + strlen(userDir);
	mainPrefix = mainDir + strlen(mainDir);

	dummy = RegOpenKeyEx(HKEY_CURRENT_USER,REGKEY "\\Restart",0,KEY_ALL_ACCESS,&hKey);

	if (dummy == ERROR_SUCCESS) {
		// key exists, so no startup "cleanup"
		RegCloseKey(hKey);
		RegDeleteKey(HKEY_CURRENT_USER,REGKEY "\\Restart");
	}
	else {
		SynchronizeDirs(STARTUP);
	}
	return true;
}

LRESULT CALLBACK MyWindowProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
  ){
	if (uMsg == WM_QUERYENDSESSION) {
		//CallWindowProc(MyWindowProc,hwnd,WM_CLOSE,NULL,NULL);
		//Sleep(5000);
		//OutputDebugString("still running...");
		noCleanup = TRUE;
		isLogOff = TRUE;
		OutputDebugString("WM_QUERYENDSESSION1");
		SynchronizeDirs(SHUTDOWN);
		BookmarkCopy();
		OutputDebugString("WM_QUERYENDSESSION2");
	}
	if (uMsg == WM_ENDSESSION) {
		OutputDebugString("WM_ENDSESSION");
	}
	if (uMsg == WM_CLOSE) {
// START TEST CODE
/*
		OutputDebugString("WM_CLOSE");
		HINSTANCE hTest = LoadLibrary("Plugins\\gen_ff.dll");
		if (hTest != NULL) {
			DWORD* pa = (DWORD*)GetProcAddress(hTest,"winampGetGeneralPurposePlugin");
			if (pa != NULL) {
				sprintf(dbuf,"waggpp: %08X",pa);
				OutputDebugString(dbuf);
				__asm {
					call pa;
					mov pa,eax;
					add pa,8;
				}
				sprintf(dbuf,"%08X",pa);
				OutputDebugString(dbuf);
				pa = (DWORD*)*pa;
				sprintf(dbuf,"%08X",pa);
				OutputDebugString(dbuf);
			FreeLibrary(hTest);
				__asm {
					call pa;
				}

			}
			OutputDebugString("freed.");
		}
		int rc = MessageBox(NULL,"CLOSE?"
			,FULLAPPNAME,MB_ICONWARNING|MB_YESNOCANCEL|MB_SYSTEMMODAL|MB_SETFOREGROUND|MB_TOPMOST);
		if (rc == IDCANCEL) return 0;*/
// END TEST CODE
		if (!noCleanup && administrativeRun) {
			administrativeRunCopyBack = 0;
			int rc = MessageBox(NULL,"Winamp was run in administrative mode.\n\n"
				"Press YES to commit your current changes to the reference directory.\n"
				"Press NO if you don't want the reference directory to be updated at this time.\n"
				"Press CANCEL to abort program shutdown.\n\n"
				"Commit changes?"
				,FULLAPPNAME,MB_ICONWARNING|MB_YESNOCANCEL|MB_SYSTEMMODAL|MB_SETFOREGROUND|MB_TOPMOST);
			if (rc == IDCANCEL) return 0;
			if (rc == IDYES) {
				administrativeRunCopyBack = 1;
				MessageBox(NULL,
				"When the copyback procedure is finished, you will hear an acoustic notification.\n"
				"Make sure you set HKLM\\" REGKEY "\\AdministrativeRun to 0\nafter you are done with your modifications.\n\n",
				FULLAPPNAME,MB_ICONINFORMATION|MB_OK|MB_SYSTEMMODAL|MB_SETFOREGROUND|MB_TOPMOST);
			}
		}
	}
	return CallWindowProc(wndProc,hwnd,uMsg,wParam,lParam);
}

// hook for catching WM_(QUERY)?ENDSESSION
LRESULT CALLBACK HookWndProc(
  int nCode,      // hook code
  WPARAM wParam,  // current-process flag
  LPARAM lParam   // address of structure with message data
  ){
	CWPSTRUCT* cwp = (CWPSTRUCT*) lParam;
	if (cwp->message == WM_CREATE) {
		UNALIGNED CREATESTRUCT* cs = (CREATESTRUCT*) cwp->lParam;
		if (!IsBadStringPtr(cs->lpszClass,4) && !strcmp(cs->lpszClass,MAINWINDOWCLASS)) {
			wndProc = (WNDPROC)SetWindowLong(cwp->hwnd,GWL_WNDPROC,(LONG)MyWindowProc);
			UnhookWindowsHookEx(hHook);
			hHook = NULL;
		}
	}
	return CallNextHookEx(hHook,nCode,wParam,lParam);
}

void ParseCommandLine() {
	HKEY hKey;
	DWORD dummy=-1;


	char *cmd = GetCommandLine();
	RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGKEY,0,KEY_WRITE,&hKey);

	if (strstr(cmd,"/muadmin:1")) {
		dummy = 1;
	}
	if (strstr(cmd,"/muadmin:0")) {
		dummy = 0;
	}
	if (dummy != -1) {
		if (ERROR_SUCCESS != RegSetValueEx(hKey,"AdministrativeRun",0,REG_DWORD,(CONST BYTE*)&dummy,sizeof(dummy))) {
			MessageBox(NULL,
				"Failed to set administrative mode. You probably do not have administrative rights."
				,FULLAPPNAME,MB_ICONWARNING);
		}
	}
	RegCloseKey(hKey);
}
bool attached = false;
void Attach(HANDLE hModule) {
	if (attached) return;
#ifdef DEBUG
	OutputDebugString("DBGVIEWCLEAR");
#endif
	attached = true;
	if (acmhReal == NULL) acmInit();
	acmhSelf = (HMODULE) hModule;

	PatchIAT(GetModuleHandle(NULL),"kernel32.dll",GetProcAddress(GetModuleHandle("kernel32.dll"),
		"CreateProcessA"), GetProcAddress(acmhSelf,"_MyCreateProcessA@40"));
	
	ParseCommandLine();
	Initialize();
	hHook = SetWindowsHookEx(WH_CALLWNDPROC,HookWndProc,(HINSTANCE)hModule,GetCurrentThreadId());
}

void Detach() {
	if (!attached) return;
	if (hHook != NULL) {
		UnhookWindowsHookEx(hHook);
	}
	attached = false;
	if (!noCleanup) SynchronizeDirs(SHUTDOWN);
	if (securityDescriptor != NULL) LocalFree(securityDescriptor);
	if (currentUser != NULL) LocalFree(currentUser);
}

// patches import address table of the given module
// needed for example to "redirect" LoadLibraryA and CreateProcessA
// and to correctly redirect our own dummy functions
void PatchIAT(HMODULE hModule, char* moduleName, PROC orgAddress, PROC newAddress) {
	ULONG ulSize;

	if (hModule == NULL) return;
	PIMAGE_IMPORT_DESCRIPTOR pImportDesc = 
		(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
			hModule,
			TRUE,
			IMAGE_DIRECTORY_ENTRY_IMPORT,
			&ulSize
		);

	if (pImportDesc == NULL) return;
	while (pImportDesc->Name)
	{
		PSTR pszModName = (PSTR)((PBYTE) hModule + pImportDesc->Name);
		if (!strcmpi(pszModName,moduleName)) break;
		pImportDesc++;
	}
	PIMAGE_THUNK_DATA pThunk = 
	(PIMAGE_THUNK_DATA)((PBYTE) hModule + pImportDesc->FirstThunk);

	while (pThunk->u1.Function)
	{
 		PROC* ppfn = (PROC*) &pThunk->u1.Function;
		BOOL bFound = (*ppfn == orgAddress);

		if (bFound) 
		{
			MEMORY_BASIC_INFORMATION mbi;
			VirtualQuery(
				ppfn,
				&mbi,
				sizeof(MEMORY_BASIC_INFORMATION)
			);
			VirtualProtect(
				mbi.BaseAddress,
				mbi.RegionSize,
				PAGE_READWRITE,
				&mbi.Protect);
			
			*ppfn = *newAddress;

			DWORD dwOldProtect;
			VirtualProtect(
				mbi.BaseAddress,
				mbi.RegionSize,
				mbi.Protect,
				&dwOldProtect
			);
			break;
		}
		pThunk++;
	}
}

extern "C" __declspec(dllexport) BOOL FAR PASCAL MyCreateProcessA(LPCTSTR lpApp,
			  LPCTSTR lpCmd, LPSECURITY_ATTRIBUTES lpProcSecAtt,
			  LPSECURITY_ATTRIBUTES lpThreadSecAtt, BOOL bInh, DWORD dwCreat,
			  LPVOID lpEnv, LPCTSTR lpDir, LPSTARTUPINFO lpStartupInfo,
			  LPPROCESS_INFORMATION lpProcInfo) {
	char buf[MAX_PATH];
	BOOL restart = FALSE;
	GetModuleFileName(GetModuleHandle(NULL),buf,sizeof(buf));
	if (!strcmp(lpCmd,buf)) restart = TRUE;
	GetModuleFileName(GetModuleHandle(NULL),buf+1,sizeof(buf)-1);
	buf[0] = '"';
	strcat(buf,"\"");
	if (!strcmp(lpCmd,buf)) restart = TRUE;
	if (restart) {
		// winamp is closing to restart itself (well, hopefully)
		HKEY hKey;
		DWORD dummy;
		RegCreateKeyEx(HKEY_CURRENT_USER,REGKEY "\\Restart",0,NULL,REG_OPTION_VOLATILE,
			KEY_ALL_ACCESS, NULL, &hKey, &dummy);
		noCleanup = TRUE;
	}
	return CreateProcess(lpApp, (char*)lpCmd, lpProcSecAtt, lpThreadSecAtt, bInh, dwCreat,
		lpEnv, lpDir, lpStartupInfo, lpProcInfo);
}


void GetRunMode() {
	char caller[MAX_PATH];
	GetModuleFileName(GetModuleHandle(NULL),caller,sizeof(caller));
	char *c = caller;
	while (*c) {
		if (*c >= 'a' && *c <= 'z') *c &= 0xDF;
		c++;
	}
	c= strstr(caller,"RUNDLL32.EXE");
	runmode = c ? RUNMODE_RUNDLL : RUNMODE_WINAMP;
}
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	if (runmode == RUNMODE_UNKNOWN) GetRunMode();
	if (runmode == RUNMODE_RUNDLL) return TRUE;
    switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH:
			Attach(hModule);
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
			break;
		case DLL_PROCESS_DETACH:
			Detach();
    }
    return TRUE;
}


// Most of the following is autogenerated from the real msacm32.dll
// the called functions are simply forwarded.

PROC pXRegThunkEntry = NULL;
PROC pacmDriverAddA = NULL;
PROC pacmDriverAddW = NULL;
PROC pacmDriverClose = NULL;
PROC pacmDriverDetailsA = NULL;
PROC pacmDriverDetailsW = NULL;
PROC pacmDriverEnum = NULL;
PROC pacmDriverID = NULL;
PROC pacmDriverMessage = NULL;
PROC pacmDriverOpen = NULL;
PROC pacmDriverPriority = NULL;
PROC pacmDriverRemove = NULL;
PROC pacmFilterChooseA = NULL;
PROC pacmFilterChooseW = NULL;
PROC pacmFilterDetailsA = NULL;
PROC pacmFilterDetailsW = NULL;
PROC pacmFilterEnumA = NULL;
PROC pacmFilterEnumW = NULL;
PROC pacmFilterTagDetailsA = NULL;
PROC pacmFilterTagDetailsW = NULL;
PROC pacmFilterTagEnumA = NULL;
PROC pacmFilterTagEnumW = NULL;
PROC pacmFormatChooseA = NULL;
PROC pacmFormatChooseW = NULL;
PROC pacmFormatDetailsA = NULL;
PROC pacmFormatDetailsW = NULL;
PROC pacmFormatEnumA = NULL;
PROC pacmFormatEnumW = NULL;
PROC pacmFormatSuggest = NULL;
PROC pacmFormatTagDetailsA = NULL;
PROC pacmFormatTagDetailsW = NULL;
PROC pacmFormatTagEnumA = NULL;
PROC pacmFormatTagEnumW = NULL;
PROC pacmGetVersion = NULL;
PROC pacmMessage32 = NULL;
PROC pacmMetrics = NULL;
PROC pacmStreamClose = NULL;
PROC pacmStreamConvert = NULL;
PROC pacmStreamMessage = NULL;
PROC pacmStreamOpen = NULL;
PROC pacmStreamPrepareHeader = NULL;
PROC pacmStreamReset = NULL;
PROC pacmStreamSize = NULL;
PROC pacmStreamUnprepareHeader = NULL;

void acmInit() {
	char realMsacm[MAX_PATH] = {0};
	GetSystemDirectory(realMsacm,sizeof(realMsacm));
	if (realMsacm[strlen(realMsacm)-1] != '\\') strcat(realMsacm,"\\");
	strcat(realMsacm,"msacm32.dll");
	acmhReal = LoadLibrary(realMsacm);
	pacmFormatSuggest = GetProcAddress(acmhReal,"acmFormatSuggest");
	pXRegThunkEntry = GetProcAddress(acmhReal,"XRegThunkEntry");
	pacmDriverAddA = GetProcAddress(acmhReal,"acmDriverAddA");
	pacmDriverAddW = GetProcAddress(acmhReal,"acmDriverAddW");
	pacmDriverClose = GetProcAddress(acmhReal,"acmDriverClose");
	pacmDriverDetailsA = GetProcAddress(acmhReal,"acmDriverDetailsA");
	pacmDriverDetailsW = GetProcAddress(acmhReal,"acmDriverDetailsW");
	pacmDriverEnum = GetProcAddress(acmhReal,"acmDriverEnum");
	pacmDriverID = GetProcAddress(acmhReal,"acmDriverID");
	pacmDriverMessage = GetProcAddress(acmhReal,"acmDriverMessage");
	pacmDriverOpen = GetProcAddress(acmhReal,"acmDriverOpen");
	pacmDriverPriority = GetProcAddress(acmhReal,"acmDriverPriority");
	pacmDriverRemove = GetProcAddress(acmhReal,"acmDriverRemove");
	pacmFilterChooseA = GetProcAddress(acmhReal,"acmFilterChooseA");
	pacmFilterChooseW = GetProcAddress(acmhReal,"acmFilterChooseW");
	pacmFilterDetailsA = GetProcAddress(acmhReal,"acmFilterDetailsA");
	pacmFilterDetailsW = GetProcAddress(acmhReal,"acmFilterDetailsW");
	pacmFilterEnumA = GetProcAddress(acmhReal,"acmFilterEnumA");
	pacmFilterEnumW = GetProcAddress(acmhReal,"acmFilterEnumW");
	pacmFilterTagDetailsA = GetProcAddress(acmhReal,"acmFilterTagDetailsA");
	pacmFilterTagDetailsW = GetProcAddress(acmhReal,"acmFilterTagDetailsW");
	pacmFilterTagEnumA = GetProcAddress(acmhReal,"acmFilterTagEnumA");
	pacmFilterTagEnumW = GetProcAddress(acmhReal,"acmFilterTagEnumW");
	pacmFormatChooseA = GetProcAddress(acmhReal,"acmFormatChooseA");
	pacmFormatChooseW = GetProcAddress(acmhReal,"acmFormatChooseW");
	pacmFormatDetailsA = GetProcAddress(acmhReal,"acmFormatDetailsA");
	pacmFormatDetailsW = GetProcAddress(acmhReal,"acmFormatDetailsW");
	pacmFormatEnumA = GetProcAddress(acmhReal,"acmFormatEnumA");
	pacmFormatEnumW = GetProcAddress(acmhReal,"acmFormatEnumW");
	pacmFormatSuggest = GetProcAddress(acmhReal,"acmFormatSuggest");
	pacmFormatTagDetailsA = GetProcAddress(acmhReal,"acmFormatTagDetailsA");
	pacmFormatTagDetailsW = GetProcAddress(acmhReal,"acmFormatTagDetailsW");
	pacmFormatTagEnumA = GetProcAddress(acmhReal,"acmFormatTagEnumA");
	pacmFormatTagEnumW = GetProcAddress(acmhReal,"acmFormatTagEnumW");
	pacmGetVersion = GetProcAddress(acmhReal,"acmGetVersion");
	pacmMessage32 = GetProcAddress(acmhReal,"acmMessage32");
	pacmMetrics = GetProcAddress(acmhReal,"acmMetrics");
	pacmStreamClose = GetProcAddress(acmhReal,"acmStreamClose");
	pacmStreamConvert = GetProcAddress(acmhReal,"acmStreamConvert");
	pacmStreamMessage = GetProcAddress(acmhReal,"acmStreamMessage");
	pacmStreamOpen = GetProcAddress(acmhReal,"acmStreamOpen");
	pacmStreamPrepareHeader = GetProcAddress(acmhReal,"acmStreamPrepareHeader");
	pacmStreamReset = GetProcAddress(acmhReal,"acmStreamReset");
	pacmStreamSize = GetProcAddress(acmhReal,"acmStreamSize");
	pacmStreamUnprepareHeader = GetProcAddress(acmhReal,"acmStreamUnprepareHeader");
}


extern "C" __declspec( dllexport ) __declspec( naked ) DWORD XRegThunkEntry() {
	__asm{
		jmp pXRegThunkEntry;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverAddA() {
	__asm{
		jmp pacmDriverAddA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverAddW() {
	__asm{
		jmp pacmDriverAddW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverClose() {
	__asm{
		jmp pacmDriverClose;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverDetailsA() {
	__asm{
		jmp pacmDriverDetailsA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverDetailsW() {
	__asm{
		jmp pacmDriverDetailsW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverEnum() {
	__asm{
		jmp pacmDriverEnum;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverID() {
	__asm{
		jmp pacmDriverID;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverMessage() {
	__asm{
		jmp pacmDriverMessage;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverOpen() {
	__asm{
		jmp pacmDriverOpen;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverPriority() {
	__asm{
		jmp pacmDriverPriority;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmDriverRemove() {
	__asm{
		jmp pacmDriverRemove;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterChooseA() {
	__asm{
		jmp pacmFilterChooseA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterChooseW() {
	__asm{
		jmp pacmFilterChooseW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterDetailsA() {
	__asm{
		jmp pacmFilterDetailsA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterDetailsW() {
	__asm{
		jmp pacmFilterDetailsW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterEnumA() {
	__asm{
		jmp pacmFilterEnumA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterEnumW() {
	__asm{
		jmp pacmFilterEnumW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterTagDetailsA() {
	__asm{
		jmp pacmFilterTagDetailsA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterTagDetailsW() {
	__asm{
		jmp pacmFilterTagDetailsW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterTagEnumA() {
	__asm{
		jmp pacmFilterTagEnumA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFilterTagEnumW() {
	__asm{
		jmp pacmFilterTagEnumW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatChooseA() {
	__asm{
		jmp pacmFormatChooseA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatChooseW() {
	__asm{
		jmp pacmFormatChooseW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatDetailsA() {
	__asm{
		jmp pacmFormatDetailsA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatDetailsW() {
	__asm{
		jmp pacmFormatDetailsW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatEnumA() {
	__asm{
		jmp pacmFormatEnumA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatEnumW() {
	__asm{
		jmp pacmFormatEnumW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatSuggest() {
	__asm{
		jmp pacmFormatSuggest;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatTagDetailsA() {
	__asm{
		jmp pacmFormatTagDetailsA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatTagDetailsW() {
	__asm{
		jmp pacmFormatTagDetailsW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatTagEnumA() {
	__asm{
		jmp pacmFormatTagEnumA;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmFormatTagEnumW() {
	__asm{
		jmp pacmFormatTagEnumW;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmGetVersion() {
	__asm{
		jmp pacmGetVersion;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmMessage32() {
	__asm{
		jmp pacmMessage32;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmMetrics() {
	__asm{
		jmp pacmMetrics;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamClose() {
	__asm{
		jmp pacmStreamClose;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamConvert() {
	__asm{
		jmp pacmStreamConvert;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamMessage() {
	__asm{
		jmp pacmStreamMessage;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamOpen() {
	__asm{
		jmp pacmStreamOpen;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamPrepareHeader() {
	__asm{
		jmp pacmStreamPrepareHeader;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamReset() {
	__asm{
		jmp pacmStreamReset;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamSize() {
	__asm{
		jmp pacmStreamSize;
	}
}
extern "C" __declspec( dllexport ) __declspec( naked ) DWORD acmStreamUnprepareHeader() {
	__asm{
		jmp pacmStreamUnprepareHeader;
	}
}