summaryrefslogblamecommitdiffstats
path: root/core/modules/run-virt/winres/src/winres.c
blob: 753daad3ac9a73313996894c4bfe6ccb866313d2 (plain) (tree)
1
2
3
4
5
6
7
8
9






                                  

                  

























                                                                                                                 

                    

















                                          
                                              
                            
                                













                                                                                                                
                               
                                

                                                                                                               
                                                                       

                          
                                          











                                        
                               
                                 





                                          
                                                                        















































                                                                                                

                                                                  
 

























                                                                                             
                                  

                       
                           
                                               


                                                                                  

                                                
                                            














                                                                                                              
                          
                                 
                                                                            


                                                                                                                                                                                                                                             









                                                                                                                                                                                                         








                                                



                                                                                          
                                          

                                    
 



                                         

                                      
                                             








                                                                                                 

                                                




                                              


                                                         
                                              
                    
                     
                                                      







                                                
                                                                               







                                                                                                           
                                  

                                                                            
                         








                                                                                                                                         
 
                                   
                                                                                                      


                                                                                                        
 
               
                           


                           
                                   


         























































































                                                                                                                      
                         





                                                                                                      






                                                                                                              

                                                                                            











                                                                                               



                                               




                                                                                
                                                                       
                              




                                                                                          

                                                                                                      

                                                                                         

                                                 





                                                                                                         
                                                   

                                                                                        

                                                                                                            

                                           











                                                                                                    



                                                                                               
         
                    

                                                                    

                                                                                      

                                    
         







                                                                                                          




                                                 

                                                                        
                                                                      



                                                                       


                               
                             














                                                                                                   



































                                                                       













































                                                                                                                                  

                                                                        

                                                                      




                      

                                                              


                                                             
 

                          
                            
                                                  


                                                                                                            

                         
                                            



















                                                                                                                                                     
                                                                                      
                 
                                
                                                                 
                                  
                                                                                           


                                 



                                                  














                                                             




                                                                                                                    
                         
                              
         
























































                                                                                                                      



                                                                     


                                                                          
                                                         
                                                 
                                                              



                                                        
         
                    









                                                                    
                                                           

                            
                               

                                                                                     
                                         
                               
                                                      

                     



                                    




                                                                              
                        

                                                          
                                                    











                                                                                                  
                 

























                                                                                                                      
                                                 
                                                     
                                                                                 
                              
         
                                                                                  

 
                                                            
 
                                                   
                               
                                                  
                                                                   
                                                                            
                                        

                                     
                                                     
                                                                                









                                                                       


                                                                                                   
         


                 









































                                                                                                                     

 



















































                                                                                                        
                                

























                                                                                                                                                
                                                                     
















                                                                                       

































                                                                         
                                                 










                                                                                                                  
                                                     






                                                                  
                        






































                                                                                                                                       

                                                                                                
                                                             
                       

                                             
                                            




                                   







                                                                                     
                           




















                                                                                  
                        
                                          
                         
                     







































                                                                                                                   
                              












                                                                          










                                                                                                           

                                                                                                              




















                                                                                                                 


                                                                                
                                                              






                                                                                             
                                                        
                                                                         

                                                                                                         
                                                                                                         
                                                                        
                        
                                                                         
                 









                                                                                                      







                                                                                                            















                                                                                                                   
         





                                                                                                                     
                                                                 

                                                          

                             






















                                                                                                                          
                                                                              
                                           
                                                      


                                                          
                                              


                                                          
                                               


                                        
                                                        
                 

                                                   

                                                                                                                         




                                                                                                 
                                                                                                     


                                     
                                                                    
                                                

                                








                                                                                                         
                                                                                                        





                                                                                                        
                 





                                
                         


















                                                                 


































































































                                                                                                                    






                                                                                                                 
























































































































































































                                                                                                                                                                                          
#define NTDDI_VERSION  NTDDI_VISTA
#define WINVER 0x0602
#define _WIN32_WINNT 0x0602
#define WIN32_LEAN_AND_MEAN
#define _UNICODE
#define UNICODE
#define NO_SHLWAPI_STRFCNS
#define ENOTSUP 95
#define EAGAIN 11
#include <windows.h>
#include <winsock2.h>
#include <winnetwk.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <initguid.h>
#include <stdint.h>
#include <stdarg.h>
#include <wchar.h>
#include <time.h>
#include <shlobj.h>
#include <shlguid.h>
#include <strsafe.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <shellapi.h>

DEFINE_GUID(ID_IAudioEndpointVolume, 0x5CDF2C82, 0x841E, 0x4546, 0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x22, 0x9A);
DEFINE_GUID(ID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6);
DEFINE_GUID(ID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);

#define WM_SOCKDATA (WM_APP+1)

#define BUFLEN (200)

typedef struct {
	const char* path;
	const char* pathIp;
	const char* letter;
	const char* shortcut;
	const char* user;
	const char* pass;
	BOOL  success;
} netdrive_t;

static const ssize_t KEYLEN = 16;
#define DRIVEMAX (100)
#define LOGFILELEN (300)
#define SETTINGS_FILE "B:\\OPENSLX.INI"
#define SETTINGS_FILE_W L"B:\\OPENSLX.INI"

static BOOL _debug = FALSE;

static HINSTANCE hKernel32, hShell32, hUser32;
static OSVERSIONINFO winVer;
static BOOL shareFileOk = FALSE;
static netdrive_t drives[DRIVEMAX];
static wchar_t desktopPath[MAX_PATH+1], tempPath[MAX_PATH+1], programsPath[MAX_PATH+1], windowsPath[MAX_PATH+1];
static wchar_t logFile[LOGFILELEN];
static DWORD _startTime;
#define FS_UNKNOWN (-1)
#define FS_ERROR (0)
#define FS_OK (1)
static int _folderStatus = FS_UNKNOWN; // -1 = Not handled yet, 0 = patching failed, 1 = remapped ok
#define RM_NONE (0)
#define RM_NATIVE (1)
#define RM_NATIVE_FALLBACK (2)
#define RM_VMWARE (3)
static int _remapMode = RM_NONE;
static const char* _remapHomeDrive = NULL;
static BOOL _passCreds = FALSE;
static BOOL _noHomeWarn = FALSE;
static BOOL _deletedCredentials = FALSE;
static BOOL _scriptDone = TRUE, _mountDone = TRUE; // Will be set to false if we actually wait for something...
static char *shost = NULL, *sport = NULL, *suser = NULL, *spass = NULL;

#define SCRIPTFILELEN (50)
static wchar_t _scriptFile[SCRIPTFILELEN];

struct {
	BOOL documents;
	BOOL downloads;
	BOOL desktop;
	BOOL media;
	BOOL other;
} remap;
static BOOL _createMissingRemap = FALSE;

static void setPowerState();
static int setResolution();
static int optimizeForRemote();
static int muteSound(BOOL bMute);
static int setShutdownText();
static void readShareFile();
static BOOL mountNetworkShares();
static int queryPasswordDaemon();
static BOOL fileExists(wchar_t* szPath);
static BOOL folderExists(wchar_t* szPath);
static wchar_t* escapeShellArg(wchar_t* in, wchar_t *out, wchar_t *end);
static void patchUserPaths(wchar_t *letter);
static void remapViaSharedFolder();

static HRESULT createFolderShortcut(wchar_t* sTargetfile, wchar_t* sLinkfile, wchar_t* comment);

static void alog(const char *fmt, ...)
{
	FILE *f = _wfopen(logFile, L"a+");
	if (f == NULL) return;
	time_t raw = time(NULL);
	struct tm *tinf;
	char buffer[80];
	tinf = localtime(&raw);
	strftime(buffer, 80, "%I:%M:%S ", tinf);
	fputs(buffer, f);
	va_list args;
	va_start(args, fmt);
	vfprintf(f, fmt, args);
	va_end(args);
	fputc('\n', f);
	fclose(f);
}

static void wlog(const wchar_t *fmt, ...)
{
	wchar_t wbuffer[1000];
	char    abuffer[1000];
	va_list args;

	FILE *f = _wfopen(logFile, L"a+");
	if (f == NULL) return;
	time_t raw = time(NULL);
	struct tm *tinf;
	tinf = localtime(&raw);
	strftime(abuffer, 1000, "%I:%M:%S ", tinf);
	fputs(abuffer, f);

	va_start(args, fmt);
	StringCchVPrintfW(wbuffer, 1000, fmt, args);
	va_end(args);
	if (WideCharToMultiByte(CP_UTF8, 0, wbuffer, -1, abuffer, 1000, NULL, NULL) == 0) {
		snprintf(abuffer, 1000, "Cannot wlog: widechar to utf8 failed.");
	}
	fputs(abuffer, f);
	fputc('\n', f);
	fclose(f);
}

#define dalog(...) do { if (_debug) alog(__VA_ARGS__); } while (0)
#define dwlog(...) do { if (_debug) wlog(__VA_ARGS__); } while (0)

static void CALLBACK resetShutdown(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	static BOOL bInProc = FALSE;
	if (!bInProc) {
		bInProc = TRUE;
		setShutdownText();
		bInProc = FALSE;
	}
}

static void CALLBACK tmrResolution(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	static BOOL bInProc = FALSE;
	if (!bInProc) {
		bInProc = TRUE;
		if (setResolution() == 0) {
			KillTimer(hWnd, idEvent);
		}
		bInProc = FALSE;
	}
}

