Add C++ to editorconfig & update formatting. No functional changes.

This commit is contained in:
Caelan Sayler
2021-09-01 16:03:27 +01:00
parent b9ab4a9336
commit 300f06cc82
23 changed files with 4060 additions and 3986 deletions

View File

@@ -123,3 +123,72 @@ insert_final_newline=false
[*.vb] [*.vb]
# Modifier preferences # Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}]
# Visual C++ Code Style settings
cpp_generate_documentation_comments = xml
# Visual C++ Formatting settings
indent_style = space
indent_size = 4
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = innermost_parenthesis
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = true
cpp_indent_case_contents = true
cpp_indent_case_labels = false
cpp_indent_case_contents_when_block = false
cpp_indent_lambda_braces_when_parameter = true
cpp_indent_goto_labels = one_left
cpp_indent_preprocessor = leftmost_column
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = true
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = new_line
cpp_new_line_before_open_brace_type = new_line
cpp_new_line_before_open_brace_function = new_line
cpp_new_line_before_open_brace_block = same_line
cpp_new_line_before_open_brace_lambda = ignore
cpp_new_line_scope_braces_on_separate_lines = false
cpp_new_line_close_brace_same_line_empty_type = false
cpp_new_line_close_brace_same_line_empty_function = false
cpp_new_line_before_catch = true
cpp_new_line_before_else = true
cpp_new_line_before_while_in_do_while = false
cpp_space_before_function_open_parenthesis = remove
cpp_space_within_parameter_list_parentheses = false
cpp_space_between_empty_parameter_list_parentheses = false
cpp_space_after_keywords_in_control_flow_statements = true
cpp_space_within_control_flow_statement_parentheses = false
cpp_space_before_lambda_open_parenthesis = false
cpp_space_within_cast_parentheses = false
cpp_space_after_cast_close_parenthesis = false
cpp_space_within_expression_parentheses = false
cpp_space_before_block_open_brace = true
cpp_space_between_empty_braces = false
cpp_space_before_initializer_list_open_brace = false
cpp_space_within_initializer_list_braces = true
cpp_space_preserve_in_initializer_list = true
cpp_space_before_open_square_bracket = false
cpp_space_within_square_brackets = false
cpp_space_before_empty_square_brackets = false
cpp_space_between_empty_square_brackets = false
cpp_space_group_square_brackets = true
cpp_space_within_lambda_brackets = false
cpp_space_between_empty_lambda_brackets = false
cpp_space_before_comma = false
cpp_space_after_comma = true
cpp_space_remove_around_member_operators = true
cpp_space_before_inheritance_colon = true
cpp_space_before_constructor_colon = true
cpp_space_remove_before_semicolon = true
cpp_space_after_semicolon = false
cpp_space_remove_around_unary_operator = true
cpp_space_around_binary_operator = insert
cpp_space_around_assignment_operator = insert
cpp_space_pointer_reference_alignment = left
cpp_space_around_ternary_operator = insert
cpp_wrap_preserve_blocks = one_liners

View File

@@ -5,246 +5,244 @@
using std::wstring; using std::wstring;
class ATL_NO_VTABLE CDownloadProgressCallback : class ATL_NO_VTABLE CDownloadProgressCallback :
public CComObjectRoot, public CComObjectRoot,
public IBindStatusCallback public IBindStatusCallback
{ {
public: public:
CDownloadProgressCallback() CDownloadProgressCallback()
{ {}
}
DECLARE_NOT_AGGREGATABLE(CDownloadProgressCallback) DECLARE_NOT_AGGREGATABLE(CDownloadProgressCallback)
BEGIN_COM_MAP(CDownloadProgressCallback)
COM_INTERFACE_ENTRY(IBindStatusCallback)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CDownloadProgressCallback) HRESULT FinalConstruct() { return S_OK; }
COM_INTERFACE_ENTRY(IBindStatusCallback)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT() void FinalRelease()
{}
HRESULT FinalConstruct() { return S_OK; } void SetProgressDialog(IProgressDialog* pd)
{
m_spProgressDialog = pd;
}
void FinalRelease() STDMETHOD(OnProgress)(ULONG ulProgress, ULONG ulProgressMax, ULONG /*ulStatusCode*/, LPCWSTR /*szStatusText*/)
{ {
} if (m_spProgressDialog != nullptr) {
if (m_spProgressDialog->HasUserCancelled()) {
return E_ABORT;
}
void SetProgressDialog(IProgressDialog* pd) m_spProgressDialog->SetProgress(ulProgress, ulProgressMax);
{ }
m_spProgressDialog = pd;
}
STDMETHOD(OnProgress)(ULONG ulProgress, ULONG ulProgressMax, ULONG /*ulStatusCode*/, LPCWSTR /*szStatusText*/) return S_OK;
{ }
if (m_spProgressDialog != nullptr) {
if (m_spProgressDialog->HasUserCancelled()) {
return E_ABORT;
}
m_spProgressDialog->SetProgress(ulProgress, ulProgressMax); STDMETHOD(OnStartBinding)(DWORD /*dwReserved*/, IBinding* pBinding) { return E_NOTIMPL; }
} STDMETHOD(GetPriority)(LONG* pnPriority) { return E_NOTIMPL; }
STDMETHOD(OnLowResource)(DWORD /*reserved*/) { return E_NOTIMPL; }
return S_OK; STDMETHOD(OnStopBinding)(HRESULT /*hresult*/, LPCWSTR /*szError*/) { return E_NOTIMPL; }
} STDMETHOD(GetBindInfo)(DWORD* pgrfBINDF, BINDINFO* pbindInfo) { return E_NOTIMPL; }
STDMETHOD(OnDataAvailable)(DWORD grfBSCF, DWORD dwSize, FORMATETC* /*pformatetc*/, STGMEDIUM* pstgmed) { return E_NOTIMPL; }
STDMETHOD(OnStartBinding)(DWORD /*dwReserved*/, IBinding *pBinding) { return E_NOTIMPL; } STDMETHOD(OnObjectAvailable)(REFIID /*riid*/, IUnknown* /*punk*/) { return E_NOTIMPL; }
STDMETHOD(GetPriority)(LONG *pnPriority) { return E_NOTIMPL; }
STDMETHOD(OnLowResource)(DWORD /*reserved*/) { return E_NOTIMPL; }
STDMETHOD(OnStopBinding)(HRESULT /*hresult*/, LPCWSTR /*szError*/) { return E_NOTIMPL; }
STDMETHOD(GetBindInfo)(DWORD *pgrfBINDF, BINDINFO *pbindInfo) { return E_NOTIMPL; }
STDMETHOD(OnDataAvailable)(DWORD grfBSCF, DWORD dwSize, FORMATETC * /*pformatetc*/, STGMEDIUM *pstgmed) { return E_NOTIMPL; }
STDMETHOD(OnObjectAvailable)(REFIID /*riid*/, IUnknown * /*punk*/) { return E_NOTIMPL; }
private: private:
CComPtr<IProgressDialog> m_spProgressDialog; CComPtr<IProgressDialog> m_spProgressDialog;
}; };
HRESULT CFxHelper::InstallDotnet(const RUNTIMEINFO* runtime, bool isQuiet) HRESULT CFxHelper::InstallDotnet(const RUNTIMEINFO* runtime, bool isQuiet)
{ {
auto runtimeName = wstring(runtime->friendlyName); auto runtimeName = wstring(runtime->friendlyName);
auto runtimeUrl = wstring(runtime->installerUrl); auto runtimeUrl = wstring(runtime->installerUrl);
if (!isQuiet) { if (!isQuiet) {
CTaskDialog dlg; CTaskDialog dlg;
TASKDIALOG_BUTTON buttons[] = { TASKDIALOG_BUTTON buttons[] = {
{ 1, L"Install", }, { 1, L"Install", },
{ 2, L"Cancel", }, { 2, L"Cancel", },
}; };
wstring txtInstruction = L"Install " + runtimeName; wstring txtInstruction = L"Install " + runtimeName;
wstring txtMain = L"This application requires " + runtimeName + L". Click the Install button to get started."; wstring txtMain = L"This application requires " + runtimeName + L". Click the Install button to get started.";
wstring txtExpanded = L"Clicking install will download the latest version of this operating system component from Microsoft and install it on your PC. Setup can not continue until this is complete."; wstring txtExpanded = L"Clicking install will download the latest version of this operating system component from Microsoft and install it on your PC. Setup can not continue until this is complete.";
dlg.SetButtons(buttons, 2); dlg.SetButtons(buttons, 2);
dlg.SetMainInstructionText(txtInstruction.c_str()); dlg.SetMainInstructionText(txtInstruction.c_str());
dlg.SetContentText(txtMain.c_str()); dlg.SetContentText(txtMain.c_str());
dlg.SetMainIcon(TD_INFORMATION_ICON); dlg.SetMainIcon(TD_INFORMATION_ICON);
dlg.SetExpandedInformationText(txtExpanded.c_str()); dlg.SetExpandedInformationText(txtExpanded.c_str());
int nButton; int nButton;
if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton)) || nButton != 1) { if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton)) || nButton != 1) {
return S_FALSE; return S_FALSE;
} }
} }
HRESULT hr = E_FAIL; HRESULT hr = E_FAIL;
WCHAR szFinalTempFileName[_MAX_PATH] = L""; WCHAR szFinalTempFileName[_MAX_PATH] = L"";
CComPtr<IBindStatusCallback> bscb; CComPtr<IBindStatusCallback> bscb;
CComPtr<IProgressDialog> pd; CComPtr<IProgressDialog> pd;
SHELLEXECUTEINFO execInfo = { sizeof(execInfo), }; SHELLEXECUTEINFO execInfo = { sizeof(execInfo), };
WCHAR szTempPath[_MAX_PATH]; WCHAR szTempPath[_MAX_PATH];
DWORD dwTempPathResult = GetTempPath(_MAX_PATH, szTempPath); DWORD dwTempPathResult = GetTempPath(_MAX_PATH, szTempPath);
if (dwTempPathResult == 0) { if (dwTempPathResult == 0) {
hr = AtlHresultFromLastError(); hr = AtlHresultFromLastError();
goto out; goto out;
} else if (dwTempPathResult > _MAX_PATH) { }
hr = DISP_E_BUFFERTOOSMALL; else if (dwTempPathResult > _MAX_PATH) {
goto out; hr = DISP_E_BUFFERTOOSMALL;
} goto out;
}
WCHAR szTempFileName[_MAX_PATH]; WCHAR szTempFileName[_MAX_PATH];
if (!GetTempFileName(szTempPath, L"NDP", 0, szTempFileName)) { if (!GetTempFileName(szTempPath, L"NDP", 0, szTempFileName)) {
hr = AtlHresultFromLastError(); hr = AtlHresultFromLastError();
goto out; goto out;
} }
szTempFileName[_countof(szTempFileName) - 1] = L'\0'; szTempFileName[_countof(szTempFileName) - 1] = L'\0';
if (wcscpy_s(szFinalTempFileName, _countof(szFinalTempFileName), szTempFileName) != 0) { if (wcscpy_s(szFinalTempFileName, _countof(szFinalTempFileName), szTempFileName) != 0) {
hr = E_FAIL; hr = E_FAIL;
goto out; goto out;
} }
WCHAR* pLastDot = wcsrchr(szFinalTempFileName, L'.'); WCHAR* pLastDot = wcsrchr(szFinalTempFileName, L'.');
if (pLastDot == nullptr) { if (pLastDot == nullptr) {
if (wcscat_s(szFinalTempFileName, _countof(szFinalTempFileName), L".exe") != 0) { if (wcscat_s(szFinalTempFileName, _countof(szFinalTempFileName), L".exe") != 0) {
hr = E_FAIL; hr = E_FAIL;
goto out; goto out;
} }
} else { }
if (wcscpy_s(pLastDot, _countof(szFinalTempFileName) - (pLastDot - szFinalTempFileName), L".exe") != 0) { else {
hr = E_FAIL; if (wcscpy_s(pLastDot, _countof(szFinalTempFileName) - (pLastDot - szFinalTempFileName), L".exe") != 0) {
goto out; hr = E_FAIL;
} goto out;
} }
}
if (!MoveFile(szTempFileName, szFinalTempFileName)) { if (!MoveFile(szTempFileName, szFinalTempFileName)) {
hr = AtlHresultFromLastError(); hr = AtlHresultFromLastError();
goto out; goto out;
} }
if (!isQuiet) { if (!isQuiet) {
pd.CoCreateInstance(CLSID_ProgressDialog); pd.CoCreateInstance(CLSID_ProgressDialog);
if (pd != nullptr) { if (pd != nullptr) {
pd->SetTitle(L"Downloading"); pd->SetTitle(L"Downloading");
pd->SetLine(1, L"Downloading the .NET installer", FALSE, nullptr); pd->SetLine(1, L"Downloading the .NET installer", FALSE, nullptr);
pd->StartProgressDialog(nullptr, nullptr, 0, nullptr); pd->StartProgressDialog(nullptr, nullptr, 0, nullptr);
CComObject<CDownloadProgressCallback>* bscbObj = nullptr; CComObject<CDownloadProgressCallback>* bscbObj = nullptr;
if (SUCCEEDED(CComObject<CDownloadProgressCallback>::CreateInstance(&bscbObj))) { if (SUCCEEDED(CComObject<CDownloadProgressCallback>::CreateInstance(&bscbObj))) {
bscbObj->SetProgressDialog(pd); bscbObj->SetProgressDialog(pd);
bscb = bscbObj; bscb = bscbObj;
} }
} }
} }
hr = URLDownloadToFile(nullptr, runtimeUrl.c_str(), szFinalTempFileName, 0, bscb); hr = URLDownloadToFile(nullptr, runtimeUrl.c_str(), szFinalTempFileName, 0, bscb);
if (pd != nullptr) { if (pd != nullptr) {
pd->StopProgressDialog(); pd->StopProgressDialog();
} }
if (hr != S_OK) { if (hr != S_OK) {
goto out; goto out;
} }
execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; execInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
execInfo.lpVerb = L"open"; execInfo.lpVerb = L"open";
execInfo.lpFile = szFinalTempFileName; execInfo.lpFile = szFinalTempFileName;
if (isQuiet) { if (isQuiet) {
execInfo.lpParameters = L"/q /norestart"; execInfo.lpParameters = L"/q /norestart";
} }
else { else {
execInfo.lpParameters = L"/passive /norestart /showrmui"; execInfo.lpParameters = L"/passive /norestart /showrmui";
} }
execInfo.nShow = SW_SHOW; execInfo.nShow = SW_SHOW;
if (!ShellExecuteEx(&execInfo)) { if (!ShellExecuteEx(&execInfo)) {
hr = AtlHresultFromLastError(); hr = AtlHresultFromLastError();
goto out; goto out;
} }
WaitForSingleObject(execInfo.hProcess, INFINITE); WaitForSingleObject(execInfo.hProcess, INFINITE);
DWORD exitCode; DWORD exitCode;
if (!GetExitCodeProcess(execInfo.hProcess, &exitCode)) { if (!GetExitCodeProcess(execInfo.hProcess, &exitCode)) {
hr = AtlHresultFromLastError(); hr = AtlHresultFromLastError();
goto out; goto out;
} }
if (exitCode == 1641 || exitCode == 3010) { if (exitCode == 1641 || exitCode == 3010) {
// The framework installer wants a reboot before we can continue // The framework installer wants a reboot before we can continue
// See https://msdn.microsoft.com/en-us/library/ee942965%28v=vs.110%29.aspx // See https://msdn.microsoft.com/en-us/library/ee942965%28v=vs.110%29.aspx
// hr = HandleRebootRequirement(isQuiet); // hr = HandleRebootRequirement(isQuiet);
// Exit as a failure, so that setup doesn't carry on now // Exit as a failure, so that setup doesn't carry on now
hr = ERROR_SUCCESS_REBOOT_REQUIRED; hr = ERROR_SUCCESS_REBOOT_REQUIRED;
} }
else { else {
hr = exitCode != 0 ? E_FAIL : S_OK; hr = exitCode != 0 ? E_FAIL : S_OK;
} }
out: out:
if (execInfo.hProcess != NULL && execInfo.hProcess != INVALID_HANDLE_VALUE) { if (execInfo.hProcess != NULL && execInfo.hProcess != INVALID_HANDLE_VALUE) {
CloseHandle(execInfo.hProcess); CloseHandle(execInfo.hProcess);
} }
if (*szFinalTempFileName != L'\0') { if (*szFinalTempFileName != L'\0') {
DeleteFile(szFinalTempFileName); DeleteFile(szFinalTempFileName);
} }
return hr; return hr;
} }
// Deal with the aftermath of the framework installer telling us that we need to reboot // Deal with the aftermath of the framework installer telling us that we need to reboot
HRESULT CFxHelper::HandleRebootRequirement(bool isQuiet) HRESULT CFxHelper::HandleRebootRequirement(bool isQuiet)
{ {
if (isQuiet) { if (isQuiet) {
// Don't silently reboot - just error-out // Don't silently reboot - just error-out
fprintf_s(stderr, "A reboot is required following .NET installation - reboot then run installer again.\n"); fprintf_s(stderr, "A reboot is required following .NET installation - reboot then run installer again.\n");
return E_FAIL; return E_FAIL;
} }
CTaskDialog dlg; CTaskDialog dlg;
TASKDIALOG_BUTTON buttons[] = { TASKDIALOG_BUTTON buttons[] = {
{ 1, L"Restart Now", }, { 1, L"Restart Now", },
{ 2, L"Cancel", }, { 2, L"Cancel", },
}; };
dlg.SetButtons(buttons, 2); dlg.SetButtons(buttons, 2);
dlg.SetMainInstructionText(L"Restart System"); dlg.SetMainInstructionText(L"Restart System");
dlg.SetContentText(L"To finish installing the .NET Framework, the system now needs to restart. The installation will finish after you restart and log-in again."); dlg.SetContentText(L"To finish installing the .NET Framework, the system now needs to restart. The installation will finish after you restart and log-in again.");
dlg.SetMainIcon(TD_INFORMATION_ICON); dlg.SetMainIcon(TD_INFORMATION_ICON);
dlg.SetExpandedInformationText(L"If you click 'Cancel', you'll need to re-run this setup program yourself, after restarting your system."); dlg.SetExpandedInformationText(L"If you click 'Cancel', you'll need to re-run this setup program yourself, after restarting your system.");
int nButton; int nButton;
if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton)) || nButton != 1) { if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton)) || nButton != 1) {
return S_FALSE; return S_FALSE;
} }
// We need to set up a runonce entry to restart this installer once the reboot has happened // We need to set up a runonce entry to restart this installer once the reboot has happened
if (!WriteRunOnceEntry()) { if (!WriteRunOnceEntry()) {
return E_FAIL; return E_FAIL;
} }
// And now, reboot // And now, reboot
if (!RebootSystem()) { if (!RebootSystem()) {
return E_FAIL; return E_FAIL;
} }
// About to reboot, but just in case... // About to reboot, but just in case...
return S_FALSE; return S_FALSE;
} }
// //
@@ -253,44 +251,44 @@ HRESULT CFxHelper::HandleRebootRequirement(bool isQuiet)
// //
bool CFxHelper::WriteRunOnceEntry() bool CFxHelper::WriteRunOnceEntry()
{ {
ATL::CRegKey key; ATL::CRegKey key;
if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", KEY_WRITE) != ERROR_SUCCESS) { if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", KEY_WRITE) != ERROR_SUCCESS) {
return false; return false;
} }
TCHAR exePath[MAX_PATH]; TCHAR exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, MAX_PATH); GetModuleFileName(NULL, exePath, MAX_PATH);
if (key.SetStringValue(L"SquirrelInstall", exePath) != ERROR_SUCCESS) { if (key.SetStringValue(L"SquirrelInstall", exePath) != ERROR_SUCCESS) {
return false; return false;
} }
return true; return true;
} }
bool CFxHelper::RebootSystem() bool CFxHelper::RebootSystem()
{ {
// First we need to enable the SE_SHUTDOWN_NAME privilege // First we need to enable the SE_SHUTDOWN_NAME privilege
LUID luid; LUID luid;
if (!LookupPrivilegeValue(L"", SE_SHUTDOWN_NAME, &luid)) { if (!LookupPrivilegeValue(L"", SE_SHUTDOWN_NAME, &luid)) {
return false; return false;
} }
HANDLE hToken = NULL; HANDLE hToken = NULL;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
return false; return false;
} }
TOKEN_PRIVILEGES tp; TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1; tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid; tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, 0)) { if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, 0)) {
CloseHandle(hToken); CloseHandle(hToken);
return false; return false;
} }
// Now we have that privilege, we can ask Windows to restart // Now we have that privilege, we can ask Windows to restart
return ExitWindowsEx(EWX_REBOOT, 0) != 0; return ExitWindowsEx(EWX_REBOOT, 0) != 0;
} }

