Merge branch 'registry' into 'master'

Detect Steam installations on Windows and Linux

See merge request OpenMW/openmw!4885
This commit is contained in:
Evil Eye 2025-09-21 05:34:10 +00:00
commit 04e566ce92
13 changed files with 151 additions and 166 deletions

View File

@ -71,11 +71,9 @@ Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* pa
setupInstallations();
setupPages();
const std::filesystem::path& installationPath = mCfgMgr.getInstallPath();
if (!installationPath.empty())
for (std::filesystem::path installationPath : mCfgMgr.getInstallPaths())
{
const std::filesystem::path& dataPath = installationPath / "Data Files";
addInstallation(Files::pathToQString(dataPath));
addInstallation(Files::pathToQString(installationPath / "Data Files"));
}
}

View File

@ -66,9 +66,9 @@ namespace Files
return std::filesystem::path(g_path_user);
}
std::filesystem::path AndroidPath::getInstallPath() const
std::vector<std::filesystem::path> AndroidPath::getInstallPaths() const
{
return std::filesystem::path();
return {};
}
} /* namespace Files */

View File

@ -4,6 +4,7 @@
#if defined(__ANDROID__)
#include <filesystem>
#include <vector>
/**
* \namespace Files
*/
@ -43,7 +44,7 @@ namespace Files
*/
std::filesystem::path getCachePath() const;
std::filesystem::path getInstallPath() const;
std::vector<std::filesystem::path> getInstallPaths() const;
};
} /* namespace Files */

View File

@ -419,9 +419,9 @@ namespace Files
return mFixedPath.getCachePath();
}
const std::filesystem::path& ConfigurationManager::getInstallPath() const
std::vector<std::filesystem::path> ConfigurationManager::getInstallPaths() const
{
return mFixedPath.getInstallPath();
return mFixedPath.getInstallPaths();
}
const std::filesystem::path& ConfigurationManager::getScreenshotPath() const

View File

@ -48,7 +48,7 @@ namespace Files
const std::filesystem::path& getUserConfigPath() const;
const std::filesystem::path& getUserDataPath() const;
const std::filesystem::path& getInstallPath() const;
std::vector<std::filesystem::path> getInstallPaths() const;
const std::vector<std::filesystem::path>& getActiveConfigPaths() const { return mActiveConfigPaths; }
const std::filesystem::path& getCachePath() const;

View File

@ -3,6 +3,7 @@
#include <filesystem>
#include <string>
#include <vector>
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__)
#ifndef ANDROID
@ -66,7 +67,6 @@ namespace Files
, mLocalPath(mPath.getLocalPath())
, mGlobalDataPath(mPath.getGlobalDataPath())
, mCachePath(mPath.getCachePath())
, mInstallPath(mPath.getInstallPath())
{
}
@ -87,7 +87,7 @@ namespace Files
*/
const std::filesystem::path& getLocalPath() const { return mLocalPath; }
const std::filesystem::path& getInstallPath() const { return mInstallPath; }
std::vector<std::filesystem::path> getInstallPaths() const { return mPath.getInstallPaths(); }
const std::filesystem::path& getGlobalDataPath() const { return mGlobalDataPath; }
@ -104,8 +104,6 @@ namespace Files
std::filesystem::path mGlobalDataPath; /**< Global application data path */
std::filesystem::path mCachePath;
std::filesystem::path mInstallPath;
};
} /* namespace Files */

View File

@ -2,13 +2,15 @@
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__)
#include <array>
#include <cstring>
#include <fstream>
#include <pwd.h>
#include <unistd.h>
#include <components/misc/strings/lower.hpp>
#include "wine.hpp"
namespace
{
std::filesystem::path getUserHome()
@ -100,75 +102,30 @@ namespace Files
return globalDataPath / mName;
}
std::filesystem::path LinuxPath::getInstallPath() const
std::vector<std::filesystem::path> LinuxPath::getInstallPaths() const
{
std::filesystem::path installPath;
std::vector<std::filesystem::path> paths;
std::filesystem::path homePath = getUserHome();
if (!homePath.empty())
{
std::filesystem::path wineDefaultRegistry(homePath);
wineDefaultRegistry /= ".wine/system.reg";
if (std::filesystem::is_regular_file(wineDefaultRegistry))
std::filesystem::path wine = Wine::getInstallPath(homePath);
if (!wine.empty())
paths.emplace_back(std::move(wine));
std::array steamPaths{
// Default (~/.steam/steam can be a symlink or a real directory)
homePath / ".steam/steam/steamapps/common/Morrowind",
// Snap
homePath / "snap/steam/common/.local/share/Steam/steamapps/common/Morrowind",
// Flatpak
homePath / ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/Morrowind",
};
for (std::filesystem::path steam : steamPaths)
{
std::ifstream file(wineDefaultRegistry);
bool isRegEntry = false;
std::string line;
std::string mwpath;
while (std::getline(file, line))
{
if (line[0] == '[') // we found an entry
{
if (isRegEntry)
{
break;
}
isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos);
}
else if (isRegEntry)
{
if (line[0] == '"') // empty line means new registry key
{
std::string key = line.substr(1, line.find('"', 1) - 1);
if (strcasecmp(key.c_str(), "Installed Path") == 0)
{
std::string::size_type valuePos = line.find('=') + 2;
mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
std::string::size_type pos = mwpath.find("\\");
while (pos != std::string::npos)
{
mwpath.replace(pos, 2, "/");
pos = mwpath.find("\\", pos + 1);
}
break;
}
}
}
}
if (!mwpath.empty())
{
// Change drive letter to lowercase, so we could use
// ~/.wine/dosdevices symlinks
mwpath[0] = Misc::StringUtils::toLower(mwpath[0]);
installPath /= homePath;
installPath /= ".wine/dosdevices/";
installPath /= mwpath;
if (!std::filesystem::is_directory(installPath))
{
installPath.clear();
}
}
if (std::filesystem::is_directory(steam))
paths.emplace_back(std::move(steam));
}
}
return installPath;
return paths;
}
} /* namespace Files */