static void CALLBACK setupNetworkDrives(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	static BOOL bInProc = FALSE;
	static int fails = 0;
	if (bInProc || _mountDone)
		return;
	bInProc = TRUE;
	if (!shareFileOk) {
		dalog("No shareFileOk in sND");
		if (_remapMode == RM_NATIVE_FALLBACK || _remapMode == RM_VMWARE) {
			remapViaSharedFolder();
		}
	} else {
		int ret = queryPasswordDaemon();
		dalog("sND: qPD = %d", ret);
		if (ret != 0) {
			if (++fails < 10)
				goto exit_func;
			alog("queryPasswordDaemon returned %d", ret);
		} else {
			if (!mountNetworkShares()) {
				if (GetTickCount() - _startTime < 30000 && ++fails < 15)
					goto exit_func;
			}
		}
		// Finished successfully or failed completely
		if (_folderStatus != FS_OK && (_remapMode == RM_NATIVE_FALLBACK || _remapMode == RM_VMWARE)) {
			remapViaSharedFolder();
		}
	}
	_mountDone = TRUE;
	KillTimer(hWnd, idEvent);
	if (!_noHomeWarn) { // Warn if mapping failed and error is not muted
		if (_folderStatus != FS_OK && shost != NULL && shost[0] == '-' && sport != NULL && sport[0] == '-') {
			MessageBoxA(NULL, "Kein Home-Verzeichnis konfiguriert. Bitte nichts Wichtiges in der VM speichern, sondern z.B. einen USB-Stick verwenden, bzw. evtl. vorhandene Netzlaufwerke verwenden.", "Warnung", MB_ICONERROR);
		} else if (_folderStatus == FS_ERROR) {
			MessageBoxA(NULL, "Fehler beim Einbinden des Home-Verzeichnisses. Bitte nichts Wichtiges in der VM speichern, sondern z.B. einen USB-Stick verwenden.", "Warnung", MB_ICONERROR);
		} else if (_folderStatus == FS_UNKNOWN) {
			MessageBoxA(NULL, "Kein Home-Verzeichnis gefunden. Bitte nichts Wichtiges in der VM speichern, sondern z.B. einen USB-Stick verwenden.", "Warnung", MB_ICONERROR);
		}
	}
	return;
exit_func:
	bInProc = FALSE;
}

static inline int mapScriptVisibility(int input)
{
	if (input == 0)
		return SW_HIDE;
	if (input == 2)
		return SW_SHOWMINNOACTIVE;
	return SW_SHOWNORMAL;
}

static void CALLBACK launchRunscript(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
	static int fails = 0;
	wchar_t params[BUFLEN] = L"";
	wchar_t emptyParams[BUFLEN] = L"";
	wchar_t nuser[BUFLEN] = L"";
	wchar_t npass[BUFLEN] = L"";

	if (_scriptDone) {
		KillTimer(hWnd, idEvent);
		return;
	}
	// Prepare credentials cmdline
	if (spass == NULL) {
		dalog("launchRun: No spass");
		goto failure;
	}
	BOOL ok = TRUE;
	ok = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)suser, -1, (LPWSTR)nuser, BUFLEN) > 0 && ok;
	ok = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)spass, -1, (LPWSTR)npass, BUFLEN) > 0 && ok;
	if (!ok) {
		alog("Could not convert user/password to unicode");
		goto failure;
	}
	// Build command line with user password
	wchar_t *end = params + BUFLEN - 2;
	wchar_t *ptr = params;
	ptr = escapeShellArg(nuser, ptr, end);
	*ptr++ = ' ';
	ptr = escapeShellArg(npass, ptr, end);
	*ptr = '\0';
	// Build command line without password, just user
	end = emptyParams + BUFLEN - 2;
	ptr = emptyParams;
	ptr = escapeShellArg(nuser, ptr, end);
	*ptr = '\0';
	if (_debug) {
		wlog(L"Params are '%s'", emptyParams);
	}

	// Scan folder
	WIN32_FIND_DATAA fdFile;
	HANDLE hFind = NULL;
	const char* sDir = "B:\\adminrun";
	char sFile[200];
	snprintf(sFile, 200, "%s\\*-*-*", sDir);
	if ((hFind = FindFirstFileA(sFile, &fdFile)) != INVALID_HANDLE_VALUE) {
		do {
			if(strcmp(fdFile.cFileName, ".") == 0 || strcmp(fdFile.cFileName, "..") == 0)
				continue;
			if (fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				continue;
			snprintf(sFile, 200, "%s\\%s", sDir, fdFile.cFileName);
			wchar_t ucPath[BUFLEN];
			ok = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)sFile, -1, (LPWSTR)ucPath, BUFLEN) > 0;
			if (!ok) {
				alog("Cannot convert %s to unicode", sFile);
				continue;
			}
			int index, visibility, passCreds;
			if (sscanf(fdFile.cFileName, "%d-%d-%d", &index, &visibility, &passCreds) < 3) {
				alog("Cannot parse %s", fdFile.cFileName);
				continue;
			}
			ShellExecuteW(NULL, L"open", ucPath, passCreds ? params : emptyParams, L"B:\\", mapScriptVisibility(visibility));
		} while(FindNextFileA(hFind, &fdFile)); //Find the next file.
		FindClose(hFind); //Always, Always, clean things up!
	}

	// Execute legacy runscript
	int scriptVisibility = GetPrivateProfileIntA("openslx", "scriptVisibility", 1, SETTINGS_FILE);
	int nShowCmd = mapScriptVisibility(scriptVisibility); // show window as default

	ShellExecuteW(NULL, L"open", _scriptFile, _passCreds ? params : emptyParams, L"B:\\", nShowCmd);

	// DONE
	_scriptDone = TRUE;
	return;
failure:
	if (++fails > 12) {
		_scriptDone = TRUE;
	}
}

typedef HRESULT (*GFPTYPE)(HWND, int, HANDLE, DWORD, wchar_t*);
typedef HRESULT (*GSFTYPE)(HWND, int, ITEMIDLIST**);
typedef BOOL (*ID2PTYPE)(const ITEMIDLIST*, wchar_t*);

/**
 * Load given path (CSIDL). Store in default (must be allocated to hold at least MAX_PATH+1 chars).
 * If it could not be retrieved by CSIDL and envName is not NULL, it will be read from the
 * environment if possible.
 * fallback will be used if everything else fails.
 * fallback can be NULL, in which case the fallback is empty.
 */
static void loadPath(wchar_t *dest, int csidl, wchar_t *envName, wchar_t *fallback)
{
	if (hShell32 != NULL) {
		GFPTYPE aGetFolderPath = (GFPTYPE)GetProcAddress(hShell32, "SHGetFolderPathW");
		if (aGetFolderPath != NULL) {
			if ((aGetFolderPath)(HWND_DESKTOP, csidl, NULL, SHGFP_TYPE_CURRENT, dest) == S_OK)
				return;
		}
		// Fallback
		GSFTYPE aGetSpecialFolder = (GSFTYPE)GetProcAddress(hShell32, "SHGetSpecialFolderLocation");
		ID2PTYPE aPathToId = (ID2PTYPE)GetProcAddress(hShell32, "SHGetPathFromIDListW");
		if (aGetSpecialFolder != NULL && aPathToId != NULL) {
			ITEMIDLIST *list = NULL;
			HRESULT ret1 = (aGetSpecialFolder)(HWND_DESKTOP, csidl, &list);
			BOOL ret2 = FALSE;
			if (ret1 == 0) {
				ret2 = (aPathToId)(list, dest);
			}
			if (list != NULL) {
				CoTaskMemFree(list);
			}
			if (ret2)
				return;
		}
	}
	if (envName != NULL) {
		// Fallback
		DWORD ret = GetEnvironmentVariableW(envName, dest, MAX_PATH+1);
		if (ret > 0 && ret <= MAX_PATH)
			return;
	}
	if (fallback != NULL) {
		StringCchPrintfW(programsPath, MAX_PATH+1, fallback);
		return;
	}
	*dest = '\0';
}

