diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
index 9d96cb9165..59e617775c 100644
--- a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp
@@ -164,7 +164,6 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* settings_dialog
dialog()->registerWidgetHelp(m_ui.rtcDateTime, tr("Real-Time Clock"), tr("Current date and time"),
tr("Real-time clock (RTC) used by the virtual PlayStation 2.
"
"This time is only applied upon booting the PS2; changing it while in-game will have no effect.
"
- "NOTE: This assumes you have your PS2 set to the default timezone of GMT+0 and default DST of Summer Time.
"
"Some games require an RTC date/time set after their release date."));
dialog()->registerWidgetHelp(m_ui.rtcUseSystemLocaleFormat, tr("Use System Locale Format"), tr("User Preference"),
tr("Uses the operating system's date/time format rather than \"yyyy-MM-dd HH:mm:ss\". May exclude seconds."));
diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp
index ffc822fef0..f627bfc0c7 100644
--- a/pcsx2/CDVD/CDVD.cpp
+++ b/pcsx2/CDVD/CDVD.cpp
@@ -37,6 +37,8 @@ cdvdStruct cdvd;
s64 PSXCLK = 36864000;
+static constexpr s32 GMT9_OFFSET_SECONDS = 9 * 60 * 60; // 32400
+
static constexpr u8 monthmap[13] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static constexpr u8 cdvdParamLength[16] = { 0, 0, 0, 0, 0, 4, 11, 11, 11, 1, 255, 255, 7, 2, 11, 1 };
@@ -920,69 +922,92 @@ void cdvdReset()
cdvd.ReadTime = cdvdBlockReadTime(MODE_DVDROM);
cdvd.RotSpeed = cdvdRotationTime(MODE_DVDROM);
+ ReadOSDConfigParames();
+
+ // Print time zone offset, DST, time format, date format, and system time basis.
+ DevCon.WriteLn(Color_StrongGreen, configParams1.timezoneOffset < 0 ? "Time Zone Offset: GMT%03d:%02d" : "Time Zone Offset: GMT+%02d:%02d",
+ configParams1.timezoneOffset / 60, std::abs(configParams1.timezoneOffset % 60));
+ DevCon.WriteLn(Color_StrongGreen, "DST: %s Time", configParams2.daylightSavings ? "Summer" : "Winter");
+ DevCon.WriteLn(Color_StrongGreen, "Time Format: %s-Hour", configParams2.timeFormat ? "12" : "24");
+ DevCon.WriteLn(Color_StrongGreen, "Date Format: %s", configParams2.dateFormat ? (configParams2.dateFormat == 2 ? "DD/MM/YYYY" : "MM/DD/YYYY") : "YYYY/MM/DD");
+ DevCon.WriteLn(Color_StrongGreen, "System Time Basis: %s",
+ EmuConfig.ManuallySetRealTimeClock ? "Manual RTC" : g_InputRecording.isActive() ? "Default Input Recording Time" : "Operating System Time");
+
+ std::tm input_tm{};
+ std::tm resulting_tm{};
+
+ const int bios_settings_offset_seconds = 60 * (configParams1.timezoneOffset + configParams2.daylightSavings * 60);
+
+ // CDVD internally uses GMT+9, 1-indexed months, and year offset of 2000 instead of 1900.
+ // tm struct uses 0-indexed months and year offset of 1900.
if (EmuConfig.ManuallySetRealTimeClock)
{
- // Convert to GMT+9 (assumes GMT+0)
- std::tm tm{};
- tm.tm_sec = EmuConfig.RtcSecond;
- tm.tm_min = EmuConfig.RtcMinute;
- tm.tm_hour = EmuConfig.RtcHour;
- tm.tm_mday = EmuConfig.RtcDay;
- tm.tm_mon = EmuConfig.RtcMonth - 1;
- tm.tm_year = EmuConfig.RtcYear + 100; // 2000 - 1900
- tm.tm_isdst = 1;
+ resulting_tm.tm_sec = EmuConfig.RtcSecond;
+ resulting_tm.tm_min = EmuConfig.RtcMinute;
+ resulting_tm.tm_hour = EmuConfig.RtcHour;
+ resulting_tm.tm_mday = EmuConfig.RtcDay;
+ resulting_tm.tm_mon = EmuConfig.RtcMonth - 1;
+ resulting_tm.tm_year = EmuConfig.RtcYear + 100;
+ resulting_tm.tm_isdst = 0;
- // Need this instead of mktime for timezone independence
- std::time_t t = 0;
- #if defined(_WIN32)
- t = _mkgmtime(&tm) + 32400; //60 * 60 * 9 for GMT+9
- gmtime_s(&tm, &t);
- #else
- t = timegm(&tm) + 32400;
- gmtime_r(&t, &tm);
- #endif
-
- cdvd.RTC.second = tm.tm_sec;
- cdvd.RTC.minute = tm.tm_min;
- cdvd.RTC.hour = tm.tm_hour;
- cdvd.RTC.day = tm.tm_mday;
- cdvd.RTC.month = tm.tm_mon + 1;
- cdvd.RTC.year = tm.tm_year - 100;
+ // Work backwards to input time by accounting for BIOS settings and GMT+9 defaultism.
+#if defined(_WIN32)
+ const std::time_t input_time = _mkgmtime(&resulting_tm) + GMT9_OFFSET_SECONDS - bios_settings_offset_seconds;
+ gmtime_s(&input_tm, &input_time);
+#else
+ const std::time_t input_time = timegm(&resulting_tm) + GMT9_OFFSET_SECONDS - bios_settings_offset_seconds;
+ gmtime_r(&input_time, &input_tm);
+#endif
}
- // If we are recording, always use the same RTC setting
- // for games that use the RTC to seed their RNG -- this is very important to be the same everytime!
else if (g_InputRecording.isActive())
{
- Console.WriteLn("Input Recording Active - Using Constant RTC of 04-03-2020 (DD-MM-YYYY)");
- // Why not just 0 everything? Some games apparently require the date to be valid in terms of when
- // the PS2 / Game actually came out. (MGS3). So set it to a value well beyond any PS2 game's release date.
- cdvd.RTC.second = 0;
- cdvd.RTC.minute = 0;
- cdvd.RTC.hour = 0;
- cdvd.RTC.day = 4;
- cdvd.RTC.month = 3;
- cdvd.RTC.year = 20;
+ // Default input recording value (2020-03-04 00:00:00) if manual RTC is off. Well beyond any PS2 game's release date.
+ // Some games require a valid date in terms of when the PS2 / game actually came out (see: MGS3).
+ // Changing this will ruin compat with old input recordings (RNG seeding).
+ input_tm.tm_sec = 0;
+ input_tm.tm_min = 0;
+ input_tm.tm_hour = 0;
+ input_tm.tm_mday = 4;
+ input_tm.tm_mon = 2;
+ input_tm.tm_year = 120;
+ input_tm.tm_isdst = 0;
+
+#if defined(_WIN32)
+ const std::time_t resulting_time = _mkgmtime(&input_tm) - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
+ gmtime_s(&resulting_tm, &resulting_time);
+#else
+ const std::time_t resulting_time = timegm(&input_tm) - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
+ gmtime_r(&resulting_time, &resulting_tm);
+#endif
}
else
{
- // CDVD internally uses GMT+9. If you think the time's wrong, you're wrong.
- // Set up your time zone and winter/summer in the BIOS. No PS2 BIOS I know of features automatic DST.
- const std::time_t utc_time = std::time(nullptr);
- const std::time_t gmt9_time = (utc_time + 32400); //60 * 60 * 9
- struct tm curtime = {};
+ // User must set time zone and winter/summer DST in the BIOS for correct time.
+ const std::time_t input_time = std::time(nullptr) + GMT9_OFFSET_SECONDS;
+ const std::time_t resulting_time = input_time - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
+
#ifdef _MSC_VER
- gmtime_s(&curtime, &gmt9_time);
+ gmtime_s(&input_tm, &input_time);
+ gmtime_s(&resulting_tm, &resulting_time);
#else
- gmtime_r(&gmt9_time, &curtime);
+ gmtime_r(&input_time, &input_tm);
+ gmtime_r(&resulting_time, &resulting_tm);
#endif
- cdvd.RTC.second = static_cast(curtime.tm_sec);
- cdvd.RTC.minute = static_cast(curtime.tm_min);
- cdvd.RTC.hour = static_cast(curtime.tm_hour);
- cdvd.RTC.day = static_cast(curtime.tm_mday);
- cdvd.RTC.month = static_cast(curtime.tm_mon + 1); // WX returns Jan as "0"
- cdvd.RTC.year = static_cast(curtime.tm_year - 100); // offset from 2000
}
+ // Send completed input time to the CDVD.
+ cdvd.RTC.second = static_cast(input_tm.tm_sec);
+ cdvd.RTC.minute = static_cast(input_tm.tm_min);
+ cdvd.RTC.hour = static_cast(input_tm.tm_hour);
+ cdvd.RTC.day = static_cast(input_tm.tm_mday);
+ cdvd.RTC.month = static_cast(input_tm.tm_mon + 1);
+ cdvd.RTC.year = static_cast(input_tm.tm_year - 100);
+
+ // Print time that will appear in the user's BIOS rather than input time.
+ DevCon.WriteLn(Color_StrongGreen, "Resulting System Time: 20%02u-%02u-%02u %02u:%02u:%02u",
+ resulting_tm.tm_year - 100, resulting_tm.tm_mon + 1, resulting_tm.tm_mday,
+ resulting_tm.tm_hour, resulting_tm.tm_min, resulting_tm.tm_sec);
+
cdvdCtrlTrayClose();
}
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index f234b02ca9..86b1c4d225 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -1314,7 +1314,7 @@ struct Pcsx2Config
InhibitScreensaver : 1,
BackupSavestate : 1,
McdFolderAutoManage : 1,
- ManuallySetRealTimeClock : 1,
+ ManuallySetRealTimeClock : 1, // passes user-set real-time clock information to cdvd at startup
UseSystemLocaleFormat : 1, // presents OS time format instead of yyyy-MM-dd HH:mm:ss for manual RTC
HostFs : 1,
diff --git a/pcsx2/ps2/BiosTools.cpp b/pcsx2/ps2/BiosTools.cpp
index 7e92f11891..c83bff720e 100644
--- a/pcsx2/ps2/BiosTools.cpp
+++ b/pcsx2/ps2/BiosTools.cpp
@@ -68,6 +68,9 @@ void ReadOSDConfigParames()
// Region settings for time/date and extended language
configParams2.UC[1] = ((u32)params[3] & 0x78) << 1; // Daylight Savings, 24hr clock, Date format
+ configParams2.daylightSavings = configParams2.UC[1] & 0x10 ? 1 : 0;
+ configParams2.timeFormat = configParams2.UC[1] & 0x20 ? 1 : 0;
+ configParams2.dateFormat = configParams2.UC[1] & 0x80 ? 2 : (configParams2.UC[1] & 0x40 ? 1 : 0);
// FIXME: format, version and language are set manually by the bios. Not sure if any game needs them, but it seems to set version to 2 and duplicate the language value.
configParams2.version = 2;
configParams2.language = configParams1.language;
diff --git a/pcsx2/ps2/BiosTools.h b/pcsx2/ps2/BiosTools.h
index 139210b2c8..a738b64c10 100644
--- a/pcsx2/ps2/BiosTools.h
+++ b/pcsx2/ps2/BiosTools.h
@@ -43,7 +43,7 @@ typedef struct
/** LANGUAGE_??? value */
/*16*/ u32 language : 5;
/** timezone minutes offset from gmt */
- /*21*/ u32 timezoneOffset : 11;
+ /*21*/ s32 timezoneOffset : 11;
};
u8 UC[4];
@@ -63,7 +63,7 @@ typedef struct
/*00*/ u8 reserved : 4;
/** 0=standard(winter), 1=daylight savings(summer) */
- /*04*/ u8 daylightSaving : 1;
+ /*04*/ u8 daylightSavings : 1;
/** 0=24 hour, 1=12 hour */
/*05*/ u8 timeFormat : 1;
/** 0=YYYYMMDD, 1=MMDDYYYY, 2=DDMMYYYY */