Skip to content

Commit

Permalink
UpdateCheck: Added minimum Windows checking when updating
Browse files Browse the repository at this point in the history
This prevents Rainmeter from downloading a new Rainmeter version that is incompatible with the users system.
  • Loading branch information
brianferguson committed Jul 20, 2023
1 parent d082c9c commit 9b4de97
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 41 deletions.
1 change: 1 addition & 0 deletions Common/Common.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
<ClInclude Include="StringUtil.h" />
<ClInclude Include="Timer.h" />
<ClInclude Include="UnitTest.h" />
<ClInclude Include="Version.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
1 change: 1 addition & 0 deletions Common/Common.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<ClInclude Include="MathParser.h" />
<ClInclude Include="UnitTest.h" />
<ClInclude Include="Timer.h" />
<ClInclude Include="Version.h" />
<ClInclude Include="Gfx\FontCollection.h">
<Filter>Gfx</Filter>
</ClInclude>
Expand Down
46 changes: 40 additions & 6 deletions Common/Platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ void Platform::Initialize()
return false;
} ();

// Retrieve build number
m_BuildNumber = GetBuildNumberFromRegistry();

// Retrieve information from registry
std::wstring ubrStr;
std::wstring servicePack;

Expand All @@ -100,7 +104,7 @@ void Platform::Initialize()
WCHAR buffer[256] = { 0 };
DWORD size = _countof(buffer);

// Get Windows 10/11 specific values
// DisplayVersion (Windows10+)
if (IsWindows10OrGreater())
{
// Prefer "DisplayVersion" over "ReleaseId"
Expand All @@ -111,6 +115,7 @@ void Platform::Initialize()
}
}

// ProductName
size = _countof(buffer);
if (RegQueryValueEx(hkey, L"ProductName", nullptr, nullptr, (LPBYTE)buffer, (LPDWORD)&size) == ERROR_SUCCESS)
{
Expand All @@ -126,6 +131,38 @@ void Platform::Initialize()
}
}

// "Raw" version number
if (IsWindows10OrGreater())
{
// Note: "CurrentVersion" is no longer updated as of Windows 10, use Major/Minor versions instead
DWORD major = 0UL;
size = sizeof(DWORD);
if (RegQueryValueEx(hkey, L"CurrentMajorVersionNumber", nullptr, nullptr, (LPBYTE)&major, (LPDWORD)&size) == ERROR_SUCCESS && major >= 10UL)
{
DWORD minor = 0UL;
size = sizeof(DWORD);
if (RegQueryValueEx(hkey, L"CurrentMinorVersionNumber", nullptr, nullptr, (LPBYTE)&minor, (LPDWORD)&size) == ERROR_SUCCESS && minor >= 0UL)
{
m_RawVersion = std::to_wstring(major);
m_RawVersion += L'.';
m_RawVersion += std::to_wstring(minor);
m_RawVersion += L'.';
m_RawVersion += m_BuildNumber;
}
}
}
else // Windows 7, 8, 8.1
{
size = _countof(buffer);
if (RegQueryValueEx(hkey, L"CurrentVersion", nullptr, nullptr, (LPBYTE)&buffer, (LPDWORD)&size) == ERROR_SUCCESS)
{
m_RawVersion = buffer;
m_RawVersion += L'.';
m_RawVersion += m_BuildNumber;
}
}

// UBR (used in "friendly name")
DWORD ubr = 0UL;
size = sizeof(DWORD);
if (RegQueryValueEx(hkey, L"UBR", nullptr, nullptr, (LPBYTE)&ubr, &size) == ERROR_SUCCESS && ubr > 0UL)
Expand All @@ -144,10 +181,7 @@ void Platform::Initialize()
hkey = nullptr;
}

// Retrieve build number
m_BuildNumber = GetBuildNumberFromRegistry();

// Set Name
// Name
const bool isServer = IsWindowsServer();
m_Name = isServer ? L"Windows Server " : L"Windows ";
m_Name += [&]() -> LPCWSTR
Expand All @@ -163,7 +197,7 @@ void Platform::Initialize()
L"Unknown";
} ();

