/*
 * ************************************************************************************************************
 * Author: Marcin Załęczny
 * Copyright (C)2010 Marcin Załęczny <mzaleczny@hotmail.com>
 * License: The GNU Lesser General Public License, version 3.0 (LGPLv3)
 * Library Name: mzstd
 * Version: 1.07
 * First release date:   2010-11-08
 * Current release date: 2011-03-04
 *
 *
 * This file is part of mzstd library
 * 
 * mzstd library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * mzstd library 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * ************************************************************************************************************
 */

//#include <stdio.h>
#include "mzFileUtils.h"
#include "mzArrayUtils.h"
#include "mzStringList.h"
#include "mzMessageBox.h"

DWORD WINAPI GetFileSize(CString fname) {
	HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (fp == INVALID_HANDLE_VALUE) {
		return 0;
	}

	LARGE_INTEGER size;
	GetFileSizeEx(fp, &size);
	CloseHandle(fp);
	return size.LowPart;
}


CString WINAPI ReadFile(CString fname, LONG encoding) {
	DWORD fsize = GetFileSize(fname);

	HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (fp == INVALID_HANDLE_VALUE) {
		return "";
	}

	DWORD read;
	CHAR *buf = new CHAR[fsize];
	ReadFile(fp, buf, fsize, &read, NULL);
	CloseHandle(fp);

	if (encoding == 0) {
		encoding = DetectDataEncoding((BYTE*)buf, read);
	}

	WCHAR *outbuf = NULL;
	ULONG outbuf_len;

	// jeśli mamy ISO2, to konwertujemy na ANSI
	if (encoding == ENC_ISO2) {
		Iso2ToAnsi((BYTE*)buf, read);
		encoding = ENC_ANSI;
	}

	switch (encoding) {
	case ENC_UTF8_WITH_BOM:
		outbuf_len = read + 1;
		outbuf = new WCHAR[outbuf_len];
		MultiByteToWideChar(CP_UTF8, 0, &buf[3], -1, outbuf, read);
		break;
	case ENC_UTF8:
		outbuf_len = read + 1;
		outbuf = new WCHAR[outbuf_len];
		MultiByteToWideChar(CP_UTF8, 0, &buf[0], -1, outbuf, read);
		break;
	case ENC_UNICODE:
		outbuf_len = read / 2 + 1;
		if (read % 2) {
			read--;
		}
		outbuf = new WCHAR[outbuf_len];
		memcpy(outbuf, buf, read);
		break;
	case ENC_ANSI:
		outbuf_len = read + 1;
		outbuf = new WCHAR[outbuf_len];
		MultiByteToWideChar(CP_ACP, 0, &buf[0], -1, outbuf, read);
		break;
	}


	delete[] buf;
	if (outbuf) {
		outbuf[outbuf_len - 1] = NULL;
		CString content(outbuf, outbuf_len);
		return content;
	}

	return "";
}


LONG WINAPI ReadBinaryFile(CString fname, BYTE* buffer) {
    if (!buffer) {
        return 0;
    }

	HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (fp == INVALID_HANDLE_VALUE) {
		return 0;
	}

	DWORD read;
    DWORD chunk = 8192;
    DWORD offset = 0;
    do {
	    ReadFile(fp, &buffer[offset], chunk, &read, NULL);
        offset += read;
    } while (read == chunk);
	CloseHandle(fp);

	return offset;
}