View File

@@ -3,10 +3,12 @@
class CFxHelper class CFxHelper
{ {
public: public:
static HRESULT InstallDotnet(const RUNTIMEINFO* runtime, bool isQuiet); static HRESULT InstallDotnet(const RUNTIMEINFO* runtime, bool isQuiet);
static HRESULT HandleRebootRequirement(bool isQuiet); static HRESULT HandleRebootRequirement(bool isQuiet);
private: private:
static bool WriteRunOnceEntry(); static bool WriteRunOnceEntry();
static bool RebootSystem(); static bool RebootSystem();
}; };

View File

@@ -12,245 +12,226 @@ void ASSERT(void* obj) {}
// https://stackoverflow.com/a/66238748/184746 // https://stackoverflow.com/a/66238748/184746
IStream* LoadImageFromResource(const wchar_t* resid, const wchar_t* restype) IStream* LoadImageFromResource(const wchar_t* resid, const wchar_t* restype)
{ {
IStream* pStream = nullptr; IStream* pStream = nullptr;
HGLOBAL hGlobal = nullptr; HGLOBAL hGlobal = nullptr;
HINSTANCE hInst = GetModuleHandle(NULL); HINSTANCE hInst = GetModuleHandle(NULL);
HRSRC hrsrc = FindResourceW(hInst, resid, restype); // get the handle to the resource HRSRC hrsrc = FindResourceW(hInst, resid, restype); // get the handle to the resource
if (hrsrc) if (hrsrc) {
{ DWORD dwResourceSize = SizeofResource(hInst, hrsrc);
DWORD dwResourceSize = SizeofResource(hInst, hrsrc); if (dwResourceSize > 0) {
if (dwResourceSize > 0) HGLOBAL hGlobalResource = LoadResource(hInst, hrsrc); // load it
{ if (hGlobalResource) {
HGLOBAL hGlobalResource = LoadResource(hInst, hrsrc); // load it void* imagebytes = LockResource(hGlobalResource); // get a pointer to the file bytes
if (hGlobalResource)
{
void* imagebytes = LockResource(hGlobalResource); // get a pointer to the file bytes
// copy image bytes into a real hglobal memory handle // copy image bytes into a real hglobal memory handle
hGlobal = ::GlobalAlloc(GHND, dwResourceSize); hGlobal = ::GlobalAlloc(GHND, dwResourceSize);
if (hGlobal) if (hGlobal) {
{ void* pBuffer = ::GlobalLock(hGlobal);
void* pBuffer = ::GlobalLock(hGlobal); if (pBuffer) {
if (pBuffer) memcpy(pBuffer, imagebytes, dwResourceSize);
{ HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
memcpy(pBuffer, imagebytes, dwResourceSize); if (SUCCEEDED(hr)) {
HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream); // pStream now owns the global handle and will invoke GlobalFree on release
if (SUCCEEDED(hr)) hGlobal = nullptr;
{ }
// pStream now owns the global handle and will invoke GlobalFree on release }
hGlobal = nullptr; }
} }
} }
} }
}
}
}
if (hGlobal) if (hGlobal) {
{ GlobalFree(hGlobal);
GlobalFree(hGlobal); hGlobal = nullptr;
hGlobal = nullptr; }
}
return pStream; return pStream;
} }
ImageEx::ImageEx(const wchar_t* resid, const wchar_t* restype) ImageEx::ImageEx(const wchar_t* resid, const wchar_t* restype)
{ {
Initialize(); Initialize();
auto stream = LoadImageFromResource(resid, restype); auto stream = LoadImageFromResource(resid, restype);
if (stream) if (stream) {
{ nativeImage = NULL;
nativeImage = NULL; lastResult = DllExports::GdipLoadImageFromStreamICM(stream, &nativeImage);
lastResult = DllExports::GdipLoadImageFromStreamICM(stream, &nativeImage); m_bIsInitialized = true;
m_bIsInitialized = true; TestForAnimatedGIF();
TestForAnimatedGIF(); stream->Release();
stream->Release(); }
}
} }
ImageEx::~ImageEx() ImageEx::~ImageEx()
{ {
Destroy(); Destroy();
} }
bool ImageEx::InitAnimation(HWND hWnd, CPoint pt) bool ImageEx::InitAnimation(HWND hWnd, CPoint pt)
{ {
m_hWnd = hWnd;
m_pt = pt;
m_hWnd = hWnd; if (!m_bIsInitialized) {
m_pt = pt; TRACE(_T("GIF not initialized\n"));
return false;
};
if (!m_bIsInitialized) if (IsAnimatedGIF()) {
{ if (m_hThread == NULL) {
TRACE(_T("GIF not initialized\n"));
return false;
};
if (IsAnimatedGIF()) unsigned int nTID = 0;
{
if (m_hThread == NULL)
{
unsigned int nTID = 0; m_hThread = (HANDLE)_beginthreadex(NULL, 0, _ThreadAnimationProc, this, CREATE_SUSPENDED, &nTID);
m_hThread = (HANDLE)_beginthreadex(NULL, 0, _ThreadAnimationProc, this, CREATE_SUSPENDED, &nTID); if (!m_hThread) {
TRACE(_T("Couldn't start a GIF animation thread\n"));
return true;
}
else
ResumeThread(m_hThread);
}
}
if (!m_hThread) return false;
{
TRACE(_T("Couldn't start a GIF animation thread\n"));
return true;
}
else
ResumeThread(m_hThread);
}
}
return false;
} }
CSize ImageEx::GetSize() CSize ImageEx::GetSize()
{ {
return CSize(GetWidth(), GetHeight()); return CSize(GetWidth(), GetHeight());
} }
bool ImageEx::TestForAnimatedGIF() bool ImageEx::TestForAnimatedGIF()
{ {
UINT count = 0; UINT count = 0;
count = GetFrameDimensionsCount(); count = GetFrameDimensionsCount();
GUID* pDimensionIDs = new GUID[count]; GUID* pDimensionIDs = new GUID[count];
// Get the list of frame dimensions from the Image object. // Get the list of frame dimensions from the Image object.
GetFrameDimensionsList(pDimensionIDs, count); GetFrameDimensionsList(pDimensionIDs, count);
// Get the number of frames in the first dimension. // Get the number of frames in the first dimension.
m_nFrameCount = GetFrameCount(&pDimensionIDs[0]); m_nFrameCount = GetFrameCount(&pDimensionIDs[0]);
// Assume that the image has a property item of type PropertyItemEquipMake. // Assume that the image has a property item of type PropertyItemEquipMake.
// Get the size of that property item. // Get the size of that property item.
int nSize = GetPropertyItemSize(PropertyTagFrameDelay); int nSize = GetPropertyItemSize(PropertyTagFrameDelay);
// Allocate a buffer to receive the property item. // Allocate a buffer to receive the property item.
m_pPropertyItem = (PropertyItem*)malloc(nSize); m_pPropertyItem = (PropertyItem*)malloc(nSize);
GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem); GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem);
delete pDimensionIDs; delete pDimensionIDs;
return m_nFrameCount > 1; return m_nFrameCount > 1;
} }
void ImageEx::Initialize() void ImageEx::Initialize()
{ {
m_nFramePosition = 0; m_nFramePosition = 0;
m_nFrameCount = 0; m_nFrameCount = 0;
lastResult = InvalidParameter; lastResult = InvalidParameter;
m_hThread = NULL; m_hThread = NULL;
m_bIsInitialized = false; m_bIsInitialized = false;
m_pPropertyItem = NULL; m_pPropertyItem = NULL;
#ifdef INDIGO_CTRL_PROJECT #ifdef INDIGO_CTRL_PROJECT
m_hInst = _Module.GetResourceInstance(); m_hInst = _Module.GetResourceInstance();
#else #else
m_hInst = GetModuleHandle(NULL);// AfxGetResourceHandle(); m_hInst = GetModuleHandle(NULL);// AfxGetResourceHandle();
#endif #endif
m_bPause = false; m_bPause = false;
m_hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); m_hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
m_hPause = CreateEvent(NULL, TRUE, TRUE, NULL); m_hPause = CreateEvent(NULL, TRUE, TRUE, NULL);
} }
UINT WINAPI ImageEx::_ThreadAnimationProc(LPVOID pParam) UINT WINAPI ImageEx::_ThreadAnimationProc(LPVOID pParam)
{ {
ASSERT(pParam); ASSERT(pParam);
ImageEx* pImage = reinterpret_cast<ImageEx*> (pParam); ImageEx* pImage = reinterpret_cast<ImageEx*> (pParam);
pImage->ThreadAnimation(); pImage->ThreadAnimation();
return 0; return 0;
} }
void ImageEx::ThreadAnimation() void ImageEx::ThreadAnimation()
{ {
m_nFramePosition = 0; m_nFramePosition = 0;
bool bExit = false; bool bExit = false;
while (bExit == false) while (bExit == false) {
{ bExit = DrawFrameGIF();
bExit = DrawFrameGIF(); }
}
} }
bool ImageEx::DrawFrameGIF() bool ImageEx::DrawFrameGIF()
{ {
::WaitForSingleObject(m_hPause, INFINITE); ::WaitForSingleObject(m_hPause, INFINITE);
GUID pageGuid = FrameDimensionTime; GUID pageGuid = FrameDimensionTime;
long hmWidth = GetWidth(); long hmWidth = GetWidth();
long hmHeight = GetHeight(); long hmHeight = GetHeight();
HDC hDC = GetDC(m_hWnd); HDC hDC = GetDC(m_hWnd);
if (hDC) if (hDC) {
{ Graphics graphics(hDC);
Graphics graphics(hDC); graphics.DrawImage(this, m_pt.x, m_pt.y, hmWidth, hmHeight);
graphics.DrawImage(this, m_pt.x, m_pt.y, hmWidth, hmHeight); ReleaseDC(m_hWnd, hDC);
ReleaseDC(m_hWnd, hDC); }
}
SelectActiveFrame(&pageGuid, m_nFramePosition++); SelectActiveFrame(&pageGuid, m_nFramePosition++);
if (m_nFramePosition == m_nFrameCount) if (m_nFramePosition == m_nFrameCount)
m_nFramePosition = 0; m_nFramePosition = 0;
long lPause = ((long*)m_pPropertyItem->value)[m_nFramePosition] * 10; long lPause = ((long*)m_pPropertyItem->value)[m_nFramePosition] * 10;
DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause); DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause);
return dwErr == WAIT_OBJECT_0; return dwErr == WAIT_OBJECT_0;
} }
void ImageEx::SetPause(bool bPause) void ImageEx::SetPause(bool bPause)
{ {
if (!IsAnimatedGIF()) if (!IsAnimatedGIF())
return; return;
if (bPause && !m_bPause) if (bPause && !m_bPause) {
{ ::ResetEvent(m_hPause);
::ResetEvent(m_hPause); }
} else {
else
{
if (m_bPause && !bPause) if (m_bPause && !bPause) {
{ ::SetEvent(m_hPause);
::SetEvent(m_hPause); }
} }
}
m_bPause = bPause; m_bPause = bPause;
} }
void ImageEx::Destroy() void ImageEx::Destroy()
{ {
if (m_hThread) if (m_hThread) {
{ // If pause un pause
// If pause un pause SetPause(false);
SetPause(false);
SetEvent(m_hExitEvent); SetEvent(m_hExitEvent);
WaitForSingleObject(m_hThread, INFINITE); WaitForSingleObject(m_hThread, INFINITE);
} }
CloseHandle(m_hThread); CloseHandle(m_hThread);
CloseHandle(m_hExitEvent); CloseHandle(m_hExitEvent);
CloseHandle(m_hPause); CloseHandle(m_hPause);
free(m_pPropertyItem); free(m_pPropertyItem);
m_pPropertyItem = NULL; m_pPropertyItem = NULL;
m_hThread = NULL; m_hThread = NULL;
m_hExitEvent = NULL; m_hExitEvent = NULL;
m_hPause = NULL; m_hPause = NULL;
} }

View File

@@ -17,36 +17,36 @@ class ImageEx : public Image
{ {
public: public:
ImageEx(const wchar_t* resid, const wchar_t* restype); ImageEx(const wchar_t* resid, const wchar_t* restype);
~ImageEx(); ~ImageEx();
public: public:
CSize GetSize(); CSize GetSize();
bool IsAnimatedGIF() { return m_nFrameCount > 1; } bool IsAnimatedGIF() { return m_nFrameCount > 1; }
void SetPause(bool bPause); void SetPause(bool bPause);
bool IsPaused() { return m_bPause; } bool IsPaused() { return m_bPause; }
bool InitAnimation(HWND hWnd, CPoint pt); bool InitAnimation(HWND hWnd, CPoint pt);
void Destroy(); void Destroy();
protected: protected:
bool TestForAnimatedGIF(); bool TestForAnimatedGIF();
void Initialize(); void Initialize();
bool DrawFrameGIF(); bool DrawFrameGIF();
void ThreadAnimation(); void ThreadAnimation();
static UINT WINAPI _ThreadAnimationProc(LPVOID pParam); static UINT WINAPI _ThreadAnimationProc(LPVOID pParam);
HANDLE m_hThread; HANDLE m_hThread;
HANDLE m_hPause; HANDLE m_hPause;
HANDLE m_hExitEvent; HANDLE m_hExitEvent;
HINSTANCE m_hInst; HINSTANCE m_hInst;
HWND m_hWnd; HWND m_hWnd;
UINT m_nFrameCount; UINT m_nFrameCount;
UINT m_nFramePosition; UINT m_nFramePosition;
bool m_bIsInitialized; bool m_bIsInitialized;
bool m_bPause; bool m_bPause;
PropertyItem* m_pPropertyItem; PropertyItem* m_pPropertyItem;
CPoint m_pt; CPoint m_pt;
}; };

View File

