summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/yuzu/CMakeLists.txt4
-rw-r--r--src/yuzu/game_list.cpp8
-rw-r--r--src/yuzu/game_list.h5
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/game_list_worker.cpp25
-rw-r--r--src/yuzu/game_list_worker.h6
-rw-r--r--src/yuzu/main.cpp11
-rw-r--r--src/yuzu/play_time.cpp177
-rw-r--r--src/yuzu/play_time.h68
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
11 files changed, 259 insertions, 270 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 89763f64f..9ebece907 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -195,8 +195,8 @@ add_executable(yuzu
195 multiplayer/state.cpp 195 multiplayer/state.cpp
196 multiplayer/state.h 196 multiplayer/state.h
197 multiplayer/validation.h 197 multiplayer/validation.h
198 play_time.cpp 198 play_time_manager.cpp
199 play_time.h 199 play_time_manager.h
200 precompiled_headers.h 200 precompiled_headers.h
201 qt_common.cpp 201 qt_common.cpp
202 qt_common.h 202 qt_common.h
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 98e410e0f..0b7415526 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
312} 312}
313 313
314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, 314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
315 Core::System& system_, GMainWindow* parent) 315 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
316 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { 316 GMainWindow* parent)
317 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
318 play_time_manager{play_time_manager_}, system{system_} {
317 watcher = new QFileSystemWatcher(this); 319 watcher = new QFileSystemWatcher(this);
318 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); 320 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
319 321
@@ -826,7 +828,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
826 emit ShouldCancelWorker(); 828 emit ShouldCancelWorker();
827 829
828 GameListWorker* worker = 830 GameListWorker* worker =
829 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 831 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
830 832
831 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 833 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
832 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, 834 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index cde6f1e1f..6e8382c0f 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
18#include "core/core.h" 18#include "core/core.h"
19#include "uisettings.h" 19#include "uisettings.h"
20#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
21#include "yuzu/play_time_manager.h"
21 22
22namespace Core { 23namespace Core {
23class System; 24class System;
@@ -79,7 +80,8 @@ public:
79 }; 80 };
80 81
81 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 82 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
82 FileSys::ManualContentProvider* provider_, Core::System& system_, 83 FileSys::ManualContentProvider* provider_,
84 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
83 GMainWindow* parent = nullptr); 85 GMainWindow* parent = nullptr);
84 ~GameList() override; 86 ~GameList() override;
85 87
@@ -168,6 +170,7 @@ private:
168 170
169 friend class GameListSearchField; 171 friend class GameListSearchField;
170 172
173 const PlayTime::PlayTimeManager& play_time_manager;
171 Core::System& system; 174 Core::System& system;
172}; 175};
173 176
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 33a929aae..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,7 +18,7 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/logging/log.h" 19#include "common/logging/log.h"
20#include "common/string_util.h" 20#include "common/string_util.h"
21#include "yuzu/play_time.h" 21#include "yuzu/play_time_manager.h"
22#include "yuzu/uisettings.h" 22#include "yuzu/uisettings.h"
23#include "yuzu/util/util.h" 23#include "yuzu/util/util.h"
24 24
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index b15ed730e..588f1dd6e 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -27,7 +27,6 @@
27#include "yuzu/game_list.h" 27#include "yuzu/game_list.h"
28#include "yuzu/game_list_p.h" 28#include "yuzu/game_list_p.h"
29#include "yuzu/game_list_worker.h" 29#include "yuzu/game_list_worker.h"
30#include "yuzu/play_time.h"
31#include "yuzu/uisettings.h" 30#include "yuzu/uisettings.h"
32 31
33namespace { 32namespace {
@@ -195,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
195 const std::size_t size, const std::vector<u8>& icon, 194 const std::size_t size, const std::vector<u8>& icon,
196 Loader::AppLoader& loader, u64 program_id, 195 Loader::AppLoader& loader, u64 program_id,
197 const CompatibilityList& compatibility_list, 196 const CompatibilityList& compatibility_list,
197 const PlayTime::PlayTimeManager& play_time_manager,
198 const FileSys::PatchManager& patch) { 198 const FileSys::PatchManager& patch) {
199 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 199 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
200 200
@@ -213,7 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
213 new GameListItemCompat(compatibility), 213 new GameListItemCompat(compatibility),
214 new GameListItem(file_type_string), 214 new GameListItem(file_type_string),
215 new GameListItemSize(size), 215 new GameListItemSize(size),
216 new GameListItemPlayTime(PlayTime::GetPlayTime(program_id)), 216 new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
217 }; 217 };
218 218
219 const auto patch_versions = GetGameListCachedObject( 219 const auto patch_versions = GetGameListCachedObject(
@@ -229,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
229GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, 229GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
230 FileSys::ManualContentProvider* provider_, 230 FileSys::ManualContentProvider* provider_,
231 QVector<UISettings::GameDir>& game_dirs_, 231 QVector<UISettings::GameDir>& game_dirs_,
232 const CompatibilityList& compatibility_list_, Core::System& system_) 232 const CompatibilityList& compatibility_list_,
233 const PlayTime::PlayTimeManager& play_time_manager_,
234 Core::System& system_)
233 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, 235 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
234 compatibility_list{compatibility_list_}, system{system_} {} 236 compatibility_list{compatibility_list_},
237 play_time_manager{play_time_manager_}, system{system_} {}
235 238
236GameListWorker::~GameListWorker() = default; 239GameListWorker::~GameListWorker() = default;
237 240
@@ -282,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
282 } 285 }
283 286
284 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, 287 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
285 program_id, compatibility_list, patch), 288 program_id, compatibility_list, play_time_manager, patch),
286 parent_dir); 289 parent_dir);
287 } 290 }
288} 291}
@@ -359,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
359 362
360 emit EntryReady(MakeGameListEntry(physical_name, name, 363 emit EntryReady(MakeGameListEntry(physical_name, name,
361 Common::FS::GetSize(physical_name), icon, 364 Common::FS::GetSize(physical_name), icon,
362 *loader, id, compatibility_list, patch), 365 *loader, id, compatibility_list,
366 play_time_manager, patch),
363 parent_dir); 367 parent_dir);
364 } 368 }
365 } else { 369 } else {
@@ -372,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
372 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 376 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
373 system.GetContentProvider()}; 377 system.GetContentProvider()};
374 378
375 emit EntryReady( 379 emit EntryReady(MakeGameListEntry(physical_name, name,
376 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), 380 Common::FS::GetSize(physical_name), icon,
377 icon, *loader, program_id, compatibility_list, patch), 381 *loader, program_id, compatibility_list,
378 parent_dir); 382 play_time_manager, patch),
383 parent_dir);
379 } 384 }
380 } 385 }
381 } else if (is_dir) { 386 } else if (is_dir) {
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..2bb0a0cb6 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -13,6 +13,7 @@
13#include <QString> 13#include <QString>
14 14
15#include "yuzu/compatibility_list.h" 15#include "yuzu/compatibility_list.h"
16#include "yuzu/play_time_manager.h"
16 17
17namespace Core { 18namespace Core {
18class System; 19class System;
@@ -36,7 +37,9 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 37 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 38 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 39 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 40 const CompatibilityList& compatibility_list_,
41 const PlayTime::PlayTimeManager& play_time_manager_,
42 Core::System& system_);
40 ~GameListWorker() override; 43 ~GameListWorker() override;
41 44
42 /// Starts the processing of directory tree information. 45 /// Starts the processing of directory tree information.
@@ -76,6 +79,7 @@ private:
76 FileSys::ManualContentProvider* provider; 79 FileSys::ManualContentProvider* provider;
77 QVector<UISettings::GameDir>& game_dirs; 80 QVector<UISettings::GameDir>& game_dirs;
78 const CompatibilityList& compatibility_list; 81 const CompatibilityList& compatibility_list;
82 const PlayTime::PlayTimeManager& play_time_manager;
79 83
80 QStringList watch_list; 84 QStringList watch_list;
81 std::atomic_bool stop_processing; 85 std::atomic_bool stop_processing;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 53ab7ada9..bfa4787e1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -146,7 +146,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
146#include "yuzu/install_dialog.h" 146#include "yuzu/install_dialog.h"
147#include "yuzu/loading_screen.h" 147#include "yuzu/loading_screen.h"
148#include "yuzu/main.h" 148#include "yuzu/main.h"
149#include "yuzu/play_time.h" 149#include "yuzu/play_time_manager.h"
150#include "yuzu/startup_checks.h" 150#include "yuzu/startup_checks.h"
151#include "yuzu/uisettings.h" 151#include "yuzu/uisettings.h"
152#include "yuzu/util/clickable_label.h" 152#include "yuzu/util/clickable_label.h"
@@ -980,7 +980,7 @@ void GMainWindow::InitializeWidgets() {
980 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); 980 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
981 render_window->hide(); 981 render_window->hide();
982 982
983 game_list = new GameList(vfs, provider.get(), *system, this); 983 game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
984 ui->horizontalLayout->addWidget(game_list); 984 ui->horizontalLayout->addWidget(game_list);
985 985
986 game_list_placeholder = new GameListPlaceholder(this); 986 game_list_placeholder = new GameListPlaceholder(this);
@@ -2469,11 +2469,8 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
2469 QMessageBox::No) != QMessageBox::Yes) { 2469 QMessageBox::No) != QMessageBox::Yes) {
2470 return; 2470 return;
2471 } 2471 }
2472 if (!play_time_manager->ResetProgramPlayTime(program_id)) { 2472
2473 QMessageBox::warning(this, tr("Error Resetting Play Time Data"), 2473 play_time_manager->ResetProgramPlayTime(program_id);
2474 tr("Play time couldn't be cleared"));
2475 return;
2476 }
2477 game_list->PopulateAsync(UISettings::values.game_dirs); 2474 game_list->PopulateAsync(UISettings::values.game_dirs);
2478} 2475}
2479 2476
diff --git a/src/yuzu/play_time.cpp b/src/yuzu/play_time.cpp
deleted file mode 100644
index 6be0327b2..000000000
--- a/src/yuzu/play_time.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/fs/file.h"
5#include "common/fs/path_util.h"
6#include "common/logging/log.h"
7#include "common/settings.h"
8#include "common/thread.h"
9#include "core/hle/service/acc/profile_manager.h"
10
11#include "yuzu/play_time.h"
12
13namespace PlayTime {
14
15void PlayTimeManager::SetProgramId(u64 program_id) {
16 this->running_program_id = program_id;
17}
18
19inline void PlayTimeManager::UpdateTimestamp() {
20 this->last_timestamp = std::chrono::steady_clock::now();
21}
22
23void PlayTimeManager::Start() {
24 UpdateTimestamp();
25 play_time_thread =
26 std::jthread([&](std::stop_token stop_token) { this->AutoTimestamp(stop_token); });
27}
28
29void PlayTimeManager::Stop() {
30 play_time_thread.request_stop();
31}
32
33void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
34 Common::SetCurrentThreadName("PlayTimeReport");
35
36 using namespace std::literals::chrono_literals;
37
38 const auto duration = 30s;
39 while (Common::StoppableTimedWait(stop_token, duration)) {
40 Save();
41 }
42
43 Save();
44}
45
46void PlayTimeManager::Save() {
47 const auto now = std::chrono::steady_clock::now();
48 const auto duration =
49 static_cast<u64>(std::chrono::duration_cast<std::chrono::seconds>(
50 std::chrono::steady_clock::duration(now - this->last_timestamp))
51 .count());
52 UpdateTimestamp();
53 if (!UpdatePlayTime(running_program_id, duration)) {
54 LOG_ERROR(Common, "Failed to update play time");
55 }
56}
57
58bool UpdatePlayTime(u64 program_id, u64 add_play_time) {
59 std::vector<PlayTimeElement> play_time_elements;
60 if (!ReadPlayTimeFile(play_time_elements)) {
61 return false;
62 }
63 const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id);
64
65 if (it == play_time_elements.end()) {
66 play_time_elements.push_back({.program_id = program_id, .play_time = add_play_time});
67 } else {
68 play_time_elements.at(it - play_time_elements.begin()).play_time += add_play_time;
69 }
70 if (!WritePlayTimeFile(play_time_elements)) {
71 return false;
72 }
73 return true;
74}
75
76u64 GetPlayTime(u64 program_id) {
77 std::vector<PlayTimeElement> play_time_elements;
78
79 if (!ReadPlayTimeFile(play_time_elements)) {
80 return 0;
81 }
82 const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id);
83 if (it == play_time_elements.end()) {
84 return 0;
85 }
86 return play_time_elements.at(it - play_time_elements.begin()).play_time;
87}
88
89bool PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
90 std::vector<PlayTimeElement> play_time_elements;
91
92 if (!ReadPlayTimeFile(play_time_elements)) {
93 return false;
94 }
95 const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id);
96 if (it == play_time_elements.end()) {
97 return false;
98 }
99 play_time_elements.erase(it);
100 if (!WritePlayTimeFile(play_time_elements)) {
101 return false;
102 }
103 return true;
104}
105
106std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
107 const Service::Account::ProfileManager manager;
108 const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
109 if (!uuid.has_value()) {
110 return std::nullopt;
111 }
112 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
113 uuid->RawString().append(".bin");
114}
115
116[[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements) {
117 const auto filename = GetCurrentUserPlayTimePath();
118 if (!filename.has_value()) {
119 LOG_ERROR(Common, "Failed to get current user path");
120 return false;
121 }
122
123 if (Common::FS::Exists(filename.value())) {
124 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
125 Common::FS::FileType::BinaryFile};
126 if (!file.IsOpen()) {
127 LOG_ERROR(Common, "Failed to open play time file: {}",
128 Common::FS::PathToUTF8String(filename.value()));
129 return false;
130 }
131 const size_t elem_num = file.GetSize() / sizeof(PlayTimeElement);
132 out_play_time_elements.resize(elem_num);
133 const bool success = file.ReadSpan<PlayTimeElement>(out_play_time_elements) == elem_num;
134 file.Close();
135 return success;
136 } else {
137 out_play_time_elements.clear();
138 return true;
139 }
140}
141
142[[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements) {
143 const auto filename = GetCurrentUserPlayTimePath();
144 if (!filename.has_value()) {
145 LOG_ERROR(Common, "Failed to get current user path");
146 return false;
147 }
148 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
149 Common::FS::FileType::BinaryFile};
150
151 if (!file.IsOpen()) {
152 LOG_ERROR(Common, "Failed to open play time file: {}",
153 Common::FS::PathToUTF8String(filename.value()));
154 return false;
155 }
156 const bool success =
157 file.WriteSpan<PlayTimeElement>(play_time_elements) == play_time_elements.size();
158 file.Close();
159 return success;
160}
161
162QString ReadablePlayTime(qulonglong time_seconds) {
163 static constexpr std::array units{"m", "h"};
164 if (time_seconds == 0) {
165 return QLatin1String("");
166 }
167 const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
168 const auto time_hours = static_cast<double>(time_seconds) / 3600;
169 const int unit = time_minutes < 60 ? 0 : 1;
170 const auto value = unit == 0 ? time_minutes : time_hours;
171
172 return QStringLiteral("%L1 %2")
173 .arg(value, 0, 'f', unit && time_seconds % 60 != 0)
174 .arg(QString::fromUtf8(units[unit]));
175}
176
177} // namespace PlayTime
diff --git a/src/yuzu/play_time.h b/src/yuzu/play_time.h
deleted file mode 100644
index 68e40955c..000000000
--- a/src/yuzu/play_time.h
+++ /dev/null
@@ -1,68 +0,0 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QString>
7
8#include <atomic>
9#include <condition_variable>
10#include <mutex>
11#include <optional>
12#include <thread>
13
14#include "common/common_types.h"
15#include "common/fs/fs.h"
16#include "common/polyfill_thread.h"
17#include "core/core.h"
18
19namespace PlayTime {
20struct PlayTimeElement {
21 u64 program_id;
22 u64 play_time;
23
24 inline bool operator==(const PlayTimeElement& other) const {
25 return program_id == other.program_id;
26 }
27
28 inline bool operator==(const u64 _program_id) const {
29 return program_id == _program_id;
30 }
31};
32
33class PlayTimeManager {
34public:
35 explicit PlayTimeManager() = default;
36 ~PlayTimeManager() = default;
37
38public:
39 YUZU_NON_COPYABLE(PlayTimeManager);
40 YUZU_NON_MOVEABLE(PlayTimeManager);
41
42public:
43 bool ResetProgramPlayTime(u64 program_id);
44 void SetProgramId(u64 program_id);
45 inline void UpdateTimestamp();
46 void Start();
47 void Stop();
48
49private:
50 u64 running_program_id;
51 std::chrono::steady_clock::time_point last_timestamp;
52 std::jthread play_time_thread;
53 void AutoTimestamp(std::stop_token stop_token);
54 void Save();
55};
56
57std::optional<std::filesystem::path> GetCurrentUserPlayTimePath();
58
59bool UpdatePlayTime(u64 program_id, u64 add_play_time);
60
61[[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements);
62[[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements);
63
64u64 GetPlayTime(u64 program_id);
65
66QString ReadablePlayTime(qulonglong time_seconds);
67
68} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/settings.h"
10#include "common/thread.h"
11#include "core/hle/service/acc/profile_manager.h"
12#include "yuzu/play_time_manager.h"
13
14namespace PlayTime {
15
16namespace {
17
18struct PlayTimeElement {
19 ProgramId program_id;
20 PlayTime play_time;
21};
22
23std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
24 const Service::Account::ProfileManager manager;
25 const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
26 if (!uuid.has_value()) {
27 return std::nullopt;
28 }
29 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
30 uuid->RawString().append(".bin");
31}
32
33[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
34 const auto filename = GetCurrentUserPlayTimePath();
35
36 if (!filename.has_value()) {
37 LOG_ERROR(Frontend, "Failed to get current user path");
38 return false;
39 }
40
41 out_play_time_db.clear();
42
43 if (Common::FS::Exists(filename.value())) {
44 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
45 Common::FS::FileType::BinaryFile};
46 if (!file.IsOpen()) {
47 LOG_ERROR(Frontend, "Failed to open play time file: {}",
48 Common::FS::PathToUTF8String(filename.value()));
49 return false;
50 }
51
52 const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
53 std::vector<PlayTimeElement> elements(num_elements);
54
55 if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
56 return false;
57 }
58
59 for (const auto& [program_id, play_time] : elements) {
60 if (program_id != 0) {
61 out_play_time_db[program_id] = play_time;
62 }
63 }
64 }
65
66 return true;
67}
68
69[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
70 const auto filename = GetCurrentUserPlayTimePath();
71
72 if (!filename.has_value()) {
73 LOG_ERROR(Frontend, "Failed to get current user path");
74 return false;
75 }
76
77 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
78 Common::FS::FileType::BinaryFile};
79 if (!file.IsOpen()) {
80 LOG_ERROR(Frontend, "Failed to open play time file: {}",
81 Common::FS::PathToUTF8String(filename.value()));
82 return false;
83 }
84
85 std::vector<PlayTimeElement> elements;
86 elements.reserve(play_time_db.size());
87
88 for (auto& [program_id, play_time] : play_time_db) {
89 if (program_id != 0) {
90 elements.push_back(PlayTimeElement{program_id, play_time});
91 }
92 }
93
94 return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
95}
96
97} // namespace
98
99PlayTimeManager::PlayTimeManager() {
100 if (!ReadPlayTimeFile(database)) {
101 LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
102 }
103}
104
105PlayTimeManager::~PlayTimeManager() {
106 Save();
107}
108
109void PlayTimeManager::SetProgramId(u64 program_id) {
110 running_program_id = program_id;
111}
112
113void PlayTimeManager::Start() {
114 play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
115}
116
117void PlayTimeManager::Stop() {
118 play_time_thread = {};
119}
120
121void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("PlayTimeReport");
123
124 using namespace std::literals::chrono_literals;
125 using std::chrono::seconds;
126 using std::chrono::steady_clock;
127
128 auto timestamp = steady_clock::now();
129
130 const auto GetDuration = [&]() -> u64 {
131 const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
132 const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
133 return static_cast<u64>(duration.count());
134 };
135
136 while (!stop_token.stop_requested()) {
137 Common::StoppableTimedWait(stop_token, 30s);
138
139 database[running_program_id] += GetDuration();
140 Save();
141 }
142}
143
144void PlayTimeManager::Save() {
145 if (!WritePlayTimeFile(database)) {
146 LOG_ERROR(Frontend, "Failed to update play time database!");
147 }
148}
149
150u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
151 auto it = database.find(program_id);
152 if (it != database.end()) {
153 return it->second;
154 } else {
155 return 0;
156 }
157}
158
159void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
160 database.erase(program_id);
161 Save();
162}
163
164QString ReadablePlayTime(qulonglong time_seconds) {
165 if (time_seconds == 0) {
166 return {};
167 }
168 const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
169 const auto time_hours = static_cast<double>(time_seconds) / 3600;
170 const bool is_minutes = time_minutes < 60;
171 const char* unit = is_minutes ? "m" : "h";
172 const auto value = is_minutes ? time_minutes : time_hours;
173
174 return QStringLiteral("%L1 %2")
175 .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
176 .arg(QString::fromUtf8(unit));
177}
178
179} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QString>
7
8#include <map>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/polyfill_thread.h"
13
14namespace PlayTime {
15
16using ProgramId = u64;
17using PlayTime = u64;
18using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
19
20class PlayTimeManager {
21public:
22 explicit PlayTimeManager();
23 ~PlayTimeManager();
24
25 YUZU_NON_COPYABLE(PlayTimeManager);
26 YUZU_NON_MOVEABLE(PlayTimeManager);
27
28 u64 GetPlayTime(u64 program_id) const;
29 void ResetProgramPlayTime(u64 program_id);
30 void SetProgramId(u64 program_id);
31 void Start();
32 void Stop();
33
34private:
35 PlayTimeDatabase database;
36 u64 running_program_id;
37 std::jthread play_time_thread;
38 void AutoTimestamp(std::stop_token stop_token);
39 void Save();
40};
41
42QString ReadablePlayTime(qulonglong time_seconds);
43
44} // namespace PlayTime