DWORD WINAPI AppendToFile(HANDLE fh, WCHAR* content, LONG encoding) {
	// ostatnie plus 3 jest na wypadek UTF8 with BOM
	ULONG output_buf_len = wcslen(content) * 2 + 3;
	BYTE *output_buf = new BYTE[output_buf_len];
    if (!output_buf) {
        return 0;
    }
    
	ULONG bytesToWrite;
	LPSTR defaultChar = ".";
	BOOL usedDefaultChar;

	switch (encoding) {
	case ENC_UTF8_WITH_BOM:
		output_buf[0] = (BYTE)0xef;
		output_buf[1] = (BYTE)0xbb;
		output_buf[2] = (BYTE)0xbf;
		bytesToWrite = WideCharToMultiByte(CP_UTF8, 0, content, -1, (LPSTR)&output_buf[3], output_buf_len - 3, NULL, NULL);
		bytesToWrite -= 1;
		bytesToWrite += 3;
		break;
	case ENC_UTF8:
		bytesToWrite = WideCharToMultiByte(CP_UTF8, 0, content, -1, (LPSTR)output_buf, output_buf_len, NULL, NULL);
		bytesToWrite -= 1;
		break;
	case ENC_UNICODE:
		bytesToWrite = wcslen(content) * 2;
		memcpy(output_buf, content, bytesToWrite);
		break;
	case ENC_ANSI:
		bytesToWrite = WideCharToMultiByte(CP_ACP, 0, content, -1, (LPSTR)output_buf, output_buf_len, defaultChar, &usedDefaultChar);
		bytesToWrite -= 1;
		break;
	case ENC_ISO2:
		bytesToWrite = WideCharToMultiByte(CP_ACP, 0, content, -1, (LPSTR)output_buf, output_buf_len, defaultChar, &usedDefaultChar);
		bytesToWrite -= 1;
		AnsiToIso2(output_buf, bytesToWrite);
		break;
	}

	if (bytesToWrite < 1) {
		return 0;
	}

	DWORD bytesWritten = 0;
	DWORD dwPos = SetFilePointer(fh, 0, NULL, FILE_END);
	LockFile(fh, dwPos, 0, bytesToWrite, 0);
	WriteFile(fh, output_buf, bytesToWrite, &bytesWritten, NULL);
	UnlockFile(fh, dwPos, 0, bytesToWrite, 0);
    
    delete[] output_buf;
    return bytesWritten;
}

DECLSPEC_MZSTD DWORD WINAPI AppendToFile(HANDLE fh, BYTE* data, ULONG datasize) {
	DWORD bytesWritten = 0;
	DWORD dwPos = SetFilePointer(fh, 0, NULL, FILE_END);
	LockFile(fh, dwPos, 0, datasize, 0);
	WriteFile(fh, data, datasize, &bytesWritten, NULL);
	UnlockFile(fh, dwPos, 0, datasize, 0);    
    return bytesWritten;
}