View File

@ -4,6 +4,7 @@
#if defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__)
#include <filesystem>
#include <vector>
/**
* \namespace Files
@ -47,9 +48,9 @@ namespace Files
std::filesystem::path getCachePath() const;
/**
* \brief Gets the path of the installed Morrowind version if there is one.
* \brief Gets the paths of any installed Morrowind versions we can find.
*/
std::filesystem::path getInstallPath() const;
std::vector<std::filesystem::path> getInstallPaths() const;
std::string mName;
};

View File

@ -4,14 +4,14 @@
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <mach-o/dyld.h>
#include <pwd.h>
#include <unistd.h>
#include <vector>
#include <components/debug/debuglog.hpp>
#include <components/misc/strings/lower.hpp>
#include "wine.hpp"
namespace
{
@ -106,74 +106,17 @@ namespace Files
return globalDataPath / mName;
}
std::filesystem::path MacOsPath::getInstallPath() const
std::vector<std::filesystem::path> MacOsPath::getInstallPaths() const
{
std::filesystem::path installPath;
std::vector<std::filesystem::path> paths;
std::filesystem::path homePath = getUserHome();
if (!homePath.empty())
{
std::filesystem::path wineDefaultRegistry(homePath);
wineDefaultRegistry /= ".wine/system.reg";
if (std::filesystem::is_regular_file(wineDefaultRegistry))
{
std::ifstream file(wineDefaultRegistry);
bool isRegEntry = false;
std::string line;
std::string mwpath;
while (std::getline(file, line))
{
if (line[0] == '[') // we found an entry
{
if (isRegEntry)
{
break;
}
isRegEntry = (line.find("Softworks\\\\Morrowind]") != std::string::npos);
}
else if (isRegEntry)
{
if (line[0] == '"') // empty line means new registry key
{
std::string key = line.substr(1, line.find('"', 1) - 1);
if (strcasecmp(key.c_str(), "Installed Path") == 0)
{
std::string::size_type valuePos = line.find('=') + 2;
mwpath = line.substr(valuePos, line.rfind('"') - valuePos);
std::string::size_type pos = mwpath.find("\\");
while (pos != std::string::npos)
{
mwpath.replace(pos, 2, "/");
pos = mwpath.find("\\", pos + 1);
}
break;
}
}
}
}
if (!mwpath.empty())
{
// Change drive letter to lowercase, so we could use ~/.wine/dosdevice symlinks
mwpath[0] = Misc::StringUtils::toLower(mwpath[0]);
installPath /= homePath;
installPath /= ".wine/dosdevices/";
installPath /= mwpath;
if (!std::filesystem::is_directory(installPath))
{
installPath.clear();
}
}
}
std::filesystem::path wine = Wine::getInstallPath(homePath);
if (!wine.empty())
paths.emplace_back(std::move(wine));
}
return installPath;
return paths;
}
} /* namespace Files */

View File

@ -4,6 +4,7 @@
#if defined(macintosh) || defined(Macintosh) || defined(__APPLE__) || defined(__MACH__)
#include <filesystem>
#include <vector>
/**
* \namespace Files
@ -56,7 +57,7 @@ namespace Files
*/
std::filesystem::path getGlobalDataPath() const;
std::filesystem::path getInstallPath() const;
std::vector<std::filesystem::path> getInstallPaths() const;
std::string mName;
};

View File