// Set "friendly" name
// "Friendly" name
m_FriendlyName = m_ProductName;
if (!m_DisplayVersion.empty())
{
Expand Down
2 changes: 2 additions & 0 deletions Common/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Platform
std::wstring GetFriendlyName() { return m_FriendlyName; }
std::wstring GetReleaseID() { return m_DisplayVersion; } // Can be empty
std::wstring GetBuildNumber() { return m_BuildNumber; }
std::wstring GetRawVersion() { return m_RawVersion; } // ex. 10.0.10240
std::wstring GetProductName() { return m_ProductName; }
std::wstring GetUserLanguage() { return m_UserLanguage; }

Expand All @@ -41,6 +42,7 @@ class Platform
std::wstring m_FriendlyName;
std::wstring m_DisplayVersion;
std::wstring m_BuildNumber;
std::wstring m_RawVersion;
std::wstring m_ProductName;
std::wstring m_UserLanguage;
};
Expand Down
71 changes: 71 additions & 0 deletions Common/Version.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* Copyright (C) 2023 Rainmeter Project Developers
*
* This Source Code Form is subject to the terms of the GNU General Public
* License; either version 2 of the License, or (at your option) any later
* version. If a copy of the GPL was not distributed with this file, You can
* obtain one at <https://www.gnu.org/licenses/gpl-2.0.html>. */

#ifndef RM_COMMON_VERSION_H_
#define RM_COMMON_VERSION_H_

#include <string>

namespace VersionHelper {

class Version
{
public:
Version() : m_Version(), m_IsValid(false) { }
~Version() { }

Version(std::wstring v) : m_Version(std::move(v)), m_IsValid(false) { Validate(); }
Version(const std::initializer_list<std::wstring> args) : m_Version(), m_IsValid(false) { for (const auto& arg : args) { Append(arg); } }

void Set(std::wstring v) { m_Version = std::move(v); Validate(); }
void Append(std::wstring v) { if (!m_Version.empty()) { m_Version += L'.'; } m_Version += v; Validate(); }

std::wstring Get() { return m_Version; }

bool IsValid() const { return m_IsValid; }

bool operator < (const Version& rhs) { return Compare(rhs.m_Version) == -1; }
bool operator > (const Version& rhs) { return Compare(rhs.m_Version) == +1; }
bool operator <= (const Version& rhs) { return Compare(rhs.m_Version) != +1; }
bool operator >= (const Version& rhs) { return Compare(rhs.m_Version) != -1; }
bool operator == (const Version& rhs) { return Compare(rhs.m_Version) == 0; }
bool operator != (const Version& rhs) { return Compare(rhs.m_Version) != 0; }

private:
Version(const Version& other) = delete;
Version& operator=(Version other) = delete;

void Validate() { m_IsValid = (!m_Version.empty() && m_Version.find_first_not_of(L".0123456789") == std::wstring::npos); }

int Compare(const std::wstring& version2) const
{
const size_t size1 = m_Version.size(), size2 = version2.size();
size_t i = 0ULL, j = 0ULL;

while (i < size1 || j < size2)
{
int m = 0, n = 0;

while (i < size1 && m_Version[i] != L'.') { m = (m * 10) + (m_Version[i] - L'0'); ++i; }
while (j < size1 && version2[j] != L'.') { n = (n * 10) + (version2[j] - L'0'); ++j; }

if (m < n) return -1;
if (m > n) return +1;

++i;
++j;
}
return 0;
}

std::wstring m_Version;
bool m_IsValid;
};

} // namespace VersionHelper