@@ -4,64 +4,65 @@
#include "resource.h" #include "resource.h"
#include <sddl.h> #include <sddl.h>
bool directoryExists(wchar_t* path) { bool directoryExists(wchar_t* path)
DWORD dwResult = GetFileAttributes(path); {
DWORD dwResult = GetFileAttributes(path);
if (dwResult != INVALID_FILE_ATTRIBUTES) { if (dwResult != INVALID_FILE_ATTRIBUTES) {
return true; return true;
} }
// NB: The directory could exist but we can't access it, let's check // NB: The directory could exist but we can't access it, let's check
DWORD dwLastError = GetLastError(); DWORD dwLastError = GetLastError();
if (dwLastError == ERROR_FILE_NOT_FOUND) return false; if (dwLastError == ERROR_FILE_NOT_FOUND) return false;
if (dwLastError == ERROR_PATH_NOT_FOUND) return false; if (dwLastError == ERROR_PATH_NOT_FOUND) return false;
return true; return true;
} }
bool MachineInstaller::ShouldSilentInstall() bool MachineInstaller::ShouldSilentInstall()
{ {
// Figure out the package name from our own EXE name // Figure out the package name from our own EXE name
// The name consist of [$pkgName]DeploymentTool.exe // The name consist of [$pkgName]DeploymentTool.exe
wchar_t ourFile[MAX_PATH]; wchar_t ourFile[MAX_PATH];
HMODULE hMod = GetModuleHandle(NULL); HMODULE hMod = GetModuleHandle(NULL);
GetModuleFileName(hMod, ourFile, _countof(ourFile)); GetModuleFileName(hMod, ourFile, _countof(ourFile));
CString fullPath = CString(ourFile); CString fullPath = CString(ourFile);
CString pkgName = CString(ourFile + fullPath.ReverseFind(L'\\')); CString pkgName = CString(ourFile + fullPath.ReverseFind(L'\\'));
pkgName.Replace(L"DeploymentTool.exe", L""); pkgName.Replace(L"DeploymentTool.exe", L"");
wchar_t installFolder[MAX_PATH];
// NB: Users often get into the sitch where they install the MSI, then try to wchar_t installFolder[MAX_PATH];
// install the standalone package on top of that. In previous versions we tried
// to detect if the app was properly installed, but now we're taking the much
// more conservative approach, that if the package dir exists in any way, we're
// bailing out
// C:\Users\Username\AppData\Local\$pkgName // NB: Users often get into the sitch where they install the MSI, then try to
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, installFolder); // install the standalone package on top of that. In previous versions we tried
wcscat(installFolder, L"\\"); // to detect if the app was properly installed, but now we're taking the much
wcscat(installFolder, pkgName); // more conservative approach, that if the package dir exists in any way, we're
// bailing out
if (directoryExists(installFolder)) { // C:\Users\Username\AppData\Local\$pkgName
return false; SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, installFolder);
} wcscat(installFolder, L"\\");
wcscat(installFolder, pkgName);
// C:\ProgramData\$pkgName\$username if (directoryExists(installFolder)) {
wchar_t username[512]; return false;
DWORD unamesize = _countof(username); }
SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, installFolder);
GetUserName(username, &unamesize);
wcscat(installFolder, L"\\");
wcscat(installFolder, pkgName);
wcscat(installFolder, L"\\");
wcscat(installFolder, username);
if (directoryExists(installFolder)) { // C:\ProgramData\$pkgName\$username
return false; wchar_t username[512];
} DWORD unamesize = _countof(username);
SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, installFolder);
GetUserName(username, &unamesize);
wcscat(installFolder, L"\\");
wcscat(installFolder, pkgName);
wcscat(installFolder, L"\\");
wcscat(installFolder, username);
// None of these exist, we should install if (directoryExists(installFolder)) {
return true; return false;
}
// None of these exist, we should install
return true;
} }

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
class MachineInstaller class MachineInstaller
{ {
public: public:
static bool ShouldSilentInstall(); static bool ShouldSilentInstall();
}; };

View File

@@ -9,104 +9,104 @@ using std::vector;
RUNTIMEINFO supported_runtimes[] = RUNTIMEINFO supported_runtimes[] =
{ {
// net45 through net46 is supported on Vista SP2 and newer // net45 through net46 is supported on Vista SP2 and newer
{ {
_WIN32_WINNT_VISTA, 2, _WIN32_WINNT_VISTA, 2,
L"net45", L".NET Framework 4.5", L"net45", L".NET Framework 4.5",
L"http://go.microsoft.com/fwlink/?LinkId=397707", L"http://go.microsoft.com/fwlink/?LinkId=397707",
378389 378389
}, },
{ {
_WIN32_WINNT_VISTA, 2, _WIN32_WINNT_VISTA, 2,
L"net451", L".NET Framework 4.5.1", L"net451", L".NET Framework 4.5.1",
L"http://go.microsoft.com/fwlink/?LinkId=397707", L"http://go.microsoft.com/fwlink/?LinkId=397707",
378675 378675
}, },
{ {
_WIN32_WINNT_VISTA, 2, _WIN32_WINNT_VISTA, 2,
L"net452", L".NET Framework 4.5.2", L"net452", L".NET Framework 4.5.2",
L"http://go.microsoft.com/fwlink/?LinkId=397707", L"http://go.microsoft.com/fwlink/?LinkId=397707",
379893 379893
}, },
// net461 through net48 supports Windows 7 and newer // net461 through net48 supports Windows 7 and newer
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net46", L".NET Framework 4.6", L"net46", L".NET Framework 4.6",
L"http://go.microsoft.com/fwlink/?LinkId=780596", L"http://go.microsoft.com/fwlink/?LinkId=780596",
393295 393295
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net461", L".NET Framework 4.6.1", L"net461", L".NET Framework 4.6.1",
L"http://go.microsoft.com/fwlink/?LinkId=780596", L"http://go.microsoft.com/fwlink/?LinkId=780596",
394254 394254
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net462", L".NET Framework 4.6.2", L"net462", L".NET Framework 4.6.2",
L"http://go.microsoft.com/fwlink/?LinkId=780596", L"http://go.microsoft.com/fwlink/?LinkId=780596",
394802 394802
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net47", L".NET Framework 4.7", L"net47", L".NET Framework 4.7",
L"http://go.microsoft.com/fwlink/?LinkId=863262", L"http://go.microsoft.com/fwlink/?LinkId=863262",
460798 460798
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net471", L".NET Framework 4.7.1", L"net471", L".NET Framework 4.7.1",
L"http://go.microsoft.com/fwlink/?LinkId=863262", L"http://go.microsoft.com/fwlink/?LinkId=863262",
461308 461308
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net472", L".NET Framework 4.7.2", L"net472", L".NET Framework 4.7.2",
L"http://go.microsoft.com/fwlink/?LinkId=863262", L"http://go.microsoft.com/fwlink/?LinkId=863262",
461808 461808
}, },
{ {
_WIN32_WINNT_WIN7, 0, _WIN32_WINNT_WIN7, 0,
L"net48", L".NET Framework 4.8", L"net48", L".NET Framework 4.8",
L"https://go.microsoft.com/fwlink/?LinkId=2085155", L"https://go.microsoft.com/fwlink/?LinkId=2085155",
528040 528040
}, },
// dotnet core is supported on Windows 7 SP1 and newer. // dotnet core is supported on Windows 7 SP1 and newer.
// update this list periodically from https://dotnet.microsoft.com/download/dotnet // update this list periodically from https://dotnet.microsoft.com/download/dotnet
// we could add support for 2.0/2.1/2.2 but since those runtimes didn't ship with desktop support it is probably not needed. // we could add support for 2.0/2.1/2.2 but since those runtimes didn't ship with desktop support it is probably not needed.
{ {
_WIN32_WINNT_WIN7, 1, _WIN32_WINNT_WIN7, 1,
L"netcoreapp3", L".NET Core 3.0.3", L"netcoreapp3", L".NET Core 3.0.3",
L"https://download.visualstudio.microsoft.com/download/pr/c525a2bb-6e98-4e6e-849e-45241d0db71c/d21612f02b9cae52fa50eb54de905986/windowsdesktop-runtime-3.0.3-win-x64.exe", L"https://download.visualstudio.microsoft.com/download/pr/c525a2bb-6e98-4e6e-849e-45241d0db71c/d21612f02b9cae52fa50eb54de905986/windowsdesktop-runtime-3.0.3-win-x64.exe",
0, L"WindowsDesktop.App 3.0" 0, L"WindowsDesktop.App 3.0"
}, },
{ {
_WIN32_WINNT_WIN7, 1, _WIN32_WINNT_WIN7, 1,
L"netcoreapp31", L".NET Core 3.1.18", L"netcoreapp31", L".NET Core 3.1.18",
L"https://download.visualstudio.microsoft.com/download/pr/aa240732-82d7-4bd1-9701-e4014d7dc735/41a460f5e9c343f7b9207f64ab0e4c90/windowsdesktop-runtime-3.1.18-win-x64.exe", L"https://download.visualstudio.microsoft.com/download/pr/aa240732-82d7-4bd1-9701-e4014d7dc735/41a460f5e9c343f7b9207f64ab0e4c90/windowsdesktop-runtime-3.1.18-win-x64.exe",
0, L"WindowsDesktop.App 3.1" 0, L"WindowsDesktop.App 3.1"
}, },
{ {
_WIN32_WINNT_WIN7, 1, _WIN32_WINNT_WIN7, 1,
L"net5", L".NET 5.0.9", L"net5", L".NET 5.0.9",
L"https://download.visualstudio.microsoft.com/download/pr/8bc41df1-cbb4-4da6-944f-6652378e9196/1014aacedc80bbcc030dabb168d2532f/windowsdesktop-runtime-5.0.9-win-x64.exe", L"https://download.visualstudio.microsoft.com/download/pr/8bc41df1-cbb4-4da6-944f-6652378e9196/1014aacedc80bbcc030dabb168d2532f/windowsdesktop-runtime-5.0.9-win-x64.exe",
0, L"WindowsDesktop.App 5.0" 0, L"WindowsDesktop.App 5.0"
}, },
}; };
@@ -114,126 +114,123 @@ RUNTIMEINFO supported_runtimes[] =
const RUNTIMEINFO* GetRuntimeByName(wstring name) const RUNTIMEINFO* GetRuntimeByName(wstring name)
{ {
for (int i = 0; i < NUM_RUNTIMES; i++) for (int i = 0; i < NUM_RUNTIMES; i++) {
{ const RUNTIMEINFO* item = &supported_runtimes[i];
const RUNTIMEINFO* item = &supported_runtimes[i]; auto itemName = wstring(item->name);
auto itemName = wstring(item->name);
if (name == itemName) if (name == itemName)
return item; return item;
} }
return nullptr; return nullptr;
} }
bool IsRuntimeSupported(const RUNTIMEINFO* runtime) bool IsRuntimeSupported(const RUNTIMEINFO* runtime)
{ {
return IsWindowsVersionOrGreater(HIBYTE(runtime->minOS), LOBYTE(runtime->minOS), runtime->minSP); return IsWindowsVersionOrGreater(HIBYTE(runtime->minOS), LOBYTE(runtime->minOS), runtime->minSP);
} }
static const wchar_t* ndpPath = L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"; static const wchar_t* ndpPath = L"SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full";
bool IsFullNetFrameworkInstalled(DWORD requiredVersion) bool IsFullNetFrameworkInstalled(DWORD requiredVersion)
{ {
ATL::CRegKey key; ATL::CRegKey key;
if (key.Open(HKEY_LOCAL_MACHINE, ndpPath, KEY_READ) != ERROR_SUCCESS) { if (key.Open(HKEY_LOCAL_MACHINE, ndpPath, KEY_READ) != ERROR_SUCCESS) {
return false; return false;
} }
DWORD dwReleaseInfo = 0; DWORD dwReleaseInfo = 0;
if (key.QueryDWORDValue(L"Release", dwReleaseInfo) != ERROR_SUCCESS || if (key.QueryDWORDValue(L"Release", dwReleaseInfo) != ERROR_SUCCESS ||
dwReleaseInfo < requiredVersion) { dwReleaseInfo < requiredVersion) {
return false; return false;
} }
return true; return true;
} }
// TODO this is extremely messy, it should certainly be re-written. // TODO this is extremely messy, it should certainly be re-written.
wstring exec(const char* cmd) { wstring exec(const char* cmd)
char buffer[128]; {
string result = ""; char buffer[128];
FILE* pipe = _popen(cmd, "r"); string result = "";
if (!pipe) FILE* pipe = _popen(cmd, "r");
return L""; if (!pipe)
try { return L"";
while (fgets(buffer, sizeof buffer, pipe) != NULL) { try {
result += buffer; while (fgets(buffer, sizeof buffer, pipe) != NULL) {
} result += buffer;
} }
catch (...) { }
_pclose(pipe); catch (...) {
return L""; _pclose(pipe);
} return L"";
_pclose(pipe); }
_pclose(pipe);
// https://stackoverflow.com/a/8969776/184746 // https://stackoverflow.com/a/8969776/184746
std::wstring wsTmp(result.begin(), result.end()); std::wstring wsTmp(result.begin(), result.end());
return wsTmp; return wsTmp;
} }
bool IsDotNetCoreInstalled(wstring searchString) bool IsDotNetCoreInstalled(wstring searchString)
{ {
// it is possible to parse this registry entry, but it only returns the newest version // it is possible to parse this registry entry, but it only returns the newest version
// and it might be necessary to install an older version of the runtime if it's not installed, // and it might be necessary to install an older version of the runtime if it's not installed,
// so we need a full list of installed runtimes. // so we need a full list of installed runtimes.
// static const wchar_t* dncPath = L"SOFTWARE\\dotnet\\Setup\\InstalledVersions"; // static const wchar_t* dncPath = L"SOFTWARE\\dotnet\\Setup\\InstalledVersions";
// note, dotnet cli will only return x64 results. // note, dotnet cli will only return x64 results.
//auto runtimes = exec("dotnet --list-runtimes"); //auto runtimes = exec("dotnet --list-runtimes");
auto runtimes = exec("dotnet --info"); auto runtimes = exec("dotnet --info");
return runtimes.find(searchString) != std::string::npos; return runtimes.find(searchString) != std::string::npos;
} }
bool IsRuntimeInstalled(const RUNTIMEINFO* runtime) bool IsRuntimeInstalled(const RUNTIMEINFO* runtime)
{ {
if (runtime->fxReleaseVersion > 0) if (runtime->fxReleaseVersion > 0) {
{ return IsFullNetFrameworkInstalled(runtime->fxReleaseVersion);
return IsFullNetFrameworkInstalled(runtime->fxReleaseVersion); }
} else {
else return IsDotNetCoreInstalled(wstring(runtime->dncRuntimeVersionName));
{ }
return IsDotNetCoreInstalled(wstring(runtime->dncRuntimeVersionName));
}
} }
int ParseRuntimeString(std::wstring version, vector<const RUNTIMEINFO*>& runtimes) int ParseRuntimeString(std::wstring version, vector<const RUNTIMEINFO*>& runtimes)
{ {
// split version string by comma // split version string by comma
int ret = S_OK; int ret = S_OK;
wstring temp; wstring temp;
std::wstringstream wss(version); std::wstringstream wss(version);
while (std::getline(wss, temp, L',')) while (std::getline(wss, temp, L',')) {
{ const RUNTIMEINFO* rt = GetRuntimeByName(temp);
const RUNTIMEINFO* rt = GetRuntimeByName(temp); if (rt != nullptr)
if (rt != nullptr) runtimes.push_back(rt);
runtimes.push_back(rt); else
else ret = S_FALSE;
ret = S_FALSE; }
} return ret;
return ret;
} }
vector<const RUNTIMEINFO*> GetRequiredRuntimes() vector<const RUNTIMEINFO*> GetRequiredRuntimes()
{ {
vector<const RUNTIMEINFO*> runtimes; vector<const RUNTIMEINFO*> runtimes;
// get comma-delimited version string from exe resources // get comma-delimited version string from exe resources
wchar_t* versionFlag = (wchar_t*)LoadResource(NULL, FindResource(NULL, (LPCWSTR)IDR_FX_VERSION_FLAG, L"FLAGS")); wchar_t* versionFlag = (wchar_t*)LoadResource(NULL, FindResource(NULL, (LPCWSTR)IDR_FX_VERSION_FLAG, L"FLAGS"));
if (versionFlag == nullptr) if (versionFlag == nullptr)
return runtimes; return runtimes;
wstring version(versionFlag); wstring version(versionFlag);
if (version.length() == 0) if (version.length() == 0)
return runtimes; return runtimes;
ParseRuntimeString(version, runtimes); ParseRuntimeString(version, runtimes);
return runtimes; return runtimes;
} }
int VerifyRuntimeString(std::wstring version) int VerifyRuntimeString(std::wstring version)
{ {
vector<const RUNTIMEINFO*> runtimes; vector<const RUNTIMEINFO*> runtimes;
return ParseRuntimeString(version, runtimes); return ParseRuntimeString(version, runtimes);
} }

View File

@@ -5,13 +5,13 @@
typedef struct typedef struct
{ {
WORD minOS; WORD minOS;
WORD minSP; WORD minSP;
wchar_t name[32]; wchar_t name[32];
wchar_t friendlyName[32]; wchar_t friendlyName[32];
wchar_t installerUrl[256]; wchar_t installerUrl[256];
DWORD fxReleaseVersion; DWORD fxReleaseVersion;
wchar_t dncRuntimeVersionName[32]; wchar_t dncRuntimeVersionName[32];
} RUNTIMEINFO; } RUNTIMEINFO;
const RUNTIMEINFO* GetRuntimeByName(std::wstring name); const RUNTIMEINFO* GetRuntimeByName(std::wstring name);

View File