@ -22,6 +22,40 @@
*/
namespace Files
{
namespace
{
struct RegistryKey
{
HKEY mKey = nullptr;
~RegistryKey()
{
if (mKey)
RegCloseKey(mKey);
}
};
std::filesystem::path getRegistryPath(LPCWSTR subKey, LPCWSTR valueName, bool use32)
{
RegistryKey key;
REGSAM flags = KEY_READ;
if (use32)
flags |= KEY_WOW64_32KEY;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, subKey, 0, flags, &key.mKey) == ERROR_SUCCESS)
{
// Key existed, let's try to read the install dir
std::array<wchar_t, 512> buf{};
DWORD len = static_cast<DWORD>(buf.size() * sizeof(wchar_t));
if (RegQueryValueExW(key.mKey, valueName, nullptr, nullptr, reinterpret_cast<LPBYTE>(buf.data()), &len)
== ERROR_SUCCESS)
{
return std::filesystem::path(buf.data(), buf.data() + len);
}
}
return {};
}
}
WindowsPath::WindowsPath(const std::string& application_name)
: mName(application_name)
@ -82,27 +116,22 @@ namespace Files
return getUserConfigPath() / "cache";
}
std::filesystem::path WindowsPath::getInstallPath() const
std::vector<std::filesystem::path> WindowsPath::getInstallPaths() const
{
std::filesystem::path installPath{};
if (HKEY hKey; RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Bethesda Softworks\\Morrowind", 0,
KEY_READ | KEY_WOW64_32KEY, &hKey)
== ERROR_SUCCESS)
std::vector<std::filesystem::path> paths;
{
// Key existed, let's try to read the install dir
std::array<wchar_t, 512> buf{};
DWORD len = static_cast<DWORD>(buf.size() * sizeof(wchar_t));
if (RegQueryValueExW(hKey, L"Installed Path", nullptr, nullptr, reinterpret_cast<LPBYTE>(buf.data()), &len)
== ERROR_SUCCESS)
{
installPath = std::filesystem::path(buf.data());
}
RegCloseKey(hKey);
std::filesystem::path disk
= getRegistryPath(L"SOFTWARE\\Bethesda Softworks\\Morrowind", L"Installed Path", true);
if (!disk.empty() && std::filesystem::is_directory(disk))
paths.emplace_back(std::move(disk));
}
return installPath;
{
std::filesystem::path steam = getRegistryPath(
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 22320", L"InstallLocation", false);
if (!steam.empty() && std::filesystem::is_directory(steam))
paths.emplace_back(std::move(steam));
}
return paths;
}
} /* namespace Files */

View File

@ -4,6 +4,7 @@
#if defined(_WIN32) || defined(__WINDOWS__)
#include <filesystem>
#include <vector>
/**
* \namespace Files
@ -63,11 +64,11 @@ namespace Files
std::filesystem::path getGlobalDataPath() const;
/**
* \brief Gets the path of the installed Morrowind version if there is one.
* \brief Gets the paths of any installed Morrowind versions we can find.
*
* \return std::filesystem::path
* \return std::vector<std::filesystem::path>
*/
std::filesystem::path getInstallPath() const;
std::vector<std::filesystem::path> getInstallPaths() const;
std::string mName;
};

56
components/files/wine.hpp Normal file
View File

@ -0,0 +1,56 @@
#ifndef COMPONENTS_FILES_WINE_HPP
#define COMPONENTS_FILES_WINE_HPP
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <components/misc/strings/algorithm.hpp>
namespace Files::Wine
{
std::filesystem::path getInstallPath(const std::filesystem::path& homePath)
{
std::filesystem::path wineDefaultRegistry = homePath / ".wine/system.reg";
if (!std::filesystem::is_regular_file(wineDefaultRegistry))
return {};
constexpr std::string_view keyStart = "\"Installed Path\"=\"";
std::ifstream file(wineDefaultRegistry);
bool isRegEntry = false;
std::string line;
while (std::getline(file, line))
{
if (line.empty())
continue;
if (line[0] == '[') // we found an entry
{
if (isRegEntry)
{
break;
}
isRegEntry = line.find("Softworks\\\\Morrowind]") != std::string::npos;
}
else if (isRegEntry && Misc::StringUtils::ciStartsWith(line, keyStart))
{
std::string mwpath = line.substr(keyStart.size(), line.rfind('"') - keyStart.size());
if (mwpath.empty())
break;
std::transform(
mwpath.begin(), mwpath.end(), mwpath.begin(), [](char c) { return c == '\\' ? '/' : c; });
// Change drive letter to lowercase, so we could use ~/.wine/dosdevices symlinks
mwpath[0] = Misc::StringUtils::toLower(mwpath[0]);
std::filesystem::path installPath = homePath / ".wine/dosdevices/" / mwpath;
if (std::filesystem::is_directory(installPath))
return installPath;
}
}
return {};
}
}
#endif /* COMPONENTS_FILES_WINE_HPP */