static void loadPaths()
{
	// Determine a couple of default directories
	// dest, id, env, fallback
	loadPath(programsPath, CSIDL_PROGRAM_FILES, L"ProgramFiles", L"C:\\Program Files");
	loadPath(windowsPath, CSIDL_WINDOWS, L"windir", L"C:\\WINDOWS");
	loadPath(desktopPath, CSIDL_DESKTOPDIRECTORY, NULL, NULL);
	if (GetTempPathW(MAX_PATH+1, tempPath) == 0) {
		DWORD ret = GetEnvironmentVariableW(L"TEMP", tempPath, MAX_PATH+1);
		if (ret == 0 || ret > MAX_PATH) {
			tempPath[0] = 0;
		}
	}
	//wlog(L"Programs: %s, Windows: %s, Desktop: %s, Temp: %s", programsPath, windowsPath, desktopPath, tempPath);
	StringCchPrintfW(logFile, LOGFILELEN, L"%s\\%s", desktopPath, L"openslx.log");
	FILE *tfh = _wfopen(logFile, L"a+");
	if (tfh == NULL) {
		StringCchPrintfW(logFile, LOGFILELEN, L"%s\\%s", tempPath, L"openslx.log");
		tfh = _wfopen(logFile, L"a+");
	}
	if (tfh != NULL) {
		fseek(tfh, 0, SEEK_END);
		long pos = ftell(tfh);
		fclose(tfh);
		if (pos < 3) {
			_wremove(logFile);
		}
	}
	// Read settings from ini file
	remap.documents = GetPrivateProfileIntA("remap", "documents", 1, SETTINGS_FILE) != 0;
	remap.downloads = GetPrivateProfileIntA("remap", "downloads", 1, SETTINGS_FILE) != 0;
	remap.desktop = GetPrivateProfileIntA("remap", "desktop", 0, SETTINGS_FILE) != 0;
	remap.media = GetPrivateProfileIntA("remap", "media", 1, SETTINGS_FILE) != 0;
	remap.other = GetPrivateProfileIntA("remap", "other", 0, SETTINGS_FILE) != 0;
	_createMissingRemap = GetPrivateProfileIntA("openslx", "createMissingRemap", 1, SETTINGS_FILE) != 0;
	_remapMode = GetPrivateProfileIntA("openslx", "remapMode", RM_NATIVE_FALLBACK, SETTINGS_FILE);
	if (_remapMode == RM_NONE) {
		_folderStatus = FS_OK;
	}
	char buffer[100];
	GetPrivateProfileStringA("openslx", "homeDrive", "H:", buffer, sizeof(buffer), SETTINGS_FILE);
	buffer[0] = toupper(buffer[0]);
	buffer[1] = ':';
	buffer[2] = '\0';
	_remapHomeDrive = strdup(buffer);
	// Get extension for autorun script
	int bl = snprintf(buffer, sizeof(buffer), "B:\\runscript");
	GetPrivateProfileStringA("openslx", "scriptExt", "", buffer + bl, sizeof(buffer) - bl, SETTINGS_FILE);
	if (MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)buffer, -1, (LPWSTR)_scriptFile, SCRIPTFILELEN) <= 0) {
		_scriptFile[0] = '\0';
	}
	// Pass creds to normal runscript?
	_passCreds = GetPrivateProfileIntA("openslx", "passCreds", 0, SETTINGS_FILE) != 0;
	// No warning if no home directory could be mounted
	_noHomeWarn = GetPrivateProfileIntA("openslx", "noHomeWarn", 0, SETTINGS_FILE) != 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	hKernel32 = LoadLibraryW(L"kernel32.dll");
	if (hKernel32 == NULL) {
		alog("Cannot load kernel32.dll");
	}
	hShell32 = LoadLibraryW(L"shell32.dll");
	if (hShell32 == NULL) {
		alog("Cannot load shell32.dll");
	}
	hUser32 = LoadLibraryW(L"user32.dll");
	if (hUser32 == NULL) {
		alog("Cannot load user32.dll");
	}
	winVer.dwOSVersionInfoSize = sizeof(winVer);
	BOOL retVer = GetVersionEx(&winVer);
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
	_startTime = GetTickCount();
	loadPaths();
	if (lpCmdLine != NULL && strstr(lpCmdLine, "/debug") != NULL) {
		_debug = TRUE;
	}
	if (!_debug && GetPrivateProfileIntA("openslx", "debug", 0, SETTINGS_FILE) != 0) {
		_debug = TRUE;
	}
	if (_debug) {
		alog("Windows Version %d.%d", (int)winVer.dwMajorVersion, (int)winVer.dwMinorVersion);
	}
	// Mute sound?
	BOOL mute = GetPrivateProfileIntA("openslx", "muteSound", 1, SETTINGS_FILE) != 0;
	if (retVer && winVer.dwMajorVersion >= 6)
		muteSound(mute);
	// Disable screen saver as it might give the false impression that the session is securely locked
	SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
	// Same with standby
	setPowerState();
	// Any network shares to mount?
	readShareFile();
	if (shareFileOk || _remapMode != RM_NONE) {
		UINT_PTR tRet = SetTimer(NULL, 0, 1650, (TIMERPROC)&setupNetworkDrives);
		dalog("init: &setupNetworkDrives");
		if (tRet == 0) {
			alog("Could not create timer for mounting network shares: %d", (int)GetLastError());
		} else {
			_mountDone = FALSE;
		}
	}
	// Shutdown button label
	if (retVer && winVer.dwMajorVersion == 6 && winVer.dwMinorVersion == 1) {
		// Only on Windows 7
		// Repeatedly set caption
		UINT_PTR tRet = SetTimer(NULL, 0, 5230, (TIMERPROC)&resetShutdown);
		if (tRet == 0) {
			alog("Could not create timer for shutdown button: %d", (int)GetLastError());
		}
	}
	// Resolution
	UINT_PTR tRet;
	tRet = SetTimer(NULL, 0, 211, (TIMERPROC)&tmrResolution);
	if (tRet == 0) {
		alog("Could not create timer for resolution setting: %d", (int)GetLastError());
	}
	// Runscript
	tRet = SetTimer(NULL, 0, 3456, (TIMERPROC)&launchRunscript);
	dalog("init: &launchRunscript");
	if (tRet == 0) {
		alog("Could not create timer for runscript: %d", (int)GetLastError());
	} else {
		_scriptDone = FALSE;
	}
	// Remote?
	do {
		char buffer[100];
		GetPrivateProfileStringA("openslx", "runMode", "", buffer, sizeof(buffer), SETTINGS_FILE);
		if (strcmp(buffer, "remoteaccess") == 0) {
			optimizeForRemote();
		}
	} while (0);
	// Message pump
	MSG Msg;
	while(GetMessage(&Msg, NULL, 0, 0) > 0) {
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
		if (!_deletedCredentials && _mountDone && _scriptDone) {
			if (spass != NULL) {
				dalog("Erasing password from memory");
				SecureZeroMemory(spass, strlen(spass));
				_deletedCredentials = TRUE;
			}
		}
	}
	FreeLibrary(hKernel32);
	FreeLibrary(hShell32);
	FreeLibrary(hUser32);
	return 0;
}

static BOOL fileExists(wchar_t* szPath)
{
	DWORD dwAttrib = GetFileAttributesW(szPath);
	return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0);
}

static BOOL folderExists(wchar_t* szPath)
{
	DWORD dwAttrib = GetFileAttributesW(szPath);
	return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
}

static wchar_t* escapeShellArg(wchar_t* in, wchar_t *out, wchar_t *end)
{
	int bs;
	end -= 2;
	if (out >= end)
		return out;

	*out++ = '"';
	while (*in != '\0') {
		bs = 0;
		while (*in == '\\' && out < end) {
			bs++;
			*out++ = *in++;
		}
		if (*in == '\0' || *in == '"') {
			while (bs-- > 0 && out < end) {
				*out++ = '\\';
			}
			if (*in == '\0') {
				break;
			}
		}
		if (*in == '"' && out < end) {
			*out++ = '\\';
		}
		if (out < end) {
			*out++ = *in++;
		}
	}
	if (out < end) {
		*out++ = '"';
	}
	*out = '\0';
	return out;
}

static int execute(wchar_t *path, wchar_t *arguments)
{
	STARTUPINFOW si;
	PROCESS_INFORMATION pi;
	ZeroMemory(&si, sizeof(si));
	ZeroMemory(&pi, sizeof(pi));
	si.cb = sizeof(si);
	if (!CreateProcessW(path, arguments, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
		return -1;
	}
	while (MsgWaitForMultipleObjects(1, &pi.hProcess, FALSE, INFINITE, QS_SENDMESSAGE) == WAIT_OBJECT_0+1) {
		MSG Msg;
		while(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE | PM_QS_SENDMESSAGE)) {
			TranslateMessage(&Msg);
			DispatchMessage(&Msg);
		}
	}
	DWORD exitCode;
	BOOL ret = GetExitCodeProcess(pi.hProcess, &exitCode);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
	if (!ret) {
		return -2;
	}
	return (int)exitCode;
}

typedef EXECUTION_STATE (WINAPI *TETYPE)(EXECUTION_STATE);