@@ -45,28 +45,23 @@ void CSplashWnd::SetImage(const wchar_t* resid, const wchar_t* restype)
void CSplashWnd::Show() void CSplashWnd::Show()
{ {
if (m_hThread == NULL) if (m_hThread == NULL) {
{
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hThread = (HANDLE)_beginthreadex(NULL, 0, SplashThreadProc, static_cast<LPVOID>(this), 0, &m_ThreadId); m_hThread = (HANDLE)_beginthreadex(NULL, 0, SplashThreadProc, static_cast<LPVOID>(this), 0, &m_ThreadId);
if (WaitForSingleObject(m_hEvent, 5000) == WAIT_TIMEOUT) if (WaitForSingleObject(m_hEvent, 5000) == WAIT_TIMEOUT) {
{
OutputDebugString(L"Error starting SplashThread\n"); OutputDebugString(L"Error starting SplashThread\n");
} }
} }
else else {
{
PostThreadMessage(m_ThreadId, WM_ACTIVATE, WA_CLICKACTIVE, 0); PostThreadMessage(m_ThreadId, WM_ACTIVATE, WA_CLICKACTIVE, 0);
} }
} }
void CSplashWnd::Hide() void CSplashWnd::Hide()
{ {
if (m_hThread) if (m_hThread) {
{
PostThreadMessage(m_ThreadId, WM_QUIT, 0, 0); PostThreadMessage(m_ThreadId, WM_QUIT, 0, 0);
if (WaitForSingleObject(m_hThread, 9000) == WAIT_TIMEOUT) if (WaitForSingleObject(m_hThread, 9000) == WAIT_TIMEOUT) {
{
::TerminateThread(m_hThread, 2222); ::TerminateThread(m_hThread, 2222);
} }
CloseHandle(m_hThread); CloseHandle(m_hThread);
@@ -101,8 +96,7 @@ unsigned int __stdcall CSplashWnd::SplashThreadProc(void* lpParameter)
wndcls.lpszClassName = L"SplashWnd"; wndcls.lpszClassName = L"SplashWnd";
wndcls.hIcon = LoadIcon(wndcls.hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); wndcls.hIcon = LoadIcon(wndcls.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
if (!RegisterClass(&wndcls)) if (!RegisterClass(&wndcls)) {
{
if (GetLastError() != 0x00000582) // already registered) if (GetLastError() != 0x00000582) // already registered)
{ {
OutputDebugString(L"Unable to register class SplashWnd\n"); OutputDebugString(L"Unable to register class SplashWnd\n");
@@ -118,13 +112,11 @@ unsigned int __stdcall CSplashWnd::SplashThreadProc(void* lpParameter)
::GetCursorPos(&point); ::GetCursorPos(&point);
hMonitor = ::MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST); hMonitor = ::MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST);
if (::GetMonitorInfo(hMonitor, &mi)) if (::GetMonitorInfo(hMonitor, &mi)) {
{
rcArea.left = (mi.rcMonitor.right + mi.rcMonitor.left - static_cast<long>(pThis->m_pImage->GetWidth())) / 2; rcArea.left = (mi.rcMonitor.right + mi.rcMonitor.left - static_cast<long>(pThis->m_pImage->GetWidth())) / 2;
rcArea.top = (mi.rcMonitor.top + mi.rcMonitor.bottom - static_cast<long>(pThis->m_pImage->GetHeight())) / 2; rcArea.top = (mi.rcMonitor.top + mi.rcMonitor.bottom - static_cast<long>(pThis->m_pImage->GetHeight())) / 2;
} }
else else {
{
SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcArea, NULL); SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcArea, NULL);
rcArea.left = (rcArea.right + rcArea.left - pThis->m_pImage->GetWidth()) / 2; rcArea.left = (rcArea.right + rcArea.left - pThis->m_pImage->GetWidth()) / 2;
rcArea.top = (rcArea.top + rcArea.bottom - pThis->m_pImage->GetHeight()) / 2; rcArea.top = (rcArea.top + rcArea.bottom - pThis->m_pImage->GetHeight()) / 2;
@@ -141,8 +133,7 @@ unsigned int __stdcall CSplashWnd::SplashThreadProc(void* lpParameter)
wndcls.hInstance, wndcls.hInstance,
NULL); NULL);
if (!pThis->m_hSplashWnd) if (!pThis->m_hSplashWnd) {
{
OutputDebugString(L"Unable to create SplashWnd\n"); OutputDebugString(L"Unable to create SplashWnd\n");
return 0; return 0;
} }
@@ -159,16 +150,13 @@ unsigned int __stdcall CSplashWnd::SplashThreadProc(void* lpParameter)
PeekMessage(&msg, NULL, 0, 0, 0); // invoke creating message queue PeekMessage(&msg, NULL, 0, 0, 0); // invoke creating message queue
SetEvent(pThis->m_hEvent); SetEvent(pThis->m_hEvent);
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
{
if (msg.message == WM_QUIT) break; if (msg.message == WM_QUIT) break;
if (bRet == -1) if (bRet == -1) {
{
// handle the error and possibly exit // handle the error and possibly exit
} }
else else {
{
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
@@ -181,24 +169,19 @@ unsigned int __stdcall CSplashWnd::SplashThreadProc(void* lpParameter)
LRESULT CALLBACK CSplashWnd::SplashWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK CSplashWnd::SplashWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ {
CSplashWnd* pInstance = reinterpret_cast<CSplashWnd*>(GetWindowLongPtr(hwnd, GWL_USERDATA)); CSplashWnd* pInstance = reinterpret_cast<CSplashWnd*>(GetWindowLongPtr(hwnd, GWL_USERDATA));
if (pInstance == NULL) if (pInstance == NULL) {
{
return DefWindowProc(hwnd, uMsg, wParam, lParam); return DefWindowProc(hwnd, uMsg, wParam, lParam);
} }
switch (uMsg) switch (uMsg) {
{
case WM_PAINT: case WM_PAINT:
{ {
if (pInstance->m_pImage) if (pInstance->m_pImage) {
{ if (pInstance->m_pImage->IsAnimatedGIF()) {
if (pInstance->m_pImage->IsAnimatedGIF())
{
// do nothing, the gif will be drawn by it's own thread. // do nothing, the gif will be drawn by it's own thread.
} }
else else {
{
Gdiplus::Graphics gdip(hwnd); Gdiplus::Graphics gdip(hwnd);
gdip.DrawImage(pInstance->m_pImage, 0, 0, pInstance->m_pImage->GetWidth(), pInstance->m_pImage->GetHeight()); gdip.DrawImage(pInstance->m_pImage, 0, 0, pInstance->m_pImage->GetWidth(), pInstance->m_pImage->GetHeight());
} }
@@ -216,8 +199,7 @@ LRESULT CALLBACK CSplashWnd::SplashWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
{ {
if (GetCapture() == hwnd) if (GetCapture() == hwnd) {
{
RECT rcWnd; RECT rcWnd;
GetWindowRect(hwnd, &rcWnd); GetWindowRect(hwnd, &rcWnd);

View File

@@ -24,19 +24,18 @@ private:
CSplashWnd& operator=(const CSplashWnd&) {}; CSplashWnd& operator=(const CSplashWnd&) {};
protected: protected:
HANDLE m_hThread; HANDLE m_hThread;
unsigned int m_ThreadId; unsigned int m_ThreadId;
HANDLE m_hEvent; HANDLE m_hEvent;
ImageEx* m_pImage; ImageEx* m_pImage;
HWND m_hSplashWnd; HWND m_hSplashWnd;
HWND m_hParentWnd; HWND m_hParentWnd;
POINT m_ptMouseDown; POINT m_ptMouseDown;
public: public:
CSplashWnd(HWND hParent = NULL); CSplashWnd(HWND hParent = NULL);
~CSplashWnd(); ~CSplashWnd();
void SetImage(const wchar_t* resid, const wchar_t* restype); void SetImage(const wchar_t* resid, const wchar_t* restype);
void SetWindowName(const wchar_t* windowName);
void Show(); void Show();
void Hide(); void Hide();

View File

@@ -6,296 +6,297 @@
void CUpdateRunner::DisplayErrorMessage(CString& errorMessage, wchar_t* logFile) void CUpdateRunner::DisplayErrorMessage(CString& errorMessage, wchar_t* logFile)
{ {
CTaskDialog dlg; CTaskDialog dlg;
TASKDIALOG_BUTTON buttons[] = { TASKDIALOG_BUTTON buttons[] = {
{ 1, L"Open Setup Log", }, { 1, L"Open Setup Log", },
{ 2, L"Close", }, { 2, L"Close", },
}; };
// TODO: Something about contacting support? // TODO: Something about contacting support?
if (logFile == NULL) { if (logFile == NULL) {
dlg.SetButtons(&buttons[1], 1, 1); dlg.SetButtons(&buttons[1], 1, 1);
} else { }
dlg.SetButtons(buttons, 2, 1); else {
} dlg.SetButtons(buttons, 2, 1);
}
dlg.SetMainInstructionText(L"Installation has failed"); dlg.SetMainInstructionText(L"Installation has failed");
dlg.SetContentText(errorMessage); dlg.SetContentText(errorMessage);
dlg.SetMainIcon(TD_ERROR_ICON); dlg.SetMainIcon(TD_ERROR_ICON);
int nButton; int nButton;
if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton))) { if (FAILED(dlg.DoModal(::GetActiveWindow(), &nButton))) {
return; return;
} }
if (nButton == 1 && logFile != NULL) { if (nButton == 1 && logFile != NULL) {
ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOW); ShellExecute(NULL, NULL, logFile, NULL, NULL, SW_SHOW);
} }
} }
HRESULT CUpdateRunner::AreWeUACElevated() HRESULT CUpdateRunner::AreWeUACElevated()
{ {
HANDLE hProcess = GetCurrentProcess(); HANDLE hProcess = GetCurrentProcess();
HANDLE hToken = 0; HANDLE hToken = 0;
HRESULT hr; HRESULT hr;
if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
goto out; goto out;
} }
TOKEN_ELEVATION_TYPE elevType; TOKEN_ELEVATION_TYPE elevType;
DWORD dontcare; DWORD dontcare;
if (!GetTokenInformation(hToken, TokenElevationType, &elevType, sizeof(TOKEN_ELEVATION_TYPE), &dontcare)) { if (!GetTokenInformation(hToken, TokenElevationType, &elevType, sizeof(TOKEN_ELEVATION_TYPE), &dontcare)) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
goto out; goto out;
} }
hr = (elevType == TokenElevationTypeFull ? S_OK : S_FALSE); hr = (elevType == TokenElevationTypeFull ? S_OK : S_FALSE);
out: out:
if (hToken) { if (hToken) {
CloseHandle(hToken); CloseHandle(hToken);
} }
return hr; return hr;
} }
HRESULT FindDesktopFolderView(REFIID riid, void **ppv) HRESULT FindDesktopFolderView(REFIID riid, void** ppv)
{ {
HRESULT hr; HRESULT hr;
CComPtr<IShellWindows> spShellWindows; CComPtr<IShellWindows> spShellWindows;
spShellWindows.CoCreateInstance(CLSID_ShellWindows); spShellWindows.CoCreateInstance(CLSID_ShellWindows);
CComVariant vtLoc(CSIDL_DESKTOP); CComVariant vtLoc(CSIDL_DESKTOP);
CComVariant vtEmpty; CComVariant vtEmpty;
long lhwnd; long lhwnd;
CComPtr<IDispatch> spdisp; CComPtr<IDispatch> spdisp;
hr = spShellWindows->FindWindowSW( hr = spShellWindows->FindWindowSW(
&vtLoc, &vtEmpty, &vtLoc, &vtEmpty,
SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp); SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
CComPtr<IShellBrowser> spBrowser; CComPtr<IShellBrowser> spBrowser;
hr = CComQIPtr<IServiceProvider>(spdisp)->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser)); hr = CComQIPtr<IServiceProvider>(spdisp)->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser));
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
CComPtr<IShellView> spView; CComPtr<IShellView> spView;
hr = spBrowser->QueryActiveShellView(&spView); hr = spBrowser->QueryActiveShellView(&spView);
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
hr = spView->QueryInterface(riid, ppv); hr = spView->QueryInterface(riid, ppv);
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
return S_OK; return S_OK;
} }
HRESULT GetDesktopAutomationObject(REFIID riid, void **ppv) HRESULT GetDesktopAutomationObject(REFIID riid, void** ppv)
{ {
HRESULT hr; HRESULT hr;
CComPtr<IShellView> spsv; CComPtr<IShellView> spsv;
hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv)); hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv));
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
CComPtr<IDispatch> spdispView; CComPtr<IDispatch> spdispView;
hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView)); hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
return spdispView->QueryInterface(riid, ppv); return spdispView->QueryInterface(riid, ppv);
} }
HRESULT CUpdateRunner::ShellExecuteFromExplorer(LPWSTR pszFile, LPWSTR pszParameters) HRESULT CUpdateRunner::ShellExecuteFromExplorer(LPWSTR pszFile, LPWSTR pszParameters)
{ {
HRESULT hr; HRESULT hr;
CComPtr<IShellFolderViewDual> spFolderView; CComPtr<IShellFolderViewDual> spFolderView;
hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView)); hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
CComPtr<IDispatch> spdispShell; CComPtr<IDispatch> spdispShell;
hr = spFolderView->get_Application(&spdispShell); hr = spFolderView->get_Application(&spdispShell);
if (FAILED(hr)) return hr; if (FAILED(hr)) return hr;
return CComQIPtr<IShellDispatch2>(spdispShell)->ShellExecute( return CComQIPtr<IShellDispatch2>(spdispShell)->ShellExecute(
CComBSTR(pszFile), CComBSTR(pszFile),
CComVariant(pszParameters ? pszParameters : L""), CComVariant(pszParameters ? pszParameters : L""),
CComVariant(L""), CComVariant(L""),
CComVariant(L""), CComVariant(L""),
CComVariant(SW_SHOWDEFAULT)); CComVariant(SW_SHOWDEFAULT));
} }
bool CUpdateRunner::DirectoryExists(wchar_t* szPath) bool CUpdateRunner::DirectoryExists(wchar_t* szPath)
{ {
DWORD dwAttrib = GetFileAttributes(szPath); DWORD dwAttrib = GetFileAttributes(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES && return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
} }
bool CUpdateRunner::DirectoryIsWritable(wchar_t * szPath) bool CUpdateRunner::DirectoryIsWritable(wchar_t* szPath)
{ {
wchar_t szTempFileName[MAX_PATH]; wchar_t szTempFileName[MAX_PATH];
UINT uRetVal = GetTempFileNameW(szPath, L"Squirrel", 0, szTempFileName); UINT uRetVal = GetTempFileNameW(szPath, L"Squirrel", 0, szTempFileName);
if (uRetVal == 0) { if (uRetVal == 0) {
return false; return false;
} }
DeleteFile(szTempFileName); DeleteFile(szTempFileName);
return true; return true;
} }
int CUpdateRunner::ExtractUpdaterAndRun(wchar_t* lpCommandLine, bool useFallbackDir, std::function<void()>& callback) int CUpdateRunner::ExtractUpdaterAndRun(wchar_t* lpCommandLine, bool useFallbackDir, std::function<void()>& callback)
{ {
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 }; STARTUPINFO si = { 0 };
CResource zipResource; CResource zipResource;
wchar_t targetDir[MAX_PATH] = { 0 }; wchar_t targetDir[MAX_PATH] = { 0 };
wchar_t logFile[MAX_PATH]; wchar_t logFile[MAX_PATH];
DWORD dwExitCode = 0; DWORD dwExitCode = 0;
std::vector<CString> to_delete; std::vector<CString> to_delete;
wchar_t* envSquirrelTemp = _wgetenv(L"SQUIRREL_TEMP"); wchar_t* envSquirrelTemp = _wgetenv(L"SQUIRREL_TEMP");
if (envSquirrelTemp && if (envSquirrelTemp &&
DirectoryExists(envSquirrelTemp) && DirectoryExists(envSquirrelTemp) &&
DirectoryIsWritable(envSquirrelTemp) && DirectoryIsWritable(envSquirrelTemp) &&
!PathIsUNCW(envSquirrelTemp)) { !PathIsUNCW(envSquirrelTemp)) {
_swprintf_c(targetDir, _countof(targetDir), L"%s", envSquirrelTemp); _swprintf_c(targetDir, _countof(targetDir), L"%s", envSquirrelTemp);
goto gotADir; goto gotADir;
} }
if (!useFallbackDir) { if (!useFallbackDir) {
SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, targetDir); SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, targetDir);
goto gotADir; goto gotADir;
} }
wchar_t username[512]; wchar_t username[512];
wchar_t appDataDir[MAX_PATH]; wchar_t appDataDir[MAX_PATH];
ULONG unameSize = _countof(username); ULONG unameSize = _countof(username);
SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, appDataDir); SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, appDataDir);
GetUserName(username, &unameSize); GetUserName(username, &unameSize);
_swprintf_c(targetDir, _countof(targetDir), L"%s\\%s", appDataDir, username); _swprintf_c(targetDir, _countof(targetDir), L"%s\\%s", appDataDir, username);
if (!CreateDirectory(targetDir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { if (!CreateDirectory(targetDir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
wchar_t err[4096]; wchar_t err[4096];
_swprintf_c(err, _countof(err), L"Unable to write to %s - IT policies may be restricting access to this folder", targetDir); _swprintf_c(err, _countof(err), L"Unable to write to %s - IT policies may be restricting access to this folder", targetDir);
callback(); callback();
DisplayErrorMessage(CString(err), NULL); DisplayErrorMessage(CString(err), NULL);
return -1; return -1;
} }
gotADir: gotADir:
wcscat_s(targetDir, _countof(targetDir), L"\\SquirrelTemp"); wcscat_s(targetDir, _countof(targetDir), L"\\SquirrelTemp");
if (!CreateDirectory(targetDir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { if (!CreateDirectory(targetDir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
wchar_t err[4096]; wchar_t err[4096];
_swprintf_c(err, _countof(err), L"Unable to write to %s - IT policies may be restricting access to this folder", targetDir); _swprintf_c(err, _countof(err), L"Unable to write to %s - IT policies may be restricting access to this folder", targetDir);
if (useFallbackDir) { if (useFallbackDir) {
callback(); callback();
DisplayErrorMessage(CString(err), NULL); DisplayErrorMessage(CString(err), NULL);
} }
goto failedExtract; goto failedExtract;
} }
swprintf_s(logFile, L"%s\\SquirrelSetup.log", targetDir); swprintf_s(logFile, L"%s\\SquirrelSetup.log", targetDir);
if (!zipResource.Load(L"DATA", IDR_UPDATE_ZIP)) { if (!zipResource.Load(L"DATA", IDR_UPDATE_ZIP)) {
goto failedExtract; goto failedExtract;
} }
DWORD dwSize = zipResource.GetSize(); DWORD dwSize = zipResource.GetSize();
if (dwSize < 0x100) { if (dwSize < 0x100) {
goto failedExtract; goto failedExtract;
} }
BYTE* pData = (BYTE*)zipResource.Lock(); BYTE* pData = (BYTE*)zipResource.Lock();
HZIP zipFile = OpenZip(pData, dwSize, NULL); HZIP zipFile = OpenZip(pData, dwSize, NULL);
SetUnzipBaseDir(zipFile, targetDir); SetUnzipBaseDir(zipFile, targetDir);
// NB: This library is kind of a disaster // NB: This library is kind of a disaster
ZRESULT zr; ZRESULT zr;
int index = 0; int index = 0;
do { do {
ZIPENTRY zentry; ZIPENTRY zentry;
wchar_t targetFile[MAX_PATH]; wchar_t targetFile[MAX_PATH];
zr = GetZipItem(zipFile, index, &zentry); zr = GetZipItem(zipFile, index, &zentry);
if (zr != ZR_OK && zr != ZR_MORE) { if (zr != ZR_OK && zr != ZR_MORE) {
break; break;
} }
// NB: UnzipItem won't overwrite data, we need to do it ourselves // NB: UnzipItem won't overwrite data, we need to do it ourselves
swprintf_s(targetFile, L"%s\\%s", targetDir, zentry.name); swprintf_s(targetFile, L"%s\\%s", targetDir, zentry.name);
DeleteFile(targetFile); DeleteFile(targetFile);
if (UnzipItem(zipFile, index, zentry.name) != ZR_OK) break; if (UnzipItem(zipFile, index, zentry.name) != ZR_OK) break;
to_delete.push_back(CString(targetFile)); to_delete.push_back(CString(targetFile));
index++; index++;
} while (zr == ZR_MORE || zr == ZR_OK); } while (zr == ZR_MORE || zr == ZR_OK);
CloseZip(zipFile); CloseZip(zipFile);
zipResource.Release(); zipResource.Release();
// nfi if the zip extract actually worked, check for Update.exe // nfi if the zip extract actually worked, check for Update.exe
wchar_t updateExePath[MAX_PATH]; wchar_t updateExePath[MAX_PATH];
swprintf_s(updateExePath, L"%s\\%s", targetDir, L"Update.exe"); swprintf_s(updateExePath, L"%s\\%s", targetDir, L"Update.exe");
if (GetFileAttributes(updateExePath) == INVALID_FILE_ATTRIBUTES) { if (GetFileAttributes(updateExePath) == INVALID_FILE_ATTRIBUTES) {
goto failedExtract; goto failedExtract;
} }
// Run Update.exe // Run Update.exe
si.cb = sizeof(STARTUPINFO); si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_SHOW; si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW; si.dwFlags = STARTF_USESHOWWINDOW;
if (!lpCommandLine || wcsnlen_s(lpCommandLine, MAX_PATH) < 1) { if (!lpCommandLine || wcsnlen_s(lpCommandLine, MAX_PATH) < 1) {
lpCommandLine = L""; lpCommandLine = L"";
} }
wchar_t cmd[MAX_PATH]; wchar_t cmd[MAX_PATH];
swprintf_s(cmd, L"\"%s\" --install . %s", updateExePath, lpCommandLine); swprintf_s(cmd, L"\"%s\" --install . %s", updateExePath, lpCommandLine);
if (!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, targetDir, &si, &pi)) { if (!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, targetDir, &si, &pi)) {
goto failedExtract; goto failedExtract;
} }
WaitForSingleObject(pi.hProcess, INFINITE); WaitForSingleObject(pi.hProcess, INFINITE);
if (!GetExitCodeProcess(pi.hProcess, &dwExitCode)) { if (!GetExitCodeProcess(pi.hProcess, &dwExitCode)) {
dwExitCode = (DWORD)-1; dwExitCode = (DWORD)-1;
} }
if (dwExitCode != 0) { if (dwExitCode != 0) {
callback(); callback();
DisplayErrorMessage(CString( DisplayErrorMessage(CString(
L"There was an error while installing the application. " L"There was an error while installing the application. "
L"Check the setup log for more information and contact the author."), logFile); L"Check the setup log for more information and contact the author."), logFile);
} }
for (unsigned int i = 0; i < to_delete.size(); i++) { for (unsigned int i = 0; i < to_delete.size(); i++) {
DeleteFile(to_delete[i]); DeleteFile(to_delete[i]);
} }
CloseHandle(pi.hProcess); CloseHandle(pi.hProcess);
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
return (int) dwExitCode; return (int)dwExitCode;
failedExtract: failedExtract:
if (!useFallbackDir) { if (!useFallbackDir) {
// Take another pass at it, using C:\ProgramData instead // Take another pass at it, using C:\ProgramData instead
return ExtractUpdaterAndRun(lpCommandLine, true, callback); return ExtractUpdaterAndRun(lpCommandLine, true, callback);
} }
callback(); callback();
DisplayErrorMessage(CString(L"Failed to extract installer"), NULL); DisplayErrorMessage(CString(L"Failed to extract installer"), NULL);
return (int) dwExitCode; return (int)dwExitCode;
} }

View File

@@ -5,10 +5,11 @@ class CUpdateRunner
{ {
public: public:
static void DisplayErrorMessage(CString& errorMessage, wchar_t* logFile); static void DisplayErrorMessage(CString& errorMessage, wchar_t* logFile);
static HRESULT AreWeUACElevated(); static HRESULT AreWeUACElevated();
static HRESULT ShellExecuteFromExplorer(LPWSTR pszFile, LPWSTR pszParameters); static HRESULT ShellExecuteFromExplorer(LPWSTR pszFile, LPWSTR pszParameters);
static bool DirectoryExists(wchar_t* szPath); static bool DirectoryExists(wchar_t* szPath);
static bool DirectoryIsWritable(wchar_t* szPath); static bool DirectoryIsWritable(wchar_t* szPath);
static int ExtractUpdaterAndRun(wchar_t* lpCommandLine, bool useFallbackDir, std::function<void()>& callback); static int ExtractUpdaterAndRun(wchar_t* lpCommandLine, bool useFallbackDir, std::function<void()>& callback);
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -18,18 +18,19 @@ typedef DWORD ZRESULT;
// return codes from any of the zip functions. Listed later. // return codes from any of the zip functions. Listed later.
typedef struct typedef struct
{ int index; // index of this file within the zip {
TCHAR name[MAX_PATH]; // filename within the zip int index; // index of this file within the zip
DWORD attr; // attributes, as in GetFileAttributes. TCHAR name[MAX_PATH]; // filename within the zip
FILETIME atime,ctime,mtime;// access, create, modify filetimes DWORD attr; // attributes, as in GetFileAttributes.
long comp_size; // sizes of item, compressed and uncompressed. These FILETIME atime, ctime, mtime;// access, create, modify filetimes
long unc_size; // may be -1 if not yet known (e.g. being streamed in) long comp_size; // sizes of item, compressed and uncompressed. These
long unc_size; // may be -1 if not yet known (e.g. being streamed in)
} ZIPENTRY; } ZIPENTRY;
HZIP OpenZip(const TCHAR *fn, const char *password); HZIP OpenZip(const TCHAR* fn, const char* password);
HZIP OpenZip(void *z,unsigned int len, const char *password); HZIP OpenZip(void* z, unsigned int len, const char* password);
HZIP OpenZipHandle(HANDLE h, const char *password); HZIP OpenZipHandle(HANDLE h, const char* password);
// OpenZip - opens a zip file and returns a handle with which you can // OpenZip - opens a zip file and returns a handle with which you can
// subsequently examine its contents. You can open a zip file from: // subsequently examine its contents. You can open a zip file from:
// from a pipe: OpenZipHandle(hpipe_read,0); // from a pipe: OpenZipHandle(hpipe_read,0);
@@ -46,7 +47,7 @@ HZIP OpenZipHandle(HANDLE h, const char *password);
// but for real windows, the zip makes its own copy of your handle, so you // but for real windows, the zip makes its own copy of your handle, so you
// can close yours anytime. // can close yours anytime.
ZRESULT GetZipItem(HZIP hz, int index, ZIPENTRY *ze); ZRESULT GetZipItem(HZIP hz, int index, ZIPENTRY* ze);
// GetZipItem - call this to get information about an item in the zip. // GetZipItem - call this to get information about an item in the zip.
// If index is -1 and the file wasn't opened through a pipe, // If index is -1 and the file wasn't opened through a pipe,
// then it returns information about the whole zipfile // then it returns information about the whole zipfile
@@ -61,14 +62,14 @@ ZRESULT GetZipItem(HZIP hz, int index, ZIPENTRY *ze);
// then then comp_size and sometimes unc_size as well may not be known until // then then comp_size and sometimes unc_size as well may not be known until
// after the item has been unzipped. // after the item has been unzipped.
ZRESULT FindZipItem(HZIP hz, const TCHAR *name, bool ic, int *index, ZIPENTRY *ze); ZRESULT FindZipItem(HZIP hz, const TCHAR* name, bool ic, int* index, ZIPENTRY* ze);
// FindZipItem - finds an item by name. ic means 'insensitive to case'. // FindZipItem - finds an item by name. ic means 'insensitive to case'.
// It returns the index of the item, and returns information about it. // It returns the index of the item, and returns information about it.
// If nothing was found, then index is set to -1 and the function returns // If nothing was found, then index is set to -1 and the function returns
// an error code. // an error code.
ZRESULT UnzipItem(HZIP hz, int index, const TCHAR *fn); ZRESULT UnzipItem(HZIP hz, int index, const TCHAR* fn);
ZRESULT UnzipItem(HZIP hz, int index, void *z,unsigned int len); ZRESULT UnzipItem(HZIP hz, int index, void* z, unsigned int len);
ZRESULT UnzipItemHandle(HZIP hz, int index, HANDLE h); ZRESULT UnzipItemHandle(HZIP hz, int index, HANDLE h);
// UnzipItem - given an index to an item, unzips it. You can unzip to: // UnzipItem - given an index to an item, unzips it. You can unzip to:
// to a pipe: UnzipItemHandle(hz,i, hpipe_write); // to a pipe: UnzipItemHandle(hz,i, hpipe_write);
@@ -85,7 +86,7 @@ ZRESULT UnzipItemHandle(HZIP hz, int index, HANDLE h);
// If you unzip a directory with ZIP_FILENAME, then the directory gets created. // If you unzip a directory with ZIP_FILENAME, then the directory gets created.
// If you unzip it to a handle or a memory block, then nothing gets created // If you unzip it to a handle or a memory block, then nothing gets created
// and it emits 0 bytes. // and it emits 0 bytes.
ZRESULT SetUnzipBaseDir(HZIP hz, const TCHAR *dir); ZRESULT SetUnzipBaseDir(HZIP hz, const TCHAR* dir);
// if unzipping to a filename, and it's a relative filename, then it will be relative to here. // if unzipping to a filename, and it's a relative filename, then it will be relative to here.
// (defaults to current-directory). // (defaults to current-directory).
@@ -93,7 +94,7 @@ ZRESULT SetUnzipBaseDir(HZIP hz, const TCHAR *dir);
ZRESULT CloseZip(HZIP hz); ZRESULT CloseZip(HZIP hz);
// CloseZip - the zip handle must be closed with this function. // CloseZip - the zip handle must be closed with this function.
unsigned int FormatZipMessage(ZRESULT code, TCHAR *buf,unsigned int len); unsigned int FormatZipMessage(ZRESULT code, TCHAR* buf, unsigned int len);
// FormatZipMessage - given an error code, formats it as a string. // FormatZipMessage - given an error code, formats it as a string.
// It returns the length of the error message. If buf/len points // It returns the length of the error message. If buf/len points
// to a real buffer, then it also writes as much as possible into there. // to a real buffer, then it also writes as much as possible into there.
@@ -199,7 +200,7 @@ unsigned int FormatZipMessage(ZRESULT code, TCHAR *buf,unsigned int len);
// one or the other of them based on a dynamic choice. If the header file // one or the other of them based on a dynamic choice. If the header file
// for only one is present, then we will bind to that particular one. // for only one is present, then we will bind to that particular one.
ZRESULT CloseZipU(HZIP hz); ZRESULT CloseZipU(HZIP hz);
unsigned int FormatZipMessageU(ZRESULT code, TCHAR *buf,unsigned int len); unsigned int FormatZipMessageU(ZRESULT code, TCHAR* buf, unsigned int len);
bool IsZipHandleU(HZIP hz); bool IsZipHandleU(HZIP hz);
#ifdef _zip_H #ifdef _zip_H
#undef CloseZip #undef CloseZip

View File

@@ -22,156 +22,146 @@ typedef BOOL(WINAPI* SetDefaultDllDirectoriesFunction)(DWORD DirectoryFlags);
// If we pre-load them with an absolute path then we are good. // If we pre-load them with an absolute path then we are good.
void PreloadLibs() void PreloadLibs()
{ {
wchar_t sys32Folder[MAX_PATH]; wchar_t sys32Folder[MAX_PATH];
GetSystemDirectory(sys32Folder, MAX_PATH); GetSystemDirectory(sys32Folder, MAX_PATH);
std::wstring version = (std::wstring(sys32Folder) + L"\\version.dll"); std::wstring version = (std::wstring(sys32Folder) + L"\\version.dll");
std::wstring logoncli = (std::wstring(sys32Folder) + L"\\logoncli.dll"); std::wstring logoncli = (std::wstring(sys32Folder) + L"\\logoncli.dll");
std::wstring sspicli = (std::wstring(sys32Folder) + L"\\sspicli.dll"); std::wstring sspicli = (std::wstring(sys32Folder) + L"\\sspicli.dll");
LoadLibrary(version.c_str()); LoadLibrary(version.c_str());
LoadLibrary(logoncli.c_str()); LoadLibrary(logoncli.c_str());
LoadLibrary(sspicli.c_str()); LoadLibrary(sspicli.c_str());
} }
void MitigateDllHijacking() void MitigateDllHijacking()
{ {
// Set the default DLL lookup directory to System32 for ourselves and kernel32.dll // Set the default DLL lookup directory to System32 for ourselves and kernel32.dll
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);
HMODULE hKernel32 = LoadLibrary(L"kernel32.dll"); HMODULE hKernel32 = LoadLibrary(L"kernel32.dll");
ATLASSERT(hKernel32 != NULL); ATLASSERT(hKernel32 != NULL);
SetDefaultDllDirectoriesFunction pfn = (SetDefaultDllDirectoriesFunction)GetProcAddress(hKernel32, "SetDefaultDllDirectories"); SetDefaultDllDirectoriesFunction pfn = (SetDefaultDllDirectoriesFunction)GetProcAddress(hKernel32, "SetDefaultDllDirectories");
if (pfn) { (*pfn)(LOAD_LIBRARY_SEARCH_SYSTEM32); } if (pfn) { (*pfn)(LOAD_LIBRARY_SEARCH_SYSTEM32); }
PreloadLibs(); PreloadLibs();
} }
int APIENTRY wWinMain( int APIENTRY wWinMain(
_In_ HINSTANCE hInstance, _In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine, _In_ LPWSTR lpCmdLine,
_In_ int nCmdShow) _In_ int nCmdShow)
{ {
MitigateDllHijacking(); MitigateDllHijacking();
int exitCode = -1; int exitCode = -1;
CString cmdLine(lpCmdLine); CString cmdLine(lpCmdLine);
// see if the requested framework(s) are supported and exit. // see if the requested framework(s) are supported and exit.
int chkFrameworkIdx = cmdLine.Find(L"--checkFramework"); int chkFrameworkIdx = cmdLine.Find(L"--checkFramework");
if (chkFrameworkIdx >= 0) { if (chkFrameworkIdx >= 0)
return VerifyRuntimeString(std::wstring(cmdLine).substr(chkFrameworkIdx + 17)); return VerifyRuntimeString(std::wstring(cmdLine).substr(chkFrameworkIdx + 17));
}
if (cmdLine.Find(L"--checkInstall") >= 0) { if (cmdLine.Find(L"--checkInstall") >= 0) {
// If we're already installed, exit as fast as possible // If we're already installed, exit as fast as possible
if (!MachineInstaller::ShouldSilentInstall()) { if (!MachineInstaller::ShouldSilentInstall())
return 0; return 0;
}
// Make sure update.exe gets silent // Make sure update.exe gets silent
wcscat(lpCmdLine, L" --silent"); wcscat(lpCmdLine, L" --silent");
} }
// Initialize COM // Initialize COM
HRESULT hr = ::CoInitialize(NULL); HRESULT hr = ::CoInitialize(NULL);
ATLASSERT(SUCCEEDED(hr)); ATLASSERT(SUCCEEDED(hr));
AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);
// Initialize ATL // Initialize ATL
_Module = new CAppModule(); _Module = new CAppModule();
hr = _Module->Init(NULL, hInstance); hr = _Module->Init(NULL, hInstance);
// Initialize GDI // Initialize GDI
ULONG_PTR gdiplusToken; ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CSplashWnd splash; CSplashWnd splash;
std::function<void()> closeSplashFn([&]() { splash.Hide(); }); std::function<void()> closeSplashFn([&]() { splash.Hide(); });
bool isQuiet = (cmdLine.Find(L"-s") >= 0); bool isQuiet = (cmdLine.Find(L"-s") >= 0);
bool weAreUACElevated = CUpdateRunner::AreWeUACElevated() == S_OK; bool weAreUACElevated = CUpdateRunner::AreWeUACElevated() == S_OK;
bool attemptingToRerun = (cmdLine.Find(L"--rerunningWithoutUAC") >= 0); bool attemptingToRerun = (cmdLine.Find(L"--rerunningWithoutUAC") >= 0);
// todo: make min windows version configurable (eg, > windows 10 18362) // todo: make min windows version configurable (eg, > windows 10 18362)
auto runtimes = GetRequiredRuntimes(); auto runtimes = GetRequiredRuntimes();
if (weAreUACElevated && attemptingToRerun) { if (weAreUACElevated && attemptingToRerun) {
CUpdateRunner::DisplayErrorMessage(CString(L"Please re-run this installer as a normal user instead of \"Run as Administrator\"."), NULL); CUpdateRunner::DisplayErrorMessage(CString(L"Please re-run this installer as a normal user instead of \"Run as Administrator\"."), NULL);
exitCode = E_FAIL; exitCode = E_FAIL;
goto out; goto out;
} }
bool rebootRequired = false; bool rebootRequired = false;
// check all required runtimes against the current operating system version // check all required runtimes against the current operating system version
for (auto rt : runtimes) for (auto rt : runtimes) {
{ if (!IsRuntimeSupported(rt)) {
if (!IsRuntimeSupported(rt)) // Explain this as nicely as possible and give up.
{ MessageBox(0L, L"This program cannot run on this computer; it requires a later version of Windows.", L"Incompatible Operating System", 0);
// Explain this as nicely as possible and give up. exitCode = E_FAIL;
MessageBox(0L, L"This program cannot run on this computer; it requires a later version of Windows.", L"Incompatible Operating System", 0); goto out;
exitCode = E_FAIL; }
goto out; }
}
}
// install any missing runtimes // install any missing runtimes
for (auto rt : runtimes) for (auto rt : runtimes) {
{ if (!IsRuntimeInstalled(rt)) {
if (!IsRuntimeInstalled(rt)) HRESULT hr = CFxHelper::InstallDotnet(rt, isQuiet);
{ if (hr == ERROR_SUCCESS_REBOOT_REQUIRED) {
HRESULT hr = CFxHelper::InstallDotnet(rt, isQuiet); // we will reboot after installing all required runtimes.
if (hr == ERROR_SUCCESS_REBOOT_REQUIRED) rebootRequired = true;
{ }
// we will reboot after installing all required runtimes. else if (FAILED(hr)) {
rebootRequired = true; exitCode = hr; // #yolo
} CUpdateRunner::DisplayErrorMessage(CString(L"Failed to install .NET, you can try installing it manually."), NULL);
else if (FAILED(hr)) goto out;
{ }
exitCode = hr; // #yolo // S_FALSE isn't failure, but we still shouldn't try to install
CUpdateRunner::DisplayErrorMessage(CString(L"Failed to install .NET, you can try installing it manually."), NULL); else if (hr != S_OK) {
goto out; exitCode = 0;
} goto out;
// S_FALSE isn't failure, but we still shouldn't try to install }
else if (hr != S_OK) }
{ }
exitCode = 0;
goto out;
}
}
}
// if any runtimes indicated a reboot is required, let's do that now // if any runtimes indicated a reboot is required, let's do that now
if (rebootRequired) if (rebootRequired) {
{ exitCode = CFxHelper::HandleRebootRequirement(isQuiet);
exitCode = CFxHelper::HandleRebootRequirement(isQuiet); goto out;
goto out; }
}
// If we're UAC-elevated, we shouldn't be because it will give us permissions // If we're UAC-elevated, we shouldn't be because it will give us permissions
// problems later. Just silently rerun ourselves. // problems later. Just silently rerun ourselves.
if (weAreUACElevated) { if (weAreUACElevated) {
wchar_t buf[4096]; wchar_t buf[4096];
HMODULE hMod = GetModuleHandle(NULL); HMODULE hMod = GetModuleHandle(NULL);
GetModuleFileNameW(hMod, buf, 4096); GetModuleFileNameW(hMod, buf, 4096);
wcscat(lpCmdLine, L" --rerunningWithoutUAC"); wcscat(lpCmdLine, L" --rerunningWithoutUAC");
CUpdateRunner::ShellExecuteFromExplorer(buf, lpCmdLine); CUpdateRunner::ShellExecuteFromExplorer(buf, lpCmdLine);
exitCode = 0; exitCode = 0;
goto out; goto out;
} }
splash.SetImage(MAKEINTRESOURCE(IDR_SPLASH_IMG), L"DATA"); splash.SetImage(MAKEINTRESOURCE(IDR_SPLASH_IMG), L"DATA");
splash.Show(); splash.Show();
// run updater // run updater
exitCode = CUpdateRunner::ExtractUpdaterAndRun(lpCmdLine, false, closeSplashFn); exitCode = CUpdateRunner::ExtractUpdaterAndRun(lpCmdLine, false, closeSplashFn);
out: out:
splash.Hide(); splash.Hide();
_Module->Term(); _Module->Term();
return exitCode; return exitCode;
} }

View File

@@ -31,82 +31,91 @@ SOFTWARE.
using namespace std; using namespace std;
namespace version { namespace version
{
namespace { namespace
{
// Compare normal version identifiers. // Compare normal version identifiers.
int compare_normal(const Version_data& l, const Version_data& r) { int compare_normal(const Version_data& l, const Version_data& r)
if (l.major > r.major) return 1; {
if (l.major < r.major) return -1; if (l.major > r.major) return 1;
if (l.minor > r.minor) return 1; if (l.major < r.major) return -1;
if (l.minor < r.minor) return -1; if (l.minor > r.minor) return 1;
if (l.patch > r.patch) return 1; if (l.minor < r.minor) return -1;
if (l.patch < r.patch) return -1; if (l.patch > r.patch) return 1;
return 0; if (l.patch < r.patch) return -1;
} return 0;
}
// Compare alphanumeric prerelease identifiers. // Compare alphanumeric prerelease identifiers.
inline int cmp_alnum_prerel_ids(const string& l, const string& r) { inline int cmp_alnum_prerel_ids(const string& l, const string& r)
auto cmp = l.compare(r); {
if (cmp == 0) { auto cmp = l.compare(r);
return cmp; if (cmp == 0) {
} else { return cmp;
return cmp > 0 ? 1 : -1; }
} else {
} return cmp > 0 ? 1 : -1;
}
}
// Compare numeric prerelease identifiers. // Compare numeric prerelease identifiers.
inline int cmp_num_prerel_ids(const string& l, const string& r) { inline int cmp_num_prerel_ids(const string& l, const string& r)
long long li = stoll(l); {
long long ri = stoll(r); long long li = stoll(l);
if (li == ri) return 0; long long ri = stoll(r);
return li > ri ? 1 : -1; if (li == ri) return 0;
} return li > ri ? 1 : -1;
}
using Prerel_type_pair = pair<Id_type, Id_type>; using Prerel_type_pair = pair<Id_type, Id_type>;
using Prerel_id_comparator = function<int(const string&, const string&)>; using Prerel_id_comparator = function<int(const string&, const string&)>;
const map<Prerel_type_pair, Prerel_id_comparator> comparators = { const map<Prerel_type_pair, Prerel_id_comparator> comparators = {
{ { Id_type::alnum, Id_type::alnum }, cmp_alnum_prerel_ids }, { { Id_type::alnum, Id_type::alnum }, cmp_alnum_prerel_ids },
{ { Id_type::alnum, Id_type::num }, [](const string&, const string&) {return 1;} }, { { Id_type::alnum, Id_type::num }, [](const string&, const string&) {return 1;} },
{ { Id_type::num, Id_type::alnum }, [](const string&, const string&) {return -1;} }, { { Id_type::num, Id_type::alnum }, [](const string&, const string&) {return -1;} },
{ { Id_type::num, Id_type::num }, cmp_num_prerel_ids } { { Id_type::num, Id_type::num }, cmp_num_prerel_ids }
}; };
// Compare prerelease identifiers based on their types. // Compare prerelease identifiers based on their types.
inline int compare_prerel_identifiers(const Prerelease_identifier& l, const Prerelease_identifier& r) { inline int compare_prerel_identifiers(const Prerelease_identifier& l, const Prerelease_identifier& r)
auto cmp = comparators.at({ l.second, r.second }); {
return cmp(l.first, r.first); auto cmp = comparators.at({ l.second, r.second });
} return cmp(l.first, r.first);
}
inline int cmp_rel_prerel(const Prerelease_identifiers& l, const Prerelease_identifiers& r) { inline int cmp_rel_prerel(const Prerelease_identifiers& l, const Prerelease_identifiers& r)
if (l.empty() && !r.empty()) return 1; {
if (r.empty() && !l.empty()) return -1; if (l.empty() && !r.empty()) return 1;
return 0; if (r.empty() && !l.empty()) return -1;
} return 0;
} }
}
int Semver200_comparator::compare(const Version_data& l, const Version_data& r) const { int Semver200_comparator::compare(const Version_data& l, const Version_data& r) const
// Compare normal version components. {
int cmp = compare_normal(l, r); // Compare normal version components.
if (cmp != 0) return cmp; int cmp = compare_normal(l, r);
if (cmp != 0) return cmp;
// Compare if one version is release and the other prerelease - release is always higher. // Compare if one version is release and the other prerelease - release is always higher.
cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids); cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids);
if (cmp != 0) return cmp; if (cmp != 0) return cmp;
// Compare prerelease by looking at each identifier: numeric ones are compared as numbers, // Compare prerelease by looking at each identifier: numeric ones are compared as numbers,
// alphanum as ASCII strings. // alphanum as ASCII strings.
auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size()); auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size());
for (size_t i = 0; i < shorter; i++) { for (size_t i = 0; i < shorter; i++) {
cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]); cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]);
if (cmp != 0) return cmp; if (cmp != 0) return cmp;
} }
// Prerelease identifiers are the same, to the length of the shorter version string; // Prerelease identifiers are the same, to the length of the shorter version string;
// if they are the same length, then versions are equal, otherwise, longer one wins. // if they are the same length, then versions are equal, otherwise, longer one wins.
if (l.prerelease_ids.size() == r.prerelease_ids.size()) return 0; if (l.prerelease_ids.size() == r.prerelease_ids.size()) return 0;
return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1; return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1;
} }
} }