#endif
133 changes: 99 additions & 34 deletions Library/UpdateCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include "StdAfx.h"
#include "../Common/FileUtil.h"
#include "../Common/Platform.h"
#include "../Common/StringUtil.h"
#include "../Common/Version.h"
#include "Rainmeter.h"
#include "System.h"
#include "TrayIcon.h"
Expand Down Expand Up @@ -282,31 +284,107 @@ void Updater::CheckVersion(json& status, bool downloadNewVersion)
{
const bool debug = GetRainmeter().GetDebug();

std::string release;
if (!status["release"]["version"].empty())
std::wstring buffer;

auto getStatusValue = [&buffer](json& key) -> bool
{
buffer.clear();
if (!key.empty())
{
std::string value = key.get<std::string>();
if (!value.empty())
{
buffer = StringUtil::Widen(value);
return true;
}
}
return false;
};

// Get "release version" from the status file
if (!getStatusValue(status["release"]["version"]))
{
release = status["release"]["version"].get<std::string>();
if (debug)
{
buffer = StringUtil::Widen(status["release"].dump());
LogErrorF(L">>Status File: Parsing error: release:%s", buffer.c_str());
}
return;
}
if (release.empty())

VersionHelper::Version availableVersion = { buffer };
if (!availableVersion.IsValid())
{
if (debug) LogError(L">>Status file: Parsing \"version\" failed");
if (debug) LogErrorF(L">>Status File: Invalid \"version\": %s", buffer.c_str());
return;
}

const std::wstring tmpSz = StringUtil::Widen(release);
LPCWSTR version = tmpSz.c_str();
if (debug) LogDebugF(L" Status file version: %s", buffer.c_str());

if (debug) LogDebugF(L" Status file version: %s", version);
// Get "Rainmeter version"
VersionHelper::Version rainmeterVersion = { APPVERSION };
if (!rainmeterVersion.IsValid())
{
if (debug) LogErrorF(L">>Status File: Invalid Rainmeter version: %s", APPVERSION); // Probably never reach this
return;
}

const int availableVersion = ParseVersion(version);
if (availableVersion > RAINMETER_VERSION)
if (availableVersion > rainmeterVersion)
{
const bool isDevBuild = []()
{
if (!LOCAL_STATUS_FILE.empty()) return false;
if (!LOCAL_STATUS_FILE.empty()) return true;
return RAINMETER_VERSION == 0;
} ();

// Get "minimum Windows version" from the status file
if (!getStatusValue(status["release"]["minimum_windows"]["version"]))
{
if (debug)
{
buffer = StringUtil::Widen(status["release"]["minimum_windows"].dump());
LogErrorF(L">>Status File: Error parsing \"version\": minimum_windows:%s", buffer.c_str());
}
return;
}

VersionHelper::Version statusWinVer = { buffer };
if (!statusWinVer.IsValid())
{
if (debug) LogErrorF(L">>Status File: Invalid \"minimum_windows\" version: %s", buffer.c_str()); // Probably never reach this
return;
}

// Get the user's Windows version
VersionHelper::Version systemWinVer = { GetPlatform().GetRawVersion() };
if (!systemWinVer.IsValid())
{
if (debug) LogErrorF(L">>Status File: Invalid system version: %s", systemWinVer.Get().c_str());
return;
}

// Check if the new Rainmeter version requires a Windows version that is newer than the user's installed version
if (statusWinVer > systemWinVer)
{
if (!getStatusValue(status["release"]["minimum_windows"]["name"]))
{
if (debug)
{
buffer = StringUtil::Widen(status["release"]["minimum_windows"].dump());
LogErrorF(L">>Status File: Error parsing \"name\": minimum_windows:%s", buffer.c_str());
}
buffer = L"Windows build"; // For error message below
}

LogNoticeF(
L"* A new version of Rainmeter is available, however, your system does not meet the minimum required Windows version."
L" Rainmeter %s requires \"%s (%s)\" or higher.",
availableVersion.Get().c_str(),
buffer.c_str(),
statusWinVer.Get().c_str());
return;
}

if (!isDevBuild)
{
LogNotice(L"* New Rainmeter version available!");
Expand All @@ -325,40 +403,27 @@ void Updater::CheckVersion(json& status, bool downloadNewVersion)
GetPrivateProfileString(L"Rainmeter", L"LastCheck", L"0", tmp, _countof(tmp), dataFile);

// Show tray notification only once per new version
const int lastVersion = ParseVersion(tmp);
VersionHelper::Version lastVersion = { tmp };
if (!lastVersion.IsValid())
{
lastVersion.Set(L"0"); // "LastCheck" probably doesn't exist yet.
}

buffer = availableVersion.Get();
if (availableVersion > lastVersion)
{
WritePrivateProfileString(L"Rainmeter", L"LastCheck", version, dataFile);
WritePrivateProfileString(L"Rainmeter", L"LastCheck", buffer.c_str(), dataFile);
if (!isDevBuild && !downloadedNewVersion)
{
GetRainmeter().GetTrayIcon()->ShowUpdateNotification(version);
GetRainmeter().GetTrayIcon()->ShowUpdateNotification(buffer.c_str());
}
}

if (!isDevBuild && downloadedNewVersion)
{
GetRainmeter().GetTrayIcon()->ShowInstallUpdateNotification(version);
}
}
}

int Updater::ParseVersion(LPCWSTR str)
{
int version = _wtoi(str) * 1000000;
const WCHAR* pos = wcschr(str, L'.');
if (pos)
{
++pos; // Skip .
version += _wtoi(pos) * 1000;

pos = wcschr(pos, '.');
if (pos)
{
++pos; // Skip .
version += _wtoi(pos);
GetRainmeter().GetTrayIcon()->ShowInstallUpdateNotification(buffer.c_str());
}
}
return version;
}

bool Updater::DownloadNewVersion(json& status)
Expand Down
1 change: 0 additions & 1 deletion Library/UpdateCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class Updater
static void GetStatus(void* pParam);
static bool DownloadStatusFile(std::string& data);
static void CheckVersion(json& status, bool downloadNewVersion);
static int ParseVersion(LPCWSTR str);
static bool DownloadNewVersion(json& status);

json m_Status;
Expand Down

0 comments on commit 9b4de97

Please sign in to comment.