From 53212ba7a9cb1a320574efe633905caeb5419fdd Mon Sep 17 00:00:00 2001 From: Bevan Weiss Date: Fri, 4 Sep 2020 23:26:46 +1000 Subject: [PATCH] Core: Add date/time offset capability for console time Provides a setting which can be applied per game, and allows for a custom date/time to be set. The console time will then apply this as an offset to the computer wallclock. This allows for games which look at the console time to determine their gameplay to be adjusted. --- rpcs3/Emu/Cell/lv2/sys_time.cpp | 10 ++- rpcs3/Emu/system_config.h | 1 + rpcs3/rpcs3qt/emu_settings.cpp | 119 ++++++++++++++++++++++++++++++ rpcs3/rpcs3qt/emu_settings.h | 4 + rpcs3/rpcs3qt/emu_settings_type.h | 2 + rpcs3/rpcs3qt/settings_dialog.cpp | 8 ++ rpcs3/rpcs3qt/settings_dialog.ui | 56 +++++++++++++- rpcs3/rpcs3qt/tooltips.h | 1 + 8 files changed, 196 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_time.cpp b/rpcs3/Emu/Cell/lv2/sys_time.cpp index bd48cc1ea9..d221b90698 100644 --- a/rpcs3/Emu/Cell/lv2/sys_time.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_time.cpp @@ -207,7 +207,8 @@ error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec) // get time since Epoch in nanoseconds const u64 time = s_time_aux_info.start_ftime * 100u + (diff * g_cfg.core.clocks_scale / 100u); - *sec = time / 1000000000ull; + // scale to seconds, and add the console time offset (which might be negative) + *sec = (time / 1000000000ull) + g_cfg.sys.console_time_offset; if (!nsec) { @@ -221,7 +222,8 @@ error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec) if (g_cfg.core.clocks_scale == 100) { - *sec = ts.tv_sec; + // get the seconds from the system clock, and add the console time offset (which might be negative) + *sec = ts.tv_sec + g_cfg.sys.console_time_offset; if (!nsec) { @@ -252,8 +254,8 @@ error_code sys_time_get_current_time(vm::ptr sec, vm::ptr nsec) // Scale nanocseconds tv_nsec = stv_nsec + (tv_nsec * g_cfg.core.clocks_scale / 100); - // Scale seconds and add from nanoseconds / 1'000'000'000 - *sec = stv_sec + (tv_sec * g_cfg.core.clocks_scale / 100u) + (tv_nsec / 1000000000ull); + // Scale seconds and add from nanoseconds / 1'000'000'000, and add the console time offset (which might be negative) + *sec = stv_sec + (tv_sec * g_cfg.core.clocks_scale / 100u) + (tv_nsec / 1000000000ull) + g_cfg.sys.console_time_offset; if (!nsec) { diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 4362da0e5e..abdbc2499b 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -249,6 +249,7 @@ struct cfg_root : cfg::node cfg::_enum language{ this, "Language", CellSysutilLang{1} }; // CELL_SYSUTIL_LANG_ENGLISH_US cfg::_enum keyboard_type{ this, "Keyboard Type", CellKbMappingType{0} }; // CELL_KB_MAPPING_101 = US cfg::_enum enter_button_assignment{ this, "Enter button assignment", enter_button_assign::cross }; + cfg::_int<-60*60*24*365*100LL, 60*60*24*365*100LL> console_time_offset{ this, "Console time offset (s)", 0 }; //console time offset, limited to +/-100years } sys{ this }; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index baf40c2f37..593ee615a5 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include "Emu/System.h" #include "Emu/system_config.h" @@ -307,6 +309,123 @@ void emu_settings::EnhanceCheckBox(QCheckBox* checkbox, emu_settings_type type) }); } +void emu_settings::EnhanceDateTimeEdit(QDateTimeEdit* date_time_edit, emu_settings_type type, const QString& format, bool use_calendar, bool as_offset_from_now, int offset_update_time) +{ + if (!date_time_edit) + { + cfg_log.fatal("EnhanceDateTimeEdit '%s' was used with an invalid object", cfg_adapter::get_setting_name(type)); + return; + } + + date_time_edit->setDisplayFormat(format); + date_time_edit->setCalendarPopup(use_calendar); + + if (as_offset_from_now) + { + // If using offset from now, then we disable the keyboard tracking to reduce the numebr of events that occur (since for each event we will lose focus) + date_time_edit->setKeyboardTracking(false); + + bool ok_def = false, ok_min = false, ok_max = false; + const QStringList range = GetSettingOptions(type); + const s64 def = qstr(GetSettingDefault(type)).toLongLong(&ok_def); + const s64 min = range.first().toLongLong(&ok_min); + const s64 max = range.last().toLongLong(&ok_max); + if (!ok_def || !ok_min || !ok_max) + { + cfg_log.fatal("EnhanceDateTimeEdit '%s' was used with an invalid emu_settings_type", cfg_adapter::get_setting_name(type)); + return; + } + + bool ok_sel = false; + s64 val = qstr(GetSetting(type)).toLongLong(&ok_sel); + if (!ok_sel || val < min || val > max) + { + cfg_log.error("EnhanceDateTimeEdit '%s' tried to set an invalid value: %d. Setting to default: %d. Allowed range: [%d, %d]", cfg_adapter::get_setting_name(type), val, def, min, max); + val = def; + m_broken_types.insert(type); + SetSetting(type, std::to_string(def)); + } + + // we'll capture the DateTime once, and apply the min/max and offset against it here. + const QDateTime now = QDateTime::currentDateTime(); + + // we set the allowed limits + date_time_edit->setDateTimeRange(now.addSecs(min), now.addSecs(max)); + + // we add the offset, and set the control to have this datetime value + const QDateTime date_time = now.addSecs(val); + date_time_edit->setDateTime(date_time); + + // if we have an invalid update time then we won't run the update timer + if (offset_update_time > 0) + { + QTimer* console_time_update = new QTimer(date_time_edit); + connect(console_time_update, &QTimer::timeout, [this, date_time_edit, min, max]() + { + if (!date_time_edit->hasFocus() && (!date_time_edit->calendarPopup() || !date_time_edit->calendarWidget()->hasFocus())) + { + const auto now = QDateTime::currentDateTime(); + const s64 offset = qstr(GetSetting(emu_settings_type::ConsoleTimeOffset)).toLongLong(); + date_time_edit->setDateTime(now.addSecs(offset)); + date_time_edit->setDateTimeRange(now.addSecs(min), now.addSecs(max)); + } + }); + + console_time_update->start(offset_update_time); + } + } + else + { + auto str = qstr(GetSettingDefault(type)); + const QStringList range = GetSettingOptions(type); + const auto def = QDateTime::fromString(str, Qt::ISODate); + const auto min = QDateTime::fromString(range.first(), Qt::ISODate); + const auto max = QDateTime::fromString(range.last(), Qt::ISODate); + if (!def.isValid() || !min.isValid() || !max.isValid()) + { + cfg_log.fatal("EnhanceDateTimeEdit '%s' was used with an invalid emu_settings_type", cfg_adapter::get_setting_name(type)); + return; + } + + str = qstr(GetSetting(type)); + auto val = QDateTime::fromString(str, Qt::ISODate); + if (!val.isValid() || val < min || val > max) + { + cfg_log.error("EnhanceDateTimeEdit '%s' tried to set an invalid value: %s. Setting to default: %s Allowed range: [%s, %s]", + cfg_adapter::get_setting_name(type), val.toString(Qt::ISODate).toStdString(), def.toString(Qt::ISODate).toStdString(), + min.toString(Qt::ISODate).toStdString(), max.toString(Qt::ISODate).toStdString()); + val = def; + m_broken_types.insert(type); + SetSetting(type, sstr(def.toString(Qt::ISODate))); + } + + // we set the allowed limits + date_time_edit->setDateTimeRange(min, max); + + // set the date_time value to the control + date_time_edit->setDateTime(val); + } + + connect(date_time_edit, &QDateTimeEdit::dateTimeChanged, [date_time_edit, type, as_offset_from_now, this](const QDateTime& datetime) + { + if (as_offset_from_now) + { + // offset will be applied in seconds + const s64 offset = QDateTime::currentDateTime().secsTo(datetime); + SetSetting(type, std::to_string(offset)); + + // HACK: We are only looking at whether the control has focus to prevent the time from updating dynamically, so we + // clear the focus, so that this dynamic updating isn't suppressed. + date_time_edit->clearFocus(); + } + else + { + // date time will be written straight into settings + SetSetting(type, sstr(datetime.toString(Qt::ISODate))); + } + }); +} + void emu_settings::EnhanceSlider(QSlider* slider, emu_settings_type type) { if (!slider) diff --git a/rpcs3/rpcs3qt/emu_settings.h b/rpcs3/rpcs3qt/emu_settings.h index 739c6d5a39..d1033da7e9 100644 --- a/rpcs3/rpcs3qt/emu_settings.h +++ b/rpcs3/rpcs3qt/emu_settings.h @@ -13,6 +13,7 @@ #include #include #include +#include constexpr auto qstr = QString::fromStdString; @@ -37,6 +38,9 @@ public: /** Connects a check box with the target settings type*/ void EnhanceCheckBox(QCheckBox* checkbox, emu_settings_type type); + /** Connects a date time edit box with the target settings type*/ + void EnhanceDateTimeEdit(QDateTimeEdit* date_time_edit, emu_settings_type type, const QString& format, bool use_calendar, bool as_offset_from_now, int offset_update_time=0); + /** Connects a slider with the target settings type*/ void EnhanceSlider(QSlider* slider, emu_settings_type type); diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 34361b6d76..ac28aa286b 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -135,6 +135,7 @@ enum class emu_settings_type EnableHostRoot, LimitCacheSize, MaximumCacheSize, + ConsoleTimeOffset, // Virtual File System emulatorLocation, @@ -275,6 +276,7 @@ static const QMap settings_location = { emu_settings_type::EnableHostRoot, { "VFS", "Enable /host_root/"}}, { emu_settings_type::LimitCacheSize, { "VFS", "Limit disk cache size"}}, { emu_settings_type::MaximumCacheSize, { "VFS", "Disk cache maximum size (MB)"}}, + { emu_settings_type::ConsoleTimeOffset, { "System", "Console time offset (s)"}}, // Virtual File System { emu_settings_type::emulatorLocation, { "VFS", "$(EmulatorDir)"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 05f48658d3..b86e49dbaa 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -848,6 +848,14 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std SubscribeTooltip(ui->gb_DiskCacheClearing, tooltips.settings.limit_cache_size); connect(ui->enableCacheClearing, &QCheckBox::stateChanged, ui->maximumCacheSize, &QSlider::setEnabled); + // Date Time Edit Box + m_emu_settings->EnhanceDateTimeEdit(ui->console_time_edit, emu_settings_type::ConsoleTimeOffset, tr("dd MMM yyyy HH:mm"), true, true, 15000); + connect(ui->console_time_reset, &QAbstractButton::clicked, [this]() + { + ui->console_time_edit->setDateTime(QDateTime::currentDateTime()); + }); + SubscribeTooltip(ui->gb_console_time, tooltips.settings.console_time_offset); + // Sliders EnhanceSlider(emu_settings_type::MaximumCacheSize, ui->maximumCacheSize, ui->maximumCacheSizeLabel, tr("Maximum size: %0 MB", "Maximum cache size")); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index e94db1d45d..2fd8554818 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1447,7 +1447,61 @@ - + + + Console Time + + + + + + + + + 10 + 0 + + + + Qt::LeftToRight + + + false + + + true + + + Qt::AlignCenter + + + false + + + QAbstractSpinBox::UpDownArrows + + + true + + + false + + + true + + + + + + + Set to Now + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 55dc635647..054e9bbb72 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -193,6 +193,7 @@ public: const QString enter_button_assignment = tr("The button used for enter/accept/confirm in system dialogs.\nChange this to use the Circle button instead, which is the default configuration on Japanese systems and in many Japanese games.\nIn these cases having the cross button assigned can often lead to confusion."); const QString enable_host_root = tr("Required for some Homebrew.\nIf unsure, don't use this option."); const QString limit_cache_size = tr("Automatically removes older files from disk cache on boot if it grows larger than the specified value.\nGames can use the cache folder to temporarily store data outside of system memory. It is not used for long-term storage."); + const QString console_time_offset = tr("Sets the time to be used within the console. This will be applied as an offset that tracks wall clock time.\nCan be reset to current wallclock time by clicking \"Set to Now\"."); } settings; const struct gamepad_settings