From 8b345d2cbd122e05cfa11dcff0220f561dfc9433 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:41:29 +0000 Subject: [PATCH] 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 --- UnleashedRecomp/CMakeLists.txt | 9 +- UnleashedRecomp/app.cpp | 2 + UnleashedRecomp/framework.h | 2 + UnleashedRecomp/main.cpp | 6 + UnleashedRecomp/os/linux/process_linux.cpp | 5 + UnleashedRecomp/os/linux/registry_linux.inl | 21 +++ UnleashedRecomp/os/process.h | 1 + UnleashedRecomp/os/registry.h | 18 +++ UnleashedRecomp/os/win32/process_win32.cpp | 5 + UnleashedRecomp/os/win32/registry_win32.inl | 153 ++++++++++++++++++++ UnleashedRecomp/user/paths.cpp | 52 +++++++ UnleashedRecomp/user/paths.h | 39 +---- UnleashedRecomp/user/registry.cpp | 14 ++ UnleashedRecomp/user/registry.h | 8 + 14 files changed, 297 insertions(+), 38 deletions(-) create mode 100644 UnleashedRecomp/os/linux/registry_linux.inl create mode 100644 UnleashedRecomp/os/registry.h create mode 100644 UnleashedRecomp/os/win32/registry_win32.inl create mode 100644 UnleashedRecomp/user/paths.cpp create mode 100644 UnleashedRecomp/user/registry.cpp create mode 100644 UnleashedRecomp/user/registry.h diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 10a6968..6bda5c8 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -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) diff --git a/UnleashedRecomp/app.cpp b/UnleashedRecomp/app.cpp index a8bff74..710aeea 100644 --- a/UnleashedRecomp/app.cpp +++ b/UnleashedRecomp/app.cpp @@ -8,6 +8,7 @@ #include #include #include +#include void App::Restart(std::vector 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); } diff --git a/UnleashedRecomp/framework.h b/UnleashedRecomp/framework.h index 9bdf1cf..db88d36 100644 --- a/UnleashedRecomp/framework.h +++ b/UnleashedRecomp/framework.h @@ -7,6 +7,8 @@ typedef returnType _##procName(__VA_ARGS__); \ _##procName* procName = (_##procName*)PROC_ADDRESS(libraryName, #procName); +#define STR(x) #x + template inline T RoundUp(const T& in_rValue, uint32_t in_round) { diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index 0ff2803..6c14d33 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -12,9 +12,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -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; diff --git a/UnleashedRecomp/os/linux/process_linux.cpp b/UnleashedRecomp/os/linux/process_linux.cpp index 20f88e4..3a9dc89 100644 --- a/UnleashedRecomp/os/linux/process_linux.cpp +++ b/UnleashedRecomp/os/linux/process_linux.cpp @@ -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& args, std::filesystem::path work) { pid_t pid = fork(); diff --git a/UnleashedRecomp/os/linux/registry_linux.inl b/UnleashedRecomp/os/linux/registry_linux.inl new file mode 100644 index 0000000..d371777 --- /dev/null +++ b/UnleashedRecomp/os/linux/registry_linux.inl @@ -0,0 +1,21 @@ +#include + +// TODO: Implement +inline bool os::registry::Init() +{ + return false; +} + +// TODO: read from file? +template +bool os::registry::ReadValue(const std::string_view& name, T& data) +{ + return false; +} + +// TODO: write to file? +template +bool os::registry::WriteValue(const std::string_view& name, const T& data) +{ + return false; +} diff --git a/UnleashedRecomp/os/process.h b/UnleashedRecomp/os/process.h index 3ddf4fc..f93ce53 100644 --- a/UnleashedRecomp/os/process.h +++ b/UnleashedRecomp/os/process.h @@ -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& args, std::filesystem::path work = {}); } diff --git a/UnleashedRecomp/os/registry.h b/UnleashedRecomp/os/registry.h new file mode 100644 index 0000000..760512e --- /dev/null +++ b/UnleashedRecomp/os/registry.h @@ -0,0 +1,18 @@ +#pragma once + +namespace os::registry +{ + bool Init(); + + template + bool ReadValue(const std::string_view& name, T& data); + + template + bool WriteValue(const std::string_view& name, const T& data); +} + +#if _WIN32 +#include +#elif defined(__linux__) +#include +#endif diff --git a/UnleashedRecomp/os/win32/process_win32.cpp b/UnleashedRecomp/os/win32/process_win32.cpp index fe7e5be..1ad6145 100644 --- a/UnleashedRecomp/os/win32/process_win32.cpp +++ b/UnleashedRecomp/os/win32/process_win32.cpp @@ -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& args, std::filesystem::path work) { if (path.empty()) diff --git a/UnleashedRecomp/os/win32/registry_win32.inl b/UnleashedRecomp/os/win32/registry_win32.inl new file mode 100644 index 0000000..32d9e9f --- /dev/null +++ b/UnleashedRecomp/os/win32/registry_win32.inl @@ -0,0 +1,153 @@ +#include +#include + +inline const wchar_t* g_registryRoot = L"Software\\UnleashedRecomp"; + +inline bool os::registry::Init() +{ + return true; +} + +template +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) + { + if (dataType == REG_SZ) + { + std::vector 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) + { + if (dataType == REG_SZ) + { + std::vector buffer{}; + buffer.reserve(bufferSize); + result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_REG_SZ, nullptr, buffer.data(), &bufferSize); + + if (result == ERROR_SUCCESS) + { + data = reinterpret_cast(buffer.data()); + } + } + } + else if constexpr (std::is_same_v) + { + result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_DWORD, nullptr, (BYTE*)&data, &bufferSize); + } + else if constexpr (std::is_same_v) + { + 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 +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) + { + pData = (BYTE*)data.c_str(); + dataSize = data.size() + 1; + dataType = REG_SZ; + } + else if constexpr (std::is_same_v) + { + pData = &data; + dataSize = sizeof(T); + dataType = REG_DWORD; + } + else if constexpr (std::is_same_v) + { + pData = &data; + dataSize = sizeof(T); + dataType = REG_QWORD; + } + else if constexpr (std::is_same_v) + { + 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; +} diff --git a/UnleashedRecomp/user/paths.cpp b/UnleashedRecomp/user/paths.cpp new file mode 100644 index 0000000..13a8588 --- /dev/null +++ b/UnleashedRecomp/user/paths.cpp @@ -0,0 +1,52 @@ +#include "paths.h" +#include + +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; +} diff --git a/UnleashedRecomp/user/paths.h b/UnleashedRecomp/user/paths.h index 003f4eb..d914213 100644 --- a/UnleashedRecomp/user/paths.h +++ b/UnleashedRecomp/user/paths.h @@ -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) { diff --git a/UnleashedRecomp/user/registry.cpp b/UnleashedRecomp/user/registry.cpp new file mode 100644 index 0000000..fa6b5a8 --- /dev/null +++ b/UnleashedRecomp/user/registry.cpp @@ -0,0 +1,14 @@ +#include "registry.h" +#include +#include +#include + +void Registry::Load() +{ + +} + +void Registry::Save() +{ + os::registry::WriteValue(STR(ExecutableFilePath), os::process::GetExecutablePath()); +} diff --git a/UnleashedRecomp/user/registry.h b/UnleashedRecomp/user/registry.h new file mode 100644 index 0000000..10867f5 --- /dev/null +++ b/UnleashedRecomp/user/registry.h @@ -0,0 +1,8 @@ +#pragma once + +class Registry +{ +public: + static void Load(); + static void Save(); +};