BOOL WINAPI WriteToFile(CString fname, CString content, LONG encoding, DWORD creationDisposition) {
	HANDLE fh = CreateFile(fname.getBuffer(), GENERIC_WRITE, FILE_SHARE_READ, NULL, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);

	if (fh == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
    DWORD bytesWritten = AppendToFile(fh, content.getBuffer(), encoding);    
	CloseHandle(fh);

	if (bytesWritten > 0) {
		return TRUE;
	} else {
		return FALSE;
	}
}

BOOL WINAPI WriteFile(CString fname, CString content, LONG encoding) {
    return WriteToFile(fname, content, encoding, CREATE_ALWAYS);
}

BOOL WINAPI AppendToFile(CString fname, CString content, LONG encoding) {
    return WriteToFile(fname, content, encoding, OPEN_ALWAYS);
}

BOOL WINAPI WriteFile(CString fname, CStringList *lst, LONG encoding) {
	HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (fp == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	ULONG output_buf_len = 0;
	BYTE *output_buf = NULL;
	ULONG bytesToWrite;
	LPSTR defaultChar = ".";
	BOOL usedDefaultChar;
	CString content;
	DWORD bytesWritten;
	DWORD dwPos;

	for (ULONG i = 0; i < lst->size(); i++) {
		content = lst->getAt(i);

		// ostatnie plus 3 jest na wypadek UTF8 with BOM
		if (output_buf_len < content.getBufferSize() * 2 + 3) {
			if (output_buf) {
				delete[] output_buf;
			}
			output_buf_len = content.getBufferSize() * 2 + 3;
			output_buf = new BYTE[output_buf_len];
		}


		switch (encoding) {
		case ENC_UTF8_WITH_BOM:
			output_buf[0] = (BYTE)0xef;
			output_buf[1] = (BYTE)0xbb;
			output_buf[2] = (BYTE)0xbf;
			bytesToWrite = WideCharToMultiByte(CP_UTF8, 0, content.getBuffer(), -1, (LPSTR)&output_buf[3], output_buf_len - 3, NULL, NULL);
			bytesToWrite -= 1;
			bytesToWrite += 3;
			break;
		case ENC_UTF8:
			bytesToWrite = WideCharToMultiByte(CP_UTF8, 0, content.getBuffer(), -1, (LPSTR)output_buf, output_buf_len, NULL, NULL);
			bytesToWrite -= 1;
			break;
		case ENC_UNICODE:
			bytesToWrite = content.length() * 2;
			memcpy(output_buf, content.getBuffer(), bytesToWrite);
			break;
		case ENC_ANSI:
			bytesToWrite = WideCharToMultiByte(CP_ACP, 0, content.getBuffer(), -1, (LPSTR)output_buf, output_buf_len, defaultChar, &usedDefaultChar);
			bytesToWrite -= 1;
			break;
		case ENC_ISO2:
			bytesToWrite = WideCharToMultiByte(CP_ACP, 0, content.getBuffer(), -1, (LPSTR)output_buf, output_buf_len, defaultChar, &usedDefaultChar);
			bytesToWrite -= 1;
			AnsiToIso2(output_buf, bytesToWrite);
			break;
		}

		if (bytesToWrite < 1) {
			continue;
		}


		dwPos = SetFilePointer(fp, 0, NULL, FILE_END);
		LockFile(fp, dwPos, 0, bytesToWrite, 0);
		WriteFile(fp, output_buf, bytesToWrite, &bytesWritten, NULL);
		UnlockFile(fp, dwPos, 0, bytesToWrite, 0);
	}

	CloseHandle(fp);
	delete[] output_buf;

	return TRUE;
}

BOOL WINAPI WriteFile(CString fname, BYTE* data, DWORD dataLength) {
	HANDLE fp = CreateFile(fname.getBuffer(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (fp == INVALID_HANDLE_VALUE) {
		return FALSE;
	}

	DWORD bytesToWrite = 8192;
    if (dataLength < bytesToWrite) {
        bytesToWrite = dataLength;
    }
    DWORD offset = 0;

    DWORD bytesWritten;
	DWORD dwPos;

    while (offset < dataLength) {
        dwPos = SetFilePointer(fp, 0, NULL, FILE_END);
	    LockFile(fp, dwPos, 0, bytesToWrite, 0);
	    WriteFile(fp, &data[offset], bytesToWrite, &bytesWritten, NULL);
	    UnlockFile(fp, dwPos, 0, bytesToWrite, 0);
        if (bytesWritten != bytesToWrite) {
            break;
        }
        offset += bytesWritten;
        if (dataLength - offset < bytesToWrite) {
            bytesToWrite = dataLength - offset;
        }
    }
	CloseHandle(fp);

    return offset == dataLength;
}

BOOL WINAPI DirectoryExists(CString dirname) {
    DWORD attribs = GetFileAttributes(dirname.getBuffer());
    if (attribs == INVALID_FILE_ATTRIBUTES) {
        return false;
    }

    return (attribs & FILE_ATTRIBUTE_DIRECTORY);
}

BOOL WINAPI FileExists(CString fname) {
    DWORD attribs = GetFileAttributes(fname.getBuffer());
    if (attribs == INVALID_FILE_ATTRIBUTES) {
        return false;
    }

    return (!(attribs & FILE_ATTRIBUTE_DIRECTORY));
}

CString WINAPI GetCurrentDirectory() {
	WCHAR buf[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, buf);
	return buf;
}

CStringList* WINAPI GetDirectoryContent(CString dir) {
    WIN32_FIND_DATA fd;
    HANDLE hDir = INVALID_HANDLE_VALUE;
    DWORD dwError = 0;
    CStringList *lstDirs = NULL, *lstFiles = NULL;
    CStringList *lstAll = NULL;

    if (dir.length() > MAX_PATH) {
        return NULL;
    }

    if (dir.lastChar() != L'\\') {
        dir += L'\\';
    }

    if (dir.lastChar() != L'*') {
        dir += L'*';
    }

    // Find the first file in the directory.
    hDir = FindFirstFile(dir.getBuffer(), &fd);
    if (hDir == INVALID_HANDLE_VALUE) {
        ShowLastSystemError();
        return NULL;
    }

    lstDirs = new CStringList();
    lstFiles = new CStringList();
    lstAll = new CStringList();
    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            lstDirs->append(CString(fd.cFileName));
        } else {
            lstFiles->append(CString(fd.cFileName));
        }
    } while (FindNextFile(hDir, &fd) != 0);

    dwError = GetLastError();
    if (dwError != ERROR_NO_MORE_FILES) {
        FindClose(hDir);
        ShowLastSystemError();
    }

    FindClose(hDir);

    lstAll->append(lstDirs);
    lstAll->append(lstFiles);

    delete lstDirs;
    delete lstFiles;

    return lstAll;
}

CStringList* WINAPI GetDirectoryDirs(CString dir) {
    WIN32_FIND_DATA fd;
    HANDLE hDir = INVALID_HANDLE_VALUE;
    DWORD dwError = 0;
    CStringList *lstDirs = NULL;

    if (dir.length() > MAX_PATH) {
        return NULL;
    }

    if (dir.lastChar() != L'\\') {
        dir += L'\\';
    }

    if (dir.lastChar() != L'*') {
        dir += L'*';
    }

    // Find the first file in the directory.
    hDir = FindFirstFile(dir.getBuffer(), &fd);
    if (hDir == INVALID_HANDLE_VALUE) {
        ShowLastSystemError();
        return NULL;
    }

    lstDirs = new CStringList();
    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            lstDirs->append(CString(fd.cFileName));
        }
    } while (FindNextFile(hDir, &fd) != 0);

    dwError = GetLastError();
    if (dwError != ERROR_NO_MORE_FILES) {
        FindClose(hDir);
        ShowLastSystemError();
    }

    FindClose(hDir);

    return lstDirs;
}

