/* Copyright (c) Mark Harmstone 2016-17
 *
 * This file is part of WinBtrfs.
 *
 * WinBtrfs is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public Licence as published by
 * the Free Software Foundation, either version 3 of the Licence, or
 * (at your option) any later version.
 *
 * WinBtrfs 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 Licence for more details.
 *
 * You should have received a copy of the GNU Lesser General Public Licence
 * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */

#include "shellext.h"
#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>
#include <stddef.h>
#include <stdexcept>
#include "factory.h"
#include "resource.h"

static const GUID CLSID_ShellBtrfsIconHandler = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf0 } };
static const GUID CLSID_ShellBtrfsContextMenu = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf1 } };
static const GUID CLSID_ShellBtrfsPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf2 } };
static const GUID CLSID_ShellBtrfsVolPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf3 } };

#define COM_DESCRIPTION_ICON_HANDLER L"WinBtrfs shell extension (icon handler)"
#define COM_DESCRIPTION_CONTEXT_MENU L"WinBtrfs shell extension (context menu)"
#define COM_DESCRIPTION_PROP_SHEET L"WinBtrfs shell extension (property sheet)"
#define COM_DESCRIPTION_VOL_PROP_SHEET L"WinBtrfs shell extension (volume property sheet)"
#define ICON_OVERLAY_NAME L"WinBtrfs"

typedef enum _PROCESS_DPI_AWARENESS {
    PROCESS_DPI_UNAWARE,
    PROCESS_SYSTEM_DPI_AWARE,
    PROCESS_PER_MONITOR_DPI_AWARE
} PROCESS_DPI_AWARENESS;

typedef ULONG (WINAPI *_RtlNtStatusToDosError)(NTSTATUS Status);
typedef HRESULT (WINAPI *_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);

HMODULE module;
LONG objs_loaded = 0;

void set_dpi_aware() {
    _SetProcessDpiAwareness SetProcessDpiAwareness;
    HMODULE shcore = LoadLibraryW(L"shcore.dll");

    if (!shcore)
        return;

    SetProcessDpiAwareness = (_SetProcessDpiAwareness)GetProcAddress(shcore, "SetProcessDpiAwareness");

    if (!SetProcessDpiAwareness)
        return;

    SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
}

void format_size(uint64_t size, wstring& s, bool show_bytes) {
    wstring t, bytes, kb, nb;
    WCHAR nb2[255];
    ULONG sr;
    float f;
    NUMBERFMTW fmt;
    WCHAR dec[2], thou[4], grouping[64], *c;

    nb = to_wstring(size);

    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thou, sizeof(thou) / sizeof(WCHAR));

    dec[0] = '.'; dec[1] = 0; // not used, but silences gcc warning

    fmt.NumDigits = 0;
    fmt.LeadingZero = 1;
    fmt.lpDecimalSep = dec;
    fmt.lpThousandSep = thou;
    fmt.NegativeOrder = 0;

    // Grouping code copied from dlls/shlwapi/string.c in Wine - thank you

    fmt.Grouping = 0;
    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, sizeof(grouping) / sizeof(WCHAR));

    c = grouping;
    while (*c) {
        if (*c >= '0' && *c < '9') {
            fmt.Grouping *= 10;
            fmt.Grouping += *c - '0';
        }

        c++;
    }

    if (fmt.Grouping % 10 == 0)
        fmt.Grouping /= 10;
    else
        fmt.Grouping *= 10;

    GetNumberFormatW(LOCALE_USER_DEFAULT, 0, nb.c_str(), &fmt, nb2, sizeof(nb2) / sizeof(WCHAR));

    if (size < 1024) {
        if (!load_string(module, size == 1 ? IDS_SIZE_BYTE : IDS_SIZE_BYTES, t))
            throw last_error(GetLastError());

        wstring_sprintf(s, t, nb2);
        return;
    }

    if (show_bytes) {
        if (!load_string(module, IDS_SIZE_BYTES, t))
            throw last_error(GetLastError());

        wstring_sprintf(bytes, t, nb2);
    }

    if (size >= 1152921504606846976) {
        sr = IDS_SIZE_EB;
        f = (float)size / 1152921504606846976.0f;
    } else if (size >= 1125899906842624) {
        sr = IDS_SIZE_PB;
        f = (float)size / 1125899906842624.0f;
    } else if (size >= 1099511627776) {
        sr = IDS_SIZE_TB;
        f = (float)size / 1099511627776.0f;
    } else if (size >= 1073741824) {
        sr = IDS_SIZE_GB;
        f = (float)size / 1073741824.0f;
    } else if (size >= 1048576) {
        sr = IDS_SIZE_MB;
        f = (float)size / 1048576.0f;
    } else {
        sr = IDS_SIZE_KB;
        f = (float)size / 1024.0f;
    }

    if (!load_string(module, sr, t))
        throw last_error(GetLastError());

    if (show_bytes) {
        wstring_sprintf(kb, t, f);

        if (!load_string(module, IDS_SIZE_LARGE, t))
            throw last_error(GetLastError());

        wstring_sprintf(s, t, kb.c_str(), bytes.c_str());
    } else
        wstring_sprintf(s, t, f);
}