static void setPowerState()
{
	// Disable standby and idle-mode (this is a VM!)
	if (hKernel32 == NULL || winVer.dwMajorVersion < 5 || (winVer.dwMajorVersion == 5 && winVer.dwMinorVersion < 1))
		return;
	TETYPE aExecState = (TETYPE)GetProcAddress(hKernel32, "SetThreadExecutionState");
	if (aExecState == NULL) {
		alog("Cannot get SetThreadExecutionState from kernel32.dll");
		return;
	}
	if (winVer.dwMajorVersion >= 6) { // Vista+
		(aExecState)(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED | ES_AWAYMODE_REQUIRED);
	} else { // XP/2003
		(aExecState)(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
	}
}

typedef LONG (WINAPI *CDSTYPE)(LPCWSTR, PDEVMODEW, HWND, DWORD, LPVOID);
typedef BOOL (WINAPI *EDDTYPE)(LPCWSTR, DWORD, PDISPLAY_DEVICEW, DWORD);
typedef BOOL (WINAPI *EDMTYPE)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
typedef BOOL (WINAPI *GMITYPE)(HMONITOR, LPMONITORINFO);

struct resolution {
	long int w, h;
};

#define MAX_SCREENS (16)
static int isResolutionFine(struct resolution *res, int nres);
static int setResWinMulti(struct resolution *res, int nres);
static int setResWinLegacy(struct resolution *res, int nres);
static int setResVMware(struct resolution *res, int nres);

static int setResolution()
{
	static int nres = 0;
	static struct resolution res[MAX_SCREENS];
	static int callCount = -1;
	callCount++;
	if (nres == -1 || callCount > 10) // We've been here before and consider config invalid, or are done
		return 0;
	if (nres == 0) {
		// use config file in floppy
		char data[300] = "";
		GetPrivateProfileStringA("openslx", "resolution2", "", data, sizeof(data), SETTINGS_FILE); // Multi-res, space separated
		if (data[0] == '\0') {
			GetPrivateProfileStringA("openslx", "resolution", "", data, sizeof(data), SETTINGS_FILE); // Fallback
		}
		char *pos = data, *end;
		while (*pos != '\0' && nres < 16) {
			res[nres].w = strtol(pos, &end, 10);
			if (*end != 'x')
				break;
			res[nres].h = strtol(end + 1, &pos, 10);
			if (*pos != '\0' && *pos != ' ')
				break;
			if (res[nres].w < 320 || res[nres].h < 240) {
				alog("Invalid resolution in " SETTINGS_FILE ": '%s' (parsed width=%ld, height=%ld)", data, res[nres].w, res[nres].h);
				continue;
			}
			nres++;
		}
		if (*pos != '\0') {
			alog("Malformed resolution in " SETTINGS_FILE ": '%s'", data);
		}
		if (nres == 0) {
			// Nothing found -- consider this success
			nres = -1;
			dalog("No resolution information in openslx.ini -- doing nothing");
			return 0;
		}
	}
	int check, ret;
	check = isResolutionFine(res, nres);
	if (check == 0)
		return 0; // Yay! Save the hassle.
	switch (callCount % 3) {
		case 0:
			// Use WinAPI first
			ret = setResWinMulti(res, nres);
			if (ret != ENOTSUP)
				break;
			// Fallthrough
		case 1:
			// Try vmware tools
			ret = setResVMware(res, nres);
			if (ret != ENOTSUP)
				break;
		default:
			// Legacy WinAPI (single screen only)
			ret = setResWinLegacy(res, nres);
			if (ret == 0 && nres > 1 && callCount == 2) {
				// Legacy winapi worked, but if we have more than one screen, pretend it failed
				// the first time, so maybe one of the methods above will work if we call them again
				// (Looking at you, VMwareResolutionSet)
				ret = EAGAIN; // Fake failure
			}
			break;
	}
	if (ret == 0 && check == ENOTSUP)
		return 0; // Couldn't query, attempt to set didn't yield an error -> OK
	return EAGAIN; // Otherwise, regardless of attempt's return value, call again,
	// in which case isResolutionFine should yield success.
}

static int isResolutionFine(struct resolution *res, int nres)
{
	EDDTYPE edd = (EDDTYPE)GetProcAddress(hUser32, "EnumDisplayDevicesW");
	if (edd == NULL) {
		// Old Windows with no multiscreen support
		DEVMODEW mode = { .dmSize = sizeof(mode) };
		int query = EnumDisplaySettingsW(NULL, ENUM_CURRENT_SETTINGS, &mode);
		if (query == 0) {
			dalog("Could not query current resolution for old Windows.");
			return ENOTSUP;
		}
		return res[0].w == mode.dmPelsWidth && res[0].h == mode.dmPelsHeight;
	}
	// Modern/multi-screen aware approach
	DISPLAY_DEVICEW screen = { .cb = sizeof(screen) };
	int offsets[MAX_SCREENS] = { 0 };
	DWORD screenNum = 0;
	// First make a list of expected offsets for our resolutions
	for (int i = 1; i < nres; ++i) {
		offsets[i] = offsets[i - 1] + res[i - 1].w;
	}
	while (edd(NULL, screenNum++, &screen, 0)) {
		DEVMODEW mode = { .dmSize = sizeof(mode) };
		int query = EnumDisplaySettingsW(screen.DeviceName, ENUM_CURRENT_SETTINGS, &mode);
		if (query == 0 || mode.dmPelsWidth == 0)
			continue; // Not active
		// See if this screen matches any of the expected res+offset
		BOOL found = FALSE;
		for (int i = 0; i < nres; ++i) {
			if (offsets[i] == mode.dmPosition.x
					&& res[i].w == mode.dmPelsWidth && res[i].h == mode.dmPelsHeight) {
				offsets[i] = -1; // Marker
				found = TRUE;
				break;
			}
		}
		if (!found) {
			dalog("Non-matching screen active (%dx%d @ %d)", mode.dmPelsWidth, mode.dmPelsHeight,
					mode.dmPosition.x);
			return EAGAIN;
		}
	}
	// If all offsets are set to -1, everything matched up perfectly
	for (int i = 0; i < nres; ++i) {
		if (offsets[i] != -1) {
			dalog("Expected screen %dx%d @ %d not found in active setup", res[i].w, res[i].h, offsets[i]);
			return EAGAIN;
		}
	}
	dalog("Current screen layout matches expected one");
	return 0;
}

static BOOL foobar(HMONITOR Arg1, HDC Arg2, LPRECT Arg3, LPARAM Arg4)
{
	GMITYPE gmi = (GMITYPE)GetProcAddress(hUser32, "GetMonitorInfoW");
	if (gmi == NULL)
		return FALSE;
	MONITORINFOEXW info = { .cbSize = sizeof(info) };
	if (gmi(Arg1, (LPMONITORINFO)&info) == 0)
		dalog("MonitorInfo FAILED for %d", (int)Arg1);
	else {
		dalog("MonitorInfo for %d:", (int)Arg1);
		dalog("Flags: %d", (int)info.dwFlags);
		dwlog(L"Name: %s", info.szDevice);
	}
	return TRUE;
}

/*
 * This seems to be broken with VirtualBox, and I'm not sure whether
 * I'm doing something wrong here. For a dualscreen setup, this
 * returns two devices, but only the first one has a monitor, i.e.
 * the inner loop never runs for the second device.
 * I'm still leaving this here as a starting-point for future
 * changes or new virtualizers.
 */
static int setResWinMulti(struct resolution *res, int nres)
{
	if (hUser32 == NULL)
		return ENOTSUP;
	CDSTYPE cdsEx = (CDSTYPE)GetProcAddress(hUser32, "ChangeDisplaySettingsExW");
	EDDTYPE edd = (EDDTYPE)GetProcAddress(hUser32, "EnumDisplayDevicesW");
	if (cdsEx == NULL || edd == NULL)
		return ENOTSUP;
	DISPLAY_DEVICEW ddev = { .cb = sizeof(ddev) };
	int ires = 0;
	int chret;
	long int sx = 0;
	BOOL ok = TRUE;
	dalog("WinAPI multiscreen");
	// XXX Debug
	EDMTYPE edm = (EDMTYPE)GetProcAddress(hUser32, "EnumDisplayMonitors");
	if (edm != NULL) {
		edm(NULL, NULL, (MONITORENUMPROC)&foobar, 0);
		dalog("End of monitor enum dump");
	}
	// XXX END DEBUG
	DISPLAY_DEVICEW screen = { .cb = sizeof(screen) };
	DWORD screenNum = 0;
	while (edd(NULL, screenNum++, &screen, 0)) {
		DEVMODEW mode = { .dmSize = sizeof(mode) };
		int query = EnumDisplaySettingsW(screen.DeviceName, ENUM_CURRENT_SETTINGS, &mode);
		if (query == 0) {
			dalog("EnumDisplaySettings: Retrying with index 0");
			query = EnumDisplaySettingsW(screen.DeviceName, 0, &mode);
		}
		mode.dmFields = 0;
		if (query == 0) {
			dalog("EnumDisplaySettings: Falling back to default values");
			mode.dmFields |= DM_BITSPERPEL | DM_DISPLAYFREQUENCY;
			mode.dmBitsPerPel = 32;
			mode.dmDisplayFrequency = 60;
		}
		dwlog(L"%s (%s) currently %dx%d+%d+%d, %dHz, %dbpp (Query: %d)",
				screen.DeviceName, screen.DeviceString,
				mode.dmPelsWidth, mode.dmPelsHeight, mode.dmPosition.x, mode.dmPosition.y,
				mode.dmDisplayFrequency, mode.dmBitsPerPel, query);
		if (ires < nres) {
			// Enable
			mode.dmPelsWidth = res[ires].w;
			mode.dmPelsHeight = res[ires].h;
			mode.dmPosition.x = sx;
			mode.dmPosition.y = 0;
			sx += mode.dmPelsWidth;
			mode.dmFields |= DM_PELSWIDTH | DM_PELSHEIGHT | DM_POSITION;
		} else {
			// Disable
			mode.dmPelsWidth = mode.dmPelsHeight = 0;
			mode.dmFields = DM_POSITION;
		}
		dwlog(L"New: %dx%d+%d+%d", mode.dmPelsWidth, mode.dmPelsHeight, mode.dmPosition.x, mode.dmPosition.y);
		chret = cdsEx(screen.DeviceName, &mode, 0, (CDS_UPDATEREGISTRY | CDS_NORESET), NULL);
		if (chret != DISP_CHANGE_SUCCESSFUL) {
			dalog("Returned %d", chret);
			ok = FALSE;
		}
		ires++;
		screen = (DISPLAY_DEVICEW){ .cb = sizeof(DISPLAY_DEVICEW) };
	}
	chret = cdsEx(NULL, NULL, NULL, 0, NULL);
	if (chret != DISP_CHANGE_SUCCESSFUL || !ok) {
		dalog("Final multiscreen change display call failed: %d", chret);
		return EAGAIN;
	}
	return ires >= nres ? 0 : EAGAIN; // Did we find enough (virtual) screens?
}

static int setResWinLegacy(struct resolution *res, int nres)
{
	dalog("Using legacy single-screen WinAPI");
	// Legacy single screen
	DEVMODE mode = { .dmSize = sizeof(mode) };
	// MSDN recommends to fill the struct first by querying....
	int query = EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &mode);
	// Then set our own desired mode
	mode.dmPelsWidth = res[0].w;
	mode.dmPelsHeight = res[0].h;
	mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
	int ret = ChangeDisplaySettings(&mode, CDS_GLOBAL | CDS_UPDATEREGISTRY);
	if (ret != DISP_CHANGE_SUCCESSFUL) {
		ret = ChangeDisplaySettings(&mode, CDS_GLOBAL);
	}
	if (ret != DISP_CHANGE_SUCCESSFUL) {
		ret = ChangeDisplaySettings(&mode, CDS_UPDATEREGISTRY);
	}
	if (ret != DISP_CHANGE_SUCCESSFUL) {
		ret = ChangeDisplaySettings(&mode, 0);
	}
	if (ret != DISP_CHANGE_SUCCESSFUL) {
		dalog("WinAPI Legacy: CDS: %d (should: 0), EDS: %d (should: !0) - Want: %ld * %ld",
				ret, query, res[0].w, res[0].h);
		return EAGAIN;
	}
	return 0;
}