CStringList* WINAPI GetDirectoryFiles(CString dir, CString ext) {
    WIN32_FIND_DATA fd;
    HANDLE hDir = INVALID_HANDLE_VALUE;
    DWORD dwError = 0;
    CStringList *lstFiles = NULL;
    BOOL checkExtension = ext.length() > 0;

    if (dir.length() > MAX_PATH) {
        return NULL;
    }

    if (dir.lastChar() != L'\\') {
        dir += L'\\';
    }

    if (dir.lastChar() != L'*') {
        dir += L'*';
    }

    // Find the first file in the directory.
    hDir = FindFirstFile(dir.getBuffer(), &fd);
    if (hDir == INVALID_HANDLE_VALUE) {
        ShowLastSystemError();
        return NULL;
    }

    lstFiles = new CStringList();
    do {
        if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            if (checkExtension) {
                if (ext == CString(fd.cFileName).strAfterLast(L'.')) {
                    lstFiles->append(CString(fd.cFileName));
                }
            } else {
                lstFiles->append(CString(fd.cFileName));
            }
        }
    } while (FindNextFile(hDir, &fd) != 0);

    dwError = GetLastError();
    if (dwError != ERROR_NO_MORE_FILES) {
        FindClose(hDir);
        ShowLastSystemError();
    }

    FindClose(hDir);

    return lstFiles;
}

CString WINAPI GetExt(CString path) {
    return path.strAfterLast(L'.');
}

CString WINAPI GetPathname(CString path) {
    // if we have only disk drive path ex. 'C:\\', then we return empty string
    if (path.length() < 4) {
        return COMMON_EMPTY_STRING;
    }

    if (path.contains(L'/') || path.contains(L'\\')) {
        return path.strAfterLastChar(L"/\\");
    }
    return path;
}