View File

@@ -35,176 +35,190 @@ SOFTWARE.
using namespace std; using namespace std;
namespace version { namespace version
{
namespace { namespace
enum class Parser_state { {
major, minor, patch, prerelease, build enum class Parser_state
}; {
major, minor, patch, prerelease, build
};
using Validator = function<void(const string&, const char)>; using Validator = function<void(const string&, const char)>;
using State_transition_hook = function<void(string&)>; using State_transition_hook = function<void(string&)>;
/// State transition is described by a character that triggers it, a state to transition to and /// State transition is described by a character that triggers it, a state to transition to and
/// optional hook to be invoked on transition. /// optional hook to be invoked on transition.
using Transition = tuple<const char, Parser_state, State_transition_hook>; using Transition = tuple<const char, Parser_state, State_transition_hook>;
using Transitions = vector<Transition>; using Transitions = vector<Transition>;
using State = tuple<Transitions, string&, Validator>; using State = tuple<Transitions, string&, Validator>;
using State_machine = map<Parser_state, State>; using State_machine = map<Parser_state, State>;
// Ranges of characters allowed in prerelease and build identifiers. // Ranges of characters allowed in prerelease and build identifiers.
const vector<pair<char, char>> allowed_prerel_id_chars = { const vector<pair<char, char>> allowed_prerel_id_chars = {
{ '0', '9' },{ 'A','Z' },{ 'a','z' },{ '-','-' } { '0', '9' },{ 'A','Z' },{ 'a','z' },{ '-','-' }
}; };
inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { inline Transition mkx(const char c, Parser_state p, State_transition_hook pth)
return make_tuple(c, p, pth); {
} return make_tuple(c, p, pth);
}
/// Advance parser state machine by a single step. /// Advance parser state machine by a single step.
/** /**
Perform single step of parser state machine: if character matches one from transition tables - Perform single step of parser state machine: if character matches one from transition tables -
trigger transition to next state; otherwise, validate if current token is in legal state trigger transition to next state; otherwise, validate if current token is in legal state
(throw Parse_error if not) and then add character to current token; State transition includes (throw Parse_error if not) and then add character to current token; State transition includes
preparing various vars for next state and invoking state transition hook (if specified) which is preparing various vars for next state and invoking state transition hook (if specified) which is
where whole tokens are validated. where whole tokens are validated.
*/ */
inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate, inline void process_char(const char c, Parser_state& cstate, Parser_state& pstate,
const Transitions& transitions, string& target, Validator validate) { const Transitions& transitions, string& target, Validator validate)
for (const auto& transition : transitions) { {
if (c == get<0>(transition)) { for (const auto& transition : transitions) {
if (get<2>(transition)) get<2>(transition)(target); if (c == get<0>(transition)) {
pstate = cstate; if (get<2>(transition)) get<2>(transition)(target);
cstate = get<1>(transition); pstate = cstate;
return; cstate = get<1>(transition);
} return;
} }
validate(target, c); }
target.push_back(c); validate(target, c);
} target.push_back(c);
}
/// Validate normal (major, minor, patch) version components. /// Validate normal (major, minor, patch) version components.
inline void normal_version_validator(const string& tgt, const char c) { inline void normal_version_validator(const string& tgt, const char c)
if (c < '0' || c > '9') throw Parse_error("invalid character encountered: " + string(1, c)); {
if (tgt.compare(0, 1, "0") == 0) throw Parse_error("leading 0 not allowed"); if (c < '0' || c > '9') throw Parse_error("invalid character encountered: " + string(1, c));
} if (tgt.compare(0, 1, "0") == 0) throw Parse_error("leading 0 not allowed");
}
/// Validate that prerelease and build version identifiers are comprised of allowed chars only. /// Validate that prerelease and build version identifiers are comprised of allowed chars only.
inline void prerelease_version_validator(const string&, const char c) { inline void prerelease_version_validator(const string&, const char c)
bool res = false; {
for (const auto& r : allowed_prerel_id_chars) { bool res = false;
res |= (c >= r.first && c <= r.second); for (const auto& r : allowed_prerel_id_chars) {
} res |= (c >= r.first && c <= r.second);
if (!res) }
throw Parse_error("invalid character encountered: " + string(1, c)); if (!res)
} throw Parse_error("invalid character encountered: " + string(1, c));
}
inline bool is_identifier_numeric(const string& id) { inline bool is_identifier_numeric(const string& id)
return id.find_first_not_of("0123456789") == string::npos; {
} return id.find_first_not_of("0123456789") == string::npos;
}
inline bool check_for_leading_0(const string& str) { inline bool check_for_leading_0(const string& str)
return str.length() > 1 && str[0] == '0'; {
} return str.length() > 1 && str[0] == '0';
}
/// Validate every individual prerelease identifier, determine it's type and add it to collection. /// Validate every individual prerelease identifier, determine it's type and add it to collection.
void prerelease_hook_impl(string& id, Prerelease_identifiers& prerelease) { void prerelease_hook_impl(string& id, Prerelease_identifiers& prerelease)
if (id.empty()) throw Parse_error("version identifier cannot be empty"); {
Id_type t = Id_type::alnum; if (id.empty()) throw Parse_error("version identifier cannot be empty");
if (is_identifier_numeric(id)) { Id_type t = Id_type::alnum;
t = Id_type::num; if (is_identifier_numeric(id)) {
if (check_for_leading_0(id)) { t = Id_type::num;
throw Parse_error("numeric identifiers cannot have leading 0"); if (check_for_leading_0(id)) {
} throw Parse_error("numeric identifiers cannot have leading 0");
} }
prerelease.push_back(Prerelease_identifier(id, t)); }
id.clear(); prerelease.push_back(Prerelease_identifier(id, t));
} id.clear();
}
/// Validate every individual build identifier and add it to collection. /// Validate every individual build identifier and add it to collection.
void build_hook_impl(string& id, Parser_state& pstate, Build_identifiers& build, void build_hook_impl(string& id, Parser_state& pstate, Build_identifiers& build,
std::string& prerelease_id, Prerelease_identifiers& prerelease) { std::string& prerelease_id, Prerelease_identifiers& prerelease)
// process last token left from parsing prerelease data {
if (pstate == Parser_state::prerelease) prerelease_hook_impl(prerelease_id, prerelease); // process last token left from parsing prerelease data
if (id.empty()) throw Parse_error("version identifier cannot be empty"); if (pstate == Parser_state::prerelease) prerelease_hook_impl(prerelease_id, prerelease);
build.push_back(id); if (id.empty()) throw Parse_error("version identifier cannot be empty");
id.clear(); build.push_back(id);
} id.clear();
}
} }
/// Parse semver 2.0.0-compatible string to Version_data structure. /// Parse semver 2.0.0-compatible string to Version_data structure.
/** /**
Version text parser is implemented as a state machine. In each step one successive character from version Version text parser is implemented as a state machine. In each step one successive character from version
string is consumed and is either added to current token or triggers state transition. Hooks can be string is consumed and is either added to current token or triggers state transition. Hooks can be
injected into state transitions for validation/customization purposes. injected into state transitions for validation/customization purposes.
*/ */
Version_data Semver200_parser::parse(const string& s) const { Version_data Semver200_parser::parse(const string& s) const
string major; {
string minor; string major;
string patch; string minor;
string prerelease_id; string patch;
string build_id; string prerelease_id;
Prerelease_identifiers prerelease; string build_id;
Build_identifiers build; Prerelease_identifiers prerelease;
Parser_state cstate{ Parser_state::major }; Build_identifiers build;
Parser_state pstate; Parser_state cstate{ Parser_state::major };
Parser_state pstate;
auto prerelease_hook = [&](string& id) { auto prerelease_hook = [&](string& id) {
prerelease_hook_impl(id, prerelease); prerelease_hook_impl(id, prerelease);
}; };
auto build_hook = [&](string& id) { auto build_hook = [&](string& id) {
build_hook_impl(id, pstate, build, prerelease_id, prerelease); build_hook_impl(id, pstate, build, prerelease_id, prerelease);
}; };
// State transition tables // State transition tables
auto major_trans = { auto major_trans = {
mkx('.', Parser_state::minor, {}) mkx('.', Parser_state::minor, {})
}; };
auto minor_trans = { auto minor_trans = {
mkx('.', Parser_state::patch, {}) mkx('.', Parser_state::patch, {})
}; };
auto patch_trans = { auto patch_trans = {
mkx('-', Parser_state::prerelease, {}), mkx('-', Parser_state::prerelease, {}),
mkx('+', Parser_state::build, {}) mkx('+', Parser_state::build, {})
}; };
auto prerelease_trans = { auto prerelease_trans = {
// When identifier separator (.) is found, stay in the same state but invoke hook // When identifier separator (.) is found, stay in the same state but invoke hook
// in order to process each individual identifier separately. // in order to process each individual identifier separately.
mkx('.', Parser_state::prerelease, prerelease_hook), mkx('.', Parser_state::prerelease, prerelease_hook),
mkx('+', Parser_state::build, {}) mkx('+', Parser_state::build, {})
}; };
auto build_trans = { auto build_trans = {
// Same stay-in-the-same-state-but-invoke-hook trick from above. // Same stay-in-the-same-state-but-invoke-hook trick from above.
mkx('.', Parser_state::build, build_hook) mkx('.', Parser_state::build, build_hook)
}; };
State_machine state_machine = { State_machine state_machine = {
{Parser_state::major, State{major_trans, major, normal_version_validator}}, {Parser_state::major, State{major_trans, major, normal_version_validator}},
{Parser_state::minor, State{minor_trans, minor, normal_version_validator}}, {Parser_state::minor, State{minor_trans, minor, normal_version_validator}},
{Parser_state::patch, State{patch_trans, patch, normal_version_validator}}, {Parser_state::patch, State{patch_trans, patch, normal_version_validator}},
{Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}}, {Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}},
{Parser_state::build, State{build_trans, build_id, prerelease_version_validator}} {Parser_state::build, State{build_trans, build_id, prerelease_version_validator}}
}; };
// Main loop. // Main loop.
for (const auto& c : s) { for (const auto& c : s) {
auto state = state_machine.at(cstate); auto state = state_machine.at(cstate);
process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state)); process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state));
} }
// Trigger appropriate hooks in order to process last token, because no state transition was // Trigger appropriate hooks in order to process last token, because no state transition was
// triggered for it. // triggered for it.
if (cstate == Parser_state::prerelease) { if (cstate == Parser_state::prerelease) {
prerelease_hook(prerelease_id); prerelease_hook(prerelease_id);
} else if (cstate == Parser_state::build) { }
build_hook(build_id); else if (cstate == Parser_state::build) {
} build_hook(build_id);
}
try { try {
return Version_data{ stoi(major), stoi(minor), stoi(patch), prerelease, build }; return Version_data{ stoi(major), stoi(minor), stoi(patch), prerelease, build };
} catch (invalid_argument& ex) { }
throw Parse_error(ex.what()); catch (invalid_argument& ex) {
} throw Parse_error(ex.what());
} }
}
} }