static int setResVMware(struct resolution *res, int nres)
{
	static wchar_t path[MAX_PATH] = L"";
	if (path[0] == 0) {
		StringCchPrintfW(path, MAX_PATH, L"%s\\VMware\\VMware Tools\\VMwareResolutionSet.exe", programsPath);
		if (!fileExists(path)) {
			// Strip (x86) if not found and try again
			wchar_t *x86 = wcsstr(path, L" (x86)");
			if (x86 != NULL) {
				while ((*x86 = *(x86 + 6)) != 0) ++x86;
			}
		}
		if (!fileExists(path)) {
			char buffer[300];
			WideCharToMultiByte(CP_UTF8, 0, path, -1, buffer, 300, NULL, NULL);
			dalog("vmware tools not found, using winapi to set resolution (path: %s)", buffer);
			return ENOTSUP;
		}
	} else if (!fileExists(path)) {
		return ENOTSUP;
	}
	wchar_t cmdline[MAX_PATH];
	wchar_t *pos = cmdline;
	size_t rem = MAX_PATH;
	long int sx = 0, i = 0;
	StringCchPrintfExW(pos, rem, &pos, &rem, 0, L"VMwareResolutionSet.exe 0 %d", nres);
	while (rem > 0 && i < nres) {
		StringCchPrintfExW(pos, rem, &pos, &rem, 0, L" , %ld 0 %ld %ld", sx, res[i].w, res[i].h);
		sx += res[i++].w;
	}
	dwlog(L"vmwareRS cmd: %s", cmdline);
	int ret = execute(path, cmdline);
	if (ret == -1) {
		alog("VmwareRes: CreateProcess failed (%d)", (int)GetLastError());
	} else if (ret == -2) {
		alog("VmwareRes: GetExitCode failed (%d)", (int)GetLastError());
	} else if (ret != 0) {
		dalog("VmwareRes: Exit code: %d", ret);
	} else {
		return 0;
	}
	return EAGAIN;
}

static int optimizeForRemote()
{
	LONG ret;
	HKEY hKey;

	ret = RegOpenKeyExW(HKEY_CURRENT_USER,
			L"Control Panel\\Cursors",
			0, KEY_WOW64_64KEY | KEY_READ | KEY_WRITE, &hKey);
	if (ret != ERROR_SUCCESS) {
		alog("Opening registry for optimizing remote access failed: %ld", (long)ret);
		return 1;
	}
	DWORD val = 0;
	ret = RegSetValueExW(hKey, L"Scheme Source", 0, REG_DWORD, (BYTE*)&val, sizeof(val));
	if (ret != ERROR_SUCCESS) {
		alog("Cannot set Scheme Source to 0: %ld", (long)ret);
	}
	static const char *keys[] = {
		"AppStarting", "Arrow", "Crosshair", "Hand", "Help", "IBeam", "No", "NWPen",
		"SizeAll", "SizeNESW", "SizeNS", "SizeNWSE", "SizeWE", "UpArrow", "Wait",
		NULL
	};
	for (const char **key = keys; *key != NULL; ++key) {
		ret = RegSetValueExA(hKey, *key, 0, REG_SZ, (const BYTE*)"", 1);
		if (ret != ERROR_SUCCESS) {
			alog("Cannot set %s to empty string", key);
		}
	}
	RegCloseKey(hKey);
	static const UINT paramsOff[] = {
		SPI_SETCLIENTAREAANIMATION, SPI_SETMOUSEVANISH, SPI_SETDROPSHADOW, SPI_SETMENUFADE,
		SPI_SETTOOLTIPFADE, SPI_SETTOOLTIPANIMATION, SPI_SETSELECTIONFADE, SPI_SETMENUANIMATION,
		SPI_SETLISTBOXSMOOTHSCROLLING, SPI_SETCURSORSHADOW, SPI_SETCOMBOBOXANIMATION,
		0
	}, paramsOn[] = {
		SPI_SETDISABLEOVERLAPPEDCONTENT,
		0
	};
	for (const UINT *s = paramsOff; *s != 0; ++s) {
		SystemParametersInfo(*s, 0, (PVOID)FALSE, SPIF_UPDATEINIFILE);
	}
	for (const UINT *s = paramsOn; *s != 0; ++s) {
		SystemParametersInfo(*s, 0, (PVOID)TRUE, SPIF_UPDATEINIFILE);
	}
	// Try this although it needs admin perms
	ANIMATIONINFO ai = { .cbSize = sizeof(ai), .iMinAnimate = 0, };
	SystemParametersInfo(SPI_SETANIMATION, 0, &ai, SPIF_UPDATEINIFILE);
	// Apply cursors, broadcast changes to whole system
	SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
	return 0;
}