CString WINAPI GetPathname(CString path, CString rtrimExt) {
    CString result = GetPathname(path);

    if (result == rtrimExt) {
        return COMMON_EMPTY_STRING;
    }

    LONG resultLen = result.length();
    LONG rtrimextLen = rtrimExt.length();

    if (resultLen == rtrimextLen) {
        return result;
    }

    if (resultLen > rtrimextLen) {
        WCHAR* pend = result.getBuffer();
        pend = pend + resultLen - rtrimextLen;
        if (rtrimExt == pend) {
            return result.substr(0, resultLen - rtrimextLen);
        }
        return result;
    }

    return COMMON_EMPTY_STRING;
}

CString WINAPI GetPathnameNoExt(CString path) {
    CString result = GetPathname(path);
    CString rtrimExt = CString(L".") + GetExt(result);

    return GetPathname(result, rtrimExt);
}

CString WINAPI GetBasename(CString path) {
    CString realPath = RealPath(path);
    if (FileExists(realPath)) {
        realPath.cutAtLastChar(L"/\\");
        return realPath;
    } else {
        if (DirectoryExists(realPath)) {
            return realPath;
        }

        // Not existing path - if has dot then assuming file else directory
        if (realPath.contains(L".")) {
            realPath.cutAtLastChar(L"/\\");
        }

        return realPath;
    }

    // we should not be here
    return realPath;
}

CString WINAPI GetParentDir(CString path) {
    if (path == L"\\\\") {
        return COMMON_EMPTY_STRING;
    }
    LONG pathLen = path.length();
    if (pathLen == 1 || ((pathLen == 2 || pathLen == 3) && path.getCharAt(1) == L':')) {
        return COMMON_EMPTY_STRING;
    }

    CString realPath = RealPath(path);
    realPath.cutAtLastChar(L"/\\");
    WCHAR* buf = realPath.getBuffer();
    LONG bufLen = wcslen(buf);

    if (bufLen == 1 && buf[0] == L'\\') {
        realPath += L'\\';
        return realPath;
    }

    if (bufLen == 2 && buf[1] == L':') {
        realPath += L'\\';
        return realPath;
    }

    return realPath;
}

VOID WINAPI TruncateEndingPathSeparator(CString& path) {
    path.rtrimAllChars(L"/\\");
}

VOID WINAPI TruncateStartingPathSeparator(CString& path) {
    path.ltrimAllChars(L"/\\");
}

VOID WINAPI TrimPathSeparators(CString& path) {
    path.rtrimAllChars(L"/\\");
    path.ltrimAllChars(L"/\\");
}

CString WINAPI RealPath(CString path) {
    WCHAR* buf = path.getBuffer();
    if (!buf || buf[0] == NULL) {
        return path;
    }

    CStringList* pathParts = GetPathnameParts(path);
    ULONG pathPartsCount = pathParts->size();

    CString resultPath = COMMON_EMPTY_STRING;
    ULONG i = 0;
    CString ppart;
    WCHAR ch;

    while (i < pathPartsCount) {
        ppart = pathParts->getAt(i);
        i++;

        if (resultPath == COMMON_EMPTY_STRING) {
            if (ppart == L"\\\\") {
                resultPath = ppart;
                continue;
            }
            if (ppart.length() > 1 && ppart.getCharAt(1) == L':') {
                if (ppart.length() > 2 && ppart.getCharAt(2) == L'\\') {
                    resultPath = ppart;
                    continue;
                }
                resultPath = ppart + L"\\";
                continue;
            }

            resultPath = GetCurrentDirectory() + L"\\" + ppart;
            continue;
        }

        if (ppart == L".") {
            continue;
        }

        TruncateEndingPathSeparator(resultPath);

        if (ppart == L"..") {
            if (resultPath.containsOneOfChars(L"/\\")) {
                resultPath.cutAtLastChar(L"/\\");
            }
            continue;
        }

        resultPath += CString(L"\\") + ppart;
    }
    delete pathParts;

    return resultPath;
}

