summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/glue
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/glue')
-rw-r--r--src/core/hle/service/glue/glue.cpp19
-rw-r--r--src/core/hle/service/glue/time/alarm_worker.cpp82
-rw-r--r--src/core/hle/service/glue/time/alarm_worker.h53
-rw-r--r--src/core/hle/service/glue/time/file_timestamp_worker.cpp23
-rw-r--r--src/core/hle/service/glue/time/file_timestamp_worker.h28
-rw-r--r--src/core/hle/service/glue/time/manager.cpp277
-rw-r--r--src/core/hle/service/glue/time/manager.h42
-rw-r--r--src/core/hle/service/glue/time/pm_state_change_handler.cpp13
-rw-r--r--src/core/hle/service/glue/time/pm_state_change_handler.h18
-rw-r--r--src/core/hle/service/glue/time/standard_steady_clock_resource.cpp123
-rw-r--r--src/core/hle/service/glue/time/standard_steady_clock_resource.h41
-rw-r--r--src/core/hle/service/glue/time/static.cpp448
-rw-r--r--src/core/hle/service/glue/time/static.h110
-rw-r--r--src/core/hle/service/glue/time/time_zone.cpp377
-rw-r--r--src/core/hle/service/glue/time/time_zone.h95
-rw-r--r--src/core/hle/service/glue/time/time_zone_binary.cpp221
-rw-r--r--src/core/hle/service/glue/time/time_zone_binary.h32
-rw-r--r--src/core/hle/service/glue/time/worker.cpp338
-rw-r--r--src/core/hle/service/glue/time/worker.h64
19 files changed, 2404 insertions, 0 deletions
diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp
index 993c3d21d..10376bfac 100644
--- a/src/core/hle/service/glue/glue.cpp
+++ b/src/core/hle/service/glue/glue.cpp
@@ -8,6 +8,9 @@
#include "core/hle/service/glue/ectx.h"
#include "core/hle/service/glue/glue.h"
#include "core/hle/service/glue/notif.h"
+#include "core/hle/service/glue/time/manager.h"
+#include "core/hle/service/glue/time/static.h"
+#include "core/hle/service/psc/time/common.h"
#include "core/hle/service/server_manager.h"
namespace Service::Glue {
@@ -31,6 +34,22 @@ void LoopProcess(Core::System& system) {
// Notification Services for application
server_manager->RegisterNamedService("notif:a", std::make_shared<NOTIF_A>(system));
+ // Time
+ auto time = std::make_shared<Time::TimeManager>(system);
+
+ server_manager->RegisterNamedService(
+ "time:u",
+ std::make_shared<Time::StaticService>(
+ system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 0}, time, "time:u"));
+ server_manager->RegisterNamedService(
+ "time:a",
+ std::make_shared<Time::StaticService>(
+ system, Service::PSC::Time::StaticServiceSetupInfo{1, 1, 0, 1, 0, 0}, time, "time:a"));
+ server_manager->RegisterNamedService(
+ "time:r",
+ std::make_shared<Time::StaticService>(
+ system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 1, 0}, time, "time:r"));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/glue/time/alarm_worker.cpp b/src/core/hle/service/glue/time/alarm_worker.cpp
new file mode 100644
index 000000000..f549ed00a
--- /dev/null
+++ b/src/core/hle/service/glue/time/alarm_worker.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/svc.h"
+#include "core/hle/service/glue/time/alarm_worker.h"
+#include "core/hle/service/psc/time/service_manager.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Glue::Time {
+
+AlarmWorker::AlarmWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource)
+ : m_system{system}, m_ctx{system, "Glue:AlarmWorker"}, m_steady_clock_resource{
+ steady_clock_resource} {}
+
+AlarmWorker::~AlarmWorker() {
+ m_system.CoreTiming().UnscheduleEvent(m_timer_timing_event);
+
+ m_ctx.CloseEvent(m_timer_event);
+}
+
+void AlarmWorker::Initialize(std::shared_ptr<Service::PSC::Time::ServiceManager> time_m) {
+ m_time_m = std::move(time_m);
+
+ m_timer_event = m_ctx.CreateEvent("Glue:AlarmWorker:TimerEvent");
+ m_timer_timing_event = Core::Timing::CreateEvent(
+ "Glue:AlarmWorker::AlarmTimer",
+ [this](s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ m_timer_event->Signal();
+ return std::nullopt;
+ });
+
+ AttachToClosestAlarmEvent();
+}
+
+bool AlarmWorker::GetClosestAlarmInfo(Service::PSC::Time::AlarmInfo& out_alarm_info,
+ s64& out_time) {
+ bool is_valid{};
+ Service::PSC::Time::AlarmInfo alarm_info{};
+ s64 closest_time{};
+
+ auto res = m_time_m->GetClosestAlarmInfo(is_valid, alarm_info, closest_time);
+ ASSERT(res == ResultSuccess);
+
+ if (is_valid) {
+ out_alarm_info = alarm_info;
+ out_time = closest_time;
+ }
+
+ return is_valid;
+}
+
+void AlarmWorker::OnPowerStateChanged() {
+ Service::PSC::Time::AlarmInfo closest_alarm_info{};
+ s64 closest_time{};
+ if (!GetClosestAlarmInfo(closest_alarm_info, closest_time)) {
+ m_system.CoreTiming().UnscheduleEvent(m_timer_timing_event);
+ m_timer_event->Clear();
+ return;
+ }
+
+ if (closest_alarm_info.alert_time <= closest_time) {
+ m_time_m->CheckAndSignalAlarms();
+ } else {
+ auto next_time{closest_alarm_info.alert_time - closest_time};
+
+ m_system.CoreTiming().UnscheduleEvent(m_timer_timing_event);
+ m_timer_event->Clear();
+
+ m_system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds(next_time),
+ m_timer_timing_event);
+ }
+}
+
+Result AlarmWorker::AttachToClosestAlarmEvent() {
+ m_time_m->GetClosestAlarmUpdatedEvent(&m_event);
+ R_SUCCEED();
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/alarm_worker.h b/src/core/hle/service/glue/time/alarm_worker.h
new file mode 100644
index 000000000..f269cffdb
--- /dev/null
+++ b/src/core/hle/service/glue/time/alarm_worker.h
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/psc/time/common.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::PSC::Time {
+class ServiceManager;
+}
+
+namespace Service::Glue::Time {
+class StandardSteadyClockResource;
+
+class AlarmWorker {
+public:
+ explicit AlarmWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource);
+ ~AlarmWorker();
+
+ void Initialize(std::shared_ptr<Service::PSC::Time::ServiceManager> time_m);
+
+ Kernel::KEvent& GetEvent() {
+ return *m_event;
+ }
+
+ Kernel::KEvent& GetTimerEvent() {
+ return *m_timer_event;
+ }
+
+ void OnPowerStateChanged();
+
+private:
+ bool GetClosestAlarmInfo(Service::PSC::Time::AlarmInfo& out_alarm_info, s64& out_time);
+ Result AttachToClosestAlarmEvent();
+
+ Core::System& m_system;
+ KernelHelpers::ServiceContext m_ctx;
+ std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
+
+ Kernel::KEvent* m_event{};
+ Kernel::KEvent* m_timer_event{};
+ std::shared_ptr<Core::Timing::EventType> m_timer_timing_event;
+ StandardSteadyClockResource& m_steady_clock_resource;
+};
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/file_timestamp_worker.cpp b/src/core/hle/service/glue/time/file_timestamp_worker.cpp
new file mode 100644
index 000000000..5a6309549
--- /dev/null
+++ b/src/core/hle/service/glue/time/file_timestamp_worker.cpp
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/glue/time/file_timestamp_worker.h"
+#include "core/hle/service/psc/time/common.h"
+#include "core/hle/service/psc/time/system_clock.h"
+#include "core/hle/service/psc/time/time_zone_service.h"
+
+namespace Service::Glue::Time {
+
+void FileTimestampWorker::SetFilesystemPosixTime() {
+ s64 time{};
+ Service::PSC::Time::CalendarTime calendar_time{};
+ Service::PSC::Time::CalendarAdditionalInfo additional_info{};
+
+ if (m_initialized && m_system_clock->GetCurrentTime(time) == ResultSuccess &&
+ m_time_zone->ToCalendarTimeWithMyRule(calendar_time, additional_info, time) ==
+ ResultSuccess) {
+ // TODO IFileSystemProxy::SetCurrentPosixTime
+ }
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/file_timestamp_worker.h b/src/core/hle/service/glue/time/file_timestamp_worker.h
new file mode 100644
index 000000000..5f8b9b049
--- /dev/null
+++ b/src/core/hle/service/glue/time/file_timestamp_worker.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace Service::PSC::Time {
+class SystemClock;
+class TimeZoneService;
+} // namespace Service::PSC::Time
+
+namespace Service::Glue::Time {
+
+class FileTimestampWorker {
+public:
+ FileTimestampWorker() = default;
+
+ void SetFilesystemPosixTime();
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> m_system_clock{};
+ std::shared_ptr<Service::PSC::Time::TimeZoneService> m_time_zone{};
+ bool m_initialized{};
+};
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/manager.cpp b/src/core/hle/service/glue/time/manager.cpp
new file mode 100644
index 000000000..6423e5089
--- /dev/null
+++ b/src/core/hle/service/glue/time/manager.cpp
@@ -0,0 +1,277 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "core/core.h"
+#include "core/core_timing.h"
+
+#include "common/settings.h"
+#include "common/time_zone.h"
+#include "core/file_sys/vfs.h"
+#include "core/hle/kernel/svc.h"
+#include "core/hle/service/glue/time/manager.h"
+#include "core/hle/service/glue/time/time_zone_binary.h"
+#include "core/hle/service/psc/time/service_manager.h"
+#include "core/hle/service/psc/time/static.h"
+#include "core/hle/service/psc/time/system_clock.h"
+#include "core/hle/service/psc/time/time_zone_service.h"
+#include "core/hle/service/set/system_settings_server.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Glue::Time {
+namespace {
+
+template <typename T>
+T GetSettingsItemValue(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys,
+ const char* category, const char* name) {
+ std::vector<u8> interval_buf;
+ auto res = set_sys->GetSettingsItemValue(interval_buf, category, name);
+ ASSERT(res == ResultSuccess);
+
+ T v{};
+ std::memcpy(&v, interval_buf.data(), sizeof(T));
+ return v;
+}
+
+s64 CalendarTimeToEpoch(Service::PSC::Time::CalendarTime calendar) {
+ constexpr auto is_leap = [](s32 year) -> bool {
+ return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
+ };
+ constexpr std::array<s32, 12> MonthStartDayOfYear{
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
+ };
+
+ s16 month_s16{calendar.month};
+ s8 month{static_cast<s8>(((month_s16 * 43) & ~std::numeric_limits<s16>::max()) +
+ ((month_s16 * 43) >> 9))};
+ s8 month_index{static_cast<s8>(calendar.month - 12 * month)};
+ if (month_index == 0) {
+ month_index = 12;
+ }
+ s32 year{(month + calendar.year) - !month_index};
+ s32 v8{year >= 0 ? year : year + 3};
+
+ s64 days_since_epoch = calendar.day + MonthStartDayOfYear[month_index - 1];
+ days_since_epoch += (year * 365) + (v8 / 4) - (year / 100) + (year / 400) - 365;
+
+ if (month_index <= 2 && is_leap(year)) {
+ days_since_epoch--;
+ }
+ auto epoch_s{((24ll * days_since_epoch + calendar.hour) * 60ll + calendar.minute) * 60ll +
+ calendar.second};
+ return epoch_s - 62135683200ll;
+}
+
+s64 GetEpochTimeFromInitialYear(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys) {
+ Service::PSC::Time::CalendarTime calendar{
+ .year = GetSettingsItemValue<s16>(set_sys, "time", "standard_user_clock_initial_year"),
+ .month = 1,
+ .day = 1,
+ .hour = 0,
+ .minute = 0,
+ .second = 0,
+ };
+ return CalendarTimeToEpoch(calendar);
+}
+
+Service::PSC::Time::LocationName GetTimeZoneString(Service::PSC::Time::LocationName& in_name) {
+ auto configured_zone = Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue());
+
+ Service::PSC::Time::LocationName configured_name{};
+ std::memcpy(configured_name.name.data(), configured_zone.data(),
+ std::min(configured_name.name.size(), configured_zone.size()));
+
+ if (!IsTimeZoneBinaryValid(configured_name)) {
+ configured_zone = Common::TimeZone::FindSystemTimeZone();
+ configured_name = {};
+ std::memcpy(configured_name.name.data(), configured_zone.data(),
+ std::min(configured_name.name.size(), configured_zone.size()));
+ }
+
+ ASSERT_MSG(IsTimeZoneBinaryValid(configured_name), "Invalid time zone {}!",
+ configured_name.name.data());
+
+ return configured_name;
+}
+
+} // namespace
+
+TimeManager::TimeManager(Core::System& system)
+ : m_steady_clock_resource{system}, m_worker{system, m_steady_clock_resource,
+ m_file_timestamp_worker} {
+ m_time_m =
+ system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+
+ auto res = m_time_m->GetStaticServiceAsServiceManager(m_time_sm);
+ ASSERT(res == ResultSuccess);
+
+ m_set_sys =
+ system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
+
+ res = MountTimeZoneBinary(system);
+ ASSERT(res == ResultSuccess);
+
+ m_worker.Initialize(m_time_sm, m_set_sys);
+
+ res = m_time_sm->GetStandardUserSystemClock(m_file_timestamp_worker.m_system_clock);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_sm->GetTimeZoneService(m_file_timestamp_worker.m_time_zone);
+ ASSERT(res == ResultSuccess);
+
+ res = SetupStandardSteadyClockCore();
+ ASSERT(res == ResultSuccess);
+
+ Service::PSC::Time::SystemClockContext user_clock_context{};
+ res = m_set_sys->GetUserSystemClockContext(user_clock_context);
+ ASSERT(res == ResultSuccess);
+
+ // TODO the local clock should initialise with this epoch time, and be updated somewhere else on
+ // first boot to update it, but I haven't been able to find that point (likely via ntc's auto
+ // correct as it's defaulted to be enabled). So to get a time that isn't stuck in the past for
+ // first boot, grab the current real seconds.
+ auto epoch_time{GetEpochTimeFromInitialYear(m_set_sys)};
+ if (user_clock_context == Service::PSC::Time::SystemClockContext{}) {
+ m_steady_clock_resource.GetRtcTimeInSeconds(epoch_time);
+ }
+
+ res = m_time_m->SetupStandardLocalSystemClockCore(user_clock_context, epoch_time);
+ ASSERT(res == ResultSuccess);
+
+ Service::PSC::Time::SystemClockContext network_clock_context{};
+ res = m_set_sys->GetNetworkSystemClockContext(network_clock_context);
+ ASSERT(res == ResultSuccess);
+
+ auto network_accuracy_m{GetSettingsItemValue<s32>(
+ m_set_sys, "time", "standard_network_clock_sufficient_accuracy_minutes")};
+ auto one_minute_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(1)).count()};
+ s64 network_accuracy_ns{network_accuracy_m * one_minute_ns};
+
+ res = m_time_m->SetupStandardNetworkSystemClockCore(network_clock_context, network_accuracy_ns);
+ ASSERT(res == ResultSuccess);
+
+ bool is_automatic_correction_enabled{};
+ res = m_set_sys->IsUserSystemClockAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+ ASSERT(res == ResultSuccess);
+
+ Service::PSC::Time::SteadyClockTimePoint automatic_correction_time_point{};
+ res = m_set_sys->GetUserSystemClockAutomaticCorrectionUpdatedTime(
+ automatic_correction_time_point);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_m->SetupStandardUserSystemClockCore(automatic_correction_time_point,
+ is_automatic_correction_enabled);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_m->SetupEphemeralNetworkSystemClockCore();
+ ASSERT(res == ResultSuccess);
+
+ res = SetupTimeZoneServiceCore();
+ ASSERT(res == ResultSuccess);
+
+ s64 rtc_time_s{};
+ res = m_steady_clock_resource.GetRtcTimeInSeconds(rtc_time_s);
+ ASSERT(res == ResultSuccess);
+
+ // TODO system report "launch"
+ // "rtc_reset" = m_steady_clock_resource.m_rtc_reset
+ // "rtc_value" = rtc_time_s
+
+ m_worker.StartThread();
+
+ m_file_timestamp_worker.m_initialized = true;
+
+ s64 system_clock_time{};
+ if (m_file_timestamp_worker.m_system_clock->GetCurrentTime(system_clock_time) ==
+ ResultSuccess) {
+ Service::PSC::Time::CalendarTime calendar_time{};
+ Service::PSC::Time::CalendarAdditionalInfo calendar_additional{};
+ if (m_file_timestamp_worker.m_time_zone->ToCalendarTimeWithMyRule(
+ calendar_time, calendar_additional, system_clock_time) == ResultSuccess) {
+ // TODO IFileSystemProxy::SetCurrentPosixTime(system_clock_time,
+ // calendar_additional.ut_offset)
+ }
+ }
+}
+
+Result TimeManager::SetupStandardSteadyClockCore() {
+ Common::UUID external_clock_source_id{};
+ auto res = m_set_sys->GetExternalSteadyClockSourceId(external_clock_source_id);
+ ASSERT(res == ResultSuccess);
+
+ s64 external_steady_clock_internal_offset_s{};
+ res = m_set_sys->GetExternalSteadyClockInternalOffset(external_steady_clock_internal_offset_s);
+ ASSERT(res == ResultSuccess);
+
+ auto one_second_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)).count()};
+ s64 external_steady_clock_internal_offset_ns{external_steady_clock_internal_offset_s *
+ one_second_ns};
+
+ s32 standard_steady_clock_test_offset_m{
+ GetSettingsItemValue<s32>(m_set_sys, "time", "standard_steady_clock_test_offset_minutes")};
+ auto one_minute_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(1)).count()};
+ s64 standard_steady_clock_test_offset_ns{standard_steady_clock_test_offset_m * one_minute_ns};
+
+ auto reset_detected = m_steady_clock_resource.GetResetDetected();
+ if (reset_detected) {
+ external_clock_source_id = {};
+ }
+
+ Common::UUID clock_source_id{};
+ m_steady_clock_resource.Initialize(&clock_source_id, &external_clock_source_id);
+
+ if (clock_source_id != external_clock_source_id) {
+ m_set_sys->SetExternalSteadyClockSourceId(clock_source_id);
+ }
+
+ res = m_time_m->SetupStandardSteadyClockCore(clock_source_id, m_steady_clock_resource.GetTime(),
+ external_steady_clock_internal_offset_ns,
+ standard_steady_clock_test_offset_ns,
+ reset_detected);
+ ASSERT(res == ResultSuccess);
+ R_SUCCEED();
+}
+
+Result TimeManager::SetupTimeZoneServiceCore() {
+ Service::PSC::Time::LocationName name{};
+ auto res = m_set_sys->GetDeviceTimeZoneLocationName(name);
+ ASSERT(res == ResultSuccess);
+
+ auto configured_zone = GetTimeZoneString(name);
+
+ if (configured_zone.name != name.name) {
+ m_set_sys->SetDeviceTimeZoneLocationName(configured_zone);
+ name = configured_zone;
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> local_clock;
+ m_time_sm->GetStandardLocalSystemClock(local_clock);
+ Service::PSC::Time::SystemClockContext context{};
+ local_clock->GetSystemClockContext(context);
+ m_set_sys->SetDeviceTimeZoneLocationUpdatedTime(context.steady_time_point);
+ }
+
+ Service::PSC::Time::SteadyClockTimePoint time_point{};
+ res = m_set_sys->GetDeviceTimeZoneLocationUpdatedTime(time_point);
+ ASSERT(res == ResultSuccess);
+
+ auto location_count = GetTimeZoneCount();
+ Service::PSC::Time::RuleVersion rule_version{};
+ GetTimeZoneVersion(rule_version);
+
+ std::span<const u8> rule_buffer{};
+ size_t rule_size{};
+ res = GetTimeZoneRule(rule_buffer, rule_size, name);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_m->SetupTimeZoneServiceCore(name, time_point, rule_version, location_count,
+ rule_buffer);
+ ASSERT(res == ResultSuccess);
+
+ R_SUCCEED();
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/manager.h b/src/core/hle/service/glue/time/manager.h
new file mode 100644
index 000000000..a46ec6364
--- /dev/null
+++ b/src/core/hle/service/glue/time/manager.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <string>
+
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/service/glue/time/file_timestamp_worker.h"
+#include "core/hle/service/glue/time/standard_steady_clock_resource.h"
+#include "core/hle/service/glue/time/worker.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::PSC::Time {
+class ServiceManager;
+class StaticService;
+} // namespace Service::PSC::Time
+
+namespace Service::Glue::Time {
+class TimeManager {
+public:
+ explicit TimeManager(Core::System& system);
+
+ std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
+
+ std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m{};
+ std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm{};
+ StandardSteadyClockResource m_steady_clock_resource;
+ FileTimestampWorker m_file_timestamp_worker;
+ TimeWorker m_worker;
+
+private:
+ Result SetupStandardSteadyClockCore();
+ Result SetupTimeZoneServiceCore();
+};
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/pm_state_change_handler.cpp b/src/core/hle/service/glue/time/pm_state_change_handler.cpp
new file mode 100644
index 000000000..7470fb225
--- /dev/null
+++ b/src/core/hle/service/glue/time/pm_state_change_handler.cpp
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/glue/time/pm_state_change_handler.h"
+
+namespace Service::Glue::Time {
+
+PmStateChangeHandler::PmStateChangeHandler(AlarmWorker& alarm_worker)
+ : m_alarm_worker{alarm_worker} {
+ // TODO Initialize IPmModule, dependent on Rtc and Fs
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/pm_state_change_handler.h b/src/core/hle/service/glue/time/pm_state_change_handler.h
new file mode 100644
index 000000000..27d9f7872
--- /dev/null
+++ b/src/core/hle/service/glue/time/pm_state_change_handler.h
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Service::Glue::Time {
+class AlarmWorker;
+
+class PmStateChangeHandler {
+public:
+ explicit PmStateChangeHandler(AlarmWorker& alarm_worker);
+
+ AlarmWorker& m_alarm_worker;
+ s32 m_priority{};
+};
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp b/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp
new file mode 100644
index 000000000..5ebaa33e0
--- /dev/null
+++ b/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/svc.h"
+#include "core/hle/service/glue/time/standard_steady_clock_resource.h"
+#include "core/hle/service/psc/time/errors.h"
+
+namespace Service::Glue::Time {
+namespace {
+[[maybe_unused]] constexpr u32 Max77620PmicSession = 0x3A000001;
+[[maybe_unused]] constexpr u32 Max77620RtcSession = 0x3B000001;
+
+Result GetTimeInSeconds(Core::System& system, s64& out_time_s) {
+ out_time_s = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+
+ if (Settings::values.custom_rtc_enabled) {
+ out_time_s += Settings::values.custom_rtc_offset.GetValue();
+ }
+ R_SUCCEED();
+}
+} // namespace
+
+StandardSteadyClockResource::StandardSteadyClockResource(Core::System& system) : m_system{system} {}
+
+void StandardSteadyClockResource::Initialize(Common::UUID* out_source_id,
+ Common::UUID* external_source_id) {
+ constexpr size_t NUM_TRIES{20};
+
+ size_t i{0};
+ Result res{ResultSuccess};
+ for (; i < NUM_TRIES; i++) {
+ res = SetCurrentTime();
+ if (res == ResultSuccess) {
+ break;
+ }
+ Kernel::Svc::SleepThread(m_system, std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::milliseconds(1))
+ .count());
+ }
+
+ if (i < NUM_TRIES) {
+ m_set_time_result = ResultSuccess;
+ if (*external_source_id != Service::PSC::Time::ClockSourceId{}) {
+ m_clock_source_id = *external_source_id;
+ } else {
+ m_clock_source_id = Common::UUID::MakeRandom();
+ }
+ } else {
+ m_set_time_result = res;
+ auto ticks{m_system.CoreTiming().GetClockTicks()};
+ m_time = -Service::PSC::Time::ConvertToTimeSpan(ticks).count();
+ m_clock_source_id = Common::UUID::MakeRandom();
+ }
+
+ if (out_source_id) {
+ *out_source_id = m_clock_source_id;
+ }
+}
+
+bool StandardSteadyClockResource::GetResetDetected() {
+ // TODO:
+ // call Rtc::GetRtcResetDetected(Max77620RtcSession)
+ // if detected:
+ // SetSys::SetExternalSteadyClockSourceId(invalid_id)
+ // Rtc::ClearRtcResetDetected(Max77620RtcSession)
+ // set m_rtc_reset to result
+ // Instead, only set reset to true if we're booting for the first time.
+ m_rtc_reset = false;
+ return m_rtc_reset;
+}
+
+Result StandardSteadyClockResource::SetCurrentTime() {
+ auto start_tick{m_system.CoreTiming().GetClockTicks()};
+
+ s64 rtc_time_s{};
+ // TODO R_TRY(Rtc::GetTimeInSeconds(rtc_time_s, Max77620RtcSession))
+ R_TRY(GetTimeInSeconds(m_system, rtc_time_s));
+
+ auto end_tick{m_system.CoreTiming().GetClockTicks()};
+ auto diff{Service::PSC::Time::ConvertToTimeSpan(end_tick - start_tick)};
+ // Why is this here?
+ R_UNLESS(diff < std::chrono::milliseconds(101), Service::PSC::Time::ResultRtcTimeout);
+
+ auto one_second_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)).count()};
+ s64 boot_time{rtc_time_s * one_second_ns -
+ Service::PSC::Time::ConvertToTimeSpan(end_tick).count()};
+
+ std::scoped_lock l{m_mutex};
+ m_time = boot_time;
+ R_SUCCEED();
+}
+
+Result StandardSteadyClockResource::GetRtcTimeInSeconds(s64& out_time) {
+ // TODO
+ // R_TRY(Rtc::GetTimeInSeconds(time_s, Max77620RtcSession)
+ R_RETURN(GetTimeInSeconds(m_system, out_time));
+}
+
+void StandardSteadyClockResource::UpdateTime() {
+ constexpr size_t NUM_TRIES{3};
+
+ size_t i{0};
+ Result res{ResultSuccess};
+ for (; i < NUM_TRIES; i++) {
+ res = SetCurrentTime();
+ if (res == ResultSuccess) {
+ break;
+ }
+ Kernel::Svc::SleepThread(m_system, std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::milliseconds(1))
+ .count());
+ }
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/standard_steady_clock_resource.h b/src/core/hle/service/glue/time/standard_steady_clock_resource.h
new file mode 100644
index 000000000..978d6b63b
--- /dev/null
+++ b/src/core/hle/service/glue/time/standard_steady_clock_resource.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/psc/time/common.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Glue::Time {
+class StandardSteadyClockResource {
+public:
+ StandardSteadyClockResource(Core::System& system);
+
+ void Initialize(Common::UUID* out_source_id, Common::UUID* external_source_id);
+
+ s64 GetTime() const {
+ return m_time;
+ }
+
+ bool GetResetDetected();
+ Result SetCurrentTime();
+ Result GetRtcTimeInSeconds(s64& out_time);
+ void UpdateTime();
+
+private:
+ Core::System& m_system;
+
+ std::mutex m_mutex;
+ Service::PSC::Time::ClockSourceId m_clock_source_id{};
+ s64 m_time{};
+ Result m_set_time_result;
+ bool m_rtc_reset;
+};
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/static.cpp b/src/core/hle/service/glue/time/static.cpp
new file mode 100644
index 000000000..63b7d91da
--- /dev/null
+++ b/src/core/hle/service/glue/time/static.cpp
@@ -0,0 +1,448 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "core/core.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/svc.h"
+#include "core/hle/service/glue/time/file_timestamp_worker.h"
+#include "core/hle/service/glue/time/static.h"
+#include "core/hle/service/psc/time/errors.h"
+#include "core/hle/service/psc/time/service_manager.h"
+#include "core/hle/service/psc/time/static.h"
+#include "core/hle/service/psc/time/steady_clock.h"
+#include "core/hle/service/psc/time/system_clock.h"
+#include "core/hle/service/psc/time/time_zone_service.h"
+#include "core/hle/service/set/system_settings_server.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Glue::Time {
+namespace {
+template <typename T>
+T GetSettingsItemValue(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys,
+ const char* category, const char* name) {
+ std::vector<u8> interval_buf;
+ auto res = set_sys->GetSettingsItemValue(interval_buf, category, name);
+ ASSERT(res == ResultSuccess);
+
+ T v{};
+ std::memcpy(&v, interval_buf.data(), sizeof(T));
+ return v;
+}
+} // namespace
+
+StaticService::StaticService(Core::System& system_,
+ Service::PSC::Time::StaticServiceSetupInfo setup_info,
+ std::shared_ptr<TimeManager> time, const char* name)
+ : ServiceFramework{system_, name}, m_system{system_}, m_time_m{time->m_time_m},
+ m_setup_info{setup_info}, m_time_sm{time->m_time_sm},
+ m_file_timestamp_worker{time->m_file_timestamp_worker}, m_standard_steady_clock_resource{
+ time->m_steady_clock_resource} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &StaticService::Handle_GetStandardUserSystemClock, "GetStandardUserSystemClock"},
+ {1, &StaticService::Handle_GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"},
+ {2, &StaticService::Handle_GetStandardSteadyClock, "GetStandardSteadyClock"},
+ {3, &StaticService::Handle_GetTimeZoneService, "GetTimeZoneService"},
+ {4, &StaticService::Handle_GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
+ {5, &StaticService::Handle_GetEphemeralNetworkSystemClock, "GetEphemeralNetworkSystemClock"},
+ {20, &StaticService::Handle_GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
+ {50, &StaticService::Handle_SetStandardSteadyClockInternalOffset, "SetStandardSteadyClockInternalOffset"},
+ {51, &StaticService::Handle_GetStandardSteadyClockRtcValue, "GetStandardSteadyClockRtcValue"},
+ {100, &StaticService::Handle_IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {101, &StaticService::Handle_SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {102, &StaticService::Handle_GetStandardUserSystemClockInitialYear, "GetStandardUserSystemClockInitialYear"},
+ {200, &StaticService::Handle_IsStandardNetworkSystemClockAccuracySufficient, "IsStandardNetworkSystemClockAccuracySufficient"},
+ {201, &StaticService::Handle_GetStandardUserSystemClockAutomaticCorrectionUpdatedTime, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"},
+ {300, &StaticService::Handle_CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"},
+ {400, &StaticService::Handle_GetClockSnapshot, "GetClockSnapshot"},
+ {401, &StaticService::Handle_GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"},
+ {500, &StaticService::Handle_CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"},
+ {501, &StaticService::Handle_CalculateSpanBetween, "CalculateSpanBetween"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ m_set_sys =
+ m_system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
+
+ if (m_setup_info.can_write_local_clock && m_setup_info.can_write_user_clock &&
+ !m_setup_info.can_write_network_clock && m_setup_info.can_write_timezone_device_location &&
+ !m_setup_info.can_write_steady_clock && !m_setup_info.can_write_uninitialized_clock) {
+ m_time_m->GetStaticServiceAsAdmin(m_wrapped_service);
+ } else if (!m_setup_info.can_write_local_clock && !m_setup_info.can_write_user_clock &&
+ !m_setup_info.can_write_network_clock &&
+ !m_setup_info.can_write_timezone_device_location &&
+ !m_setup_info.can_write_steady_clock &&
+ !m_setup_info.can_write_uninitialized_clock) {
+ m_time_m->GetStaticServiceAsUser(m_wrapped_service);
+ } else if (!m_setup_info.can_write_local_clock && !m_setup_info.can_write_user_clock &&
+ !m_setup_info.can_write_network_clock &&
+ !m_setup_info.can_write_timezone_device_location &&
+ m_setup_info.can_write_steady_clock && !m_setup_info.can_write_uninitialized_clock) {
+ m_time_m->GetStaticServiceAsRepair(m_wrapped_service);
+ } else {
+ UNREACHABLE();
+ }
+
+ auto res = m_wrapped_service->GetTimeZoneService(m_time_zone);
+ ASSERT(res == ResultSuccess);
+}
+
+void StaticService::Handle_GetStandardUserSystemClock(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> service{};
+ auto res = GetStandardUserSystemClock(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface<Service::PSC::Time::SystemClock>(std::move(service));
+}
+
+void StaticService::Handle_GetStandardNetworkSystemClock(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> service{};
+ auto res = GetStandardNetworkSystemClock(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface<Service::PSC::Time::SystemClock>(std::move(service));
+}
+
+void StaticService::Handle_GetStandardSteadyClock(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<Service::PSC::Time::SteadyClock> service{};
+ auto res = GetStandardSteadyClock(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface(std::move(service));
+}
+
+void StaticService::Handle_GetTimeZoneService(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<TimeZoneService> service{};
+ auto res = GetTimeZoneService(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface(std::move(service));
+}
+
+void StaticService::Handle_GetStandardLocalSystemClock(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> service{};
+ auto res = GetStandardLocalSystemClock(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface<Service::PSC::Time::SystemClock>(std::move(service));
+}
+
+void StaticService::Handle_GetEphemeralNetworkSystemClock(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ std::shared_ptr<Service::PSC::Time::SystemClock> service{};
+ auto res = GetEphemeralNetworkSystemClock(service);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(res);
+ rb.PushIpcInterface<Service::PSC::Time::SystemClock>(std::move(service));
+}
+
+void StaticService::Handle_GetSharedMemoryNativeHandle(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Kernel::KSharedMemory* shared_memory{};
+ auto res = GetSharedMemoryNativeHandle(&shared_memory);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(res);
+ rb.PushCopyObjects(shared_memory);
+}
+
+void StaticService::Handle_SetStandardSteadyClockInternalOffset(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto offset_ns{rp.Pop<s64>()};
+
+ auto res = SetStandardSteadyClockInternalOffset(offset_ns);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void StaticService::Handle_GetStandardSteadyClockRtcValue(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ s64 rtc_value{};
+ auto res = GetStandardSteadyClockRtcValue(rtc_value);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(res);
+ rb.Push(rtc_value);
+}
+
+void StaticService::Handle_IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ bool is_enabled{};
+ auto res = IsStandardUserSystemClockAutomaticCorrectionEnabled(is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push<bool>(is_enabled);
+}
+
+void StaticService::Handle_SetStandardUserSystemClockAutomaticCorrectionEnabled(
+ HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto automatic_correction{rp.Pop<bool>()};
+
+ auto res = SetStandardUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void StaticService::Handle_GetStandardUserSystemClockInitialYear(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ s32 initial_year{};
+ auto res = GetStandardUserSystemClockInitialYear(initial_year);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push(initial_year);
+}
+
+void StaticService::Handle_IsStandardNetworkSystemClockAccuracySufficient(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ bool is_sufficient{};
+ auto res = IsStandardNetworkSystemClockAccuracySufficient(is_sufficient);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push<bool>(is_sufficient);
+}
+
+void StaticService::Handle_GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
+ HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::SteadyClockTimePoint time_point{};
+ auto res = GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
+
+ IPC::ResponseBuilder rb{ctx,
+ 2 + sizeof(Service::PSC::Time::SteadyClockTimePoint) / sizeof(u32)};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::SteadyClockTimePoint>(time_point);
+}
+
+void StaticService::Handle_CalculateMonotonicSystemClockBaseTimePoint(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto context{rp.PopRaw<Service::PSC::Time::SystemClockContext>()};
+
+ s64 time{};
+ auto res = CalculateMonotonicSystemClockBaseTimePoint(time, context);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(res);
+ rb.Push<s64>(time);
+}
+
+void StaticService::Handle_GetClockSnapshot(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto type{rp.PopEnum<Service::PSC::Time::TimeType>()};
+
+ Service::PSC::Time::ClockSnapshot snapshot{};
+ auto res = GetClockSnapshot(snapshot, type);
+
+ ctx.WriteBuffer(snapshot);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void StaticService::Handle_GetClockSnapshotFromSystemClockContext(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto clock_type{rp.PopEnum<Service::PSC::Time::TimeType>()};
+ [[maybe_unused]] auto alignment{rp.Pop<u32>()};
+ auto user_context{rp.PopRaw<Service::PSC::Time::SystemClockContext>()};
+ auto network_context{rp.PopRaw<Service::PSC::Time::SystemClockContext>()};
+
+ Service::PSC::Time::ClockSnapshot snapshot{};
+ auto res =
+ GetClockSnapshotFromSystemClockContext(snapshot, user_context, network_context, clock_type);
+
+ ctx.WriteBuffer(snapshot);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void StaticService::Handle_CalculateStandardUserSystemClockDifferenceByUser(
+ HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::ClockSnapshot a{};
+ Service::PSC::Time::ClockSnapshot b{};
+
+ auto a_buffer{ctx.ReadBuffer(0)};
+ auto b_buffer{ctx.ReadBuffer(1)};
+
+ std::memcpy(&a, a_buffer.data(), sizeof(Service::PSC::Time::ClockSnapshot));
+ std::memcpy(&b, b_buffer.data(), sizeof(Service::PSC::Time::ClockSnapshot));
+
+ s64 difference{};
+ auto res = CalculateStandardUserSystemClockDifferenceByUser(difference, a, b);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(res);
+ rb.Push(difference);
+}
+
+void StaticService::Handle_CalculateSpanBetween(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::ClockSnapshot a{};
+ Service::PSC::Time::ClockSnapshot b{};
+
+ auto a_buffer{ctx.ReadBuffer(0)};
+ auto b_buffer{ctx.ReadBuffer(1)};
+
+ std::memcpy(&a, a_buffer.data(), sizeof(Service::PSC::Time::ClockSnapshot));
+ std::memcpy(&b, b_buffer.data(), sizeof(Service::PSC::Time::ClockSnapshot));
+
+ s64 time{};
+ auto res = CalculateSpanBetween(time, a, b);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(res);
+ rb.Push(time);
+}
+
+// =============================== Implementations ===========================
+
+Result StaticService::GetStandardUserSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service) {
+ R_RETURN(m_wrapped_service->GetStandardUserSystemClock(out_service));
+}
+
+Result StaticService::GetStandardNetworkSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service) {
+ R_RETURN(m_wrapped_service->GetStandardNetworkSystemClock(out_service));
+}
+
+Result StaticService::GetStandardSteadyClock(
+ std::shared_ptr<Service::PSC::Time::SteadyClock>& out_service) {
+ R_RETURN(m_wrapped_service->GetStandardSteadyClock(out_service));
+}
+
+Result StaticService::GetTimeZoneService(std::shared_ptr<TimeZoneService>& out_service) {
+ out_service = std::make_shared<TimeZoneService>(m_system, m_file_timestamp_worker,
+ m_setup_info.can_write_timezone_device_location,
+ m_time_zone);
+ R_SUCCEED();
+}
+
+Result StaticService::GetStandardLocalSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service) {
+ R_RETURN(m_wrapped_service->GetStandardLocalSystemClock(out_service));
+}
+
+Result StaticService::GetEphemeralNetworkSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service) {
+ R_RETURN(m_wrapped_service->GetEphemeralNetworkSystemClock(out_service));
+}
+
+Result StaticService::GetSharedMemoryNativeHandle(Kernel::KSharedMemory** out_shared_memory) {
+ R_RETURN(m_wrapped_service->GetSharedMemoryNativeHandle(out_shared_memory));
+}
+
+Result StaticService::SetStandardSteadyClockInternalOffset(s64 offset_ns) {
+ R_UNLESS(m_setup_info.can_write_steady_clock, Service::PSC::Time::ResultPermissionDenied);
+
+ R_RETURN(m_set_sys->SetExternalSteadyClockInternalOffset(
+ offset_ns /
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)).count()));
+}
+
+Result StaticService::GetStandardSteadyClockRtcValue(s64& out_rtc_value) {
+ R_RETURN(m_standard_steady_clock_resource.GetRtcTimeInSeconds(out_rtc_value));
+}
+
+Result StaticService::IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ bool& out_automatic_correction) {
+ R_RETURN(m_wrapped_service->IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ out_automatic_correction));
+}
+
+Result StaticService::SetStandardUserSystemClockAutomaticCorrectionEnabled(
+ bool automatic_correction) {
+ R_RETURN(m_wrapped_service->SetStandardUserSystemClockAutomaticCorrectionEnabled(
+ automatic_correction));
+}
+
+Result StaticService::GetStandardUserSystemClockInitialYear(s32& out_year) {
+ out_year = GetSettingsItemValue<s32>(m_set_sys, "time", "standard_user_clock_initial_year");
+ R_SUCCEED();
+}
+
+Result StaticService::IsStandardNetworkSystemClockAccuracySufficient(bool& out_is_sufficient) {
+ R_RETURN(m_wrapped_service->IsStandardNetworkSystemClockAccuracySufficient(out_is_sufficient));
+}
+
+Result StaticService::GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
+ Service::PSC::Time::SteadyClockTimePoint& out_time_point) {
+ R_RETURN(m_wrapped_service->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
+ out_time_point));
+}
+
+Result StaticService::CalculateMonotonicSystemClockBaseTimePoint(
+ s64& out_time, Service::PSC::Time::SystemClockContext& context) {
+ R_RETURN(m_wrapped_service->CalculateMonotonicSystemClockBaseTimePoint(out_time, context));
+}
+
+Result StaticService::GetClockSnapshot(Service::PSC::Time::ClockSnapshot& out_snapshot,
+ Service::PSC::Time::TimeType type) {
+ R_RETURN(m_wrapped_service->GetClockSnapshot(out_snapshot, type));
+}
+
+Result StaticService::GetClockSnapshotFromSystemClockContext(
+ Service::PSC::Time::ClockSnapshot& out_snapshot,
+ Service::PSC::Time::SystemClockContext& user_context,
+ Service::PSC::Time::SystemClockContext& network_context, Service::PSC::Time::TimeType type) {
+ R_RETURN(m_wrapped_service->GetClockSnapshotFromSystemClockContext(out_snapshot, user_context,
+ network_context, type));
+}
+
+Result StaticService::CalculateStandardUserSystemClockDifferenceByUser(
+ s64& out_time, Service::PSC::Time::ClockSnapshot& a, Service::PSC::Time::ClockSnapshot& b) {
+ R_RETURN(m_wrapped_service->CalculateStandardUserSystemClockDifferenceByUser(out_time, a, b));
+}
+
+Result StaticService::CalculateSpanBetween(s64& out_time, Service::PSC::Time::ClockSnapshot& a,
+ Service::PSC::Time::ClockSnapshot& b) {
+ R_RETURN(m_wrapped_service->CalculateSpanBetween(out_time, a, b));
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/static.h b/src/core/hle/service/glue/time/static.h
new file mode 100644
index 000000000..75fe4e2cd
--- /dev/null
+++ b/src/core/hle/service/glue/time/static.h
@@ -0,0 +1,110 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/glue/time/manager.h"
+#include "core/hle/service/glue/time/time_zone.h"
+#include "core/hle/service/psc/time/common.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Set {
+class ISystemSettingsServer;
+}
+
+namespace Service::PSC::Time {
+class StaticService;
+class SystemClock;
+class SteadyClock;
+class TimeZoneService;
+class ServiceManager;
+} // namespace Service::PSC::Time
+
+namespace Service::Glue::Time {
+class FileTimestampWorker;
+class StandardSteadyClockResource;
+
+class StaticService final : public ServiceFramework<StaticService> {
+public:
+ explicit StaticService(Core::System& system,
+ Service::PSC::Time::StaticServiceSetupInfo setup_info,
+ std::shared_ptr<TimeManager> time, const char* name);
+
+ ~StaticService() override = default;
+
+ Result GetStandardUserSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service);
+ Result GetStandardNetworkSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service);
+ Result GetStandardSteadyClock(std::shared_ptr<Service::PSC::Time::SteadyClock>& out_service);
+ Result GetTimeZoneService(std::shared_ptr<TimeZoneService>& out_service);
+ Result GetStandardLocalSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service);
+ Result GetEphemeralNetworkSystemClock(
+ std::shared_ptr<Service::PSC::Time::SystemClock>& out_service);
+ Result GetSharedMemoryNativeHandle(Kernel::KSharedMemory** out_shared_memory);
+ Result SetStandardSteadyClockInternalOffset(s64 offset);
+ Result GetStandardSteadyClockRtcValue(s64& out_rtc_value);
+ Result IsStandardUserSystemClockAutomaticCorrectionEnabled(bool& out_automatic_correction);
+ Result SetStandardUserSystemClockAutomaticCorrectionEnabled(bool automatic_correction);
+ Result GetStandardUserSystemClockInitialYear(s32& out_year);
+ Result IsStandardNetworkSystemClockAccuracySufficient(bool& out_is_sufficient);
+ Result GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(
+ Service::PSC::Time::SteadyClockTimePoint& out_time_point);
+ Result CalculateMonotonicSystemClockBaseTimePoint(
+ s64& out_time, Service::PSC::Time::SystemClockContext& context);
+ Result GetClockSnapshot(Service::PSC::Time::ClockSnapshot& out_snapshot,
+ Service::PSC::Time::TimeType type);
+ Result GetClockSnapshotFromSystemClockContext(
+ Service::PSC::Time::ClockSnapshot& out_snapshot,
+ Service::PSC::Time::SystemClockContext& user_context,
+ Service::PSC::Time::SystemClockContext& network_context, Service::PSC::Time::TimeType type);
+ Result CalculateStandardUserSystemClockDifferenceByUser(s64& out_time,
+ Service::PSC::Time::ClockSnapshot& a,
+ Service::PSC::Time::ClockSnapshot& b);
+ Result CalculateSpanBetween(s64& out_time, Service::PSC::Time::ClockSnapshot& a,
+ Service::PSC::Time::ClockSnapshot& b);
+
+private:
+ Result GetClockSnapshotImpl(Service::PSC::Time::ClockSnapshot& out_snapshot,
+ Service::PSC::Time::SystemClockContext& user_context,
+ Service::PSC::Time::SystemClockContext& network_context,
+ Service::PSC::Time::TimeType type);
+
+ void Handle_GetStandardUserSystemClock(HLERequestContext& ctx);
+ void Handle_GetStandardNetworkSystemClock(HLERequestContext& ctx);
+ void Handle_GetStandardSteadyClock(HLERequestContext& ctx);
+ void Handle_GetTimeZoneService(HLERequestContext& ctx);
+ void Handle_GetStandardLocalSystemClock(HLERequestContext& ctx);
+ void Handle_GetEphemeralNetworkSystemClock(HLERequestContext& ctx);
+ void Handle_GetSharedMemoryNativeHandle(HLERequestContext& ctx);
+ void Handle_SetStandardSteadyClockInternalOffset(HLERequestContext& ctx);
+ void Handle_GetStandardSteadyClockRtcValue(HLERequestContext& ctx);
+ void Handle_IsStandardUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx);
+ void Handle_SetStandardUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx);
+ void Handle_GetStandardUserSystemClockInitialYear(HLERequestContext& ctx);
+ void Handle_IsStandardNetworkSystemClockAccuracySufficient(HLERequestContext& ctx);
+ void Handle_GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(HLERequestContext& ctx);
+ void Handle_CalculateMonotonicSystemClockBaseTimePoint(HLERequestContext& ctx);
+ void Handle_GetClockSnapshot(HLERequestContext& ctx);
+ void Handle_GetClockSnapshotFromSystemClockContext(HLERequestContext& ctx);
+ void Handle_CalculateStandardUserSystemClockDifferenceByUser(HLERequestContext& ctx);
+ void Handle_CalculateSpanBetween(HLERequestContext& ctx);
+
+ Core::System& m_system;
+
+ std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
+ std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
+ std::shared_ptr<Service::PSC::Time::StaticService> m_wrapped_service;
+
+ Service::PSC::Time::StaticServiceSetupInfo m_setup_info;
+ std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
+ std::shared_ptr<Service::PSC::Time::TimeZoneService> m_time_zone;
+ FileTimestampWorker& m_file_timestamp_worker;
+ StandardSteadyClockResource& m_standard_steady_clock_resource;
+};
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/time_zone.cpp b/src/core/hle/service/glue/time/time_zone.cpp
new file mode 100644
index 000000000..503c327dd
--- /dev/null
+++ b/src/core/hle/service/glue/time/time_zone.cpp
@@ -0,0 +1,377 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <chrono>
+
+#include "core/core.h"
+#include "core/hle/kernel/svc.h"
+#include "core/hle/service/glue/time/file_timestamp_worker.h"
+#include "core/hle/service/glue/time/time_zone.h"
+#include "core/hle/service/glue/time/time_zone_binary.h"
+#include "core/hle/service/psc/time/time_zone_service.h"
+#include "core/hle/service/set/system_settings_server.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Glue::Time {
+namespace {
+static std::mutex g_list_mutex;
+static Common::IntrusiveListBaseTraits<Service::PSC::Time::OperationEvent>::ListType g_list_nodes{};
+} // namespace
+
+TimeZoneService::TimeZoneService(
+ Core::System& system_, FileTimestampWorker& file_timestamp_worker,
+ bool can_write_timezone_device_location,
+ std::shared_ptr<Service::PSC::Time::TimeZoneService> time_zone_service)
+ : ServiceFramework{system_, "ITimeZoneService"}, m_system{system},
+ m_can_write_timezone_device_location{can_write_timezone_device_location},
+ m_file_timestamp_worker{file_timestamp_worker},
+ m_wrapped_service{std::move(time_zone_service)}, m_operation_event{m_system} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &TimeZoneService::Handle_GetDeviceLocationName, "GetDeviceLocationName"},
+ {1, &TimeZoneService::Handle_SetDeviceLocationName, "SetDeviceLocationName"},
+ {2, &TimeZoneService::Handle_GetTotalLocationNameCount, "GetTotalLocationNameCount"},
+ {3, &TimeZoneService::Handle_LoadLocationNameList, "LoadLocationNameList"},
+ {4, &TimeZoneService::Handle_LoadTimeZoneRule, "LoadTimeZoneRule"},
+ {5, &TimeZoneService::Handle_GetTimeZoneRuleVersion, "GetTimeZoneRuleVersion"},
+ {6, &TimeZoneService::Handle_GetDeviceLocationNameAndUpdatedTime, "GetDeviceLocationNameAndUpdatedTime"},
+ {7, &TimeZoneService::Handle_SetDeviceLocationNameWithTimeZoneRule, "SetDeviceLocationNameWithTimeZoneRule"},
+ {8, &TimeZoneService::Handle_ParseTimeZoneBinary, "ParseTimeZoneBinary"},
+ {20, &TimeZoneService::Handle_GetDeviceLocationNameOperationEventReadableHandle, "GetDeviceLocationNameOperationEventReadableHandle"},
+ {100, &TimeZoneService::Handle_ToCalendarTime, "ToCalendarTime"},
+ {101, &TimeZoneService::Handle_ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
+ {201, &TimeZoneService::Handle_ToPosixTime, "ToPosixTime"},
+ {202, &TimeZoneService::Handle_ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"},
+ };
+ // clang-format on
+ RegisterHandlers(functions);
+
+ g_list_nodes.clear();
+ m_set_sys =
+ m_system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
+}
+
+TimeZoneService::~TimeZoneService() = default;
+
+void TimeZoneService::Handle_GetDeviceLocationName(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::LocationName name{};
+ auto res = GetDeviceLocationName(name);
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(Service::PSC::Time::LocationName) / sizeof(u32)};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::LocationName>(name);
+}
+
+void TimeZoneService::Handle_SetDeviceLocationName(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto name{rp.PopRaw<Service::PSC::Time::LocationName>()};
+
+ auto res = SetDeviceLocation(name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void TimeZoneService::Handle_GetTotalLocationNameCount(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ u32 count{};
+ auto res = GetTotalLocationNameCount(count);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push(count);
+}
+
+void TimeZoneService::Handle_LoadLocationNameList(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto index{rp.Pop<u32>()};
+
+ auto max_names{ctx.GetWriteBufferSize() / sizeof(Service::PSC::Time::LocationName)};
+
+ std::vector<Service::PSC::Time::LocationName> names{};
+ u32 count{};
+ auto res = LoadLocationNameList(count, names, max_names, index);
+
+ ctx.WriteBuffer(names);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push(count);
+}
+
+void TimeZoneService::Handle_LoadTimeZoneRule(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto name{rp.PopRaw<Service::PSC::Time::LocationName>()};
+
+ Tz::Rule rule{};
+ auto res = LoadTimeZoneRule(rule, name);
+
+ ctx.WriteBuffer(rule);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void TimeZoneService::Handle_GetTimeZoneRuleVersion(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::RuleVersion rule_version{};
+ auto res = GetTimeZoneRuleVersion(rule_version);
+
+ IPC::ResponseBuilder rb{ctx, 2 + sizeof(Service::PSC::Time::RuleVersion) / sizeof(u32)};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::RuleVersion>(rule_version);
+}
+
+void TimeZoneService::Handle_GetDeviceLocationNameAndUpdatedTime(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Service::PSC::Time::LocationName name{};
+ Service::PSC::Time::SteadyClockTimePoint time_point{};
+ auto res = GetDeviceLocationNameAndUpdatedTime(time_point, name);
+
+ IPC::ResponseBuilder rb{ctx,
+ 2 + (sizeof(Service::PSC::Time::LocationName) / sizeof(u32)) +
+ (sizeof(Service::PSC::Time::SteadyClockTimePoint) / sizeof(u32))};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::LocationName>(name);
+ rb.PushRaw<Service::PSC::Time::SteadyClockTimePoint>(time_point);
+}
+
+void TimeZoneService::Handle_SetDeviceLocationNameWithTimeZoneRule(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ auto res = SetDeviceLocationNameWithTimeZoneRule();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(res);
+}
+
+void TimeZoneService::Handle_ParseTimeZoneBinary(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(Service::PSC::Time::ResultNotImplemented);
+}
+
+void TimeZoneService::Handle_GetDeviceLocationNameOperationEventReadableHandle(
+ HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ Kernel::KEvent* event{};
+ auto res = GetDeviceLocationNameOperationEventReadableHandle(&event);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(res);
+ rb.PushCopyObjects(event->GetReadableEvent());
+}
+
+void TimeZoneService::Handle_ToCalendarTime(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto time{rp.Pop<s64>()};
+
+ auto rule_buffer{ctx.ReadBuffer()};
+ Tz::Rule rule{};
+ std::memcpy(&rule, rule_buffer.data(), sizeof(Tz::Rule));
+
+ Service::PSC::Time::CalendarTime calendar_time{};
+ Service::PSC::Time::CalendarAdditionalInfo additional_info{};
+ auto res = ToCalendarTime(calendar_time, additional_info, time, rule);
+
+ IPC::ResponseBuilder rb{ctx,
+ 2 + (sizeof(Service::PSC::Time::CalendarTime) / sizeof(u32)) +
+ (sizeof(Service::PSC::Time::CalendarAdditionalInfo) / sizeof(u32))};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::CalendarTime>(calendar_time);
+ rb.PushRaw<Service::PSC::Time::CalendarAdditionalInfo>(additional_info);
+}
+
+void TimeZoneService::Handle_ToCalendarTimeWithMyRule(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto time{rp.Pop<s64>()};
+
+ LOG_DEBUG(Service_Time, "called. time={}", time);
+
+ Service::PSC::Time::CalendarTime calendar_time{};
+ Service::PSC::Time::CalendarAdditionalInfo additional_info{};
+ auto res = ToCalendarTimeWithMyRule(calendar_time, additional_info, time);
+
+ IPC::ResponseBuilder rb{ctx,
+ 2 + (sizeof(Service::PSC::Time::CalendarTime) / sizeof(u32)) +
+ (sizeof(Service::PSC::Time::CalendarAdditionalInfo) / sizeof(u32))};
+ rb.Push(res);
+ rb.PushRaw<Service::PSC::Time::CalendarTime>(calendar_time);
+ rb.PushRaw<Service::PSC::Time::CalendarAdditionalInfo>(additional_info);
+}
+
+void TimeZoneService::Handle_ToPosixTime(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto calendar{rp.PopRaw<Service::PSC::Time::CalendarTime>()};
+
+ LOG_DEBUG(Service_Time, "called. calendar year {} month {} day {} hour {} minute {} second {}",
+ calendar.year, calendar.month, calendar.day, calendar.hour, calendar.minute,
+ calendar.second);
+
+ auto binary{ctx.ReadBuffer()};
+
+ Tz::Rule rule{};
+ std::memcpy(&rule, binary.data(), sizeof(Tz::Rule));
+
+ u32 count{};
+ std::array<s64, 2> times{};
+ u32 times_count{static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(s64))};
+
+ auto res = ToPosixTime(count, times, times_count, calendar, rule);
+
+ ctx.WriteBuffer(times);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push(count);
+}
+
+void TimeZoneService::Handle_ToPosixTimeWithMyRule(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called.");
+
+ IPC::RequestParser rp{ctx};
+ auto calendar{rp.PopRaw<Service::PSC::Time::CalendarTime>()};
+
+ u32 count{};
+ std::array<s64, 2> times{};
+ u32 times_count{static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(s64))};
+
+ auto res = ToPosixTimeWithMyRule(count, times, times_count, calendar);
+
+ ctx.WriteBuffer(times);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(res);
+ rb.Push(count);
+}
+
+// =============================== Implementations ===========================
+
+Result TimeZoneService::GetDeviceLocationName(Service::PSC::Time::LocationName& out_location_name) {
+ R_RETURN(m_wrapped_service->GetDeviceLocationName(out_location_name));
+}
+
+Result TimeZoneService::SetDeviceLocation(Service::PSC::Time::LocationName& location_name) {
+ R_UNLESS(m_can_write_timezone_device_location, Service::PSC::Time::ResultPermissionDenied);
+ R_UNLESS(IsTimeZoneBinaryValid(location_name), Service::PSC::Time::ResultTimeZoneNotFound);
+
+ std::scoped_lock l{m_mutex};
+
+ std::span<const u8> binary{};
+ size_t binary_size{};
+ R_TRY(GetTimeZoneRule(binary, binary_size, location_name))
+
+ R_TRY(m_wrapped_service->SetDeviceLocationNameWithTimeZoneRule(location_name, binary));
+
+ m_file_timestamp_worker.SetFilesystemPosixTime();
+
+ Service::PSC::Time::SteadyClockTimePoint time_point{};
+ Service::PSC::Time::LocationName name{};
+ R_TRY(m_wrapped_service->GetDeviceLocationNameAndUpdatedTime(time_point, name));
+
+ m_set_sys->SetDeviceTimeZoneLocationName(name);
+ m_set_sys->SetDeviceTimeZoneLocationUpdatedTime(time_point);
+
+ std::scoped_lock m{g_list_mutex};
+ for (auto& operation_event : g_list_nodes) {
+ operation_event.m_event->Signal();
+ }
+ R_SUCCEED();
+}
+
+Result TimeZoneService::GetTotalLocationNameCount(u32& out_count) {
+ R_RETURN(m_wrapped_service->GetTotalLocationNameCount(out_count));
+}
+
+Result TimeZoneService::LoadLocationNameList(
+ u32& out_count, std::vector<Service::PSC::Time::LocationName>& out_names, size_t max_names,
+ u32 index) {
+ std::scoped_lock l{m_mutex};
+ R_RETURN(GetTimeZoneLocationList(out_count, out_names, max_names, index));
+}
+
+Result TimeZoneService::LoadTimeZoneRule(Tz::Rule& out_rule,
+ Service::PSC::Time::LocationName& name) {
+ std::scoped_lock l{m_mutex};
+ std::span<const u8> binary{};
+ size_t binary_size{};
+ R_TRY(GetTimeZoneRule(binary, binary_size, name))
+ R_RETURN(m_wrapped_service->ParseTimeZoneBinary(out_rule, binary));
+}
+
+Result TimeZoneService::GetTimeZoneRuleVersion(Service::PSC::Time::RuleVersion& out_rule_version) {
+ R_RETURN(m_wrapped_service->GetTimeZoneRuleVersion(out_rule_version));
+}
+
+Result TimeZoneService::GetDeviceLocationNameAndUpdatedTime(
+ Service::PSC::Time::SteadyClockTimePoint& out_time_point,
+ Service::PSC::Time::LocationName& location_name) {
+ R_RETURN(m_wrapped_service->GetDeviceLocationNameAndUpdatedTime(out_time_point, location_name));
+}
+
+Result TimeZoneService::SetDeviceLocationNameWithTimeZoneRule() {
+ R_UNLESS(m_can_write_timezone_device_location, Service::PSC::Time::ResultPermissionDenied);
+ R_RETURN(Service::PSC::Time::ResultNotImplemented);
+}
+
+Result TimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(
+ Kernel::KEvent** out_event) {
+ if (!operation_event_initialized) {
+ operation_event_initialized = false;
+
+ m_operation_event.m_ctx.CloseEvent(m_operation_event.m_event);
+ m_operation_event.m_event =
+ m_operation_event.m_ctx.CreateEvent("Psc:TimeZoneService:OperationEvent");
+ operation_event_initialized = true;
+ std::scoped_lock l{m_mutex};
+ g_list_nodes.push_back(m_operation_event);
+ }
+
+ *out_event = m_operation_event.m_event;
+ R_SUCCEED();
+}
+
+Result TimeZoneService::ToCalendarTime(
+ Service::PSC::Time::CalendarTime& out_calendar_time,
+ Service::PSC::Time::CalendarAdditionalInfo& out_additional_info, s64 time, Tz::Rule& rule) {
+ R_RETURN(m_wrapped_service->ToCalendarTime(out_calendar_time, out_additional_info, time, rule));
+}
+
+Result TimeZoneService::ToCalendarTimeWithMyRule(
+ Service::PSC::Time::CalendarTime& out_calendar_time,
+ Service::PSC::Time::CalendarAdditionalInfo& out_additional_info, s64 time) {
+ R_RETURN(
+ m_wrapped_service->ToCalendarTimeWithMyRule(out_calendar_time, out_additional_info, time));
+}
+
+Result TimeZoneService::ToPosixTime(u32& out_count, std::span<s64, 2> out_times,
+ u32 out_times_count,
+ Service::PSC::Time::CalendarTime& calendar_time,
+ Tz::Rule& rule) {
+ R_RETURN(
+ m_wrapped_service->ToPosixTime(out_count, out_times, out_times_count, calendar_time, rule));
+}
+
+Result TimeZoneService::ToPosixTimeWithMyRule(u32& out_count, std::span<s64, 2> out_times,
+ u32 out_times_count,
+ Service::PSC::Time::CalendarTime& calendar_time) {
+ R_RETURN(m_wrapped_service->ToPosixTimeWithMyRule(out_count, out_times, out_times_count,
+ calendar_time));
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/time_zone.h b/src/core/hle/service/glue/time/time_zone.h
new file mode 100644
index 000000000..3c8ae4bf8
--- /dev/null
+++ b/src/core/hle/service/glue/time/time_zone.h
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <span>
+#include <vector>
+
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/psc/time/common.h"
+#include "core/hle/service/server_manager.h"
+#include "core/hle/service/service.h"
+
+namespace Core {
+class System;
+}
+
+namespace Tz {
+struct Rule;
+}
+
+namespace Service::Set {
+class ISystemSettingsServer;
+}
+
+namespace Service::PSC::Time {
+class TimeZoneService;
+}
+
+namespace Service::Glue::Time {
+class FileTimestampWorker;
+
+class TimeZoneService final : public ServiceFramework<TimeZoneService> {
+public:
+ explicit TimeZoneService(
+ Core::System& system, FileTimestampWorker& file_timestamp_worker,
+ bool can_write_timezone_device_location,
+ std::shared_ptr<Service::PSC::Time::TimeZoneService> time_zone_service);
+
+ ~TimeZoneService() override;
+
+ Result GetDeviceLocationName(Service::PSC::Time::LocationName& out_location_name);
+ Result SetDeviceLocation(Service::PSC::Time::LocationName& location_name);
+ Result GetTotalLocationNameCount(u32& out_count);
+ Result LoadLocationNameList(u32& out_count,
+ std::vector<Service::PSC::Time::LocationName>& out_names,
+ size_t max_names, u32 index);
+ Result LoadTimeZoneRule(Tz::Rule& out_rule, Service::PSC::Time::LocationName& name);
+ Result GetTimeZoneRuleVersion(Service::PSC::Time::RuleVersion& out_rule_version);
+ Result GetDeviceLocationNameAndUpdatedTime(
+ Service::PSC::Time::SteadyClockTimePoint& out_time_point,
+ Service::PSC::Time::LocationName& location_name);
+ Result SetDeviceLocationNameWithTimeZoneRule();
+ Result GetDeviceLocationNameOperationEventReadableHandle(Kernel::KEvent** out_event);
+ Result ToCalendarTime(Service::PSC::Time::CalendarTime& out_calendar_time,
+ Service::PSC::Time::CalendarAdditionalInfo& out_additional_info, s64 time,
+ Tz::Rule& rule);
+ Result ToCalendarTimeWithMyRule(Service::PSC::Time::CalendarTime& out_calendar_time,
+ Service::PSC::Time::CalendarAdditionalInfo& out_additional_info,
+ s64 time);
+ Result ToPosixTime(u32& out_count, std::span<s64, 2> out_times, u32 out_times_count,
+ Service::PSC::Time::CalendarTime& calendar_time, Tz::Rule& rule);
+ Result ToPosixTimeWithMyRule(u32& out_count, std::span<s64, 2> out_times, u32 out_times_count,
+ Service::PSC::Time::CalendarTime& calendar_time);
+
+private:
+ void Handle_GetDeviceLocationName(HLERequestContext& ctx);
+ void Handle_SetDeviceLocationName(HLERequestContext& ctx);
+ void Handle_GetTotalLocationNameCount(HLERequestContext& ctx);
+ void Handle_LoadLocationNameList(HLERequestContext& ctx);
+ void Handle_LoadTimeZoneRule(HLERequestContext& ctx);
+ void Handle_GetTimeZoneRuleVersion(HLERequestContext& ctx);
+ void Handle_GetDeviceLocationNameAndUpdatedTime(HLERequestContext& ctx);
+ void Handle_SetDeviceLocationNameWithTimeZoneRule(HLERequestContext& ctx);
+ void Handle_ParseTimeZoneBinary(HLERequestContext& ctx);
+ void Handle_GetDeviceLocationNameOperationEventReadableHandle(HLERequestContext& ctx);
+ void Handle_ToCalendarTime(HLERequestContext& ctx);
+ void Handle_ToCalendarTimeWithMyRule(HLERequestContext& ctx);
+ void Handle_ToPosixTime(HLERequestContext& ctx);
+ void Handle_ToPosixTimeWithMyRule(HLERequestContext& ctx);
+
+ Core::System& m_system;
+ std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
+
+ bool m_can_write_timezone_device_location;
+ FileTimestampWorker& m_file_timestamp_worker;
+ std::shared_ptr<Service::PSC::Time::TimeZoneService> m_wrapped_service;
+ std::mutex m_mutex;
+ bool operation_event_initialized{};
+ Service::PSC::Time::OperationEvent m_operation_event;
+};
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/time_zone_binary.cpp b/src/core/hle/service/glue/time/time_zone_binary.cpp
new file mode 100644
index 000000000..67969aa3f
--- /dev/null
+++ b/src/core/hle/service/glue/time/time_zone_binary.cpp
@@ -0,0 +1,221 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/system_archive.h"
+#include "core/file_sys/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/glue/time/time_zone_binary.h"
+
+namespace Service::Glue::Time {
+namespace {
+constexpr u64 TimeZoneBinaryId = 0x10000000000080E;
+
+static FileSys::VirtualDir g_time_zone_binary_romfs{};
+static Result g_time_zone_binary_mount_result{ResultUnknown};
+static std::vector<u8> g_time_zone_scratch_space(0x2800, 0);
+
+Result TimeZoneReadBinary(size_t& out_read_size, std::span<u8> out_buffer, size_t out_buffer_size,
+ std::string_view path) {
+ R_UNLESS(g_time_zone_binary_mount_result == ResultSuccess, g_time_zone_binary_mount_result);
+
+ auto vfs_file{g_time_zone_binary_romfs->GetFileRelative(path)};
+ R_UNLESS(vfs_file, ResultUnknown);
+
+ auto file_size{vfs_file->GetSize()};
+ R_UNLESS(file_size > 0, ResultUnknown);
+
+ R_UNLESS(file_size <= out_buffer_size, Service::PSC::Time::ResultFailed);
+
+ out_read_size = vfs_file->Read(out_buffer.data(), file_size);
+ R_UNLESS(out_read_size > 0, ResultUnknown);
+
+ R_SUCCEED();
+}
+} // namespace
+
+void ResetTimeZoneBinary() {
+ g_time_zone_binary_romfs = {};
+ g_time_zone_binary_mount_result = ResultUnknown;
+ g_time_zone_scratch_space.clear();
+ g_time_zone_scratch_space.resize(0x2800, 0);
+}
+
+Result MountTimeZoneBinary(Core::System& system) {
+ ResetTimeZoneBinary();
+
+ auto& fsc{system.GetFileSystemController()};
+ std::unique_ptr<FileSys::NCA> nca{};
+
+ auto* bis_system = fsc.GetSystemNANDContents();
+
+ R_UNLESS(bis_system, ResultUnknown);
+
+ nca = bis_system->GetEntry(TimeZoneBinaryId, FileSys::ContentRecordType::Data);
+
+ if (nca) {
+ g_time_zone_binary_romfs = FileSys::ExtractRomFS(nca->GetRomFS());
+ }
+
+ if (g_time_zone_binary_romfs) {
+ // Validate that the romfs is readable, using invalid firmware keys can cause this to get
+ // set but the files to be garbage. In that case, we want to hit the next path and
+ // synthesise them instead.
+ Service::PSC::Time::LocationName name{"Etc/GMT"};
+ if (!IsTimeZoneBinaryValid(name)) {
+ ResetTimeZoneBinary();
+ }
+ }
+
+ if (!g_time_zone_binary_romfs) {
+ g_time_zone_binary_romfs = FileSys::ExtractRomFS(
+ FileSys::SystemArchive::SynthesizeSystemArchive(TimeZoneBinaryId));
+ }
+
+ R_UNLESS(g_time_zone_binary_romfs, ResultUnknown);
+
+ g_time_zone_binary_mount_result = ResultSuccess;
+ R_SUCCEED();
+}
+
+void GetTimeZoneBinaryListPath(std::string& out_path) {
+ if (g_time_zone_binary_mount_result != ResultSuccess) {
+ return;
+ }
+ // out_path = fmt::format("{}:/binaryList.txt", "TimeZoneBinary");
+ out_path = "/binaryList.txt";
+}
+
+void GetTimeZoneBinaryVersionPath(std::string& out_path) {
+ if (g_time_zone_binary_mount_result != ResultSuccess) {
+ return;
+ }
+ // out_path = fmt::format("{}:/version.txt", "TimeZoneBinary");
+ out_path = "/version.txt";
+}
+
+void GetTimeZoneZonePath(std::string& out_path, Service::PSC::Time::LocationName& name) {
+ if (g_time_zone_binary_mount_result != ResultSuccess) {
+ return;
+ }
+ // out_path = fmt::format("{}:/zoneinfo/{}", "TimeZoneBinary", name);
+ out_path = fmt::format("/zoneinfo/{}", name.name.data());
+}
+
+bool IsTimeZoneBinaryValid(Service::PSC::Time::LocationName& name) {
+ std::string path{};
+ GetTimeZoneZonePath(path, name);
+
+ auto vfs_file{g_time_zone_binary_romfs->GetFileRelative(path)};
+ if (!vfs_file) {
+ LOG_INFO(Service_Time, "Could not find timezone file {}", path);
+ return false;
+ }
+ return vfs_file->GetSize() != 0;
+}
+
+u32 GetTimeZoneCount() {
+ std::string path{};
+ GetTimeZoneBinaryListPath(path);
+
+ size_t bytes_read{};
+ if (TimeZoneReadBinary(bytes_read, g_time_zone_scratch_space, 0x2800, path) != ResultSuccess) {
+ return 0;
+ }
+ if (bytes_read == 0) {
+ return 0;
+ }
+
+ auto chars = std::span(reinterpret_cast<char*>(g_time_zone_scratch_space.data()), bytes_read);
+ u32 count{};
+ for (auto chr : chars) {
+ if (chr == '\n') {
+ count++;
+ }
+ }
+ return count;
+}
+
+Result GetTimeZoneVersion(Service::PSC::Time::RuleVersion& out_rule_version) {
+ std::string path{};
+ GetTimeZoneBinaryVersionPath(path);
+
+ auto rule_version_buffer{std::span(reinterpret_cast<u8*>(&out_rule_version),
+ sizeof(Service::PSC::Time::RuleVersion))};
+ size_t bytes_read{};
+ R_TRY(TimeZoneReadBinary(bytes_read, rule_version_buffer, rule_version_buffer.size_bytes(),
+ path));
+
+ rule_version_buffer[bytes_read] = 0;
+ R_SUCCEED();
+}
+
+Result GetTimeZoneRule(std::span<const u8>& out_rule, size_t& out_rule_size,
+ Service::PSC::Time::LocationName& name) {
+ std::string path{};
+ GetTimeZoneZonePath(path, name);
+
+ size_t bytes_read{};
+ R_TRY(TimeZoneReadBinary(bytes_read, g_time_zone_scratch_space,
+ g_time_zone_scratch_space.size(), path));
+
+ out_rule = std::span(g_time_zone_scratch_space.data(), bytes_read);
+ out_rule_size = bytes_read;
+ R_SUCCEED();
+}
+
+Result GetTimeZoneLocationList(u32& out_count,
+ std::vector<Service::PSC::Time::LocationName>& out_names,
+ size_t max_names, u32 index) {
+ std::string path{};
+ GetTimeZoneBinaryListPath(path);
+
+ size_t bytes_read{};
+ R_TRY(TimeZoneReadBinary(bytes_read, g_time_zone_scratch_space,
+ g_time_zone_scratch_space.size(), path));
+
+ out_count = 0;
+ R_SUCCEED_IF(bytes_read == 0);
+
+ Service::PSC::Time::LocationName current_name{};
+ size_t current_name_len{};
+ std::span<const u8> chars{g_time_zone_scratch_space};
+ u32 name_count{};
+
+ for (auto chr : chars) {
+ if (chr == '\r') {
+ continue;
+ }
+
+ if (chr == '\n') {
+ if (name_count >= index) {
+ out_names.push_back(current_name);
+ out_count++;
+ if (out_count >= max_names) {
+ break;
+ }
+ }
+ name_count++;
+ current_name_len = 0;
+ current_name = {};
+ continue;
+ }
+
+ if (chr == '\0') {
+ break;
+ }
+
+ R_UNLESS(current_name_len <= current_name.name.size() - 2,
+ Service::PSC::Time::ResultFailed);
+
+ current_name.name[current_name_len++] = chr;
+ }
+
+ R_SUCCEED();
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/time_zone_binary.h b/src/core/hle/service/glue/time/time_zone_binary.h
new file mode 100644
index 000000000..2cad6b458
--- /dev/null
+++ b/src/core/hle/service/glue/time/time_zone_binary.h
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <string>
+#include <string_view>
+
+#include "core/hle/service/psc/time/common.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Glue::Time {
+
+void ResetTimeZoneBinary();
+Result MountTimeZoneBinary(Core::System& system);
+void GetTimeZoneBinaryListPath(std::string& out_path);
+void GetTimeZoneBinaryVersionPath(std::string& out_path);
+void GetTimeZoneZonePath(std::string& out_path, Service::PSC::Time::LocationName& name);
+bool IsTimeZoneBinaryValid(Service::PSC::Time::LocationName& name);
+u32 GetTimeZoneCount();
+Result GetTimeZoneVersion(Service::PSC::Time::RuleVersion& out_rule_version);
+Result GetTimeZoneRule(std::span<const u8>& out_rule, size_t& out_rule_size,
+ Service::PSC::Time::LocationName& name);
+Result GetTimeZoneLocationList(u32& out_count,
+ std::vector<Service::PSC::Time::LocationName>& out_names,
+ size_t max_names, u32 index);
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/worker.cpp b/src/core/hle/service/glue/time/worker.cpp
new file mode 100644
index 000000000..ea0e49b90
--- /dev/null
+++ b/src/core/hle/service/glue/time/worker.cpp
@@ -0,0 +1,338 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/scope_exit.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/service/glue/time/file_timestamp_worker.h"
+#include "core/hle/service/glue/time/standard_steady_clock_resource.h"
+#include "core/hle/service/glue/time/worker.h"
+#include "core/hle/service/psc/time/common.h"
+#include "core/hle/service/psc/time/service_manager.h"
+#include "core/hle/service/psc/time/static.h"
+#include "core/hle/service/psc/time/system_clock.h"
+#include "core/hle/service/set/system_settings_server.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::Glue::Time {
+namespace {
+
+bool g_ig_report_network_clock_context_set{};
+Service::PSC::Time::SystemClockContext g_report_network_clock_context{};
+bool g_ig_report_ephemeral_clock_context_set{};
+Service::PSC::Time::SystemClockContext g_report_ephemeral_clock_context{};
+
+template <typename T>
+T GetSettingsItemValue(std::shared_ptr<Service::Set::ISystemSettingsServer>& set_sys,
+ const char* category, const char* name) {
+ std::vector<u8> interval_buf;
+ auto res = set_sys->GetSettingsItemValue(interval_buf, category, name);
+ ASSERT(res == ResultSuccess);
+
+ T v{};
+ std::memcpy(&v, interval_buf.data(), sizeof(T));
+ return v;
+}
+
+} // namespace
+
+TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
+ FileTimestampWorker& file_timestamp_worker)
+ : m_system{system}, m_ctx{m_system, "Glue:58"}, m_event{m_ctx.CreateEvent("Glue:58:Event")},
+ m_steady_clock_resource{steady_clock_resource},
+ m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
+ "Glue:58:SteadyClockTimerEvent")},
+ m_timer_file_system{m_ctx.CreateEvent("Glue:58:FileTimeTimerEvent")},
+ m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} {
+ g_ig_report_network_clock_context_set = false;
+ g_report_network_clock_context = {};
+ g_ig_report_ephemeral_clock_context_set = false;
+ g_report_ephemeral_clock_context = {};
+
+ m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
+ "Time::SteadyClockEvent",
+ [this](s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ m_timer_steady_clock->Signal();
+ return std::nullopt;
+ });
+
+ m_timer_file_system_timing_event = Core::Timing::CreateEvent(
+ "Time::SteadyClockEvent",
+ [this](s64 time,
+ std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
+ m_timer_file_system->Signal();
+ return std::nullopt;
+ });
+}
+
+TimeWorker::~TimeWorker() {
+ m_local_clock_event->Signal();
+ m_network_clock_event->Signal();
+ m_ephemeral_clock_event->Signal();
+ std::this_thread::sleep_for(std::chrono::milliseconds(16));
+
+ m_thread.request_stop();
+ m_event->Signal();
+ m_thread.join();
+
+ m_ctx.CloseEvent(m_event);
+ m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
+ m_ctx.CloseEvent(m_timer_steady_clock);
+ m_system.CoreTiming().UnscheduleEvent(m_timer_file_system_timing_event);
+ m_ctx.CloseEvent(m_timer_file_system);
+}
+
+void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> time_sm,
+ std::shared_ptr<Service::Set::ISystemSettingsServer> set_sys) {
+ m_set_sys = std::move(set_sys);
+ m_time_m =
+ m_system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+ m_time_sm = std::move(time_sm);
+
+ m_alarm_worker.Initialize(m_time_m);
+
+ auto steady_clock_interval_m = GetSettingsItemValue<s32>(
+ m_set_sys, "time", "standard_steady_clock_rtc_update_interval_minutes");
+
+ auto one_minute_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::minutes(1)).count()};
+ s64 steady_clock_interval_ns{steady_clock_interval_m * one_minute_ns};
+
+ m_system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
+ std::chrono::nanoseconds(steady_clock_interval_ns),
+ m_timer_steady_clock_timing_event);
+
+ auto fs_notify_time_s =
+ GetSettingsItemValue<s32>(m_set_sys, "time", "notify_time_to_fs_interval_seconds");
+ auto one_second_ns{
+ std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)).count()};
+ s64 fs_notify_time_ns{fs_notify_time_s * one_second_ns};
+
+ m_system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
+ std::chrono::nanoseconds(fs_notify_time_ns),
+ m_timer_file_system_timing_event);
+
+ auto res = m_time_sm->GetStandardLocalSystemClock(m_local_clock);
+ ASSERT(res == ResultSuccess);
+ res = m_time_m->GetStandardLocalClockOperationEvent(&m_local_clock_event);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_sm->GetStandardNetworkSystemClock(m_network_clock);
+ ASSERT(res == ResultSuccess);
+ res = m_time_m->GetStandardNetworkClockOperationEventForServiceManager(&m_network_clock_event);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_sm->GetEphemeralNetworkSystemClock(m_ephemeral_clock);
+ ASSERT(res == ResultSuccess);
+ res =
+ m_time_m->GetEphemeralNetworkClockOperationEventForServiceManager(&m_ephemeral_clock_event);
+ ASSERT(res == ResultSuccess);
+
+ res = m_time_m->GetStandardUserSystemClockAutomaticCorrectionUpdatedEvent(
+ &m_standard_user_auto_correct_clock_event);
+ ASSERT(res == ResultSuccess);
+}
+
+void TimeWorker::StartThread() {
+ m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
+}
+
+void TimeWorker::ThreadFunc(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("TimeWorker");
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
+
+ enum class EventType {
+ Exit = 0,
+ IpmModuleService_GetEvent = 1,
+ PowerStateChange = 2,
+ SignalAlarms = 3,
+ UpdateLocalSystemClock = 4,
+ UpdateNetworkSystemClock = 5,
+ UpdateEphemeralSystemClock = 6,
+ UpdateSteadyClock = 7,
+ UpdateFileTimestamp = 8,
+ AutoCorrect = 9,
+ Max = 10,
+ };
+
+ s32 num_objs{};
+ std::array<Kernel::KSynchronizationObject*, static_cast<u32>(EventType::Max)> wait_objs{};
+ std::array<EventType, static_cast<u32>(EventType::Max)> wait_indices{};
+
+ const auto AddWaiter{
+ [&](Kernel::KSynchronizationObject* synchronization_object, EventType type) {
+ // Open a new reference to the object.
+ synchronization_object->Open();
+
+ // Insert into the list.
+ wait_indices[num_objs] = type;
+ wait_objs[num_objs++] = synchronization_object;
+ }};
+
+ while (!stop_token.stop_requested()) {
+ SCOPE_EXIT({
+ for (s32 i = 0; i < num_objs; i++) {
+ wait_objs[i]->Close();
+ }
+ });
+
+ num_objs = {};
+ wait_objs = {};
+ if (m_pm_state_change_handler.m_priority != 0) {
+ AddWaiter(&m_event->GetReadableEvent(), EventType::Exit);
+ // TODO
+ // AddWaiter(gIPmModuleService::GetEvent(), 1);
+ AddWaiter(&m_alarm_worker.GetEvent().GetReadableEvent(), EventType::PowerStateChange);
+ } else {
+ AddWaiter(&m_event->GetReadableEvent(), EventType::Exit);
+ // TODO
+ // AddWaiter(gIPmModuleService::GetEvent(), 1);
+ AddWaiter(&m_alarm_worker.GetEvent().GetReadableEvent(), EventType::PowerStateChange);
+ AddWaiter(&m_alarm_worker.GetTimerEvent().GetReadableEvent(), EventType::SignalAlarms);
+ AddWaiter(&m_local_clock_event->GetReadableEvent(), EventType::UpdateLocalSystemClock);
+ AddWaiter(&m_network_clock_event->GetReadableEvent(),
+ EventType::UpdateNetworkSystemClock);
+ AddWaiter(&m_ephemeral_clock_event->GetReadableEvent(),
+ EventType::UpdateEphemeralSystemClock);
+ AddWaiter(&m_timer_steady_clock->GetReadableEvent(), EventType::UpdateSteadyClock);
+ AddWaiter(&m_timer_file_system->GetReadableEvent(), EventType::UpdateFileTimestamp);
+ AddWaiter(&m_standard_user_auto_correct_clock_event->GetReadableEvent(),
+ EventType::AutoCorrect);
+ }
+
+ s32 out_index{-1};
+ Kernel::KSynchronizationObject::Wait(m_system.Kernel(), &out_index, wait_objs.data(),
+ num_objs, -1);
+ ASSERT(out_index >= 0 && out_index < num_objs);
+
+ if (stop_token.stop_requested()) {
+ return;
+ }
+
+ switch (wait_indices[out_index]) {
+ case EventType::Exit:
+ return;
+
+ case EventType::IpmModuleService_GetEvent:
+ // TODO
+ // IPmModuleService::GetEvent()
+ // clear the event
+ // Handle power state change event
+ break;
+
+ case EventType::PowerStateChange:
+ m_alarm_worker.GetEvent().Clear();
+ if (m_pm_state_change_handler.m_priority <= 1) {
+ m_alarm_worker.OnPowerStateChanged();
+ }
+ break;
+
+ case EventType::SignalAlarms:
+ m_alarm_worker.GetTimerEvent().Clear();
+ m_time_m->CheckAndSignalAlarms();
+ break;
+
+ case EventType::UpdateLocalSystemClock: {
+ m_local_clock_event->Clear();
+
+ Service::PSC::Time::SystemClockContext context{};
+ auto res = m_local_clock->GetSystemClockContext(context);
+ ASSERT(res == ResultSuccess);
+
+ m_set_sys->SetUserSystemClockContext(context);
+
+ m_file_timestamp_worker.SetFilesystemPosixTime();
+ } break;
+
+ case EventType::UpdateNetworkSystemClock: {
+ m_network_clock_event->Clear();
+ Service::PSC::Time::SystemClockContext context{};
+ auto res = m_network_clock->GetSystemClockContext(context);
+ ASSERT(res == ResultSuccess);
+ m_set_sys->SetNetworkSystemClockContext(context);
+
+ s64 time{};
+ if (m_network_clock->GetCurrentTime(time) != ResultSuccess) {
+ break;
+ }
+
+ [[maybe_unused]] auto offset_before{
+ g_ig_report_network_clock_context_set ? g_report_network_clock_context.offset : 0};
+ // TODO system report "standard_netclock_operation"
+ // "clock_time" = time
+ // "context_offset_before" = offset_before
+ // "context_offset_after" = context.offset
+ g_report_network_clock_context = context;
+ if (!g_ig_report_network_clock_context_set) {
+ g_ig_report_network_clock_context_set = true;
+ }
+
+ m_file_timestamp_worker.SetFilesystemPosixTime();
+ } break;
+
+ case EventType::UpdateEphemeralSystemClock: {
+ m_ephemeral_clock_event->Clear();
+
+ Service::PSC::Time::SystemClockContext context{};
+ auto res = m_ephemeral_clock->GetSystemClockContext(context);
+ if (res != ResultSuccess) {
+ break;
+ }
+
+ s64 time{};
+ res = m_ephemeral_clock->GetCurrentTime(time);
+ if (res != ResultSuccess) {
+ break;
+ }
+
+ [[maybe_unused]] auto offset_before{g_ig_report_ephemeral_clock_context_set
+ ? g_report_ephemeral_clock_context.offset
+ : 0};
+ // TODO system report "ephemeral_netclock_operation"
+ // "clock_time" = time
+ // "context_offset_before" = offset_before
+ // "context_offset_after" = context.offset
+ g_report_ephemeral_clock_context = context;
+ if (!g_ig_report_ephemeral_clock_context_set) {
+ g_ig_report_ephemeral_clock_context_set = true;
+ }
+ } break;
+
+ case EventType::UpdateSteadyClock:
+ m_timer_steady_clock->Clear();
+
+ m_steady_clock_resource.UpdateTime();
+ m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
+ break;
+
+ case EventType::UpdateFileTimestamp:
+ m_timer_file_system->Clear();
+
+ m_file_timestamp_worker.SetFilesystemPosixTime();
+ break;
+
+ case EventType::AutoCorrect: {
+ m_standard_user_auto_correct_clock_event->Clear();
+
+ bool automatic_correction{};
+ auto res = m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
+ automatic_correction);
+ ASSERT(res == ResultSuccess);
+
+ Service::PSC::Time::SteadyClockTimePoint time_point{};
+ res = m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
+ ASSERT(res == ResultSuccess);
+
+ m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
+ m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
+ } break;
+
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+}
+
+} // namespace Service::Glue::Time
diff --git a/src/core/hle/service/glue/time/worker.h b/src/core/hle/service/glue/time/worker.h
new file mode 100644
index 000000000..adbbe6b6d
--- /dev/null
+++ b/src/core/hle/service/glue/time/worker.h
@@ -0,0 +1,64 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/glue/time/alarm_worker.h"
+#include "core/hle/service/glue/time/pm_state_change_handler.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::Set {
+class ISystemSettingsServer;
+}
+
+namespace Service::PSC::Time {
+class StaticService;
+class SystemClock;
+} // namespace Service::PSC::Time
+
+namespace Service::Glue::Time {
+class FileTimestampWorker;
+class StandardSteadyClockResource;
+
+class TimeWorker {
+public:
+ explicit TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
+ FileTimestampWorker& file_timestamp_worker);
+ ~TimeWorker();
+
+ void Initialize(std::shared_ptr<Service::PSC::Time::StaticService> time_sm,
+ std::shared_ptr<Service::Set::ISystemSettingsServer> set_sys);
+
+ void StartThread();
+
+private:
+ void ThreadFunc(std::stop_token stop_token);
+
+ Core::System& m_system;
+ KernelHelpers::ServiceContext m_ctx;
+ std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
+
+ std::jthread m_thread;
+ Kernel::KEvent* m_event{};
+ std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
+ std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
+ std::shared_ptr<Service::PSC::Time::SystemClock> m_network_clock;
+ std::shared_ptr<Service::PSC::Time::SystemClock> m_local_clock;
+ std::shared_ptr<Service::PSC::Time::SystemClock> m_ephemeral_clock;
+ StandardSteadyClockResource& m_steady_clock_resource;
+ FileTimestampWorker& m_file_timestamp_worker;
+ Kernel::KEvent* m_local_clock_event{};
+ Kernel::KEvent* m_network_clock_event{};
+ Kernel::KEvent* m_ephemeral_clock_event{};
+ Kernel::KEvent* m_standard_user_auto_correct_clock_event{};
+ Kernel::KEvent* m_timer_steady_clock{};
+ std::shared_ptr<Core::Timing::EventType> m_timer_steady_clock_timing_event;
+ Kernel::KEvent* m_timer_file_system{};
+ std::shared_ptr<Core::Timing::EventType> m_timer_file_system_timing_event;
+ AlarmWorker m_alarm_worker;
+ PmStateChangeHandler m_pm_state_change_handler;
+};
+
+} // namespace Service::Glue::Time