static int muteSound(BOOL bMute)
{
	IMMDeviceEnumerator *deviceEnumerator = NULL;
	HRESULT hr = CoCreateInstance(&ID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &ID_IMMDeviceEnumerator, (LPVOID *)&deviceEnumerator);
	if (hr != S_OK) {
		alog("CoCreateInstance failed. Cannot mute.");
		return 1;
	}
	//deviceEnumerator->lpVtbl->AddRef(deviceEnumerator);
	IMMDevice *defaultDevice = NULL;
	hr = deviceEnumerator->lpVtbl->GetDefaultAudioEndpoint(deviceEnumerator, eRender, eConsole, &defaultDevice);
	if (hr != S_OK) {
		alog("GetDefaultAudioEndpoint failed. Cannot mute.");
		return 2;
	}
	//defaultDevice->lpVtbl->AddRef(defaultDevice);
	//deviceEnumerator->lpVtbl->Release(deviceEnumerator);
	IAudioEndpointVolume *endpointVolume = NULL;
	hr = defaultDevice->lpVtbl->Activate(defaultDevice, &ID_IAudioEndpointVolume, CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
	if (hr != S_OK) {
		alog("IMMDevice::Activate() failed. Cannot mute.");
		return 3;
	}
	//endpointVolume->lpVtbl->AddRef(endpointVolume);
	//defaultDevice->lpVtbl->Release(defaultDevice);
	float targetVolume = 1;
	endpointVolume->lpVtbl->SetMasterVolumeLevelScalar(endpointVolume, targetVolume, NULL);
	endpointVolume->lpVtbl->SetMute(endpointVolume, bMute, NULL);
	//endpointVolume->lpVtbl->Release(endpointVolume);
	//CoUninitialize();
	return 0;
}

static int setShutdownText()
{
	HWND hMenu = FindWindowA("DV2ControlHost", NULL);
	if (hMenu == NULL) return 1; // TODO: Enum all of them
	HWND hPane = FindWindowExA(hMenu, NULL, "DesktopLogoffPane", NULL);
	if (hMenu == NULL) return 2;
	HWND hButton = FindWindowExA(hPane, NULL, "Button", NULL);
	if (hButton == NULL) return 3;
	if (SendMessageA(hButton, WM_SETTEXT, 0, (LPARAM)"Abmelden") != TRUE) return 4;
	return 0;
}

static uint8_t *bkey1 = NULL, *bkey2 = NULL;

static char* xorString(const uint8_t* text, int len, const uint8_t* key);
static int getbin(int x);
static uint8_t* hex2bin(char *szHexString);

static char* getToken(char **ptr, BOOL doDup)
{
	if (*ptr == NULL || **ptr == '\0') return NULL;
	char *dest = *ptr;
	while (**ptr != '\0') {
		if (**ptr == '\n' || **ptr == '\r' || **ptr == '\t') {
			*(*ptr)++ = '\0';
			break;
		}
		(*ptr)++;
	}
	if (doDup) {
		dest = strdup(dest);
	}
	return dest;
}

const char* uncReplaceFqdnByIp(const char* path)
{
	if (path == NULL || path[0] != '\\' || path[1] != '\\')
		return NULL;
	const char *host = path + 2;
	const char *rest = strchr(host, '\\');
	if (rest == NULL || rest - host >= 200)
		return NULL;
	char name[201];
	strncpy(name, host, rest - host);
	name[rest-host] = '\0';
	dalog("Trying to resolve '%s'...", name);
	struct hostent *remote = gethostbyname(name);
	if (remote == NULL || remote->h_addrtype != AF_INET || remote->h_addr_list[0] == 0)
		return NULL;
	struct in_addr addr;
	addr.s_addr = *(u_long*)remote->h_addr_list[0];
	char *ip = inet_ntoa(addr);
	if (ip == NULL)
		return NULL;
	size_t len = 2 /* \\ */ + strlen(ip) /* 1.2.3.4 */ + strlen(rest) /* \path\to\share */ + 1 /* nullchar */;
	char *retval = malloc(len);
	snprintf(retval, len, "\\\\%s%s", ip, rest);
	dalog("Turned '%s' into '%s'", path, retval);
	return retval;
}

#define FREENULL(x) do { free((void*)(x)); (x) = NULL; } while (0)

static void readShareFile()
{
	if (shareFileOk)
		return;
	memset(drives, 0, sizeof(drives));
	FILE *h = fopen("B:\\shares.dat", "r");
	if (h == NULL) return;
	char creds[300] = "", buffer[500] = "";
	char *skey1 = NULL, *skey2 = NULL;
	if (fgets(creds, sizeof(creds), h) != NULL) {
		char *ptr = creds;
		shost = getToken(&ptr, TRUE);
		sport = getToken(&ptr, TRUE);
		skey1 = getToken(&ptr, FALSE);
		skey2 = getToken(&ptr, FALSE);
		suser = getToken(&ptr, TRUE);
	}
	int idx = 0;
	while (fgets(buffer, sizeof(buffer), h) != NULL && idx < DRIVEMAX) {
		char *ptr = buffer;
		netdrive_t *d = &drives[idx];
		d->path = getToken(&ptr, TRUE);
		d->letter = getToken(&ptr, TRUE);
		d->shortcut = getToken(&ptr, TRUE);
		d->user = getToken(&ptr, TRUE);
		d->pass = getToken(&ptr, TRUE);
		if (d->path == NULL || d->path[0] == '\0')
			goto drive_fail;
		d->success = FALSE;
		// Hack: For unknown reasons, with some servers mounting fails using a fqdn, but using the ip address instead succeeds.
		d->pathIp = uncReplaceFqdnByIp(d->path);
		//
		idx++;
		continue;
drive_fail:
		FREENULL(d->path);
		FREENULL(d->letter);
		FREENULL(d->shortcut);
		FREENULL(d->user);
		FREENULL(d->pass);
	}
	fclose(h);
	if (shost == NULL || sport == NULL || skey1 == NULL || skey2 == NULL || suser == NULL) {
		// Credential stuff missing
		dalog("Incomplete first line in shares.dat");
		return;
	}
	if (*shost == '-' && *sport == '-') {
		dalog("Demo mode detected");
		shareFileOk = TRUE;
		spass = malloc(1);
		*spass = '\0';
		return;
	}
	if (strlen(skey1) != KEYLEN*2 || strlen(skey2) != KEYLEN*2) // Messed up keys
		return;
	if (atoi(sport) < 1000 || atoi(sport) > 65535) // Invalid port
		return;
	bkey1 = hex2bin(skey1);
	bkey2 = hex2bin(skey2);
	if (bkey1 == NULL || bkey2 == NULL)
		return;
	shareFileOk = TRUE;
}

static void udpReceived(SOCKET sock);

LRESULT CALLBACK slxWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg != WM_SOCKDATA) {
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}
	// Socket event
	int event = LOWORD(lParam);
	int errorCode = HIWORD(lParam);
	if (errorCode == 0 && event == FD_READ) {
		udpReceived((SOCKET)wParam);
	}
	return 0;
}

static int queryPasswordDaemon()
{
	// See if preconditions are met
	dalog("in qPD");
	if (!shareFileOk || spass != NULL)
		return 0;
	dalog("...");
	static int wsaInit = 1337;
	static SOCKET sock = INVALID_SOCKET;
	static HWND sockWnd = NULL;
	// Init socket stuff
	if (wsaInit == 1337) {
		WSADATA wsa;
		wsaInit = WSAStartup(MAKEWORD(2, 2), &wsa);
	}
	if (wsaInit != 0)
		return 2;
	// Create window for socket events
	if (sockWnd == NULL) {
		sockWnd = CreateWindowA("STATIC", "OpenSLX mystery window", 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
		if (sockWnd == NULL)
			return GetLastError();
		SetWindowLong(sockWnd, GWL_WNDPROC, (LONG)&slxWindowProc);
	}
	// Create socket
	if (sock == INVALID_SOCKET) {
		sock = socket(AF_INET, SOCK_DGRAM, 0);
		if (sock == INVALID_SOCKET)
			return 3;
		if (WSAAsyncSelect(sock, sockWnd, WM_SOCKDATA, FD_READ) != 0) {
			alog("WSAAsyncSelect returned %d", (int)WSAGetLastError());
		}
	}
	SOCKADDR_IN remote;
	remote.sin_family = AF_INET;
	remote.sin_port = htons((u_short)atoi(sport));
	remote.sin_addr.s_addr = inet_addr(shost);
	// Send out request for password
	if (sendto(sock, (const char*)bkey1, KEYLEN, 0, (struct sockaddr*)&remote, sizeof(remote)) != KEYLEN)
		return 4;
	if (spass == NULL)
		return -1;
	return 0;
}

static void udpReceived(SOCKET sock)
{
	dalog("UDP received");
	int ret;
	uint8_t buffer[200];
	ret = recv(sock, (char*)buffer, sizeof(buffer), 0);
	// See if reply is valid
	if (ret < 2) return;
	uint16_t len = (uint16_t)(((uint16_t)buffer[0] << 8) | buffer[1]);
	if (ret - 2 != len) return;
	// Success
	spass = xorString(buffer + 2, len, bkey2);
	closesocket(sock);
	mountNetworkShares();
}

static DWORD mount(LPNETRESOURCEW share, LPWSTR pass, LPWSTR user)
{
	DWORD retval;
	// Now try to mount
	if ((pass && *pass) || (user && *user)) {
		retval = WNetAddConnection2W(share, pass, user, CONNECT_TEMPORARY | CONNECT_CURRENT_MEDIA);
		if (retval == NO_ERROR) {
			return retval;
		}
		if (retval != ERROR_INVALID_PASSWORD && retval != ERROR_LOGON_FAILURE
				&& retval != ERROR_BAD_USERNAME && retval != ERROR_ACCESS_DENIED
				&& retval != ERROR_SESSION_CREDENTIAL_CONFLICT && retval != ERROR_BAD_NET_NAME
				&& retval != ERROR_NOT_AUTHENTICATED && retval != ERROR_NOT_LOGGED_ON) {
			return retval;
		}
	}
	static wchar_t nuser[BUFLEN] = L"\0", npass[BUFLEN] = L"\0";
	if (nuser[0] == 0 && npass[0] == 0) {
		BOOL ok = TRUE;
		if (suser != NULL) {
			ok = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)suser, -1, (LPWSTR)nuser, BUFLEN) > 0 && ok;
		}
		if (spass != NULL) {
			ok = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)spass, -1, (LPWSTR)npass, BUFLEN) > 0 && ok;
		}
		if (!ok)
			return ERROR_INVALID_PARAMETER;
	}
	retval = WNetAddConnection2W(share, npass, nuser, CONNECT_TEMPORARY | CONNECT_CURRENT_MEDIA);
	return retval;
}

static void postSuccessfulMount(const netdrive_t *d, wchar_t *letter)
{
	wchar_t wShortcut[MAX_PATH] = L"", wUncPath[MAX_PATH];
	BOOL isPrinter = d->letter != NULL && strcmp(d->letter, "PRINTER") == 0;
	MultiByteToWideChar(CP_UTF8, 0, d->path, -1, wUncPath, MAX_PATH);
	if (d->shortcut != NULL && strlen(d->shortcut) != 0) {
		MultiByteToWideChar(CP_UTF8, 0, d->shortcut, -1, wShortcut, MAX_PATH);
	}
	if (wShortcut[0] != '\0' && !isPrinter) {
		wchar_t wLinkName[MAX_PATH], wTarget[MAX_PATH];
		StringCchPrintfW(wLinkName, MAX_PATH, L"%s\\%s.lnk", desktopPath, wShortcut);
		StringCchPrintfW(wTarget, MAX_PATH, L"\"%s\"", wUncPath); // Quote
		DeleteFileW(wLinkName);
		if (letter == NULL || *letter == '\0') {
			createFolderShortcut(wTarget, wLinkName, letter);
		} else if (strstr(d->path, "@SSL") != NULL || strstr(d->path, "webdav") != NULL
				|| strstr(d->path, "WebDav") != NULL || strstr(d->path, "WebDAV") != NULL
				|| strstr(d->path, "@ssl") != NULL || strncmp(d->path, "http", 4) == 0) {
			createFolderShortcut(letter, wLinkName, letter);
		} else {
			createFolderShortcut(wTarget, wLinkName, letter);
		}
		// Fix paths and kill explorer if it's the home directory
		if (_folderStatus != FS_OK && strncmp(d->shortcut, "Home-", 5) == 0) {
			BOOL isVmware = strcmp(d->path, "\\\\vmware-host\\Shared Folders\\home") == 0;
			if (_remapMode == RM_NATIVE_FALLBACK
					|| (isVmware && _remapMode == RM_VMWARE)
					|| (!isVmware && _remapMode == RM_NATIVE)) {
				patchUserPaths(letter);
			}
		}
	}
	if (isPrinter) {
		wchar_t cmdline[MAX_PATH];
		BOOL def = FALSE;
		if (wShortcut[0] != '\0') {
			def = d->shortcut[0] == '@';
			//int offs = (def ? 1 : 0); TODO
		}
		StringCchPrintfW(cmdline, MAX_PATH, L"printui.dll PrintUIEntry /q /in /n \"%s\"", wUncPath);
		SHELLEXECUTEINFOW e = {0}, f = {0};
		e.cbSize = sizeof(e);
		e.lpFile = L"rundll32";
		e.lpParameters = cmdline;
		e.nShow = SW_HIDE;
		f = e; // Copy struct before we set the following flag for the first one
		e.fMask = SEE_MASK_NOCLOSEPROCESS; // So we can wait for the first one
		ShellExecuteExW(&e);
		if (def) { // Only actually do so if we want to set it as default
			WaitForSingleObject(e.hProcess, INFINITE);
		}
		CloseHandle(e.hProcess);
		if (def) {
			StringCchPrintfW(cmdline, MAX_PATH, L"printui.dll PrintUIEntry /q /y /n \"%s\"", wUncPath);
			ShellExecuteExW(&f);
		}
	}
}

