Implemented Windows registry read/write (#225)

* Implemented Windows registry read/write

* Simplify registry API and handle path writes

* Linux SetWorkingDirectory

* Implement reading path and unicode string from windows registry

* Use string_view value names for registry

* Use RegGetValueW

* Paths adjustments

* /

* Update working directory update failure message

* Updated linux SetWorkingDirectory

* Update flatpak define

* Remove RootDirectoryPath and save registry at startup

* dont save registry on exit

* Slight formatting update

---------

Co-authored-by: Sajid <sajidur78@gmail.com>
This commit is contained in:
Hyper 2025-01-28 11:41:29 +00:00 committed by GitHub
parent 7b9b4245de
commit 8b345d2cbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 297 additions and 38 deletions

View file

@ -178,7 +178,9 @@ set(UNLEASHED_RECOMP_INSTALL_CXX_SOURCES
set(UNLEASHED_RECOMP_USER_CXX_SOURCES
"user/achievement_data.cpp"
"user/achievement_manager.cpp"
"user/config.cpp"
"user/config.cpp"
"user/registry.cpp"
"user/paths.cpp"
)
set(UNLEASHED_RECOMP_MOD_CXX_SOURCES
@ -296,7 +298,10 @@ else()
endif()
if (UNLEASHED_RECOMP_FLATPAK)
target_compile_definitions(UnleashedRecomp PRIVATE "GAME_INSTALL_DIRECTORY=\"/var/data\"")
target_compile_definitions(UnleashedRecomp PRIVATE
"UNLEASHED_RECOMP_FLATPAK"
"GAME_INSTALL_DIRECTORY=\"/var/data\""
)
endif()
if (UNLEASHED_RECOMP_D3D12)

View file

@ -8,6 +8,7 @@
#include <ui/game_window.h>
#include <user/config.h>
#include <user/paths.h>
#include <user/registry.h>
void App::Restart(std::vector<std::string> restartArgs)
{
@ -33,6 +34,7 @@ PPC_FUNC(sub_824EB490)
App::s_isInit = true;
App::s_isMissingDLC = !Installer::checkAllDLC(GetGamePath());
App::s_language = Config::Language;
Registry::Save();
__imp__sub_824EB490(ctx, base);
}

View file

@ -7,6 +7,8 @@
typedef returnType _##procName(__VA_ARGS__); \
_##procName* procName = (_##procName*)PROC_ADDRESS(libraryName, #procName);
#define STR(x) #x
template<typename T>
inline T RoundUp(const T& in_rValue, uint32_t in_round)
{

View file

@ -12,9 +12,12 @@
#include <hid/hid.h>
#include <user/config.h>
#include <user/paths.h>
#include <user/registry.h>
#include <kernel/xdbf.h>
#include <install/installer.h>
#include <os/logger.h>
#include <os/process.h>
#include <os/registry.h>
#include <ui/installer_wizard.h>
#include <mod/mod_loader.h>
@ -144,6 +147,9 @@ int main(int argc, char *argv[])
timeBeginPeriod(1);
#endif
if (!os::registry::Init())
LOGN_WARNING("OS doesn't support registry");
os::logger::Init();
bool forceInstaller = false;

View file

@ -29,6 +29,11 @@ std::filesystem::path os::process::GetWorkingDirectory()
}
}
bool os::process::SetWorkingDirectory(const std::filesystem::path& path)
{
return chdir(path.c_str()) == 0;
}
bool os::process::StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work)
{
pid_t pid = fork();

View file

@ -0,0 +1,21 @@
#include <os/registry.h>
// TODO: Implement
inline bool os::registry::Init()
{
return false;
}
// TODO: read from file?
template<typename T>
bool os::registry::ReadValue(const std::string_view& name, T& data)
{
return false;
}
// TODO: write to file?
template<typename T>
bool os::registry::WriteValue(const std::string_view& name, const T& data)
{
return false;
}

View file

@ -4,5 +4,6 @@ namespace os::process
{
std::filesystem::path GetExecutablePath();
std::filesystem::path GetWorkingDirectory();
bool SetWorkingDirectory(const std::filesystem::path& path);
bool StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work = {});
}

View file

@ -0,0 +1,18 @@
#pragma once
namespace os::registry
{
bool Init();
template<typename T>
bool ReadValue(const std::string_view& name, T& data);
template<typename T>
bool WriteValue(const std::string_view& name, const T& data);
}
#if _WIN32
#include <os/win32/registry_win32.inl>
#elif defined(__linux__)
#include <os/linux/registry_linux.inl>
#endif

View file

@ -20,6 +20,11 @@ std::filesystem::path os::process::GetWorkingDirectory()
return std::filesystem::path(workPath);
}
bool os::process::SetWorkingDirectory(const std::filesystem::path& path)
{
return SetCurrentDirectoryW(path.c_str());
}
bool os::process::StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work)
{
if (path.empty())

View file

@ -0,0 +1,153 @@
#include <os/registry.h>
#include <unordered_map>
inline const wchar_t* g_registryRoot = L"Software\\UnleashedRecomp";
inline bool os::registry::Init()
{
return true;
}
template<typename T>
bool os::registry::ReadValue(const std::string_view& name, T& data)
{
HKEY hKey;
if (RegOpenKeyExW(HKEY_CURRENT_USER, g_registryRoot, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
wchar_t wideName[128];
int wideNameSize = MultiByteToWideChar(CP_UTF8, 0, name.data(), name.size(), wideName, sizeof(wideName));
if (wideNameSize == 0)
{
return false;
}
wideName[wideNameSize] = 0;
DWORD bufferSize = 0;
DWORD dataType = 0;
auto result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_ANY, &dataType, nullptr, &bufferSize);
if (result != ERROR_SUCCESS)
{
RegCloseKey(hKey);
return false;
}
result = ERROR_INVALID_FUNCTION;
if constexpr (std::is_same_v<T, std::string>)
{
if (dataType == REG_SZ)
{
std::vector<uint8_t> buffer{};
buffer.reserve(bufferSize);
result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_REG_SZ, nullptr, buffer.data(), &bufferSize);
if (result == ERROR_SUCCESS)
{
int valueSize = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)buffer.data(), (bufferSize / sizeof(wchar_t)) - 1, nullptr, 0, nullptr, nullptr);
data.resize(valueSize);
WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)buffer.data(), (bufferSize / sizeof(wchar_t)) - 1, data.data(), valueSize, nullptr, nullptr);
}
}
}
else if constexpr (std::is_same_v<T, std::filesystem::path>)
{
if (dataType == REG_SZ)
{
std::vector<uint8_t> buffer{};
buffer.reserve(bufferSize);
result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_REG_SZ, nullptr, buffer.data(), &bufferSize);
if (result == ERROR_SUCCESS)
{
data = reinterpret_cast<wchar_t*>(buffer.data());
}
}
}
else if constexpr (std::is_same_v<T, uint32_t>)
{
result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_DWORD, nullptr, (BYTE*)&data, &bufferSize);
}
else if constexpr (std::is_same_v<T, uint64_t>)
{
result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_QWORD, nullptr, (BYTE*)&data, &bufferSize);
}
else
{
static_assert(false, "Unsupported data type.");
}
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
}
template<typename T>
bool os::registry::WriteValue(const std::string_view& name, const T& data)
{
HKEY hKey;
if (RegCreateKeyExW(HKEY_CURRENT_USER, g_registryRoot, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
return false;
BYTE* pData = nullptr;
DWORD dataSize = 0;
DWORD dataType = 0;
bool wideString = false;
if constexpr (std::is_same_v<T, std::string>)
{
pData = (BYTE*)data.c_str();
dataSize = data.size() + 1;
dataType = REG_SZ;
}
else if constexpr (std::is_same_v<T, uint32_t>)
{
pData = &data;
dataSize = sizeof(T);
dataType = REG_DWORD;
}
else if constexpr (std::is_same_v<T, uint64_t>)
{
pData = &data;
dataSize = sizeof(T);
dataType = REG_QWORD;
}
else if constexpr (std::is_same_v<T, std::filesystem::path>)
{
pData = (BYTE*)data.c_str();
dataSize = (wcslen((const wchar_t*)pData) + 1) * sizeof(wchar_t);
dataType = REG_SZ;
wideString = true;
}
else
{
static_assert(false, "Unsupported data type.");
}
LSTATUS result = ERROR_INVALID_FUNCTION;
if (wideString)
{
wchar_t wideName[128];
int wideNameSize = MultiByteToWideChar(CP_UTF8, 0, name.data(), name.size(), wideName, sizeof(wideName));
if (wideNameSize == 0)
{
return false;
}
wideName[wideNameSize] = 0;
result = RegSetValueExW(hKey, wideName, 0, dataType, pData, dataSize);
}
else
{
result = RegSetValueExA(hKey, name.data(), 0, dataType, pData, dataSize);
}
RegCloseKey(hKey);
if (result != ERROR_SUCCESS)
return false;
return true;
}

View file

@ -0,0 +1,52 @@
#include "paths.h"
#include <os/process.h>
std::filesystem::path g_executableRoot = os::process::GetExecutablePath().remove_filename();
std::filesystem::path g_userPath = BuildUserPath();
bool CheckPortable()
{
return std::filesystem::exists(g_executableRoot / "portable.txt");
}
std::filesystem::path BuildUserPath()
{
if (CheckPortable())
return g_executableRoot;
std::filesystem::path userPath;
#if defined(_WIN32)
PWSTR knownPath = NULL;
if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK)
userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY;
CoTaskMemFree(knownPath);
#elif defined(__linux__)
const char* homeDir = getenv("HOME");
if (homeDir == nullptr)
{
homeDir = getpwuid(getuid())->pw_dir;
}
if (homeDir != nullptr)
{
// Prefer to store in the .config directory if it exists. Use the home directory otherwise.
std::filesystem::path homePath = homeDir;
std::filesystem::path configPath = homePath / ".config";
if (std::filesystem::exists(configPath))
userPath = configPath / USER_DIRECTORY;
else
userPath = homePath / ("." USER_DIRECTORY);
}
#else
static_assert(false, "GetUserPath() not implemented for this platform.");
#endif
return userPath;
}
const std::filesystem::path& GetUserPath()
{
return g_userPath;
}

View file

@ -13,42 +13,9 @@ inline std::filesystem::path GetGamePath()
return GAME_INSTALL_DIRECTORY;
}
inline std::filesystem::path GetUserPath()
{
if (std::filesystem::exists(GAME_INSTALL_DIRECTORY "/portable.txt"))
return GAME_INSTALL_DIRECTORY;
std::filesystem::path userPath;
#if defined(_WIN32)
PWSTR knownPath = NULL;
if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK)
userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY;
CoTaskMemFree(knownPath);
#elif defined(__linux__)
const char *homeDir = getenv("HOME");
if (homeDir == nullptr)
{
homeDir = getpwuid(getuid())->pw_dir;
}
if (homeDir != nullptr)
{
// Prefer to store in the .config directory if it exists. Use the home directory otherwise.
std::filesystem::path homePath = homeDir;
std::filesystem::path configPath = homePath / ".config";
if (std::filesystem::exists(configPath))
userPath = configPath / USER_DIRECTORY;
else
userPath = homePath / ("." USER_DIRECTORY);
}
#else
static_assert(false, "GetUserPath() not implemented for this platform.");
#endif
return userPath;
}
bool CheckPortable();
std::filesystem::path BuildUserPath();
const std::filesystem::path& GetUserPath();
inline std::filesystem::path GetSavePath(bool checkForMods)
{

View file

@ -0,0 +1,14 @@
#include "registry.h"
#include <os/process.h>
#include <os/registry.h>
#include <user/config.h>
void Registry::Load()
{
}
void Registry::Save()
{
os::registry::WriteValue(STR(ExecutableFilePath), os::process::GetExecutablePath());
}

View file

@ -0,0 +1,8 @@
#pragma once
class Registry
{
public:
static void Load();
static void Save();
};