wstring format_message(ULONG last_error) {
    WCHAR* buf;
    wstring s;

    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
        last_error, 0, (WCHAR*)&buf, 0, nullptr) == 0) {
        return L"(error retrieving message)";
    }

    s = buf;

    LocalFree(buf);

    // remove trailing newline
    while (s.length() > 0 && (s.substr(s.length() - 1, 1) == L"\r" || s.substr(s.length() - 1, 1) == L"\n"))
        s = s.substr(0, s.length() - 1);

    return s;
}

wstring format_ntstatus(NTSTATUS Status) {
    _RtlNtStatusToDosError RtlNtStatusToDosError;
    wstring s;
    HMODULE ntdll = LoadLibraryW(L"ntdll.dll");

    if (!ntdll)
        return L"(error loading ntdll.dll)";

    RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, "RtlNtStatusToDosError");

    if (!RtlNtStatusToDosError) {
        FreeLibrary(ntdll);
        return L"(error loading RtlNtStatusToDosError)";
    }

    s = format_message(RtlNtStatusToDosError(Status));

    FreeLibrary(ntdll);

    return s;
}

bool load_string(HMODULE module, UINT id, wstring& s) {
    int len;
    LPWSTR retstr = nullptr;

    len = LoadStringW(module, id, (LPWSTR)&retstr, 0);

    if (len == 0)
        return false;

    s = wstring(retstr, len);

    return true;
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996)
#endif

void wstring_sprintf(wstring& s, wstring fmt, ...) {
    int len;
    va_list args;

    va_start(args, fmt);
    len = _vsnwprintf(nullptr, 0, fmt.c_str(), args);

    if (len == 0)
        s = L"";
    else {
        s.resize(len);
        _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args);
    }

    va_end(args);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

extern "C" STDAPI DllCanUnloadNow(void) {
    return objs_loaded == 0 ? S_OK : S_FALSE;
}

extern "C" STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
    if (rclsid == CLSID_ShellBtrfsIconHandler) {
        Factory* fact = new Factory;
        if (!fact)
            return E_OUTOFMEMORY;
        else {
            fact->type = FactoryIconHandler;

            return fact->QueryInterface(riid, ppv);
        }
    } else if (rclsid == CLSID_ShellBtrfsContextMenu) {
        Factory* fact = new Factory;
        if (!fact)
            return E_OUTOFMEMORY;
        else {
            fact->type = FactoryContextMenu;

            return fact->QueryInterface(riid, ppv);
        }
    } else if (rclsid == CLSID_ShellBtrfsPropSheet) {
        Factory* fact = new Factory;
        if (!fact)
            return E_OUTOFMEMORY;
        else {
            fact->type = FactoryPropSheet;

            return fact->QueryInterface(riid, ppv);
        }
    } else if (rclsid == CLSID_ShellBtrfsVolPropSheet) {
        Factory* fact = new Factory;
        if (!fact)
            return E_OUTOFMEMORY;
        else {
            fact->type = FactoryVolPropSheet;

            return fact->QueryInterface(riid, ppv);
        }
    }

    return CLASS_E_CLASSNOTAVAILABLE;
}

static void write_reg_key(HKEY root, const wstring& keyname, const WCHAR* val, const wstring& data) {
    LONG l;
    HKEY hk;
    DWORD dispos;

    l = RegCreateKeyExW(root, keyname.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hk, &dispos);
    if (l != ERROR_SUCCESS)
        throw string_error(IDS_REGCREATEKEY_FAILED, l);

    l = RegSetValueExW(hk, val, 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(WCHAR)));
    if (l != ERROR_SUCCESS)
        throw string_error(IDS_REGSETVALUEEX_FAILED, l);

    l = RegCloseKey(hk);
    if (l != ERROR_SUCCESS)
        throw string_error(IDS_REGCLOSEKEY_FAILED, l);
}

