// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "core/core.h" #include "core/core_timing.h" #include "common/settings.h" #include "common/time_zone.h" #include "core/file_sys/vfs/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 T GetSettingsItemValue(std::shared_ptr& set_sys, const char* category, const char* name) { std::vector 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 MonthStartDayOfYear{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, }; s16 month_s16{calendar.month}; s8 month{static_cast(((month_s16 * 43) & ~std::numeric_limits::max()) + ((month_s16 * 43) >> 9))}; s8 month_index{static_cast(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& set_sys) { Service::PSC::Time::CalendarTime calendar{ .year = GetSettingsItemValue(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("time:m", true); auto res = m_time_m->GetStaticServiceAsServiceManager(m_time_sm); ASSERT(res == ResultSuccess); m_set_sys = system.ServiceManager().GetService("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( m_set_sys, "time", "standard_network_clock_sufficient_accuracy_minutes")}; auto one_minute_ns{ std::chrono::duration_cast(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::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(m_set_sys, "time", "standard_steady_clock_test_offset_minutes")}; auto one_minute_ns{ std::chrono::duration_cast(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 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 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