From 9d29d8813bbd361fc5238daecfa9bc84d838efea Mon Sep 17 00:00:00 2001 From: Davide Beatrici Date: Fri, 12 Mar 2021 05:46:20 +0100 Subject: [PATCH] New vpndrvinst implementation, independent from Cedar and Mayaqua This greatly improves performance and reduces the binary's size (~0.2 MB vs ~5 MB). All recent Windows versions are supported, starting with Vista. No dialogs are created, aside from error/warning ones in case of failure. The only dependency (aside from Windows libraries) is libhamcore. --- src/Cedar/CM.c | 10 + src/Mayaqua/Microsoft.c | 1 + src/vpndrvinst/CMakeLists.txt | 22 +- src/vpndrvinst/Device.c | 564 ++++++++++++++++++++++++++++++++++ src/vpndrvinst/Device.h | 30 ++ src/vpndrvinst/Dialog.c | 36 +++ src/vpndrvinst/Dialog.h | 9 + src/vpndrvinst/Driver.c | 378 +++++++++++++++++++++++ src/vpndrvinst/Driver.h | 27 ++ src/vpndrvinst/Str.c | 111 +++++++ src/vpndrvinst/Str.h | 19 ++ src/vpndrvinst/main.c | 59 ++++ src/vpndrvinst/vpndrvinst.c | 253 --------------- src/vpndrvinst/vpndrvinst.h | 23 -- src/vpndrvinst/vpndrvinst.rc | 73 +---- 15 files changed, 1265 insertions(+), 350 deletions(-) create mode 100644 src/vpndrvinst/Device.c create mode 100644 src/vpndrvinst/Device.h create mode 100644 src/vpndrvinst/Dialog.c create mode 100644 src/vpndrvinst/Dialog.h create mode 100644 src/vpndrvinst/Driver.c create mode 100644 src/vpndrvinst/Driver.h create mode 100644 src/vpndrvinst/Str.c create mode 100644 src/vpndrvinst/Str.h create mode 100644 src/vpndrvinst/main.c delete mode 100644 src/vpndrvinst/vpndrvinst.c delete mode 100644 src/vpndrvinst/vpndrvinst.h diff --git a/src/Cedar/CM.c b/src/Cedar/CM.c index fda76b33..84ac5544 100644 --- a/src/Cedar/CM.c +++ b/src/Cedar/CM.c @@ -5514,6 +5514,8 @@ void CmMainWindowOnCommandEx(HWND hWnd, WPARAM wParam, LPARAM lParam, bool easy) ShowWindow(hWnd, SW_SHOWNORMAL); } Free(name); + + CmRefresh(hWnd); } break; case CMD_DELETE_VLAN: @@ -5554,6 +5556,8 @@ void CmMainWindowOnCommandEx(HWND hWnd, WPARAM wParam, LPARAM lParam, bool easy) false, false); } } + + CmRefresh(hWnd); } break; case CMD_ENABLE_VLAN: @@ -5573,6 +5577,8 @@ void CmMainWindowOnCommandEx(HWND hWnd, WPARAM wParam, LPARAM lParam, bool easy) CALL(hWnd, CcEnableVLan(cm->Client, &c)); } Free(s); + + CmRefresh(hWnd); } } break; @@ -5593,6 +5599,8 @@ void CmMainWindowOnCommandEx(HWND hWnd, WPARAM wParam, LPARAM lParam, bool easy) CALL(hWnd, CcDisableVLan(cm->Client, &c)); } Free(s); + + CmRefresh(hWnd); } } break; @@ -5643,6 +5651,8 @@ void CmMainWindowOnCommandEx(HWND hWnd, WPARAM wParam, LPARAM lParam, bool easy) } } Free(s); + + CmRefresh(hWnd); } } break; diff --git a/src/Mayaqua/Microsoft.c b/src/Mayaqua/Microsoft.c index a3592cdf..802dcb09 100644 --- a/src/Mayaqua/Microsoft.c +++ b/src/Mayaqua/Microsoft.c @@ -2129,6 +2129,7 @@ bool MsExecDriverInstaller(char *arg) info.cbSize = sizeof(info); info.lpVerb = L"open"; info.lpFile = tmp; + info.lpDirectory = MsGetMyTempDirW(); info.fMask = SEE_MASK_NOCLOSEPROCESS; info.lpParameters = arg_w; info.nShow = SW_SHOWNORMAL; diff --git a/src/vpndrvinst/CMakeLists.txt b/src/vpndrvinst/CMakeLists.txt index 0ebb1887..19b7f98b 100644 --- a/src/vpndrvinst/CMakeLists.txt +++ b/src/vpndrvinst/CMakeLists.txt @@ -5,7 +5,17 @@ endif() set(COMPONENT_NAME "Driver Installer") set(COMPONENT_INTERNAL_NAME "vpndrvinst") -add_executable(vpndrvinst WIN32 vpndrvinst.c vpndrvinst.h) +add_executable(vpndrvinst + main.c + Device.c + Device.h + Dialog.c + Dialog.h + Driver.c + Driver.h + Str.c + Str.h +) get_filename_component(COMPONENT_FILE_NAME vpndrvinst NAME) set(COMPONENT_FILE_NAME "${COMPONENT_FILE_NAME}.exe") @@ -18,6 +28,9 @@ if(MSVC) set_target_properties(vpndrvinst PROPERTIES LINK_FLAGS "/manifestuac:level='requireAdministrator'") endif() +# Hide console while keeping main() as entry point +target_link_options(vpndrvinst PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup") + if(${COMPILER_ARCHITECTURE} STREQUAL "x64") target_sources(vpndrvinst PRIVATE "${TOP_DIRECTORY}/src/BuildFiles/Manifests/x64_admin.manifest") else() @@ -32,4 +45,9 @@ set_target_properties(vpndrvinst PDB_OUTPUT_DIRECTORY "${BUILD_DIRECTORY}" ) -target_link_libraries(vpndrvinst cedar mayaqua) +target_link_libraries(vpndrvinst + PRIVATE + libhamcore + newdev.lib + SetupAPI.Lib +) diff --git a/src/vpndrvinst/Device.c b/src/vpndrvinst/Device.c new file mode 100644 index 00000000..6efcb08b --- /dev/null +++ b/src/vpndrvinst/Device.c @@ -0,0 +1,564 @@ +#include "Device.h" + +#include "Dialog.h" +#include "Driver.h" +#include "Str.h" + +#include "Hamcore.h" + +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include +#include +#include +#include +#include + +HDEVINFO GetDeviceInfo(SP_DEVINFO_DATA *devinfo_data, const char *instance) +{ + if (!devinfo_data || !instance) + { + return NULL; + } + + HDEVINFO devinfo = SetupDiGetClassDevs(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT); + if (devinfo == INVALID_HANDLE_VALUE) + { + ShowWarning("GetDeviceInfo()", "SetupDiGetClassDevs() failed with error %lu!", GetLastError()); + return NULL; + } + + SP_DEVINFO_LIST_DETAIL_DATA detail_data; + detail_data.cbSize = sizeof(detail_data); + + if (!SetupDiGetDeviceInfoListDetail(devinfo, &detail_data)) + { + ShowWarning("GetDeviceInfo()", "SetupDiGetDeviceInfoListDetail() failed with error %lu!", GetLastError()); + FreeDeviceInfo(devinfo); + return NULL; + } + + char id[MAX_PATH]; + snprintf(id, sizeof(id), DRIVER_DEVICE_ID_TAG, instance); + + bool found = false; + SP_DEVINFO_DATA data; + data.cbSize = sizeof(data); + + for (DWORD i = 0; SetupDiEnumDeviceInfo(devinfo, i, &data); ++i) + { + DWORD size; + if (!SetupDiGetDeviceRegistryProperty(devinfo, &data, SPDRP_HARDWAREID, NULL, NULL, 0, &size)) + { + const DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) + { + ShowWarning("GetDeviceInfo()", "SetupDiGetDeviceRegistryProperty() failed with error %lu!", error); + continue; + } + } + + char *buffer = malloc(size); + if (!SetupDiGetDeviceRegistryProperty(devinfo, &data, SPDRP_HARDWAREID, NULL, (BYTE *)buffer, size, NULL)) + { + ShowWarning("GetDeviceInfo()", "SetupDiGetDeviceRegistryProperty() failed with error %lu!", GetLastError()); + free(buffer); + continue; + } + + if (strcmp(buffer, id) == 0) + { + found = true; + } + + free(buffer); + + if (found) + { + break; + } + } + + if (!found) + { + FreeDeviceInfo(devinfo); + return NULL; + } + + memcpy(devinfo_data, &data, sizeof(data)); + return devinfo; +} + +void FreeDeviceInfo(HDEVINFO info) +{ + if (info) + { + SetupDiDestroyDeviceInfoList(info); + } +} + +bool ToggleDevice(const char *instance, const bool enable) +{ + if (!instance) + { + return false; + } + + SP_DEVINFO_DATA data; + HDEVINFO info = GetDeviceInfo(&data, instance); + if (!info) + { + ShowWarning("ToggleDevice()", "The specified device was not found!"); + return false; + } + + bool ok = false; + + SP_PROPCHANGE_PARAMS params; + params.HwProfile = 0; + params.Scope = DICS_FLAG_CONFIGSPECIFIC; + params.StateChange = enable ? DICS_ENABLE : DICS_DISABLE; + params.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + params.ClassInstallHeader.cbSize = sizeof(params.ClassInstallHeader); + + if (!SetupDiSetClassInstallParams(info, &data, ¶ms.ClassInstallHeader, sizeof(params))) + { + ShowWarning("ToggleDevice()", "SetupDiSetClassInstallParams() failed with error %lu!", GetLastError()); + goto FINAL; + } + + if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, info, &data)) + { + ShowWarning("ToggleDevice()", "SetupDiCallClassInstaller() failed with error %lu!", GetLastError()); + + // Clear parameters, otherwise the device may remain in an inconsistent state + // (e.g. with the enabled icon even if disabled). + SetupDiSetClassInstallParams(info, &data, NULL, 0); + + goto FINAL; + } + + ok = true; +FINAL: + FreeDeviceInfo(info); + return ok; +} + +bool InstallDevice(const char *instance) +{ + if (!instance) + { + return false; + } + + char mac[MAC_BUFFER_SIZE]; + GenMacAddress(mac, sizeof(mac)); + + return InstallDeviceWithMac(instance, mac); +} + +bool InstallDeviceWithMac(const char *instance, const char *mac) +{ + if (!instance || !mac) + { + return false; + } + + SP_DEVINFO_DATA data; + HDEVINFO info = GetDeviceInfo(&data, instance); + if (info) + { + ShowWarning("InstallDevice()", "The specified device already exists!"); + return false; + } + + HAMCORE *hamcore = HamcoreOpen("hamcore.se2"); + if (!hamcore) + { + ShowWarning("InstallDevice()", "Failed to open hamcore.se2!"); + return false; + } + + bool ok = false; + bool delete_files = false; + + if (!IsInstanceNameOK(hamcore, instance)) + { + ShowWarning("InstallDevice()", "\"%s\" cannot be used as instance name, please choose another!", instance); + goto FINAL; + } + + char cat[MAX_PATH]; + if (!PrepareCat(hamcore, cat, sizeof(cat), instance)) + { + goto FINAL; + } + + char sys[MAX_PATH]; + if (!PrepareSys(hamcore, sys, sizeof(sys), instance)) + { + goto FINAL; + } + + char inf[MAX_PATH]; + if (!PrepareInf(hamcore, inf, sizeof(inf), instance, sys, mac)) + { + goto FINAL; + } + + delete_files = true; + + GUID inf_guid; + char inf_class[MAX_CLASS_NAME_LEN]; + if (!SetupDiGetINFClass(inf, &inf_guid, inf_class, sizeof(inf_class), NULL)) + { + ShowWarning("InstallDevice()", "SetupDiGetINFClass() failed with error %lu!", GetLastError()); + goto FINAL; + } + + info = SetupDiCreateDeviceInfoList(&inf_guid, NULL); + if (info == INVALID_HANDLE_VALUE) + { + ShowWarning("InstallDevice()", "SetupDiCreateDeviceInfoList() failed with error %lu!", GetLastError()); + goto FINAL; + } + + SP_DEVINFO_DATA info_data; + info_data.cbSize = sizeof(info_data); + if (!SetupDiCreateDeviceInfo(info, inf_class, &inf_guid, NULL, NULL, DICD_GENERATE_ID, &info_data)) + { + ShowWarning("InstallDevice()", "SetupDiCreateDeviceInfo() failed with error %lu!", GetLastError()); + goto FINAL; + } + + char id[MAX_PATH]; + snprintf(id, sizeof(id), DRIVER_DEVICE_ID_TAG, instance); + + // Passing the full buffer size caused a second hardware ID containing random symbols to appear + // on a fresh Windows 7 VM several times when using long instance names. + // As a simple and effective solution, we simply pass the string length + 1 for the NULL char. + if (!SetupDiSetDeviceRegistryProperty(info, &info_data, SPDRP_HARDWAREID, (BYTE *)id, (DWORD)strlen(id) + 1)) + { + ShowWarning("InstallDevice()", "SetupDiSetDeviceRegistryProperty() failed with error %lu!", GetLastError()); + goto FINAL; + } + + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, info, &info_data)) + { + ShowWarning("InstallDevice()", "SetupDiCallClassInstaller() failed with error %lu!", GetLastError()); + goto FINAL; + } + + BOOL reboot_required; + if (!UpdateDriverForPlugAndPlayDevices(NULL, id, inf, INSTALLFLAG_FORCE, &reboot_required)) + { + ShowWarning("InstallDevice()", "UpdateDriverForPlugAndPlayDevices() failed with error %lu!", GetLastError()); + + if (!SetupDiCallClassInstaller(DIF_REMOVE, info, &info_data)) + { + ShowWarning("InstallDevice()", "SetupDiCallClassInstaller() failed with error %lu!", GetLastError()); + } + + if (!SetupDiRemoveDevice(info, &info_data)) + { + ShowWarning("InstallDevice()", "SetupDiRemoveDevice() failed with error %lu!", GetLastError()); + } + + goto FINAL; + } + + if (IsMacAddressManual()) + { + SetDeviceMac(instance, mac); + } + + SetDeviceNetConfig(instance); + + ok = true; +FINAL: + if (delete_files) + { + DeleteFile(cat); + DeleteFile(sys); + DeleteFile(inf); + } + + HamcoreClose(hamcore); + FreeDeviceInfo(info); + return ok; +} + +bool UninstallDevice(const char *instance) +{ + if (!instance) + { + return false; + } + + SP_DEVINFO_DATA info_data; + HDEVINFO info = GetDeviceInfo(&info_data, instance); + if (!info) + { + ShowWarning("UninstallDevice()", "The specified device was not found!"); + return false; + } + + bool ok = false; + + SP_DEVINFO_LIST_DETAIL_DATA detail_data; + detail_data.cbSize = sizeof(detail_data); + if (!SetupDiGetDeviceInfoListDetail(info, &detail_data)) + { + ShowWarning("UninstallDevice()", "SetupDiGetDeviceInfoListDetail() failed with error %lu!", GetLastError()); + goto FINAL; + } + + SP_REMOVEDEVICE_PARAMS params; + params.Scope = DI_REMOVEDEVICE_GLOBAL; + params.ClassInstallHeader.InstallFunction = DIF_REMOVE; + params.ClassInstallHeader.cbSize = sizeof(params.ClassInstallHeader); + + if (!SetupDiSetClassInstallParams(info, &info_data, ¶ms.ClassInstallHeader, sizeof(params))) + { + ShowWarning("UninstallDevice()", "SetupDiSetClassInstallParams() failed with error %lu!", GetLastError()); + goto FINAL; + } + + if (!SetupDiCallClassInstaller(DIF_REMOVE, info, &info_data)) + { + ShowWarning("UninstallDevice()", "SetupDiCallClassInstaller() failed with error %lu!", GetLastError()); + goto FINAL; + } + + ok = true; +FINAL: + FreeDeviceInfo(info); + return ok; +} + +bool UpgradeDevice(const char *instance) +{ + if (!instance) + { + return false; + } + + SP_DEVINFO_DATA data; + HDEVINFO info = GetDeviceInfo(&data, instance); + if (!info) + { + ShowWarning("UpgradeDevice()", "The specified device was not found!"); + return false; + } + + FreeDeviceInfo(info); + + char mac[MAC_BUFFER_SIZE]; + if (!GetDeviceMac(instance, mac, sizeof(mac))) + { + return false; + } + + if (!UninstallDevice(instance)) + { + return false; + } + + if (!InstallDeviceWithMac(instance, mac)) + { + return false; + } + + if (IsMacAddressManual()) + { + SetDeviceMac(instance, mac); + } + + return true; +} + +bool GetDeviceMac(const char *instance, char *dst, const size_t size) +{ + if (!instance || !dst || size == 0) + { + return false; + } + + HKEY key = GetDeviceRegKey(instance, false); + if (!key) + { + return false; + } + + DWORD buffer_size = (DWORD)size; + LSTATUS ret = RegGetValue(key, NULL, "NetworkAddress", RRF_RT_REG_SZ, NULL, dst, &buffer_size); + RegCloseKey(key); + + if (ret != ERROR_SUCCESS) + { + ShowWarning("GetDeviceMac()", "RegGetValue() failed with error %ld!", ret); + return false; + } + + return true; +} + +bool SetDeviceMac(const char *instance, const char *src) +{ + if (!instance || !src) + { + return false; + } + + HKEY key = GetDeviceRegKey(instance, true); + if (!key) + { + return false; + } + + LSTATUS ret = RegSetKeyValue(key, NULL, "NetworkAddress", REG_SZ, src, (DWORD)strlen(src) + 1); + RegCloseKey(key); + + if (ret != ERROR_SUCCESS) + { + ShowWarning("SetDeviceMac()", "RegSetValue() failed with error %ld!", ret); + return false; + } + + ToggleDevice(instance, false); + ToggleDevice(instance, true); + + return true; +} + +bool SetDeviceNetConfig(const char *instance) +{ + if (!instance) + { + return false; + } + + HKEY key = GetDeviceRegKey(instance, true); + if (!key) + { + return false; + } + + char path[MAX_PATH] = REGSTR_PATH_SERVICES "\\Tcpip\\Parameters\\Interfaces\\"; + const size_t path_len = strlen(path); + + DWORD buffer_size = sizeof(path) - path_len; + LSTATUS ret = RegGetValue(key, NULL, "NetCfgInstanceId", RRF_RT_REG_SZ, NULL, path + path_len, &buffer_size); + RegCloseKey(key); + + if (ret != ERROR_SUCCESS) + { + ShowWarning("SetDeviceNetConfig()", "RegGetValue() failed with error %ld!", ret); + return false; + } + + bool ok = true; + + DWORD tmp = 0; + ret = RegSetKeyValue(HKEY_LOCAL_MACHINE, path, "EnableDeadGWDetect", REG_DWORD, &tmp, sizeof(tmp)); + if (ret != ERROR_SUCCESS) + { + ShowWarning("SetDeviceNetConfig()", "RegSetKeyValue() failed to set EnableDeadGWDetect with error %ld!", ret); + ok = false; + } + + tmp = 1; + ret = RegSetKeyValue(HKEY_LOCAL_MACHINE, path, "InterfaceMetric", REG_DWORD, &tmp, sizeof(tmp)); + if (ret != ERROR_SUCCESS) + { + ShowWarning("SetDeviceNetConfig()", "RegSetKeyValue() failed to set InterfaceMetric with error %ld!", ret); + ok = false; + } + + return ok; +} + +HKEY GetDeviceRegKey(const char *instance, const bool writable) +{ + if (!instance) + { + return NULL; + } + + char path[MAX_PATH] = REGSTR_PATH_CLASS_NT "\\"; + const size_t path_len = strlen(path); + StrFromGUID(path + path_len, sizeof(path) - path_len, &GUID_DEVCLASS_NET); + + HKEY key_list; + LSTATUS ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &key_list); + if (ret != ERROR_SUCCESS) + { + ShowWarning("GetDeviceRegKey()", "RegOpenKeyEx() failed to open \"%s\", with error %ld!", path, ret); + return NULL; + } + + char device_id[MAX_PATH]; + snprintf(device_id, sizeof(device_id), DRIVER_DEVICE_ID_TAG, instance); + + char driver_desc[MAX_PATH]; + snprintf(driver_desc, sizeof(driver_desc), VLAN_ADAPTER_NAME_TAG, instance); + + for (DWORD i = 0; ++i;) + { + char key_name[MAX_PATH]; + DWORD key_name_size = sizeof(key_name); + ret = RegEnumKeyEx(key_list, i, key_name, &key_name_size, 0, NULL, 0, NULL); + if (ret != ERROR_SUCCESS) + { + if (ret != ERROR_NO_MORE_ITEMS) + { + ShowWarning("GetDeviceRegKey()", "RegEnumKeyEx() failed at index %lu with error %ld!", i, ret); + } + + break; + } + + HKEY key; + if (RegOpenKeyEx(key_list, key_name, 0, writable ? KEY_READ | KEY_WRITE : KEY_READ, &key) != ERROR_SUCCESS) + { + continue; + } + + char buffer[MAX_PATH]; + DWORD buffer_size = sizeof(buffer); + + if (RegGetValue(key, NULL, REGSTR_VAL_MATCHINGDEVID, RRF_RT_REG_SZ, NULL, buffer, &buffer_size) != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + + if (strncmp(buffer, device_id, buffer_size) == 0) + { + return key; + } + + buffer_size = sizeof(buffer); + + if (RegGetValue(key, NULL, REGSTR_VAL_DRVDESC, RRF_RT_REG_SZ, NULL, buffer, &buffer_size) != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + + if (strncmp(buffer, driver_desc, buffer_size) == 0) + { + return key; + } + } + + return NULL; +} diff --git a/src/vpndrvinst/Device.h b/src/vpndrvinst/Device.h new file mode 100644 index 00000000..1f3903ba --- /dev/null +++ b/src/vpndrvinst/Device.h @@ -0,0 +1,30 @@ +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include + +typedef void *PVOID; +typedef PVOID HDEVINFO; + +typedef struct HKEY__ *HKEY; +typedef struct _SP_DEVINFO_DATA SP_DEVINFO_DATA; + +HDEVINFO GetDeviceInfo(SP_DEVINFO_DATA *devinfo_data, const char *instance); +void FreeDeviceInfo(HDEVINFO info); + +bool ToggleDevice(const char *instance, const bool enable); + +bool InstallDevice(const char *instance); +bool InstallDeviceWithMac(const char *instance, const char *mac); +bool UninstallDevice(const char *instance); +bool UpgradeDevice(const char *instance); + +bool GetDeviceMac(const char *instance, char *dst, const size_t size); +bool SetDeviceMac(const char *instance, const char *src); + +bool SetDeviceNetConfig(const char *instance); + +HKEY GetDeviceRegKey(const char *instance, const bool writable); + +#endif diff --git a/src/vpndrvinst/Dialog.c b/src/vpndrvinst/Dialog.c new file mode 100644 index 00000000..d98cca76 --- /dev/null +++ b/src/vpndrvinst/Dialog.c @@ -0,0 +1,36 @@ +#include "Dialog.h" + +#include + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include + +int ShowMessage(const char *title, const char *message, const unsigned int type, const va_list args) +{ + char buf[MAX_MESSAGE_SIZE]; + vsnprintf(buf, sizeof(buf), message, args); + return MessageBox(NULL, buf, title, type); +} + +int ShowInformation(const char *title, const char *message, ...) +{ + va_list args; + va_start(args, message); + const int ret = ShowMessage(title, message, MB_OK | MB_ICONINFORMATION, args); + va_end(args); + + return ret; +} + +int ShowWarning(const char *title, const char *message, ...) +{ + va_list args; + va_start(args, message); + const int ret = ShowMessage(title, message, MB_OK | MB_ICONWARNING, args); + va_end(args); + + return ret; +} diff --git a/src/vpndrvinst/Dialog.h b/src/vpndrvinst/Dialog.h new file mode 100644 index 00000000..b4544f03 --- /dev/null +++ b/src/vpndrvinst/Dialog.h @@ -0,0 +1,9 @@ +#ifndef DIALOG_H +#define DIALOG_H + +#define MAX_MESSAGE_SIZE 1024 + +int ShowInformation(const char *title, const char *message, ...); +int ShowWarning(const char *title, const char *message, ...); + +#endif diff --git a/src/vpndrvinst/Driver.c b/src/vpndrvinst/Driver.c new file mode 100644 index 00000000..faed468c --- /dev/null +++ b/src/vpndrvinst/Driver.c @@ -0,0 +1,378 @@ +#include "Driver.h" + +#include "Dialog.h" +#include "Str.h" + +#include + +#include + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include + +const char *GetArch() +{ + SYSTEM_INFO info; + GetNativeSystemInfo(&info); + switch (info.wProcessorArchitecture) + { + case PROCESSOR_ARCHITECTURE_AMD64: + return "x64"; + case PROCESSOR_ARCHITECTURE_INTEL: + return "x86"; + case PROCESSOR_ARCHITECTURE_ARM64: + return "arm64"; + case PROCESSOR_ARCHITECTURE_ARM: + return "arm"; + case PROCESSOR_ARCHITECTURE_IA64: + return "ia64"; + } + + return NULL; +} + +const char *GetDriverPath() +{ + static char path[MAX_PATH]; + + static bool set = false; + if (set) + { + return path; + } + + const char *type_folder; + if (IsWindows10OrGreater()) + { + type_folder = "Neo6_Win10"; + } + else if (IsWindows8OrGreater()) + { + type_folder = "Neo6_Win8"; + } + else if (IsWindows7OrGreater()) + { + type_folder = "Neo6"; + } + else + { + type_folder = "Neo"; + } + + snprintf(path, sizeof(path), "DriverPackages/%s/%s/", type_folder, GetArch()); + + set = true; + return path; +} + +const char *GetTmpPath() +{ + static char path[MAX_PATH]; + + static bool set = false; + if (set) + { + return path; + } + + if (!GetTempPath(sizeof(path), path)) + { + ShowWarning("GetTmpPath()", "GetTempPath() failed with error %lu!", GetLastError()); + return NULL; + } + + set = true; + return path; +} + +void GetCatPath(char *dst, const size_t size, const char *instance) +{ + if (!dst || size == 0) + { + return; + } + + if (IsWindows10OrGreater()) + { + if (!instance) + { + return; + } + + snprintf(dst, size, "%sNeo6_%s_%s.cat", GetDriverPath(), GetArch(), instance); + } + else if (IsWindows8OrGreater()) + { + snprintf(dst, size, "%sinf2.cat", GetDriverPath()); + } +} + +void GetInfPath(char *dst, const size_t size, const char *instance) +{ + if (!dst || size == 0) + { + return; + } + + if (IsWindows8OrGreater()) + { + if (!instance) + { + return; + } + + snprintf(dst, size, "%sNeo6_%s_%s.inf", GetDriverPath(), GetArch(), instance); + } + else if (IsWindows7OrGreater()) + { + snprintf(dst, size, "%sNeo6_%s.inf", GetDriverPath(), GetArch()); + } + else + { + snprintf(dst, size, "%sNeo_%s.inf", GetDriverPath(), GetArch()); + } +} + +void GetSysPath(char *dst, const size_t size, const char *instance) +{ + if (!dst || size == 0) + { + return; + } + + if (IsWindows10OrGreater()) + { + if (!instance) + { + return; + } + + snprintf(dst, size, "%sNeo6_%s_%s.sys", GetDriverPath(), GetArch(), instance); + } + else if (IsWindows7OrGreater()) + { + snprintf(dst, size, "%sNeo6_%s.sys", GetDriverPath(), GetArch()); + } + else + { + snprintf(dst, size, "%sNeo_%s.sys", GetDriverPath(), GetArch()); + } +} + +bool IsInstanceNameOK(HAMCORE *hamcore, const char *instance) +{ + if (!IsWindows8OrGreater()) + { + return true; + } + + if (!hamcore || !instance) + { + return false; + } + + char path[MAX_PATH]; + GetInfPath(path, sizeof(path), instance); + + const HAMCORE_FILE *file = HamcoreFind(hamcore, path); + return file ? true : false; +} + +bool IsMacAddressManual() +{ + return IsWindows8OrGreater(); +} + +bool PrepareCat(HAMCORE *hamcore, char *dst, const size_t size, const char *instance) +{ + if (!IsWindows8OrGreater()) + { + return true; + } + + if (!hamcore || !dst || size == 0 || !instance) + { + return false; + } + + char src[MAX_PATH]; + GetCatPath(src, sizeof(src), instance); + + const HAMCORE_FILE *hamcore_file = HamcoreFind(hamcore, src); + if (!hamcore_file) + { + ShowWarning("PrepareCat()", "%s not found in hamcore archive!", src); + return false; + } + + void *buf = malloc(hamcore_file->OriginalSize); + if (!HamcoreRead(hamcore, buf, hamcore_file)) + { + ShowWarning("PrepareCat()", "Failed to read %s from hamcore archive!", src); + free(buf); + return false; + } + + if (IsWindows10OrGreater()) + { + snprintf(dst, size, "%s%s", GetTmpPath(), PathFileName(src, false)); + } + else + { + snprintf(dst, size, "%sinf_%s.cat", GetTmpPath(), instance); + } + + bool ok = false; + + HANDLE file = CreateFile(dst, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + { + ShowWarning("PrepareCat()", "CreateFile() failed to open \"%s\" with error %lu!", dst, GetLastError()); + goto FINAL; + } + + DWORD processed; + ok = WriteFile(file, buf, (DWORD)hamcore_file->OriginalSize, &processed, NULL); + CloseHandle(file); + + if (!ok) + { + ShowWarning("PrepareCat()", "WriteFile() failed with error %lu!", src, GetLastError()); + DeleteFile(dst); + } +FINAL: + free(buf); + return ok; +} + +bool PrepareInf(HAMCORE *hamcore, char *dst, const size_t size, const char *instance, const char *sys, const char *mac) +{ + if (!hamcore || !dst || size == 0 || !instance || !sys || !mac) + { + return false; + } + + char src[MAX_PATH]; + GetInfPath(src, sizeof(src), instance); + + const HAMCORE_FILE *hamcore_file = HamcoreFind(hamcore, src); + if (!hamcore_file) + { + ShowWarning("PrepareInf()", "%s not found in hamcore archive!", src); + return false; + } + + size_t buf_size = hamcore_file->OriginalSize; + char *buf = malloc(buf_size); + + if (!HamcoreRead(hamcore, buf, hamcore_file)) + { + ShowWarning("PrepareInf()", "Failed to read %s from hamcore archive!", src); + free(buf); + return false; + } + + if (IsWindows10OrGreater()) + { + snprintf(dst, size, "%s%s", GetTmpPath(), PathFileName(src, false)); + } + else if (IsWindows7OrGreater()) + { + snprintf(dst, size, "%sNeo6_%s_%s.inf", GetTmpPath(), GetArch(), instance); + } + else + { + snprintf(dst, size, "%sNeo_%s_%s.inf", GetTmpPath(), GetArch(), instance); + } + + if (!IsWindows8OrGreater()) + { + buf = StrReplace(buf, &buf_size, "$TAG_INSTANCE_NAME$", instance, false); + buf = StrReplace(buf, &buf_size, "$TAG_MAC_ADDRESS$", mac, false); + buf = StrReplace(buf, &buf_size, "$TAG_SYS_NAME$", PathFileName(sys, true), true); + } + + bool ok = false; + + HANDLE file = CreateFile(dst, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + { + ShowWarning("PrepareInf()", "CreateFile() failed to open \"%s\" with error %lu!", dst, GetLastError()); + goto FINAL; + } + + DWORD processed; + ok = WriteFile(file, buf, (DWORD)buf_size, &processed, NULL); + CloseHandle(file); + + if (!ok) + { + ShowWarning("PrepareInf()", "WriteFile() failed with error %lu!", src, GetLastError()); + DeleteFile(dst); + } +FINAL: + free(buf); + return ok; +} + +bool PrepareSys(HAMCORE *hamcore, char *dst, const size_t size, const char *instance) +{ + if (!hamcore || !dst || size == 0 || !instance) + { + return false; + } + + char src[MAX_PATH]; + GetSysPath(src, sizeof(src), instance); + + const HAMCORE_FILE *hamcore_file = HamcoreFind(hamcore, src); + if (!hamcore_file) + { + ShowWarning("PrepareSys()", "%s not found in hamcore archive!", src); + return false; + } + + void *buf = malloc(hamcore_file->OriginalSize); + if (!HamcoreRead(hamcore, buf, hamcore_file)) + { + ShowWarning("PrepareSys()", "Failed to read %s from hamcore archive!", src); + free(buf); + return false; + } + + if (IsWindows10OrGreater()) + { + snprintf(dst, size, "%s%s", GetTmpPath(), PathFileName(src, false)); + } + else + { + snprintf(dst, size, "%sNeo_%s.sys", GetTmpPath(), instance); + } + + bool ok = false; + + HANDLE file = CreateFile(dst, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + { + ShowWarning("PrepareSys()", "CreateFile() failed to open \"%s\" with error %lu!", dst, GetLastError()); + goto FINAL; + } + + DWORD processed; + ok = WriteFile(file, buf, (DWORD)hamcore_file->OriginalSize, &processed, NULL); + CloseHandle(file); + + if (!ok) + { + ShowWarning("PrepareSys()", "WriteFile() failed with error %lu!", src, GetLastError()); + DeleteFile(dst); + } +FINAL: + free(buf); + return ok; +} diff --git a/src/vpndrvinst/Driver.h b/src/vpndrvinst/Driver.h new file mode 100644 index 00000000..27042815 --- /dev/null +++ b/src/vpndrvinst/Driver.h @@ -0,0 +1,27 @@ +#ifndef DRIVER_H +#define DRIVER_H + +#include +#include + +#define DRIVER_DEVICE_ID_TAG "NeoAdapter_%s" +#define VLAN_ADAPTER_NAME_TAG "VPN Client Adapter - %s" + +typedef struct HAMCORE HAMCORE; + +const char *GetArch(); +const char *GetDriverPath(); +const char *GetTmpPath(); + +void GetCatPath(char *dst, const size_t size, const char *instance); +void GetInfPath(char *dst, const size_t size, const char *instance); +void GetSysPath(char *dst, const size_t size, const char *instance); + +bool IsInstanceNameOK(HAMCORE *hamcore, const char *instance); +bool IsMacAddressManual(); + +bool PrepareCat(HAMCORE *hamcore, char *dst, const size_t size, const char *instance); +bool PrepareInf(HAMCORE *hamcore, char *dst, const size_t size, const char *instance, const char *sys, const char *mac); +bool PrepareSys(HAMCORE *hamcore, char *dst, const size_t size, const char *instance); + +#endif diff --git a/src/vpndrvinst/Str.c b/src/vpndrvinst/Str.c new file mode 100644 index 00000000..52f442b3 --- /dev/null +++ b/src/vpndrvinst/Str.c @@ -0,0 +1,111 @@ +#include "Str.h" + +#include +#include +#include +#include +#include + +#include + +void GenMacAddress(char *dst, const size_t size) +{ + if (!dst || size == 0) + { + return; + } + + srand((unsigned int)time(NULL)); + + uint8_t mac[6]; + mac[0] = 0x5E; + mac[1] = rand() % 256; + mac[2] = rand() % 256; + mac[3] = rand() % 256; + mac[4] = rand() % 256; + mac[5] = rand() % 256; + + snprintf(dst, size, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +const char *PathFileName(const char *path, const bool backslash) +{ + if (!path) + { + return NULL; + } + + const char *ret = strrchr(path, backslash ? '\\' : '/'); + if (ret) + { + ++ret; + } + + return ret; +} + +void StrFromGUID(char *dst, const size_t size, const GUID *guid) +{ + if (!dst || size == 0 || !guid) + { + return; + } + + snprintf(dst, size, "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + guid->Data1, guid->Data2, guid->Data3, + guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], + guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); +} + +char *StrReplace(char *str, size_t *size, const char *target, const char *replacement, const bool shrink) +{ + if (!str || !size || !target || !replacement) + { + return str; + } + + const char *seek = str; + + size_t str_len = strlen(str); + const size_t target_len = strlen(target); + const size_t replacement_len = strlen(replacement); + + char *at_target; + while ((at_target = strstr(seek, target))) + { + size_t new_str_len = str_len; + + if (target_len > replacement_len) + { + new_str_len -= target_len - replacement_len; + } + else + { + new_str_len += replacement_len - target_len; + const size_t required_size = new_str_len + 1; + if (*size < required_size) + { + const char *old_str = str; + + *size = required_size; + str = realloc(str, *size); + seek = str + (seek - old_str); + at_target = str + (at_target - old_str); + } + } + + const char *after_target = at_target + target_len; + memmove(at_target + replacement_len, after_target, str_len - (after_target - seek) + 1); + memcpy(at_target, replacement, replacement_len); + + str_len = new_str_len; + } + + if (shrink && *size > str_len + 1) + { + *size = str_len + 1; + str = realloc(str, *size); + } + + return str; +} diff --git a/src/vpndrvinst/Str.h b/src/vpndrvinst/Str.h new file mode 100644 index 00000000..b47e7951 --- /dev/null +++ b/src/vpndrvinst/Str.h @@ -0,0 +1,19 @@ +#ifndef STR_H +#define STR_H + +#include +#include + +#define MAC_BUFFER_SIZE 13 + +typedef struct _GUID GUID; + +void GenMacAddress(char *dst, const size_t size); + +const char *PathFileName(const char *path, const bool backslash); + +void StrFromGUID(char *dst, const size_t size, const GUID *guid); + +char *StrReplace(char *str, size_t *size, const char *target, const char *replacement, const bool shrink); + +#endif diff --git a/src/vpndrvinst/main.c b/src/vpndrvinst/main.c new file mode 100644 index 00000000..658f92a1 --- /dev/null +++ b/src/vpndrvinst/main.c @@ -0,0 +1,59 @@ +#include "Device.h" +#include "Dialog.h" + +#include + +void ShowUsage() +{ + const char *message = + "Usage: vpndrvinst \n" + "\n" + "\"instvlan\": Installs a new virtual network interface\n" + "\"uninstvlan\": Uninstalls an existing virtual network interface\n" + "\"upgradevlan\": Updates the driver for an existing virtual network interface\n" + "\"enablevlan\": Enables an existing virtual network interface\n" + "\"disablevlan\": Disables an existing virtual network interface\n" + "\n" + "Example: vpndrvinst instvlan VPN21"; + + ShowInformation("Usage", message); +} + +int main(const int argc, const char **argv) +{ + if (argc < 3) + { + ShowUsage(); + return 0; + } + + bool ok = true; + + const char* action = argv[1]; + if (strcmp(action, "instvlan") == 0) + { + ok = InstallDevice(argv[2]); + } + else if (strcmp(action, "uninstvlan") == 0) + { + ok = UninstallDevice(argv[2]); + } + else if (strcmp(action, "upgradevlan") == 0) + { + ok = UpgradeDevice(argv[2]); + } + else if (strcmp(action, "enablevlan") == 0) + { + ok = ToggleDevice(argv[2], true); + } + else if (strcmp(action, "disablevlan") == 0) + { + ok = ToggleDevice(argv[2], false); + } + else + { + ShowUsage(); + } + + return ok ? 0 : 1; +} diff --git a/src/vpndrvinst/vpndrvinst.c b/src/vpndrvinst/vpndrvinst.c deleted file mode 100644 index 5baf1d7a..00000000 --- a/src/vpndrvinst/vpndrvinst.c +++ /dev/null @@ -1,253 +0,0 @@ -// SoftEther VPN Source Code - Developer Edition Master Branch -// VPN Driver Installer - - -#include - -#ifdef WIN32 -#define HAM_WIN32 -#define _WIN32_WINNT 0x0502 -#define WINVER 0x0502 -#include -#include -#include -#include -#include -#include -#include "../pencore/resource.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "vpndrvinst.h" - -void disablevlan(UINT num, char **arg) -{ - bool ok; - if (num < 1) - { - return; - } - - ok = MsDisableVLan(arg[0]); - - if (ok == false) - { - _exit(1); - } - else - { - _exit(0); - } -} - -void enablevlan(UINT num, char **arg) -{ - bool ok; - if (num < 1) - { - return; - } - - ok = MsEnableVLan(arg[0]); - - if (ok == false) - { - _exit(1); - } - else - { - _exit(0); - } -} - -void instvlan(UINT num, char **arg) -{ - KAKUSHI *k = NULL; - MS_DRIVER_VER ver; - bool ok; - if (num < 1) - { - return; - } - - InitWinUi(L"VPN", _SS("DEFAULT_FONT"), _II("DEFAULT_FONT_SIZE")); - - if (MsIsNt()) - { - k = InitKakushi(); - } - - CiInitDriverVerStruct(&ver); - - ok = MsInstallVLan(VLAN_ADAPTER_NAME_TAG, VLAN_CONNECTION_NAME, arg[0], &ver); - - FreeKakushi(k); - - FreeWinUi(); - - if (ok == false) - { - _exit(1); - } - else - { - _exit(0); - } -} - -void upgradevlan(UINT num, char **arg) -{ - bool ok; - KAKUSHI *k = NULL; - MS_DRIVER_VER ver; - if (num < 1) - { - return; - } - - InitWinUi(L"VPN", _SS("DEFAULT_FONT"), _II("DEFAULT_FONT_SIZE")); - - if (MsIsNt()) - { - k = InitKakushi(); - } - - CiInitDriverVerStruct(&ver); - - ok = MsUpgradeVLan(VLAN_ADAPTER_NAME_TAG, VLAN_CONNECTION_NAME, arg[0], &ver); - - FreeKakushi(k); - - FreeWinUi(); - - if (ok == false) - { - _exit(1); - } - else - { - _exit(0); - } -} - -void uninstvlan(UINT num, char **arg) -{ - bool ok; - if (num < 1) - { - return; - } - - ok = MsUninstallVLan(arg[0]); - - if (ok == false) - { - _exit(1); - } - else - { - _exit(0); - } -} - -TEST_LIST test_list[] = -{ - {"instvlan", instvlan}, - {"uninstvlan", uninstvlan}, - {"upgradevlan", upgradevlan}, - {"enablevlan", enablevlan}, - {"disablevlan", disablevlan}, -}; - -// Main function -void MainFunction(char *cmd) -{ - char tmp[MAX_SIZE]; - bool first = true; - bool exit_now = false; - - while (true) - { - if (first && StrLen(cmd) != 0 && g_memcheck == false) - { - first = false; - StrCpy(tmp, sizeof(tmp), cmd); - exit_now = true; - Print("%s\n", cmd); - } - else - { - _exit(0); - } - Trim(tmp); - if (StrLen(tmp) != 0) - { - UINT i, num; - bool b = false; - TOKEN_LIST *token = ParseCmdLine(tmp); - char *cmd = token->Token[0]; - - num = sizeof(test_list) / sizeof(TEST_LIST); - for (i = 0;i < num;i++) - { - if (!StrCmpi(test_list[i].command_str, cmd)) - { - char **arg = Malloc(sizeof(char *) * (token->NumTokens - 1)); - UINT j; - for (j = 0;j < token->NumTokens - 1;j++) - { - arg[j] = CopyStr(token->Token[j + 1]); - } - test_list[i].proc(token->NumTokens - 1, arg); - for (j = 0;j < token->NumTokens - 1;j++) - { - Free(arg[j]); - } - Free(arg); - b = true; - _exit(1); - break; - } - } - FreeToken(token); - - if (exit_now) - { - break; - } - } - } -} - -// winmain function -int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, char *CmdLine, int CmdShow) -{ - InitProcessCallOnce(); - -#if defined(_DEBUG) || defined(DEBUG) // In VC++ compilers, the macro is "_DEBUG", not "DEBUG". - // If set memcheck = true, the program will be vitally slow since it will log all malloc() / realloc() / free() calls to find the cause of memory leak. - // For normal debug we set memcheck = false. - // Please set memcheck = true if you want to test the cause of memory leaks. - InitMayaqua(false, true, 0, NULL); -#else - InitMayaqua(false, false, 0, NULL); -#endif - EnableProbe(false); - InitCedar(); - SetHamMode(); - MainFunction(cmdline); - FreeCedar(); - FreeMayaqua(); - - return 0; -} - diff --git a/src/vpndrvinst/vpndrvinst.h b/src/vpndrvinst/vpndrvinst.h deleted file mode 100644 index a0de9345..00000000 --- a/src/vpndrvinst/vpndrvinst.h +++ /dev/null @@ -1,23 +0,0 @@ -// SoftEther VPN Source Code - Developer Edition Master Branch -// VPN Driver Installer - -// List of test functions -typedef void (TEST_PROC)(UINT num, char **arg); - -typedef struct TEST_LIST -{ - char *command_str; - TEST_PROC *proc; -} TEST_LIST; - -// function prototypes -void disablevlan(UINT num, char **arg); -void enablevlan(UINT num, char **arg); -void instvlan(UINT num, char **arg); -void upgradevlan(UINT num, char **arg); -void uninstvlan(UINT num, char **arg); - -void MainFunction(char *cmd); -int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, char *CmdLine, int CmdShow); - - diff --git a/src/vpndrvinst/vpndrvinst.rc b/src/vpndrvinst/vpndrvinst.rc index b9b417f9..47e38e17 100644 --- a/src/vpndrvinst/vpndrvinst.rc +++ b/src/vpndrvinst/vpndrvinst.rc @@ -1,72 +1 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// Japanese resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) -#ifdef _WIN32 -LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT -#pragma code_page(932) -#endif //_WIN32 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_ICON2 ICON "vpndrvinst.ico" -#endif // Japanese resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +IDI_ICON1 ICON DISCARDABLE "vpndrvinst.ico"