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.
This commit is contained in:
Bevan Weiss 2020-09-04 23:26:46 +10:00 committed by Megamouse
parent 3372409590
commit 53212ba7a9
8 changed files with 196 additions and 5 deletions

View File

@ -207,7 +207,8 @@ error_code sys_time_get_current_time(vm::ptr<s64> sec, vm::ptr<s64> 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<s64> sec, vm::ptr<s64> 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<s64> sec, vm::ptr<s64> 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)
{

View File

@ -249,6 +249,7 @@ struct cfg_root : cfg::node
cfg::_enum<CellSysutilLang> language{ this, "Language", CellSysutilLang{1} }; // CELL_SYSUTIL_LANG_ENGLISH_US
cfg::_enum<CellKbMappingType> keyboard_type{ this, "Keyboard Type", CellKbMappingType{0} }; // CELL_KB_MAPPING_101 = US
cfg::_enum<enter_button_assign> 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 };

View File

@ -3,6 +3,8 @@
#include <QMessageBox>
#include <QLineEdit>
#include <QTimer>
#include <QCalendarWidget>
#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)

View File

@ -13,6 +13,7 @@
#include <QStringList>
#include <QComboBox>
#include <QSpinBox>
#include <QDateTimeEdit>
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);

View File

@ -135,6 +135,7 @@ enum class emu_settings_type
EnableHostRoot,
LimitCacheSize,
MaximumCacheSize,
ConsoleTimeOffset,
// Virtual File System
emulatorLocation,
@ -275,6 +276,7 @@ static const QMap<emu_settings_type, cfg_location> 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)"}},

View File

@ -848,6 +848,14 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> 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"));

View File

@ -1447,7 +1447,61 @@
</widget>
</item>
<item>
<widget class="QWidget" name="systemTabSpacerWidget1" native="true"/>
<widget class="QGroupBox" name="gb_console_time">
<property name="title">
<string>Console Time</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QDateTimeEdit" name="console_time_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="wrapping">
<bool>false</bool>
</property>
<property name="frame">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::UpDownArrows</enum>
</property>
<property name="accelerated">
<bool>true</bool>
</property>
<property name="showGroupSeparator" stdset="0">
<bool>false</bool>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="console_time_reset">
<property name="text">
<string>Set to Now</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="systemTabSpacerWidget2" native="true"/>

View File

@ -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