View File

@@ -8,81 +8,81 @@
using namespace std; using namespace std;
wchar_t* FindRootAppDir() wchar_t* FindRootAppDir()
{ {
wchar_t* ourDirectory = new wchar_t[MAX_PATH]; wchar_t* ourDirectory = new wchar_t[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), ourDirectory, MAX_PATH); GetModuleFileName(GetModuleHandle(NULL), ourDirectory, MAX_PATH);
wchar_t* lastSlash = wcsrchr(ourDirectory, L'\\'); wchar_t* lastSlash = wcsrchr(ourDirectory, L'\\');
if (!lastSlash) { if (!lastSlash) {
delete[] ourDirectory; delete[] ourDirectory;
return NULL; return NULL;
} }
// Null-terminate the string at the slash so now it's a directory // Null-terminate the string at the slash so now it's a directory
*lastSlash = 0x0; *lastSlash = 0x0;
return ourDirectory; return ourDirectory;
} }
wchar_t* FindOwnExecutableName() wchar_t* FindOwnExecutableName()
{ {
wchar_t* ourDirectory = new wchar_t[MAX_PATH]; wchar_t* ourDirectory = new wchar_t[MAX_PATH];
GetModuleFileName(GetModuleHandle(NULL), ourDirectory, MAX_PATH); GetModuleFileName(GetModuleHandle(NULL), ourDirectory, MAX_PATH);
wchar_t* lastSlash = wcsrchr(ourDirectory, L'\\'); wchar_t* lastSlash = wcsrchr(ourDirectory, L'\\');
if (!lastSlash) { if (!lastSlash) {
delete[] ourDirectory; delete[] ourDirectory;
return NULL; return NULL;
} }
wchar_t* ret = _wcsdup(lastSlash + 1); wchar_t* ret = _wcsdup(lastSlash + 1);
delete[] ourDirectory; delete[] ourDirectory;
return ret; return ret;
} }
std::wstring FindLatestAppDir() std::wstring FindLatestAppDir()
{ {
std::wstring ourDir; std::wstring ourDir;
ourDir.assign(FindRootAppDir()); ourDir.assign(FindRootAppDir());
ourDir += L"\\app-*"; ourDir += L"\\app-*";
WIN32_FIND_DATA fileInfo = { 0 }; WIN32_FIND_DATA fileInfo = { 0 };
HANDLE hFile = FindFirstFile(ourDir.c_str(), &fileInfo); HANDLE hFile = FindFirstFile(ourDir.c_str(), &fileInfo);
if (hFile == INVALID_HANDLE_VALUE) { if (hFile == INVALID_HANDLE_VALUE) {
return NULL; return NULL;
} }
version::Semver200_version acc("0.0.0"); version::Semver200_version acc("0.0.0");
std::wstring acc_s; std::wstring acc_s;
do { do {
std::wstring appVer = fileInfo.cFileName; std::wstring appVer = fileInfo.cFileName;
appVer = appVer.substr(4); // Skip 'app-' appVer = appVer.substr(4); // Skip 'app-'
if (!(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { if (!(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
continue; continue;
} }
std::string s(appVer.begin(), appVer.end()); std::string s(appVer.begin(), appVer.end());
version::Semver200_version thisVer(s); version::Semver200_version thisVer(s);
if (thisVer > acc) { if (thisVer > acc) {
acc = thisVer; acc = thisVer;
acc_s = appVer; acc_s = appVer;
} }
} while (FindNextFile(hFile, &fileInfo)); } while (FindNextFile(hFile, &fileInfo));
if (acc == version::Semver200_version("0.0.0")) { if (acc == version::Semver200_version("0.0.0")) {
return NULL; return NULL;
} }
ourDir.assign(FindRootAppDir()); ourDir.assign(FindRootAppDir());
std::wstringstream ret; std::wstringstream ret;
ret << ourDir << L"\\app-" << acc_s; ret << ourDir << L"\\app-" << acc_s;
FindClose(hFile); FindClose(hFile);
return ret.str(); return ret.str();
} }
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
@@ -90,31 +90,31 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_ LPWSTR lpCmdLine, _In_ LPWSTR lpCmdLine,
_In_ int nCmdShow) _In_ int nCmdShow)
{ {
std::wstring appName; std::wstring appName;
appName.assign(FindOwnExecutableName()); appName.assign(FindOwnExecutableName());
std::wstring workingDir(FindLatestAppDir()); std::wstring workingDir(FindLatestAppDir());
std::wstring fullPath(workingDir + L"\\" + appName); std::wstring fullPath(workingDir + L"\\" + appName);
STARTUPINFO si = { 0 }; STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = { 0 };
si.cb = sizeof(si); si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW; si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = nCmdShow; si.wShowWindow = nCmdShow;
std::wstring cmdLine(L"\""); std::wstring cmdLine(L"\"");
cmdLine += fullPath; cmdLine += fullPath;
cmdLine += L"\" "; cmdLine += L"\" ";
cmdLine += lpCmdLine; cmdLine += lpCmdLine;
wchar_t* lpCommandLine = _wcsdup(cmdLine.c_str()); wchar_t* lpCommandLine = _wcsdup(cmdLine.c_str());
wchar_t* lpCurrentDirectory = _wcsdup(workingDir.c_str()); wchar_t* lpCurrentDirectory = _wcsdup(workingDir.c_str());
if (!CreateProcess(NULL, lpCommandLine, NULL, NULL, true, 0, NULL, lpCurrentDirectory, &si, &pi)) { if (!CreateProcess(NULL, lpCommandLine, NULL, NULL, true, 0, NULL, lpCurrentDirectory, &si, &pi)) {
return -1; return -1;
} }
AllowSetForegroundWindow(pi.dwProcessId); AllowSetForegroundWindow(pi.dwProcessId);
WaitForInputIdle(pi.hProcess, 5 * 1000); WaitForInputIdle(pi.hProcess, 5 * 1000);
return 0; return 0;
} }

View File

@@ -26,26 +26,32 @@ SOFTWARE.
#include "version.h" #include "version.h"
namespace version { namespace version
{
/// Parse string into Version_data structure according to semantic versioning 2.0.0 rules. /// Parse string into Version_data structure according to semantic versioning 2.0.0 rules.
struct Semver200_parser { struct Semver200_parser
Version_data parse(const std::string&) const; {
}; Version_data parse(const std::string&) const;
};
/// Compare Version_data to another using semantic versioning 2.0.0 rules. /// Compare Version_data to another using semantic versioning 2.0.0 rules.
struct Semver200_comparator { struct Semver200_comparator
int compare(const Version_data&, const Version_data&) const; {
}; int compare(const Version_data&, const Version_data&) const;
};
/// Concrete version class that binds all semver 2.0.0 functionality together. /// Concrete version class that binds all semver 2.0.0 functionality together.
class Semver200_version : public Basic_version<Semver200_parser, Semver200_comparator> { class Semver200_version : public Basic_version<Semver200_parser, Semver200_comparator>
public: {
Semver200_version() public:
: Basic_version{ Semver200_parser(), Semver200_comparator() } {} Semver200_version()
: Basic_version{ Semver200_parser(), Semver200_comparator() }
{}
Semver200_version(const std::string& v) Semver200_version(const std::string& v)
: Basic_version{ v, Semver200_parser(), Semver200_comparator() } {} : Basic_version{ v, Semver200_parser(), Semver200_comparator() }
}; {}
};
} }

View File

@@ -28,129 +28,134 @@ SOFTWARE.
#include <string> #include <string>
#include <vector> #include <vector>
namespace version { namespace version
{
/// Any error in parsing or validation of version string will result in Parse_error exception being thrown. /// Any error in parsing or validation of version string will result in Parse_error exception being thrown.
class Parse_error : public std::runtime_error { class Parse_error : public std::runtime_error
using std::runtime_error::runtime_error; {
}; using std::runtime_error::runtime_error;
};
/// Type of prerelease identifier: alphanumeric or numeric. /// Type of prerelease identifier: alphanumeric or numeric.
/** /**
Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while
numeric identifiers are compared as numbers. numeric identifiers are compared as numbers.
*/ */
enum class Id_type { enum class Id_type
alnum, ///< Identifier is alphanumerical {
num ///< Identifier is numeric alnum, ///< Identifier is alphanumerical
}; num ///< Identifier is numeric
};
/// Container for prerelease identifier value and it's type. /// Container for prerelease identifier value and it's type.
/** /**
Prerelease version string consist of an optional series of dot-separated identifiers. Prerelease version string consist of an optional series of dot-separated identifiers.
These identifiers can be either numerical or alphanumerical. These identifiers can be either numerical or alphanumerical.
This structure describes one such identifier. This structure describes one such identifier.
*/ */
using Prerelease_identifier = std::pair<std::string, Id_type>; using Prerelease_identifier = std::pair<std::string, Id_type>;
/// Container for all prerelease identifiers for a given version string. /// Container for all prerelease identifiers for a given version string.
using Prerelease_identifiers = std::vector<Prerelease_identifier>; using Prerelease_identifiers = std::vector<Prerelease_identifier>;
/// Build identifier is arbitrary string with no special meaning with regards to version precedence. /// Build identifier is arbitrary string with no special meaning with regards to version precedence.
using Build_identifier = std::string; using Build_identifier = std::string;
/// Container for all build identifiers of a given version string. /// Container for all build identifiers of a given version string.
using Build_identifiers = std::vector<Build_identifier>; using Build_identifiers = std::vector<Build_identifier>;
/// Description of version broken into parts, as per semantic versioning specification. /// Description of version broken into parts, as per semantic versioning specification.
struct Version_data { struct Version_data
int major; ///< Major version, change only on incompatible API modifications. {
int minor; ///< Minor version, change on backwards-compatible API modifications. int major; ///< Major version, change only on incompatible API modifications.
int patch; ///< Patch version, change only on bugfixes. int minor; ///< Minor version, change on backwards-compatible API modifications.
int patch; ///< Patch version, change only on bugfixes.
/// Optional series of prerelease identifiers. /// Optional series of prerelease identifiers.
Prerelease_identifiers prerelease_ids; Prerelease_identifiers prerelease_ids;
/// Optional series of build identifiers. /// Optional series of build identifiers.
Build_identifiers build_ids; Build_identifiers build_ids;
}; };
// Forward declaration required for operators' template declarations. // Forward declaration required for operators' template declarations.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
class Basic_version; class Basic_version;
/// Test if left-hand version operand is of lower precedence than the right-hand version. /// Test if left-hand version operand is of lower precedence than the right-hand version.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator<(const Basic_version<Parser, Comparator>&, bool operator<(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Test if left-hand version operand if of equal precedence as the right-hand version. /// Test if left-hand version operand if of equal precedence as the right-hand version.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator==(const Basic_version<Parser, Comparator>&, bool operator==(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Output version object to stream using standard semver format (X.Y.Z-PR+B). /// Output version object to stream using standard semver format (X.Y.Z-PR+B).
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
std::ostream& operator<<(std::ostream&, std::ostream& operator<<(std::ostream&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Test if left-hand version and right-hand version are of different precedence. /// Test if left-hand version and right-hand version are of different precedence.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator!=(const Basic_version<Parser, Comparator>&, bool operator!=(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Test if left-hand version operand is of higher precedence than the right-hand version. /// Test if left-hand version operand is of higher precedence than the right-hand version.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator>(const Basic_version<Parser, Comparator>&, bool operator>(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Test if left-hand version operand is of higher or equal precedence as the right-hand version. /// Test if left-hand version operand is of higher or equal precedence as the right-hand version.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator>=(const Basic_version<Parser, Comparator>&, bool operator>=(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Test if left-hand version operand is of lower or equal precedence as the right-hand version. /// Test if left-hand version operand is of lower or equal precedence as the right-hand version.
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
bool operator<=(const Basic_version<Parser, Comparator>&, bool operator<=(const Basic_version<Parser, Comparator>&,
const Basic_version<Parser, Comparator>&); const Basic_version<Parser, Comparator>&);
/// Base class for various version parsing and precedence ordering schemes. /// Base class for various version parsing and precedence ordering schemes.
/** /**
Basic_version class describes general version object without prescribing parsing, Basic_version class describes general version object without prescribing parsing,
validation and comparison rules. These rules are implemented by supplied Parser and validation and comparison rules. These rules are implemented by supplied Parser and
Comparator objects. Comparator objects.
*/ */
template<typename Parser, typename Comparator> template<typename Parser, typename Comparator>
class Basic_version { class Basic_version
public: {
/// Construct Basic_version object using Parser object to parse default ("0.0.0") version string and Comparator for comparison. public:
Basic_version(Parser, Comparator); /// Construct Basic_version object using Parser object to parse default ("0.0.0") version string and Comparator for comparison.
Basic_version(Parser, Comparator);
/// Construct Basic_version object using Parser to parse supplied version string and Comparator for comparison. /// Construct Basic_version object using Parser to parse supplied version string and Comparator for comparison.
Basic_version(const std::string&, Parser, Comparator); Basic_version(const std::string&, Parser, Comparator);
/// Construct Basic_version by copying data from another one. /// Construct Basic_version by copying data from another one.
Basic_version(const Basic_version&); Basic_version(const Basic_version&);
/// Copy version data from another Basic_version to this one. /// Copy version data from another Basic_version to this one.
Basic_version& operator=(const Basic_version&); Basic_version& operator=(const Basic_version&);
int major() const; ///< Get major version. int major() const; ///< Get major version.
int minor() const; ///< Get minor version. int minor() const; ///< Get minor version.
int patch() const; ///< Get patch version. int patch() const; ///< Get patch version.
const std::string prerelease() const; ///< Get prerelease version string. const std::string prerelease() const; ///< Get prerelease version string.
const std::string build() const; ///< Get build version string. const std::string build() const; ///< Get build version string.
friend bool operator< <>(const Basic_version&, const Basic_version&); friend bool operator< <>(const Basic_version&, const Basic_version&);
friend bool operator== <>(const Basic_version&, const Basic_version&); friend bool operator== <>(const Basic_version&, const Basic_version&);
friend std::ostream& operator<< <>(std::ostream&s, const Basic_version&); friend std::ostream& operator<< <>(std::ostream& s, const Basic_version&);
private: private:
Parser parser_; Parser parser_;
Comparator comparator_; Comparator comparator_;
Version_data ver_; Version_data ver_;
}; };
} }
#include "version.inl" #include "version.inl"

View File

@@ -13,171 +13,171 @@ using namespace std;
BOOL CALLBACK EnumResLangProc(HMODULE hModule, LPCTSTR lpszType, LPCTSTR lpszName, WORD wIDLanguage, LONG_PTR lParam) BOOL CALLBACK EnumResLangProc(HMODULE hModule, LPCTSTR lpszType, LPCTSTR lpszName, WORD wIDLanguage, LONG_PTR lParam)
{ {
HANDLE hUpdate = (HANDLE)lParam; HANDLE hUpdate = (HANDLE)lParam;
HRSRC hFindItAgain = FindResourceEx(hModule, lpszType, lpszName, wIDLanguage); HRSRC hFindItAgain = FindResourceEx(hModule, lpszType, lpszName, wIDLanguage);
HGLOBAL hGlobal = LoadResource(hModule, hFindItAgain); HGLOBAL hGlobal = LoadResource(hModule, hFindItAgain);
if (!hGlobal) return true; if (!hGlobal) return true;
UpdateResource(hUpdate, lpszType, lpszName, wIDLanguage, LockResource(hGlobal), SizeofResource(hModule, hFindItAgain)); UpdateResource(hUpdate, lpszType, lpszName, wIDLanguage, LockResource(hGlobal), SizeofResource(hModule, hFindItAgain));
return true; return true;
} }
BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam) BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG_PTR lParam)
{ {
HANDLE hUpdate = (HANDLE)lParam; HANDLE hUpdate = (HANDLE)lParam;
EnumResourceLanguages(hModule, lpszType, lpszName, EnumResLangProc, (LONG_PTR)hUpdate); EnumResourceLanguages(hModule, lpszType, lpszName, EnumResLangProc, (LONG_PTR)hUpdate);
return true; return true;
} }
BOOL CALLBACK EnumResTypeProc(HMODULE hMod, LPTSTR lpszType, LONG_PTR lParam) BOOL CALLBACK EnumResTypeProc(HMODULE hMod, LPTSTR lpszType, LONG_PTR lParam)
{ {
std::vector<wchar_t*>* typeList = (std::vector<wchar_t*>*)lParam; std::vector<wchar_t*>* typeList = (std::vector<wchar_t*>*)lParam;
if (IS_INTRESOURCE(lpszType)) { if (IS_INTRESOURCE(lpszType)) {
typeList->push_back(lpszType); typeList->push_back(lpszType);
} }
else { else {
typeList->push_back(_wcsdup(lpszType)); typeList->push_back(_wcsdup(lpszType));
} }
return true; return true;
} }
int CopyResourcesToStubExecutable(wchar_t* src, wchar_t* dest) int CopyResourcesToStubExecutable(wchar_t* src, wchar_t* dest)
{ {
HMODULE hSrc = LoadLibraryEx(src, NULL, LOAD_LIBRARY_AS_DATAFILE); HMODULE hSrc = LoadLibraryEx(src, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (!hSrc) return GetLastError(); if (!hSrc) return GetLastError();
HANDLE hUpdate = BeginUpdateResource(dest, true); HANDLE hUpdate = BeginUpdateResource(dest, true);
if (!hUpdate) return GetLastError(); if (!hUpdate) return GetLastError();
std::vector<wchar_t*> typeList; std::vector<wchar_t*> typeList;
EnumResourceTypes(hSrc, EnumResTypeProc, (LONG_PTR)&typeList); EnumResourceTypes(hSrc, EnumResTypeProc, (LONG_PTR)&typeList);
for (auto& type : typeList) { for (auto& type : typeList) {
EnumResourceNames(hSrc, type, EnumResNameProc, (LONG_PTR)hUpdate); EnumResourceNames(hSrc, type, EnumResNameProc, (LONG_PTR)hUpdate);
} }
EndUpdateResource(hUpdate, false); EndUpdateResource(hUpdate, false);
return 0; return 0;
} }
int LoadFileIntoMemory(wstring fpath, BYTE** pBuf, int* cBuf) int LoadFileIntoMemory(wstring fpath, BYTE** pBuf, int* cBuf)
{ {
HANDLE hFile = CreateFile(fpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); HANDLE hFile = CreateFile(fpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) { if (hFile == INVALID_HANDLE_VALUE) {
printf("Can't open file\n"); printf("Can't open file\n");
return E_FAIL; return E_FAIL;
} }
BY_HANDLE_FILE_INFORMATION fileInfo; BY_HANDLE_FILE_INFORMATION fileInfo;
if (!GetFileInformationByHandle(hFile, &fileInfo)) { if (!GetFileInformationByHandle(hFile, &fileInfo)) {
printf("Can't read file handle\n"); printf("Can't read file handle\n");
return E_FAIL; return E_FAIL;
} }
*cBuf = fileInfo.nFileSizeLow; *cBuf = fileInfo.nFileSizeLow;
*pBuf = new BYTE[fileInfo.nFileSizeLow + 0x1000]; *pBuf = new BYTE[fileInfo.nFileSizeLow + 0x1000];
BYTE* pCurrent = *pBuf; BYTE* pCurrent = *pBuf;
DWORD dwBytesRead; DWORD dwBytesRead;
printf("Starting to read file!\n"); printf("Starting to read file!\n");
do { do {
if (!ReadFile(hFile, pCurrent, 0x1000, &dwBytesRead, NULL)) { if (!ReadFile(hFile, pCurrent, 0x1000, &dwBytesRead, NULL)) {
printf("Failed to read file! 0x%u\n", GetLastError()); printf("Failed to read file! 0x%u\n", GetLastError());
return E_FAIL; return E_FAIL;
} }
pCurrent += dwBytesRead; pCurrent += dwBytesRead;
} while (dwBytesRead > 0); } while (dwBytesRead > 0);
return S_OK; return S_OK;
} }
int fail() int fail()
{ {
printf("Usage: WriteZipToSetup [Setup.exe template] [Zip File]\n"); printf("Usage: WriteZipToSetup [Setup.exe template] [Zip File]\n");
return -1; return -1;
} }
int wmain(int argc, wchar_t* argv[]) int wmain(int argc, wchar_t* argv[])
{ {
// short circuit exit for stub executable // short circuit exit for stub executable
if (argc > 1 && wcscmp(argv[1], L"--copy-stub-resources") == 0) { if (argc > 1 && wcscmp(argv[1], L"--copy-stub-resources") == 0) {
if (argc != 4) return fail(); if (argc != 4) return fail();
return CopyResourcesToStubExecutable(argv[2], argv[3]); return CopyResourcesToStubExecutable(argv[2], argv[3]);
} }
// parse command line arguments // parse command line arguments
const flags::args args(argc, argv); const flags::args args(argc, argv);
const auto& parg = args.positional(); const auto& parg = args.positional();
if (parg.size() != 2) { if (parg.size() != 2) {
return fail(); return fail();
} }
const auto setupFile = wstring(parg[0]); const auto setupFile = wstring(parg[0]);
const auto zipFile = wstring(parg[1]); const auto zipFile = wstring(parg[1]);
const auto requiredFramework = args.get<wstring>(L"set-required-framework"); const auto requiredFramework = args.get<wstring>(L"set-required-framework");
const auto splashImage = args.get<wstring>(L"set-splash"); const auto splashImage = args.get<wstring>(L"set-splash");
wprintf(L"Setup: %s, Zip: %s\n", setupFile.c_str(), zipFile.c_str()); wprintf(L"Setup: %s, Zip: %s\n", setupFile.c_str(), zipFile.c_str());
// Read the entire zip file into memory, yolo // Read the entire zip file into memory, yolo
BYTE *pZipBuf, *pSplashBuf; BYTE* pZipBuf, * pSplashBuf;
int cZipBuf, cSplashBuf; int cZipBuf, cSplashBuf;
if (FAILED(LoadFileIntoMemory(zipFile, &pZipBuf, &cZipBuf))) { if (FAILED(LoadFileIntoMemory(zipFile, &pZipBuf, &cZipBuf))) {
printf("Couldn't read zip file.\n"); printf("Couldn't read zip file.\n");
return fail(); return fail();
} }
printf("Updating Resource!\n"); printf("Updating Resource!\n");
HANDLE hRes = BeginUpdateResource(setupFile.c_str(), false); HANDLE hRes = BeginUpdateResource(setupFile.c_str(), false);
if (!hRes) { if (!hRes) {
printf("Couldn't open setup.exe for writing\n"); printf("Couldn't open setup.exe for writing\n");
return fail(); return fail();
} }
if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_UPDATE_ZIP), RESOURCE_LANG, pZipBuf, cZipBuf)) { if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_UPDATE_ZIP), RESOURCE_LANG, pZipBuf, cZipBuf)) {
printf("Failed to update zip resource\n"); printf("Failed to update zip resource\n");
return fail(); return fail();
} }
if (requiredFramework.has_value()) { if (requiredFramework.has_value()) {
wstring sReq = requiredFramework.value(); wstring sReq = requiredFramework.value();
LPVOID pReq = &sReq[0]; LPVOID pReq = &sReq[0];
if (!UpdateResource(hRes, L"FLAGS", MAKEINTRESOURCE(IDR_FX_VERSION_FLAG), RESOURCE_LANG, pReq, (sReq.length() + 1) * sizeof(wchar_t))) { if (!UpdateResource(hRes, L"FLAGS", MAKEINTRESOURCE(IDR_FX_VERSION_FLAG), RESOURCE_LANG, pReq, (sReq.length() + 1) * sizeof(wchar_t))) {
printf("Failed to update required version resource\n"); printf("Failed to update required version resource\n");
return fail(); return fail();
} }
} }
if (splashImage.has_value()) { if (splashImage.has_value()) {
if (FAILED(LoadFileIntoMemory(splashImage.value(), &pSplashBuf, &cSplashBuf))) { if (FAILED(LoadFileIntoMemory(splashImage.value(), &pSplashBuf, &cSplashBuf))) {
printf("Couldn't read splash image.\n"); printf("Couldn't read splash image.\n");
return fail(); return fail();
} }
if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_SPLASH_IMG), RESOURCE_LANG, pSplashBuf, cSplashBuf)) { if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_SPLASH_IMG), RESOURCE_LANG, pSplashBuf, cSplashBuf)) {
printf("Failed to update splash resource\n"); printf("Failed to update splash resource\n");
return fail(); return fail();
} }
} }
else { else {
// if the user hasn't given us a splash screen, let's remove the default (there will be no splash at all) // if the user hasn't given us a splash screen, let's remove the default (there will be no splash at all)
if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_SPLASH_IMG), RESOURCE_LANG, 0, 0)) { if (!UpdateResource(hRes, L"DATA", MAKEINTRESOURCE(IDR_SPLASH_IMG), RESOURCE_LANG, 0, 0)) {
printf("Failed to update splash resource\n"); printf("Failed to update splash resource\n");
return fail(); return fail();
} }
} }
printf("Finished!\n"); printf("Finished!\n");
if (!EndUpdateResource(hRes, false)) { if (!EndUpdateResource(hRes, false)) {
printf("Failed to update resource\n"); printf("Failed to update resource\n");
return fail(); return fail();
} }
printf("It worked!\n"); printf("It worked!\n");
return 0; return 0;
} }