static BOOL mountNetworkShare(const netdrive_t *d, BOOL useIp)
{
	wchar_t path[BUFLEN] = L"", user[BUFLEN] = L"", pass[BUFLEN] = L"", letter[10] = L"", shortcut[BUFLEN] = L"";
	int ok = -1;
	if (useIp && (d->pathIp == NULL || d->pathIp[0] == '\0'))
		return FALSE;
	const char *uncPath = useIp ? d->pathIp : d->path;
	if (uncPath == NULL)
		return FALSE;
	ok &= MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)uncPath, -1, (LPWSTR)path, BUFLEN) > 0;
	if (d->letter != NULL) {
		ok &= MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)d->letter, -1, (LPWSTR)letter, 10) > 0;
	}
	if (d->user != NULL) {
		ok &= MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)d->user, -1, (LPWSTR)user, BUFLEN) > 0;
	}
	if (d->pass != NULL) {
		ok &= MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)d->pass, -1, (LPWSTR)pass, BUFLEN) > 0;
	}
	if (d->shortcut != NULL) {
		ok &= MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)d->shortcut, -1, (LPWSTR)shortcut, BUFLEN) > 0;
	}
	if (!ok || path[0] == 0) { // Convert failed/no path - return true anyways since retrying wouldn't change anything
		alog("mountNetworkShare: utf8 to utf16 failed, or path empty (src: '%s')", uncPath);
		return TRUE;
	}
	DWORD retval;
	NETRESOURCEW share = { 0 };
	share.dwType = RESOURCETYPE_DISK;
	share.lpLocalName = letter;
	share.lpRemoteName = path;
	share.lpProvider = NULL;
	if (letter[0] != 0 && letter[0] != '?') { // ? will pick automatically
		// Try with specific letter
		if (wcscmp(L"PRINTER", letter) == 0) {
			// Printer
			letter[0] = 0;
			share.dwType = RESOURCETYPE_PRINT;
			dalog("Is a printer");
		} else if (letter[0] == '-') {
			// No letter, just use as resource
			letter[0] = 0;
			dalog("Is a resource");
		} else {
			letter[1] = ':';
			letter[2] = 0;
			dalog("Is a share with letter");
		}
		// Connect defined share
		retval = mount(&share, pass, user);
		if (retval == NO_ERROR
				|| (share.dwType == RESOURCETYPE_PRINT && retval == ERROR_SESSION_CREDENTIAL_CONFLICT)) {
			postSuccessfulMount(d, letter);
			return TRUE;
		}
		if (retval != ERROR_ALREADY_ASSIGNED && retval != ERROR_DEVICE_ALREADY_REMEMBERED
				&& retval != ERROR_CONNECTION_UNAVAIL) {
			alog("mountNetworkShare: '%s' with letter failed: %d", uncPath, (int)retval);
			return FALSE;
		}
	}
	if (share.dwType == RESOURCETYPE_DISK && letter[0] != '-') {
		// Try to find free drive letter
		letter[1] = ':';
		letter[2] = 0;
		for (letter[0] = 'Z'; letter[0] > 'C'; --letter[0]) {
			retval = mount(&share, pass, user);
			if (retval == ERROR_ALREADY_ASSIGNED || retval == ERROR_DEVICE_ALREADY_REMEMBERED
					|| retval == ERROR_CONNECTION_UNAVAIL)
				continue;
			if (retval == NO_ERROR) {
				postSuccessfulMount(d, letter);
				return TRUE;
			}
			alog("mountNetworkShare: '%s' without letter failed: %d", uncPath, (int)retval);
			if (retval == ERROR_INVALID_PASSWORD || retval == ERROR_LOGON_FAILURE
					|| retval == ERROR_BAD_USERNAME || retval == ERROR_ACCESS_DENIED
					|| retval == ERROR_SESSION_CREDENTIAL_CONFLICT) {
				return TRUE;
			}
			return FALSE;
		}
	}
	return FALSE;
}

static BOOL mountNetworkShares()
{
	if (!shareFileOk)
		return TRUE;
	if (spass == NULL)
		return FALSE;
	int failCount = 0;
	for (int i = 0; i < DRIVEMAX; ++i) {
		if (drives[i].path == NULL)
			break;
		if (drives[i].success)
			continue;
		if (mountNetworkShare(&drives[i], FALSE)) {
			drives[i].success = TRUE;
		} else if (mountNetworkShare(&drives[i], TRUE)) {
			drives[i].success = TRUE;
		} else {
			failCount++;
		}
	}
	if (failCount > 0)
		return FALSE;
	return TRUE;
}

static void remapViaSharedFolder()
{
	static const char* homeDirA = "\\\\vmware-host\\Shared Folders\\home"; // thiscase!
	static const wchar_t* homeDirW = L"\\\\vmware-host\\shared folders\\home"; // lowercase!
	static BOOL once = FALSE;
	if (once) return;
	once = TRUE;
	netdrive_t d;
	d.path = homeDirA;
	d.letter = _remapHomeDrive;
	d.shortcut = "Home-Verzeichnis";
	d.user = "";
	d.pass = "";
	d.success = FALSE;
	// See if it's already mapped
	wchar_t letter[5] = L"C:\\";
	char buffer[600];
	UNIVERSAL_NAME_INFOW *uni = (UNIVERSAL_NAME_INFOW*)buffer;
	for (letter[0] = 'D'; letter[0] <= 'Z'; ++letter[0]) {
		//wlog(L"Checking %s", letter);
		DWORD len = (DWORD)sizeof(buffer);
		if (NO_ERROR == WNetGetUniversalNameW(letter, UNIVERSAL_NAME_INFO_LEVEL, uni, &len)) {
			_wcslwr(uni->lpUniversalName);
			//wlog(L"Is %s", uni->lpUniversalName);
			if (wcscmp(uni->lpUniversalName, homeDirW) == 0) {
				letter[2] = '\0';
				postSuccessfulMount(&d, letter);
				return;
			}
		}
	}
	// Map vmware shared folder
	mountNetworkShare(&d, FALSE);
}

static char* xorString(const uint8_t* text, int len, const uint8_t* key)
{
	int i;
	uint8_t *retval = malloc(len + 1);
	uint8_t *ptr = retval;
	for (i = 0; i < len; ++i) {
		ptr[i] = text[i] ^ key[i % KEYLEN];
	}
	ptr[len] = '\0';
	return (char*)retval;
}

static int getbin(int x)
{
	if (x >= '0' && x <= '9')
		return x - '0';
	if (x >= 'A' && x <= 'F')
		return x - 'A' + 10;
	return x - 'a' + 10;
}

static uint8_t* hex2bin(char *szHexString)
{
	int size = strlen(szHexString) / 2, i;
	char *p = szHexString;
	uint8_t *pBinary = malloc(size);

	for(i = 0; i < size; i++, p += 2) {
		pBinary[i] = (uint8_t)((getbin(p[0]) << 4) | getbin(p[1]));
	}
	return pBinary;
}

// Stuff for creating a simple shortcut (.lnk)