static void register_clsid(const GUID clsid, const WCHAR* description) {
    WCHAR* clsidstring;
    wstring inproc, progid, clsidkeyname;
    WCHAR dllpath[MAX_PATH];

    StringFromCLSID(clsid, &clsidstring);

    try {
        inproc = L"CLSID\\"s + clsidstring + L"\\InprocServer32"s;
        progid = L"CLSID\\"s + clsidstring + L"\\ProgId"s;
        clsidkeyname = L"CLSID\\"s + clsidstring;

        write_reg_key(HKEY_CLASSES_ROOT, clsidkeyname, nullptr, description);

        GetModuleFileNameW(module, dllpath, sizeof(dllpath));

        write_reg_key(HKEY_CLASSES_ROOT, inproc, nullptr, dllpath);

        write_reg_key(HKEY_CLASSES_ROOT, inproc, L"ThreadingModel", L"Apartment");
    } catch (...) {
        CoTaskMemFree(clsidstring);
        throw;
    }

    CoTaskMemFree(clsidstring);
}

// implementation of RegDeleteTreeW, only available from Vista on
static void reg_delete_tree(HKEY hkey, const wstring& keyname) {
    HKEY k;
    LSTATUS ret;

    ret = RegOpenKeyExW(hkey, keyname.c_str(), 0, KEY_READ, &k);

    if (ret != ERROR_SUCCESS)
        throw last_error(ret);

    try {
        WCHAR* buf;
        ULONG bufsize;

        ret = RegQueryInfoKeyW(k, nullptr, nullptr, nullptr, nullptr, &bufsize, nullptr,
                               nullptr, nullptr, nullptr, nullptr, nullptr);
        if (ret != ERROR_SUCCESS)
            throw last_error(ret);

        bufsize++;
        buf = new WCHAR[bufsize];

        try {
            do {
                ULONG size = bufsize;

                ret = RegEnumKeyExW(k, 0, buf, &size, nullptr, nullptr, nullptr, nullptr);

                if (ret == ERROR_NO_MORE_ITEMS)
                    break;
                else if (ret != ERROR_SUCCESS)
                    throw last_error(ret);

                reg_delete_tree(k, buf);
            } while (true);

            ret = RegDeleteKeyW(hkey, keyname.c_str());
            if (ret != ERROR_SUCCESS)
                throw last_error(ret);
        } catch (...) {
            delete[] buf;
            throw;
        }

        delete[] buf;
    } catch (...) {
        RegCloseKey(k);
        throw;
    }

    RegCloseKey(k);
}

static void unregister_clsid(const GUID clsid) {
    WCHAR* clsidstring;

    StringFromCLSID(clsid, &clsidstring);

    try {
        reg_delete_tree(HKEY_CLASSES_ROOT, L"CLSID\\"s + clsidstring);
    } catch (...) {
        CoTaskMemFree(clsidstring);
        throw;
    }

    CoTaskMemFree(clsidstring);
}

static void reg_icon_overlay(const GUID clsid, const wstring& name) {
    WCHAR* clsidstring;

    StringFromCLSID(clsid, &clsidstring);

    try {
        wstring path = L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers\\"s + name;

        write_reg_key(HKEY_LOCAL_MACHINE, path, nullptr, clsidstring);
    } catch (...) {
        CoTaskMemFree(clsidstring);
        throw;
    }

    CoTaskMemFree(clsidstring);
}

static void unreg_icon_overlay(const wstring& name) {
    reg_delete_tree(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers\\"s + name);
}

static void reg_context_menu_handler(const GUID clsid, const wstring& filetype, const wstring& name) {
    WCHAR* clsidstring;

    StringFromCLSID(clsid, &clsidstring);

    try {
        wstring path = filetype + L"\\ShellEx\\ContextMenuHandlers\\"s + name;

        write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring);
    } catch (...) {
        CoTaskMemFree(clsidstring);
        throw;
    }
}

static void unreg_context_menu_handler(const wstring& filetype, const wstring& name) {
    reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L"\\ShellEx\\ContextMenuHandlers\\"s + name);
}

static void reg_prop_sheet_handler(const GUID clsid, const wstring& filetype, const wstring& name) {
    WCHAR* clsidstring;

    StringFromCLSID(clsid, &clsidstring);

    try {
        wstring path = filetype + L"\\ShellEx\\PropertySheetHandlers\\"s + name;

        write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring);
    } catch (...) {
        CoTaskMemFree(clsidstring);
        throw;
    }
}

