diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp
index b5348bc9a0..038b8e8d81 100644
--- a/rpcs3/main.cpp
+++ b/rpcs3/main.cpp
@@ -24,6 +24,7 @@
#include "rpcs3qt/fatal_error_dialog.h"
#include "rpcs3qt/curl_handle.h"
#include "rpcs3qt/main_window.h"
+#include "rpcs3qt/uuid.h"
#include "headless_application.h"
#include "Utilities/sema.h"
@@ -869,6 +870,9 @@ int main(int argc, char** argv)
return 0;
}
+ // Log unique ID
+ gui::utils::log_uuid();
+
std::string active_user;
if (parser.isSet(arg_user_id))
diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj
index eab3c38693..f1cada13b2 100644
--- a/rpcs3/rpcs3.vcxproj
+++ b/rpcs3/rpcs3.vcxproj
@@ -654,6 +654,7 @@
+
@@ -1258,6 +1259,7 @@
.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"
+
$(QTDIR)\bin\moc.exe;%(FullPath)
diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters
index ed4efaf2a1..3f4230c5e4 100644
--- a/rpcs3/rpcs3.vcxproj.filters
+++ b/rpcs3/rpcs3.vcxproj.filters
@@ -861,6 +861,9 @@
Generated Files\Release
+
+ Gui\utils
+
@@ -1010,6 +1013,9 @@
Io\music
+
+ Gui\utils
+
diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt
index a74f3c9140..9d2a06df9c 100644
--- a/rpcs3/rpcs3qt/CMakeLists.txt
+++ b/rpcs3/rpcs3qt/CMakeLists.txt
@@ -81,6 +81,7 @@ set(SRC_FILES
update_manager.cpp
user_account.cpp
user_manager_dialog.cpp
+ uuid.cpp
vfs_dialog.cpp
vfs_dialog_path_widget.cpp
vfs_dialog_tab.cpp
diff --git a/rpcs3/rpcs3qt/uuid.cpp b/rpcs3/rpcs3qt/uuid.cpp
new file mode 100644
index 0000000000..0ade0218d3
--- /dev/null
+++ b/rpcs3/rpcs3qt/uuid.cpp
@@ -0,0 +1,131 @@
+#include "stdafx.h"
+#include "uuid.h"
+#include "Utilities/StrUtil.h"
+
+#include
+#include
+
+LOG_CHANNEL(uuid_log, "UUID");
+
+namespace gui
+{
+ namespace utils
+ {
+ std::string get_uuid_path()
+ {
+#ifdef _WIN32
+ const std::string config_dir = fs::get_config_dir() + "config/";
+ const std::string uuid_path = config_dir + "uuid";
+
+ if (!fs::create_path(config_dir))
+ {
+ uuid_log.error("Could not create path: %s (%s)", uuid_path, fs::g_tls_error);
+ }
+
+ return uuid_path;
+#else
+ return fs::get_config_dir() + "uuid";
+#endif
+ }
+
+ std::string make_uuid()
+ {
+ return QUuid::createUuid().toString().toStdString();
+ }
+
+ std::string load_uuid()
+ {
+ const std::string uuid_path = get_uuid_path();
+
+ if (!fs::is_file(uuid_path))
+ {
+ uuid_log.notice("File does not exist: %s (%s)", uuid_path, fs::g_tls_error);
+ return {};
+ }
+
+ if (fs::file uuid_file = fs::file(uuid_path); uuid_file)
+ {
+ const std::string uuid = fmt::trim(uuid_file.to_string());
+
+ if (!validate_uuid(uuid))
+ {
+ uuid_log.error("Invalid uuid '%s' found in file: %s", uuid, uuid_path);
+ return {};
+ }
+
+ return uuid;
+ }
+
+ uuid_log.error("Could not open file: %s (%s)", uuid_path, fs::g_tls_error);
+ return {};
+ }
+
+ bool validate_uuid(const std::string& uuid)
+ {
+ const QRegularExpressionValidator validator(QRegularExpression("^[a-fA-F0-9{}-]*$"));
+ QString test_string = QString::fromStdString(uuid);
+ int pos = 0;
+
+ if (uuid.empty() || !uuid.starts_with("{") || !uuid.ends_with("}") || validator.validate(test_string, pos) == QValidator::State::Invalid)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool save_uuid(const std::string& uuid)
+ {
+ if (!validate_uuid(uuid))
+ {
+ uuid_log.error("Can not save invalid uuid '%s'", uuid);
+ return false;
+ }
+
+ const std::string uuid_path = get_uuid_path();
+
+ if (fs::file uuid_file(uuid_path, fs::rewrite); !uuid_file || !uuid_file.write(uuid))
+ {
+ uuid_log.error("Could not write file: %s (%s)", uuid_path, fs::g_tls_error);
+ return false;
+ }
+
+ uuid_log.notice("Wrote to file: %s", uuid_path);
+ return true;
+ }
+
+ bool create_new_uuid(std::string& uuid)
+ {
+ uuid = make_uuid();
+
+ if (uuid.empty())
+ {
+ uuid_log.error("Empty uuid");
+ return false;
+ }
+
+ if (!save_uuid(uuid))
+ {
+ uuid_log.error("Failed to save uuid");
+ return false;
+ }
+
+ return true;
+ }
+
+ void log_uuid()
+ {
+ std::string uuid = load_uuid();
+
+ if (uuid.empty())
+ {
+ if (!create_new_uuid(uuid))
+ {
+ return;
+ }
+ }
+
+ uuid_log.notice("Installation ID: %s", uuid);
+ }
+ }
+}
diff --git a/rpcs3/rpcs3qt/uuid.h b/rpcs3/rpcs3qt/uuid.h
new file mode 100644
index 0000000000..b12b605943
--- /dev/null
+++ b/rpcs3/rpcs3qt/uuid.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace gui
+{
+ namespace utils
+ {
+ std::string get_uuid_path();
+ std::string make_uuid();
+ std::string load_uuid();
+
+ bool validate_uuid(const std::string& uuid);
+ bool save_uuid(const std::string& uuid);
+ bool create_new_uuid(std::string& uuid);
+
+ void log_uuid();
+ }
+}