static HRESULT createFolderShortcut(wchar_t* targetDir, wchar_t* linkFile, wchar_t* comment)
{
	HRESULT       hRes;                  /* Returned COM result code */
	IShellLink*   pShellLink;            /* IShellLink object pointer */
	IPersistFile* pPersistFile;          /* IPersistFile object pointer */

	hRes = E_INVALIDARG;
	if (
		(targetDir != NULL) && (wcslen(targetDir) > 0) &&
		(linkFile != NULL) && (wcslen(linkFile) > 0)
	) {
		hRes = CoCreateInstance(
					&CLSID_ShellLink,     /* pre-defined CLSID of the IShellLink object */
					NULL,                 /* pointer to parent interface if part of aggregate */
					CLSCTX_INPROC_SERVER, /* caller and called code are in same process */
					&IID_IShellLink,      /* pre-defined interface of the IShellLink object */
					(void**)&pShellLink); /* Returns a pointer to the IShellLink object */
		if (SUCCEEDED(hRes)) {
			wchar_t explorer[MAX_PATH];
			StringCchPrintfW(explorer, MAX_PATH, L"\"%s\\explorer.exe\"", windowsPath);
			// Set the fields in the IShellLink object
			hRes = pShellLink->lpVtbl->SetPath(pShellLink, explorer);
			hRes = pShellLink->lpVtbl->SetArguments(pShellLink, targetDir);
			if (comment != NULL) {
				hRes = pShellLink->lpVtbl->SetDescription(pShellLink, comment);
			}
			if (winVer.dwMajorVersion >= 6) { // Vista+
				StringCchPrintfW(explorer, MAX_PATH, L"%s\\system32\\imageres.dll", windowsPath);
				hRes = pShellLink->lpVtbl->SetIconLocation(pShellLink, explorer, 137);
			} else {
				StringCchPrintfW(explorer, MAX_PATH, L"%s\\system32\\shell32.dll", windowsPath);
				hRes = pShellLink->lpVtbl->SetIconLocation(pShellLink, explorer, 85);
			}

			/* Use the IPersistFile object to save the shell link */
			hRes = pShellLink->lpVtbl->QueryInterface(
					pShellLink,                /* existing IShellLink object */
					&IID_IPersistFile,         /* pre-defined interface of the IPersistFile object */
					(void**)&pPersistFile);    /* returns a pointer to the IPersistFile object */
			if (SUCCEEDED(hRes)) {
				hRes = pPersistFile->lpVtbl->Save(pPersistFile, linkFile, TRUE);
				pPersistFile->lpVtbl->Release(pPersistFile);
			}
			pShellLink->lpVtbl->Release(pShellLink);
		}

	}
	return (hRes);
}

// Patch user directories

static BOOL patchRegPath(BOOL *patchOk, BOOL *anyMapped, HKEY hKey, wchar_t *letter, wchar_t *value, ...)
{
	wchar_t *folder = NULL;
	wchar_t first[MAX_PATH] = {0};
	wchar_t path[MAX_PATH];
	wchar_t oldvalue[MAX_PATH];
	va_list args;
	LONG ret;
	DWORD type;
	DWORD len;
	// Let's check the path in the registry first
	len = (DWORD)(sizeof(oldvalue) - sizeof(wchar_t));
	ret = RegQueryValueExW(hKey, value, NULL, &type, (BYTE*)oldvalue, &len);
	if (ret == ERROR_SUCCESS && (type == REG_EXPAND_SZ || type == REG_SZ)) {
		len /= 2;
		oldvalue[len] = '\0';
		if (towlower(oldvalue[0]) == towlower(letter[0]) && folderExists(oldvalue)) // Same drive, folder exists, yay
			return TRUE;
	}
	// Old registry value doesn't fit - figure out new value
	va_start(args, value);
	while ((folder = va_arg(args, wchar_t*)) != NULL) {
		StringCchPrintfW(path, MAX_PATH, L"%s\\%s", letter, folder);
		if (folderExists(path))
			break;
		if (*first == 0) {
			wcsncpy(first, path, MAX_PATH);
		}
	}
	va_end(args);
	if (folder != NULL) {
		// Found something existing
		folder = path;
	} else if (!_createMissingRemap) {
		// Nothing found, must not create
		wlog(L"Cannot remap %s to %s: target not found!", value, first);
		return FALSE;
	} else {
		// Nothing found, use first element of list and create it
		folder = first;
		CreateDirectoryW(folder, NULL);
	}
	_wcslwr(folder);
	_wcslwr(oldvalue);
	if (wcscmp(folder, oldvalue) == 0) {
		// Path already in registry, don't update
		return TRUE;
	}
	ret = RegSetValueExW(hKey, value, 0, REG_SZ, (BYTE*)folder, (wcslen(folder) + 1) * sizeof(wchar_t));
	if (ret == ERROR_SUCCESS) {
		*anyMapped = TRUE;
		return TRUE;
	}
	wlog(L"Setting reg key %s to %s failed (return value %ld)", value, folder, (long)ret);
	*patchOk = FALSE;
	return FALSE;
}

typedef HANDLE (WINAPI *SNTYPE)(DWORD, DWORD);
typedef BOOL (WINAPI *P32TYPE)(HANDLE, PROCESSENTRY32W*);

static void patchUserPaths(wchar_t *letter)
{
	LONG ret;
	HKEY hKey;
	BOOL patchOk = TRUE;
	BOOL killOk = FALSE;
	BOOL anyMapped = FALSE;
	_folderStatus = FS_ERROR;
	ret = RegOpenKeyExW(HKEY_CURRENT_USER,
			L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
			0, KEY_WOW64_64KEY | KEY_READ | KEY_WRITE, &hKey);
	if (ret != ERROR_SUCCESS) {
		alog("Opening registry for patching of pathes failed with return code %ld", (long)ret);
		return;
	}
	// Ha!
	const BOOL win10 = winVer.dwMajorVersion >= 10;
	if (remap.other) {
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{56784854-C6CB-462B-8169-88E350ACB882}", L"Contacts", L"Profile\\Contacts", L"Kontakte", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"Favorites", L"Favorites", L"Profile\\Favorites", L"Favoriten", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}", L"Searches", L"Profile\\Searches", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}", L"Links", L"Profile\\Links", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}", L"Saved Games", L"SavedGames", L"Profile\\SavedGames", NULL);
	}
	if (remap.media) {
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"My Video", L"Videos", L"My Videos", L"Eigene Videos", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"My Pictures", L"Pictures", L"My Pictures", L"Eigene Bilder", L"Bilder", NULL);
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"My Music", L"Music", L"My Music", L"Eigene Musik", L"Musik", NULL);
		if (win10) {
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{35286a68-3c57-41a1-bbb1-0eae73d76c95}", L"Videos", L"My Videos", L"Eigene Videos", NULL);
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{0ddd015d-b06c-45d5-8c4c-f59713854639}", L"Pictures", L"My Pictures", L"Eigene Bilder", L"Bilder", NULL);
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{a0c69a99-21c8-4671-8703-7934162fcf1d}", L"Music", L"My Music", L"Eigene Musik", L"Musik", NULL);
		}
	}
	if (remap.downloads) {
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{374DE290-123F-4565-9164-39C4925E467B}", L"Downloads", L"Profile\\Downloads", NULL);
		if (win10) {
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{7d83ee9b-2244-4e70-b1f5-5393042af1e4}", L"Downloads", L"Profile\\Downloads", NULL);
		}
	}
	if (remap.documents) {
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"Personal", L"Documents", L"Dokumente", L"My Documents", L"Eigene Dateien", NULL);
		if (win10) {
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{f42ee2d3-909f-4907-8871-4c22fc0bf756}", L"Documents", L"Dokumente", L"My Documents", L"Eigene Dateien", NULL);
		}
	}
	if (remap.desktop) {
		patchRegPath(&patchOk, &anyMapped, hKey, letter, L"Desktop", L"Windows Desktop", L"Desktop", L"Arbeitsfl\u00E4che", NULL);
		if (win10) {
			patchRegPath(&patchOk, &anyMapped, hKey, letter, L"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", L"Windows Desktop", L"Desktop", L"Arbeitsfl\u00E4che", NULL);
		}
	}
	RegCloseKey(hKey);
	if (!anyMapped) {
		_folderStatus = FS_OK;
		return;
	}
	// Kill explorer
	// Late binding
	if (hKernel32 == NULL || winVer.dwMajorVersion < 5 || (winVer.dwMajorVersion == 5 && winVer.dwMinorVersion < 1)) {
		return;
	}
	SNTYPE aCreateSnapshot;
	P32TYPE aProcessFirst, aProcessNext;
	aCreateSnapshot = (SNTYPE)GetProcAddress(hKernel32, "CreateToolhelp32Snapshot");
	if (aCreateSnapshot == NULL) {
		alog("No CreateToolhelp32Snapshot in kernel.dll");
		return;
	}
	aProcessFirst = (P32TYPE)GetProcAddress(hKernel32, "Process32FirstW");
	if (aProcessFirst == NULL) {
		alog("No Process32FirstW in kernel.dll");
		return;
	}
	aProcessNext = (P32TYPE)GetProcAddress(hKernel32, "Process32NextW");
	if (aProcessNext == NULL) {
		alog("No Process32NextW in kernel.dll");
		return;
	}
	PROCESSENTRY32W entry;
	entry.dwSize = sizeof(PROCESSENTRY32W);
	HANDLE snapshot = (aCreateSnapshot)(TH32CS_SNAPPROCESS, 0);
	if (snapshot != INVALID_HANDLE_VALUE && (aProcessFirst)(snapshot, &entry)) {
		do {
			if (_wcsicmp(entry.szExeFile, L"explorer.exe") == 0) {
				HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
				if (hProcess == NULL) {
					alog("Cannot OpenProcess explorer.exe: Remapped paths will not work properly (GetLastError=%d)", (int)GetLastError());
				} else {
					if (TerminateProcess(hProcess, 23)) {
						killOk = TRUE;
					}
					CloseHandle(hProcess);
				}
			}
		} while ((aProcessNext)(snapshot, &entry));
	} else {
		alog("Could not get process list");
	}
	CloseHandle(snapshot);
	if (patchOk && killOk) {
		_folderStatus = FS_OK;
	}
}