static void unreg_prop_sheet_handler(const wstring& filetype, const wstring& name) {
    reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L"\\ShellEx\\PropertySheetHandlers\\"s + name);
}

extern "C" STDAPI DllRegisterServer(void) {
    try {
        register_clsid(CLSID_ShellBtrfsIconHandler, COM_DESCRIPTION_ICON_HANDLER);
        register_clsid(CLSID_ShellBtrfsContextMenu, COM_DESCRIPTION_CONTEXT_MENU);
        register_clsid(CLSID_ShellBtrfsPropSheet, COM_DESCRIPTION_PROP_SHEET);
        register_clsid(CLSID_ShellBtrfsVolPropSheet, COM_DESCRIPTION_VOL_PROP_SHEET);

        reg_icon_overlay(CLSID_ShellBtrfsIconHandler, ICON_OVERLAY_NAME);

        reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L"Directory\\Background", ICON_OVERLAY_NAME);
        reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L"Folder", ICON_OVERLAY_NAME);

        reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L"Folder", ICON_OVERLAY_NAME);
        reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L"*", ICON_OVERLAY_NAME);
        reg_prop_sheet_handler(CLSID_ShellBtrfsVolPropSheet, L"Drive", ICON_OVERLAY_NAME);
    } catch (const exception& e) {
        error_message(nullptr, e.what());
        return E_FAIL;
    }

    return S_OK;
}

extern "C" STDAPI DllUnregisterServer(void) {
    try {
        unreg_prop_sheet_handler(L"Folder", ICON_OVERLAY_NAME);
        unreg_prop_sheet_handler(L"*", ICON_OVERLAY_NAME);
        unreg_prop_sheet_handler(L"Drive", ICON_OVERLAY_NAME);
        unreg_context_menu_handler(L"Folder", ICON_OVERLAY_NAME);
        unreg_context_menu_handler(L"Directory\\Background", ICON_OVERLAY_NAME);
        unreg_icon_overlay(ICON_OVERLAY_NAME);

        unregister_clsid(CLSID_ShellBtrfsVolPropSheet);
        unregister_clsid(CLSID_ShellBtrfsPropSheet);
        unregister_clsid(CLSID_ShellBtrfsContextMenu);
        unregister_clsid(CLSID_ShellBtrfsIconHandler);
    } catch (const exception& e) {
        error_message(nullptr, e.what());
        return E_FAIL;
    }

    return S_OK;
}

extern "C" STDAPI DllInstall(BOOL bInstall, LPCWSTR pszCmdLine) {
    if (bInstall)
        return DllRegisterServer();
    else
        return DllUnregisterServer();
}

extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH)
        module = (HMODULE)hModule;

    return true;
}

static void create_subvol(const wstring& fn) {
    size_t found = fn.rfind(L"\\");
    wstring path, file;
    win_handle h;
    btrfs_create_subvol* bcs;
    IO_STATUS_BLOCK iosb;

    if (found == wstring::npos) {
        path = L"";
        file = fn;
    } else {
        path = fn.substr(0, found);
        file = fn.substr(found + 1);
    }
    path += L"\\";

    h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);

    if (h == INVALID_HANDLE_VALUE)
        return;

    size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (file.length() * sizeof(WCHAR));
    bcs = (btrfs_create_subvol*)malloc(bcslen);

    bcs->readonly = false;
    bcs->posix = false;
    bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR));
    memcpy(bcs->name, file.c_str(), bcs->namelen);

    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);
}

extern "C" void CALLBACK CreateSubvolW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
    vector<wstring> args;

    command_line_to_args(lpszCmdLine, args);

    if (args.size() >= 1)
        create_subvol(args[0]);
}

static void create_snapshot2(const wstring& source, const wstring& fn) {
    size_t found = fn.rfind(L"\\");
    wstring path, file;
    win_handle h, src;
    btrfs_create_snapshot* bcs;
    IO_STATUS_BLOCK iosb;

    if (found == wstring::npos) {
        path = L"";
        file = fn;
    } else {
        path = fn.substr(0, found);
        file = fn.substr(found + 1);
    }
    path += L"\\";

    src = CreateFileW(source.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
    if (src == INVALID_HANDLE_VALUE)
        return;

    h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);

    if (h == INVALID_HANDLE_VALUE)
        return;

    size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (file.length() * sizeof(WCHAR));
    bcs = (btrfs_create_snapshot*)malloc(bcslen);

    bcs->readonly = false;
    bcs->posix = false;
    bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR));
    memcpy(bcs->name, file.c_str(), bcs->namelen);
    bcs->subvol = src;

    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0);
}