DECLSPEC_MZSTD CStringList* WINAPI GetPathnameParts(CString path) {
    if (path.startsWith(L"\\\\")) {
        CString sub = path.substr(2);
        CStringList* lst = sub.explodeAtChar(L"/\\");
        lst->addAsFirst(L"\\\\");
        return lst;
    }

    return path.explodeAtChar(L"/\\");
}

DECLSPEC_MZSTD BOOL WINAPI CreateDirectory(CString dir, BOOL createAll) {
    if (!createAll) {
        CreateDirectory((LPCWSTR)dir.getBuffer(), (LPSECURITY_ATTRIBUTES)NULL);
        if (!DirectoryExists(dir)) {
            return FALSE;
        }
        return TRUE;
    }

    CString realPath = RealPath(dir);
    CStringList* dirs = GetPathnameParts(realPath);
    if (!dirs) {
        return FALSE;
    }
    LONG count = dirs->size();

    if (count > 0) {
        CString partDir = dirs->getAt(0);
        if (!PathEndsWithPathSeparator(partDir)) {
            partDir += L"\\";
        }

        for (LONG i = 1; i < count; i++) {
            if (i == 1) {
                partDir += dirs->getAt(i);
            } else {
                partDir += CString(L"\\") + dirs->getAt(i);
            }

            if (!DirectoryExists(partDir)) {
                CreateDirectory((LPCWSTR)partDir.getBuffer(), (LPSECURITY_ATTRIBUTES)NULL);
                if (!DirectoryExists(partDir)) {
                    delete dirs;
                    return FALSE;
                }
            }
        }

        delete dirs;
        return TRUE;
    }

    delete dirs;
    return FALSE;
}

BOOL WINAPI DeleteDirectory(CString dir) {
    WIN32_FIND_DATA fd;
    HANDLE hDir = INVALID_HANDLE_VALUE;
    DWORD dwError = 0;
    BOOL result = TRUE;

    CString allFilesMask = dir;
    CString item;

    if (!DirectoryExists(dir)) {
        return FALSE;
    }

    AddPathPart(allFilesMask, L"*");
    hDir = FindFirstFile(allFilesMask.getBuffer(), &fd);
    if (hDir == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0) {
                continue;
            }
            item = dir;
            AddPathPart(item, fd.cFileName);
            //wprintf(L"Deleting subdirectory: %s\n", item.getBuffer());
            DeleteDirectory(item);
        } else {
            item = dir;
            AddPathPart(item, fd.cFileName);
            SetFileAttributes(item.getBuffer(), FILE_ATTRIBUTE_NORMAL);
            //wprintf(L"Deleting file: %s\n", item.getBuffer());
            DeleteFile(item.getBuffer());
        }
    } while (FindNextFile(hDir, &fd) != 0);
    FindClose(hDir);

    //wprintf(L"Deleting directory: %s\n", dir.getBuffer());
    result = RemoveDirectory(dir.getBuffer());
    return result;
}

BOOL WINAPI PathEndsWithPathSeparator(CString& path) {
    return path.endsWith(L"/\\");
}

BOOL WINAPI PathStartsWithPathSeparator(CString& path) {
    return path.startsWith(L"/\\");
}

VOID WINAPI AddPathPart(CString& path, CString partPathname) {
    TrimPathSeparators(partPathname);
    if (PathEndsWithPathSeparator(path)) {
        path += partPathname;
    } else {
        path += CString(L"\\") + partPathname;
    }
}

CString WINAPI GetWindowsDirectory() {
    WCHAR buf[MAX_PATH];
    GetWindowsDirectory((WCHAR*)buf, MAX_PATH);
    return buf;
}

CString WINAPI GetSystemDirectory() {
    WCHAR buf[MAX_PATH];
    GetSystemDirectory((WCHAR*)buf, MAX_PATH);
    return buf;
}
