diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 682c686c7d7f..b1be94b60d4e 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -505,6 +505,8 @@ add_library(core SysConf.h System.cpp System.h + TimePlayed.cpp + TimePlayed.h TitleDatabase.cpp TitleDatabase.h WiiRoot.cpp diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 1a7c523d6e60..b7d17bb79824 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -445,6 +445,8 @@ const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE{{System::Main, "GameList", "Col false}; const Info MAIN_GAMELIST_COLUMN_COMPRESSION{{System::Main, "GameList", "ColumnCompression"}, false}; +const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED{{System::Main, "GameList", "ColumnTimePlayed"}, + true}; const Info MAIN_GAMELIST_COLUMN_TAGS{{System::Main, "GameList", "ColumnTags"}, false}; // Main.FifoPlayer diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 546696476ff1..5b7d74ff8279 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -286,6 +286,7 @@ extern const Info MAIN_GAMELIST_COLUMN_FILE_SIZE; extern const Info MAIN_GAMELIST_COLUMN_FILE_FORMAT; extern const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE; extern const Info MAIN_GAMELIST_COLUMN_COMPRESSION; +extern const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED; extern const Info MAIN_GAMELIST_COLUMN_TAGS; // Main.FifoPlayer diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 05898b1f8cfa..8f9e33b2c422 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -10,11 +10,14 @@ #include "AudioCommon/AudioCommon.h" #include "Common/CommonTypes.h" #include "Common/Event.h" +#include "Common/Timer.h" +#include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/Host.h" #include "Core/PowerPC/GDBStub.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" +#include "Core/TimePlayed.h" #include "VideoCommon/Fifo.h" namespace CPU @@ -86,12 +89,34 @@ static void ExecutePendingJobs(std::unique_lock& state_lock) } } +static void RunTimer() +{ + const std::string game_id = SConfig::GetInstance().GetGameID(); + TimePlayed time_played(game_id); + Common::Timer time_tracker; + time_tracker.Start(); + + while (s_state != State::PowerDown) + { + if (time_tracker.ElapsedMs() > 60000) + { + auto emulated_milliseconds = time_tracker.ElapsedMs(); + time_tracker.Stop(); + time_played.AddTime(emulated_milliseconds); + time_tracker.Start(); + } + } +} + void Run() { // Updating the host CPU's rounding mode must be done on the CPU thread. // We can't rely on PowerPC::Init doing it, since it's called from EmuThread. PowerPC::RoundingModeUpdated(); + //start a separate timing thread here + auto timing = std::thread(RunTimer); + std::unique_lock state_lock(s_state_change_lock); while (s_state != State::PowerDown) { @@ -101,7 +126,7 @@ void Run() Common::Event gdb_step_sync_event; switch (s_state) { - case State::Running: + case State::Running: s_state_cpu_thread_active = true; state_lock.unlock(); @@ -183,6 +208,8 @@ void Run() break; } } + + timing.join(); state_lock.unlock(); Host_UpdateDisasmDialog(); } diff --git a/Source/Core/Core/TimePlayed.cpp b/Source/Core/Core/TimePlayed.cpp new file mode 100644 index 000000000000..3ed6368a5088 --- /dev/null +++ b/Source/Core/Core/TimePlayed.cpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "Common/IniFile.h" +#include "Common/FileUtil.h" +#include "TimePlayed.h" + +TimePlayed::TimePlayed(std::string game_id) +{ + m_game_id = game_id; +} + +void TimePlayed::AddTime(u64 time_emulated) +{ + IniFile ini; + std::string ini_path = File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini"; + ini.Load(ini_path); + + auto time_list = ini.GetOrCreateSection("Time Played"); + int previous_time = 0; + + time_list->Get(m_game_id, &previous_time); + time_list->Set(m_game_id, previous_time + int(time_emulated)); //unlikely u64 to int conversion issue + ini.Save(ini_path); +} + +int TimePlayed::GetTimePlayed() +{ + IniFile ini; + std::string ini_path = File::GetUserPath(D_CONFIG_IDX) + "TimePlayed.ini"; + ini.Load(ini_path); + + auto time_list = ini.GetOrCreateSection("Time Played"); + int previous_time; + time_list->Get(m_game_id, &previous_time); + return previous_time; +} diff --git a/Source/Core/Core/TimePlayed.h b/Source/Core/Core/TimePlayed.h new file mode 100644 index 000000000000..247b110db4b1 --- /dev/null +++ b/Source/Core/Core/TimePlayed.h @@ -0,0 +1,15 @@ +#pragma once +#include "Common/CommonTypes.h" + +class TimePlayed +{ +public: + TimePlayed(std::string game_id); + + void AddTime(u64 time_emulated); + + int GetTimePlayed(); + +private: + std::string m_game_id; +}; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 7150e8d7837a..c9383df1cccf 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -430,6 +430,7 @@ + @@ -1044,6 +1045,7 @@ + diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 69afc13a74c3..0bea0b1d4751 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -204,6 +204,7 @@ void GameList::MakeListView() SetResizeMode(Column::FileFormat, Mode::Fixed); SetResizeMode(Column::BlockSize, Mode::Fixed); SetResizeMode(Column::Compression, Mode::Fixed); + SetResizeMode(Column::TimePlayed, Mode::Interactive); SetResizeMode(Column::Tags, Mode::Interactive); // Cells have 3 pixels of padding, so the width of these needs to be image width + 6. Banners @@ -269,6 +270,7 @@ void GameList::UpdateColumnVisibility() SetVisiblity(Column::FileFormat, Config::Get(Config::MAIN_GAMELIST_COLUMN_FILE_FORMAT)); SetVisiblity(Column::BlockSize, Config::Get(Config::MAIN_GAMELIST_COLUMN_BLOCK_SIZE)); SetVisiblity(Column::Compression, Config::Get(Config::MAIN_GAMELIST_COLUMN_COMPRESSION)); + SetVisiblity(Column::TimePlayed, Config::Get(Config::MAIN_GAMELIST_COLUMN_TIME_PLAYED)); SetVisiblity(Column::Tags, Config::Get(Config::MAIN_GAMELIST_COLUMN_TAGS)); } @@ -981,6 +983,7 @@ void GameList::OnColumnVisibilityToggled(const QString& row, bool visible) {tr("File Format"), Column::FileFormat}, {tr("Block Size"), Column::BlockSize}, {tr("Compression"), Column::Compression}, + {tr("Time Played"), Column::TimePlayed}, {tr("Tags"), Column::Tags}, }; diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp index 8eb2124a170b..3f7d4bf33473 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp @@ -9,6 +9,7 @@ #include #include "Core/Config/MainSettings.h" +#include "Core/TimePlayed.h" #include "DiscIO/Enums.h" @@ -187,6 +188,22 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const return compression.isEmpty() ? tr("No Compression") : compression; } break; + case Column::TimePlayed: + if (role == Qt::DisplayRole || role == SORT_ROLE) + { + TimePlayed timer(game.GetGameID()); + + //milliseconds to minutes conversion + int game_milliseconds = timer.GetTimePlayed(); + int game_minutes = game_milliseconds / (60000); + int game_hours = game_minutes / 60; + int remaining_game_minutes = game_minutes % 60; + + // i18n: A time displayed as hours and minutes + QString formatted_time = tr("%0h %1m").arg(game_hours, remaining_game_minutes); + return formatted_time; + } + break; case Column::Tags: if (role == Qt::DisplayRole || role == SORT_ROLE) { @@ -231,6 +248,8 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int return tr("Block Size"); case Column::Compression: return tr("Compression"); + case Column::TimePlayed: + return tr("Time Played"); case Column::Tags: return tr("Tags"); default: diff --git a/Source/Core/DolphinQt/GameList/GameListModel.h b/Source/Core/DolphinQt/GameList/GameListModel.h index b76e0a37c275..0ca28c1c4aa7 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.h +++ b/Source/Core/DolphinQt/GameList/GameListModel.h @@ -58,6 +58,7 @@ class GameListModel final : public QAbstractTableModel FileFormat, BlockSize, Compression, + TimePlayed, Tags, Count, }; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 0acc8a36b981..741d13e9b7ee 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -625,6 +625,7 @@ void MenuBar::AddListColumnsMenu(QMenu* view_menu) {tr("File Format"), &Config::MAIN_GAMELIST_COLUMN_FILE_FORMAT}, {tr("Block Size"), &Config::MAIN_GAMELIST_COLUMN_BLOCK_SIZE}, {tr("Compression"), &Config::MAIN_GAMELIST_COLUMN_COMPRESSION}, + {tr("Time Played"), &Config::MAIN_GAMELIST_COLUMN_TIME_PLAYED}, {tr("Tags"), &Config::MAIN_GAMELIST_COLUMN_TAGS}}; QActionGroup* column_group = new QActionGroup(this);