extern "C" void CALLBACK CreateSnapshotW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int nCmdShow) {
    vector<wstring> args;

    command_line_to_args(lpszCmdLine, args);

    if (args.size() >= 2)
        create_snapshot2(args[0], args[1]);
}

void command_line_to_args(LPWSTR cmdline, vector<wstring>& args) {
    LPWSTR* l;
    int num_args;

    args.clear();

    l = CommandLineToArgvW(cmdline, &num_args);

    if (!l)
        return;

    try {
        args.reserve(num_args);

        for (unsigned int i = 0; i < (unsigned int)num_args; i++) {
            args.push_back(l[i]);
        }
    } catch (...) {
        LocalFree(l);
        throw;
    }

    LocalFree(l);
}

static string utf16_to_utf8(const wstring_view& utf16) {
    string utf8;
    char* buf;

    if (utf16.empty())
        return "";

    auto utf8len = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.length()), nullptr, 0, nullptr, nullptr);

    if (utf8len == 0)
        throw last_error(GetLastError());

    buf = (char*)malloc(utf8len + sizeof(char));

    if (!buf)
        throw string_error(IDS_OUT_OF_MEMORY);

    if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.length()), buf, utf8len, nullptr, nullptr) == 0) {
        auto le = GetLastError();
        free(buf);
        throw last_error(le);
    }

    buf[utf8len] = 0;

    utf8 = buf;

    free(buf);

    return utf8;
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996)
#endif

string_error::string_error(int resno, ...) {
    wstring fmt, s;
    int len;
    va_list args;

    if (!load_string(module, resno, fmt))
        throw runtime_error("LoadString failed."); // FIXME

    va_start(args, resno);
    len = _vsnwprintf(nullptr, 0, fmt.c_str(), args);

    if (len == 0)
        s = L"";
    else {
        s.resize(len);
        _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args);
    }

    va_end(args);

    msg = utf16_to_utf8(s);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

wstring utf8_to_utf16(const string_view& utf8) {
    wstring ret;
    WCHAR* buf;

    if (utf8.empty())
        return L"";

    auto utf16len = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), nullptr, 0);

    if (utf16len == 0)
        throw last_error(GetLastError());

    buf = (WCHAR*)malloc((utf16len + 1) * sizeof(WCHAR));

    if (!buf)
        throw string_error(IDS_OUT_OF_MEMORY);

    if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), buf, utf16len) == 0) {
        auto le = GetLastError();
        free(buf);
        throw last_error(le);
    }

    buf[utf16len] = 0;

    ret = buf;

    free(buf);

    return ret;
}

last_error::last_error(DWORD errnum) {
    WCHAR* buf;

    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
        errnum, 0, (WCHAR*)&buf, 0, nullptr) == 0)
        throw runtime_error("FormatMessage failed");

    try {
        msg = utf16_to_utf8(buf);
    } catch (...) {
        LocalFree(buf);
        throw;
    }

    LocalFree(buf);
}

void error_message(HWND hwnd, const char* msg) {
    wstring title;

    load_string(module, IDS_ERROR, title);

    auto wmsg = utf8_to_utf16(msg);

    MessageBoxW(hwnd, wmsg.c_str(), title.c_str(), MB_ICONERROR);
}

ntstatus_error::ntstatus_error(NTSTATUS Status) : Status(Status) {
    _RtlNtStatusToDosError RtlNtStatusToDosError;
    HMODULE ntdll = LoadLibraryW(L"ntdll.dll");
    WCHAR* buf;

    if (!ntdll)
        throw runtime_error("Error loading ntdll.dll.");

    try {
        RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, "RtlNtStatusToDosError");

        if (!RtlNtStatusToDosError)
            throw runtime_error("Error loading RtlNtStatusToDosError in ntdll.dll.");

        if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
            RtlNtStatusToDosError(Status), 0, (WCHAR*)&buf, 0, nullptr) == 0)
            throw runtime_error("FormatMessage failed");

        try {
            msg = utf16_to_utf8(buf);
        } catch (...) {
            LocalFree(buf);
            throw;
        }

        LocalFree(buf);
    } catch (...) {
        FreeLibrary(ntdll);
        throw;
    }

    FreeLibrary(ntdll);
}