View File

@@ -11,8 +11,10 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace flags { namespace flags
namespace detail { {
namespace detail
{
using argument_map = using argument_map =
std::unordered_map<std::wstring_view, std::optional<std::wstring_view>>; std::unordered_map<std::wstring_view, std::optional<std::wstring_view>>;
@@ -21,8 +23,10 @@ namespace flags {
// * If the token does not begin with a -, it will be considered a value for the // * If the token does not begin with a -, it will be considered a value for the
// previous option. If there was no previous option, it will be considered a // previous option. If there was no previous option, it will be considered a
// positional argument. // positional argument.
struct parser { struct parser
parser(const int argc, wchar_t** argv) { {
parser(const int argc, wchar_t** argv)
{
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
churn(argv[i]); churn(argv[i]);
} }
@@ -32,22 +36,26 @@ namespace flags {
parser& operator=(const parser&) = delete; parser& operator=(const parser&) = delete;
const argument_map& options() const { return options_; } const argument_map& options() const { return options_; }
const std::vector<std::wstring_view>& positional_arguments() const { const std::vector<std::wstring_view>& positional_arguments() const
{
return positional_arguments_; return positional_arguments_;
} }
private: private:
// Advance the state machine for the current token. // Advance the state machine for the current token.
void churn(const std::wstring_view& item) { void churn(const std::wstring_view& item)
{
item.at(0) == '-' ? on_option(item) : on_value(item); item.at(0) == '-' ? on_option(item) : on_value(item);
} }
// Consumes the current option if there is one. // Consumes the current option if there is one.
void flush() { void flush()
{
if (current_option_) on_value(); if (current_option_) on_value();
} }
void on_option(const std::wstring_view& option) { void on_option(const std::wstring_view& option)
{
// Consume the current_option and reassign it to the new option while // Consume the current_option and reassign it to the new option while
// removing all leading dashes. // removing all leading dashes.
flush(); flush();
@@ -64,7 +72,8 @@ namespace flags {
} }
} }
void on_value(const std::optional<std::wstring_view>& value = std::nullopt) { void on_value(const std::optional<std::wstring_view>& value = std::nullopt)
{
// If there's not an option preceding the value, it's a positional argument. // If there's not an option preceding the value, it's a positional argument.
if (!current_option_) { if (!current_option_) {
if (value) positional_arguments_.emplace_back(*value); if (value) positional_arguments_.emplace_back(*value);
@@ -82,7 +91,8 @@ namespace flags {
// If a key exists, return an optional populated with its value. // If a key exists, return an optional populated with its value.
inline std::optional<std::wstring_view> get_value( inline std::optional<std::wstring_view> get_value(
const argument_map& options, const std::wstring_view& option) { const argument_map& options, const std::wstring_view& option)
{
if (const auto it = options.find(option); it != options.end()) { if (const auto it = options.find(option); it != options.end()) {
return it->second; return it->second;
} }
@@ -94,7 +104,8 @@ namespace flags {
// nullopt. // nullopt.
template <class T> template <class T>
std::optional<T> get(const argument_map& options, std::optional<T> get(const argument_map& options,
const std::wstring_view& option) { const std::wstring_view& option)
{
if (const auto view = get_value(options, option)) { if (const auto view = get_value(options, option)) {
if (T value; std::istringstream(std::wstring(*view)) >> value) return value; if (T value; std::istringstream(std::wstring(*view)) >> value) return value;
} }
@@ -104,13 +115,15 @@ namespace flags {
// Since the values are already stored as strings, there's no need to use `>>`. // Since the values are already stored as strings, there's no need to use `>>`.
template <> template <>
inline std::optional<std::wstring_view> get(const argument_map& options, inline std::optional<std::wstring_view> get(const argument_map& options,
const std::wstring_view& option) { const std::wstring_view& option)
{
return get_value(options, option); return get_value(options, option);
} }
template <> template <>
inline std::optional<std::wstring> get(const argument_map& options, inline std::optional<std::wstring> get(const argument_map& options,
const std::wstring_view& option) { const std::wstring_view& option)
{
if (const auto view = get<std::wstring_view>(options, option)) { if (const auto view = get<std::wstring_view>(options, option)) {
return std::wstring(*view); return std::wstring(*view);
} }
@@ -123,7 +136,8 @@ namespace flags {
constexpr std::array<const wchar_t*, 5> falsities{ {L"0", L"n", L"no", L"f", L"false"} }; constexpr std::array<const wchar_t*, 5> falsities{ {L"0", L"n", L"no", L"f", L"false"} };
template <> template <>
inline std::optional<bool> get(const argument_map& options, inline std::optional<bool> get(const argument_map& options,
const std::wstring_view& option) { const std::wstring_view& option)
{
if (const auto value = get_value(options, option)) { if (const auto value = get_value(options, option)) {
return std::none_of(falsities.begin(), falsities.end(), return std::none_of(falsities.begin(), falsities.end(),
[&value](auto falsity) { return *value == falsity; }); [&value](auto falsity) { return *value == falsity; });
@@ -137,7 +151,8 @@ namespace flags {
// nullopt. // nullopt.
template <class T> template <class T>
std::optional<T> get(const std::vector<std::wstring_view>& positional_arguments, std::optional<T> get(const std::vector<std::wstring_view>& positional_arguments,
size_t positional_index) { size_t positional_index)
{
if (positional_index < positional_arguments.size()) { if (positional_index < positional_arguments.size()) {
if (T value; std::istringstream( if (T value; std::istringstream(
std::wstring(positional_arguments[positional_index])) >> std::wstring(positional_arguments[positional_index])) >>
@@ -151,7 +166,8 @@ namespace flags {
template <> template <>
inline std::optional<std::wstring_view> get( inline std::optional<std::wstring_view> get(
const std::vector<std::wstring_view>& positional_arguments, const std::vector<std::wstring_view>& positional_arguments,
size_t positional_index) { size_t positional_index)
{
if (positional_index < positional_arguments.size()) { if (positional_index < positional_arguments.size()) {
return positional_arguments[positional_index]; return positional_arguments[positional_index];
} }
@@ -161,7 +177,8 @@ namespace flags {
template <> template <>
inline std::optional<std::wstring> get( inline std::optional<std::wstring> get(
const std::vector<std::wstring_view>& positional_arguments, const std::vector<std::wstring_view>& positional_arguments,
size_t positional_index) { size_t positional_index)
{
if (positional_index < positional_arguments.size()) { if (positional_index < positional_arguments.size()) {
return std::wstring(positional_arguments[positional_index]); return std::wstring(positional_arguments[positional_index]);
} }
@@ -169,30 +186,36 @@ namespace flags {
} }
} // namespace detail } // namespace detail
struct args { struct args
{
args(const int argc, wchar_t** argv) : parser_(argc, argv) {} args(const int argc, wchar_t** argv) : parser_(argc, argv) {}
template <class T> template <class T>
std::optional<T> get(const std::wstring_view& option) const { std::optional<T> get(const std::wstring_view& option) const
{
return detail::get<T>(parser_.options(), option); return detail::get<T>(parser_.options(), option);
} }
template <class T> template <class T>
T get(const std::wstring_view& option, T&& default_value) const { T get(const std::wstring_view& option, T&& default_value) const
{
return get<T>(option).value_or(default_value); return get<T>(option).value_or(default_value);
} }
template <class T> template <class T>
std::optional<T> get(size_t positional_index) const { std::optional<T> get(size_t positional_index) const
{
return detail::get<T>(parser_.positional_arguments(), positional_index); return detail::get<T>(parser_.positional_arguments(), positional_index);
} }
template <class T> template <class T>
T get(size_t positional_index, T&& default_value) const { T get(size_t positional_index, T&& default_value) const
{
return get<T>(positional_index).value_or(default_value); return get<T>(positional_index).value_or(default_value);
} }
const std::vector<std::wstring_view>& positional() const { const std::vector<std::wstring_view>& positional() const
{
return parser_.positional_arguments(); return parser_.positional_arguments();
} }