diff options
author | bunnei <bunneidev@gmail.com> | 2019-12-22 23:49:51 +0100 |
---|---|---|
committer | bunnei <bunneidev@gmail.com> | 2020-01-04 19:48:29 +0100 |
commit | 78f977c980e125e92b86261335447d0a254f18ee (patch) | |
tree | eca0bcfdcabe32dd737fc545b1ce42395cdad2fc | |
parent | core: Initialize several structs that make use of Common::UUID. (diff) | |
download | yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar.gz yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar.bz2 yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar.lz yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar.xz yuzu-78f977c980e125e92b86261335447d0a254f18ee.tar.zst yuzu-78f977c980e125e92b86261335447d0a254f18ee.zip |
35 files changed, 2834 insertions, 444 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7fd226050..23d88d747 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -461,12 +461,40 @@ add_library(core STATIC hle/service/spl/spl.h hle/service/ssl/ssl.cpp hle/service/ssl/ssl.h + hle/service/time/clock_types.h + hle/service/time/ephemeral_network_system_clock_context_writer.h + hle/service/time/ephemeral_network_system_clock_core.h + hle/service/time/errors.h hle/service/time/interface.cpp hle/service/time/interface.h + hle/service/time/local_system_clock_context_writer.h + hle/service/time/network_system_clock_context_writer.h + hle/service/time/standard_local_system_clock_core.h + hle/service/time/standard_network_system_clock_core.h + hle/service/time/standard_steady_clock_core.cpp + hle/service/time/standard_steady_clock_core.h + hle/service/time/standard_user_system_clock_core.cpp + hle/service/time/standard_user_system_clock_core.h + hle/service/time/steady_clock_core.h + hle/service/time/system_clock_context_update_callback.cpp + hle/service/time/system_clock_context_update_callback.h + hle/service/time/system_clock_core.cpp + hle/service/time/system_clock_core.h + hle/service/time/tick_based_steady_clock_core.cpp + hle/service/time/tick_based_steady_clock_core.h hle/service/time/time.cpp hle/service/time/time.h + hle/service/time/time_manager.cpp + hle/service/time/time_manager.h hle/service/time/time_sharedmemory.cpp hle/service/time/time_sharedmemory.h + hle/service/time/time_zone_content_manager.cpp + hle/service/time/time_zone_content_manager.h + hle/service/time/time_zone_manager.cpp + hle/service/time/time_zone_manager.h + hle/service/time/time_zone_service.cpp + hle/service/time/time_zone_service.h + hle/service/time/time_zone_types.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/display/vi_display.cpp diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h new file mode 100644 index 000000000..f2ef3ec53 --- /dev/null +++ b/src/core/hle/service/time/clock_types.h @@ -0,0 +1,91 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/uuid.h" +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time::Clock { + +/// https://switchbrew.org/wiki/Glue_services#SteadyClockTimePoint +struct SteadyClockTimePoint { + s64 time_point; + Common::UUID clock_source_id; + + static SteadyClockTimePoint GetRandom() { + return {0, Common::UUID::Generate()}; + } +}; +static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); +static_assert(std::is_trivially_copyable_v<SteadyClockTimePoint>, + "SteadyClockTimePoint must be trivially copyable"); + +struct SteadyClockContext { + u64 internal_offset; + Common::UUID steady_time_point; +}; +static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size"); +static_assert(std::is_trivially_copyable_v<SteadyClockContext>, + "SteadyClockContext must be trivially copyable"); + +struct SystemClockContext { + s64 offset; + SteadyClockTimePoint steady_time_point; +}; +static_assert(sizeof(SystemClockContext) == 0x20, "SystemClockContext is incorrect size"); +static_assert(std::is_trivially_copyable_v<SystemClockContext>, + "SystemClockContext must be trivially copyable"); + +/// https://switchbrew.org/wiki/Glue_services#TimeSpanType +struct TimeSpanType { + s64 nanoseconds{}; + static constexpr s64 ns_per_second{1000000000ULL}; + + s64 ToSeconds() const { + return nanoseconds / ns_per_second; + } + + static TimeSpanType FromSeconds(s64 seconds) { + return {seconds * ns_per_second}; + } + + static TimeSpanType FromTicks(u64 ticks, u64 frequency) { + return FromSeconds(static_cast<s64>(ticks) / static_cast<s64>(frequency)); + } +}; +static_assert(sizeof(TimeSpanType) == 8, "TimeSpanType is incorrect size"); + +struct ClockSnapshot { + SystemClockContext user_context{}; + SystemClockContext network_context{}; + s64 user_time{}; + s64 network_time{}; + TimeZone::CalendarTime user_calendar_time{}; + TimeZone::CalendarTime network_calendar_time{}; + TimeZone::CalendarAdditionalInfo user_calendar_additional_time{}; + TimeZone::CalendarAdditionalInfo network_calendar_additional_time{}; + SteadyClockTimePoint steady_clock_time_point{}; + TimeZone::LocationName location_name{}; + u8 is_automatic_correction_enabled{}; + u8 type{}; + INSERT_PADDING_BYTES(0x2); + + static ResultCode GetCurrentTime(s64& current_time, + const SteadyClockTimePoint& steady_clock_time_point, + const SystemClockContext& context) { + if (steady_clock_time_point.clock_source_id != context.steady_time_point.clock_source_id) { + current_time = 0; + return ERROR_TIME_MISMATCH; + } + current_time = steady_clock_time_point.time_point + context.offset; + return RESULT_SUCCESS; + } +}; +static_assert(sizeof(ClockSnapshot) == 0xD0, "ClockSnapshot is incorrect size"); + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h new file mode 100644 index 000000000..42893e3f6 --- /dev/null +++ b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_context_update_callback.h" + +namespace Service::Time::Clock { + +class EphemeralNetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + EphemeralNetworkSystemClockContextWriter() : SystemClockContextUpdateCallback{} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_core.h b/src/core/hle/service/time/ephemeral_network_system_clock_core.h new file mode 100644 index 000000000..4c6cdef86 --- /dev/null +++ b/src/core/hle/service/time/ephemeral_network_system_clock_core.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +class EphemeralNetworkSystemClockCore final : public SystemClockCore { +public: + explicit EphemeralNetworkSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/errors.h b/src/core/hle/service/time/errors.h new file mode 100644 index 000000000..8501a3e8c --- /dev/null +++ b/src/core/hle/service/time/errors.h @@ -0,0 +1,22 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Time { + +constexpr ResultCode ERROR_PERMISSION_DENIED{ErrorModule::Time, 1}; +constexpr ResultCode ERROR_TIME_MISMATCH{ErrorModule::Time, 102}; +constexpr ResultCode ERROR_UNINITIALIZED_CLOCK{ErrorModule::Time, 103}; +constexpr ResultCode ERROR_TIME_NOT_FOUND{ErrorModule::Time, 200}; +constexpr ResultCode ERROR_OVERFLOW{ErrorModule::Time, 201}; +constexpr ResultCode ERROR_LOCATION_NAME_TOO_LONG{ErrorModule::Time, 801}; +constexpr ResultCode ERROR_OUT_OF_RANGE{ErrorModule::Time, 902}; +constexpr ResultCode ERROR_TIME_ZONE_CONVERSION_FAILED{ErrorModule::Time, 903}; +constexpr ResultCode ERROR_TIME_ZONE_NOT_FOUND{ErrorModule::Time, 989}; +constexpr ResultCode ERROR_NOT_IMPLEMENTED{ErrorModule::Time, 990}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index bc74f1e1d..b1307a434 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -6,31 +6,30 @@ namespace Service::Time { -Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, - Core::System& system, const char* name) - : Module::Interface(std::move(time), std::move(shared_memory), system, name) { +Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* name) + : Module::Interface(std::move(module), system, name) { // clang-format off static const FunctionInfo functions[] = { {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, {1, &Time::GetStandardNetworkSystemClock, "GetStandardNetworkSystemClock"}, {2, &Time::GetStandardSteadyClock, "GetStandardSteadyClock"}, {3, &Time::GetTimeZoneService, "GetTimeZoneService"}, - {4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, + {4, nullptr, "GetStandardLocalSystemClock"}, {5, nullptr, "GetEphemeralNetworkSystemClock"}, {20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, {30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"}, {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"}, {50, nullptr, "SetStandardSteadyClockInternalOffset"}, {51, nullptr, "GetStandardSteadyClockRtcValue"}, - {100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, - {101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, {102, nullptr, "GetStandardUserSystemClockInitialYear"}, {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"}, {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"}, - {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, + {300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"}, {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, - {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"}, + {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, {501, nullptr, "CalculateSpanBetween"}, }; // clang-format on diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h index 5c63a07f4..4f49e1f07 100644 --- a/src/core/hle/service/time/interface.h +++ b/src/core/hle/service/time/interface.h @@ -1,4 +1,4 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -6,14 +6,15 @@ #include "core/hle/service/time/time.h" -namespace Service::Time { +namespace Core { +class System; +} -class SharedMemory; +namespace Service::Time { class Time final : public Module::Interface { public: - explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, - Core::System& system, const char* name); + explicit Time(std::shared_ptr<Module> time, Core::System& system, const char* name); ~Time() override; }; diff --git a/src/core/hle/service/time/local_system_clock_context_writer.h b/src/core/hle/service/time/local_system_clock_context_writer.h new file mode 100644 index 000000000..7050844c6 --- /dev/null +++ b/src/core/hle/service/time/local_system_clock_context_writer.h @@ -0,0 +1,28 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/time_sharedmemory.h" + +namespace Service::Time::Clock { + +class LocalSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + explicit LocalSystemClockContextWriter(SharedMemory& shared_memory) + : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {} + +protected: + ResultCode Update() override { + shared_memory.UpdateLocalSystemClockContext(context); + return RESULT_SUCCESS; + } + +private: + SharedMemory& shared_memory; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/network_system_clock_context_writer.h b/src/core/hle/service/time/network_system_clock_context_writer.h new file mode 100644 index 000000000..94d8788ff --- /dev/null +++ b/src/core/hle/service/time/network_system_clock_context_writer.h @@ -0,0 +1,28 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/time_sharedmemory.h" + +namespace Service::Time::Clock { + +class NetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback { +public: + explicit NetworkSystemClockContextWriter(SharedMemory& shared_memory) + : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {} + +protected: + ResultCode Update() override { + shared_memory.UpdateNetworkSystemClockContext(context); + return RESULT_SUCCESS; + } + +private: + SharedMemory& shared_memory; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_local_system_clock_core.h b/src/core/hle/service/time/standard_local_system_clock_core.h new file mode 100644 index 000000000..8c1882eb1 --- /dev/null +++ b/src/core/hle/service/time/standard_local_system_clock_core.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +class StandardLocalSystemClockCore final : public SystemClockCore { +public: + explicit StandardLocalSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_network_system_clock_core.h b/src/core/hle/service/time/standard_network_system_clock_core.h new file mode 100644 index 000000000..467285160 --- /dev/null +++ b/src/core/hle/service/time/standard_network_system_clock_core.h @@ -0,0 +1,26 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +class StandardNetworkSystemClockCore final : public SystemClockCore { +public: + explicit StandardNetworkSystemClockCore(SteadyClockCore& steady_clock_core) + : SystemClockCore{steady_clock_core} {} + + void SetStandardNetworkClockSufficientAccuracy(TimeSpanType value) { + standard_network_clock_sufficient_accuracy = value; + } + +private: + TimeSpanType standard_network_clock_sufficient_accuracy{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp new file mode 100644 index 000000000..ca1a783fc --- /dev/null +++ b/src/core/hle/service/time/standard_steady_clock_core.cpp @@ -0,0 +1,26 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/standard_steady_clock_core.h" + +namespace Service::Time::Clock { + +TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { + const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds}; + + if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) { + raw_time_point.nanoseconds = cached_raw_time_point.nanoseconds; + } + + cached_raw_time_point = raw_time_point; + return raw_time_point; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_steady_clock_core.h b/src/core/hle/service/time/standard_steady_clock_core.h new file mode 100644 index 000000000..f56f3fd95 --- /dev/null +++ b/src/core/hle/service/time/standard_steady_clock_core.h @@ -0,0 +1,42 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class StandardSteadyClockCore final : public SteadyClockCore { +public: + SteadyClockTimePoint GetTimePoint(Core::System& system) override { + return {GetCurrentRawTimePoint(system).ToSeconds(), GetClockSourceId()}; + } + + TimeSpanType GetInternalOffset() const override { + return internal_offset; + } + + void SetInternalOffset(TimeSpanType value) override { + internal_offset = value; + } + + TimeSpanType GetCurrentRawTimePoint(Core::System& system) override; + + void SetSetupValue(TimeSpanType value) { + setup_value = value; + } + +private: + TimeSpanType setup_value{}; + TimeSpanType internal_offset{}; + TimeSpanType cached_raw_time_point{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp new file mode 100644 index 000000000..8af17091c --- /dev/null +++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp @@ -0,0 +1,77 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/standard_local_system_clock_core.h" +#include "core/hle/service/time/standard_network_system_clock_core.h" +#include "core/hle/service/time/standard_user_system_clock_core.h" + +namespace Service::Time::Clock { + +StandardUserSystemClockCore::StandardUserSystemClockCore( + StandardLocalSystemClockCore& local_system_clock_core, + StandardNetworkSystemClockCore& network_system_clock_core, Core::System& system) + : SystemClockCore(local_system_clock_core.GetSteadyClockCore()), + local_system_clock_core{local_system_clock_core}, + network_system_clock_core{network_system_clock_core}, auto_correction_enabled{}, + auto_correction_time{SteadyClockTimePoint::GetRandom()}, + auto_correction_event{Kernel::WritableEvent::CreateEventPair( + system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {} + +ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system, + bool value) { + if (const ResultCode result{ApplyAutomaticCorrection(system, value)}; + result != RESULT_SUCCESS) { + return result; + } + + auto_correction_enabled = value; + + return RESULT_SUCCESS; +} + +ResultCode StandardUserSystemClockCore::GetClockContext(Core::System& system, + SystemClockContext& context) const { + if (const ResultCode result{ApplyAutomaticCorrection(system, false)}; + result != RESULT_SUCCESS) { + return result; + } + + return local_system_clock_core.GetClockContext(system, context); +} + +ResultCode StandardUserSystemClockCore::Flush(const SystemClockContext& context) { + UNREACHABLE(); + return ERROR_NOT_IMPLEMENTED; +} + +ResultCode StandardUserSystemClockCore::SetClockContext(const SystemClockContext& context) { + UNREACHABLE(); + return ERROR_NOT_IMPLEMENTED; +} + +ResultCode StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& system, + bool value) const { + if (auto_correction_enabled == value) { + return RESULT_SUCCESS; + } + + if (!network_system_clock_core.IsClockSetup(system)) { + return ERROR_UNINITIALIZED_CLOCK; + } + + SystemClockContext context{}; + if (const ResultCode result{network_system_clock_core.GetClockContext(system, context)}; + result != RESULT_SUCCESS) { + return result; + } + + local_system_clock_core.SetClockContext(context); + + return RESULT_SUCCESS; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h new file mode 100644 index 000000000..ef3d468b7 --- /dev/null +++ b/src/core/hle/service/time/standard_user_system_clock_core.h @@ -0,0 +1,57 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class StandardLocalSystemClockCore; +class StandardNetworkSystemClockCore; + +class StandardUserSystemClockCore final : public SystemClockCore { +public: + StandardUserSystemClockCore(StandardLocalSystemClockCore& local_system_clock_core, + StandardNetworkSystemClockCore& network_system_clock_core, + Core::System& system); + + ResultCode SetAutomaticCorrectionEnabled(Core::System& system, bool value); + + ResultCode GetClockContext(Core::System& system, SystemClockContext& context) const override; + + bool IsAutomaticCorrectionEnabled() const { + return auto_correction_enabled; + } + + void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steady_clock_time_point) { + auto_correction_time = steady_clock_time_point; + } + +protected: + ResultCode Flush(const SystemClockContext& context) override; + + ResultCode SetClockContext(const SystemClockContext&) override; + + ResultCode ApplyAutomaticCorrection(Core::System& system, bool value) const; + + const SteadyClockTimePoint& GetAutomaticCorrectionUpdatedTime() const { + return auto_correction_time; + } + +private: + StandardLocalSystemClockCore& local_system_clock_core; + StandardNetworkSystemClockCore& network_system_clock_core; + bool auto_correction_enabled{}; + SteadyClockTimePoint auto_correction_time; + Kernel::EventPair auto_correction_event; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/steady_clock_core.h b/src/core/hle/service/time/steady_clock_core.h new file mode 100644 index 000000000..84af3d105 --- /dev/null +++ b/src/core/hle/service/time/steady_clock_core.h @@ -0,0 +1,55 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/uuid.h" +#include "core/hle/service/time/clock_types.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class SteadyClockCore { +public: + SteadyClockCore() = default; + + const Common::UUID& GetClockSourceId() const { + return clock_source_id; + } + + void SetClockSourceId(const Common::UUID& value) { + clock_source_id = value; + } + + virtual TimeSpanType GetInternalOffset() const = 0; + + virtual void SetInternalOffset(TimeSpanType internal_offset) = 0; + + virtual SteadyClockTimePoint GetTimePoint(Core::System& system) = 0; + + virtual TimeSpanType GetCurrentRawTimePoint(Core::System& system) = 0; + + SteadyClockTimePoint GetCurrentTimePoint(Core::System& system) { + SteadyClockTimePoint result{GetTimePoint(system)}; + result.time_point += GetInternalOffset().ToSeconds(); + return result; + } + + bool IsInitialized() const { + return is_initialized; + } + + void MarkAsInitialized() { + is_initialized = true; + } + +private: + Common::UUID clock_source_id{Common::UUID::Generate()}; + bool is_initialized{}; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp new file mode 100644 index 000000000..5cdb80703 --- /dev/null +++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp @@ -0,0 +1,55 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/time/errors.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" + +namespace Service::Time::Clock { + +SystemClockContextUpdateCallback::SystemClockContextUpdateCallback() = default; +SystemClockContextUpdateCallback::~SystemClockContextUpdateCallback() = default; + +bool SystemClockContextUpdateCallback::NeedUpdate(const SystemClockContext& value) const { + if (has_context) { + return context.offset != value.offset || + context.steady_time_point.clock_source_id != value.steady_time_point.clock_source_id; + } + + return true; +} + +void SystemClockContextUpdateCallback::RegisterOperationEvent( + std::shared_ptr<Kernel::WritableEvent>&& writable_event) { + operation_event_list.emplace_back(std::move(writable_event)); +} + +void SystemClockContextUpdateCallback::BroadcastOperationEvent() { + for (const auto& writable_event : operation_event_list) { + writable_event->Signal(); + } +} + +ResultCode SystemClockContextUpdateCallback::Update(const SystemClockContext& value) { + ResultCode result{RESULT_SUCCESS}; + + if (NeedUpdate(value)) { + context = value; + has_context = true; + + result = Update(); + + if (result == RESULT_SUCCESS) { + BroadcastOperationEvent(); + } + } + + return result; +} + +ResultCode SystemClockContextUpdateCallback::Update() { + return RESULT_SUCCESS; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h new file mode 100644 index 000000000..6260de6c3 --- /dev/null +++ b/src/core/hle/service/time/system_clock_context_update_callback.h @@ -0,0 +1,43 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "core/hle/service/time/clock_types.h" + +namespace Kernel { +class WritableEvent; +} + +namespace Service::Time::Clock { + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class SystemClockContextUpdateCallback { +public: + SystemClockContextUpdateCallback(); + ~SystemClockContextUpdateCallback(); + + bool NeedUpdate(const SystemClockContext& value) const; + + void RegisterOperationEvent(std::shared_ptr<Kernel::WritableEvent>&& writable_event); + + void BroadcastOperationEvent(); + + ResultCode Update(const SystemClockContext& value); + +protected: + virtual ResultCode Update(); + + SystemClockContext context{}; + +private: + bool has_context{}; + std::vector<std::shared_ptr<Kernel::WritableEvent>> operation_event_list; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp new file mode 100644 index 000000000..1a3ab8cfa --- /dev/null +++ b/src/core/hle/service/time/system_clock_core.cpp @@ -0,0 +1,72 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/time/steady_clock_core.h" +#include "core/hle/service/time/system_clock_context_update_callback.h" +#include "core/hle/service/time/system_clock_core.h" + +namespace Service::Time::Clock { + +SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core) + : steady_clock_core{steady_clock_core}, is_initialized{} { + context.steady_time_point.clock_source_id = steady_clock_core.GetClockSourceId(); +} + +SystemClockCore ::~SystemClockCore() = default; + +ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const { + posix_time = 0; + + const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)}; + + SystemClockContext clock_context{}; + if (const ResultCode result{GetClockContext(system, clock_context)}; result != RESULT_SUCCESS) { + return result; + } + + if (current_time_point.clock_source_id != clock_context.steady_time_point.clock_source_id) { + return ERROR_TIME_MISMATCH; + } + + posix_time = clock_context.offset + current_time_point.time_point; + + return RESULT_SUCCESS; +} + +ResultCode SystemClockCore::SetCurrentTime(Core::System& system, s64 posix_time) { + const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)}; + const SystemClockContext clock_context{posix_time - current_time_point.time_point, + current_time_point}; + + if (const ResultCode result{SetClockContext(clock_context)}; result != RESULT_SUCCESS) { + return result; + } + return Flush(clock_context); +} + +ResultCode SystemClockCore::Flush(const SystemClockContext& context) { + if (!system_clock_context_update_callback) { + return RESULT_SUCCESS; + } + return system_clock_context_update_callback->Update(context); +} + +ResultCode SystemClockCore::SetSystemClockContext(const SystemClockContext& context) { + if (const ResultCode result{SetClockContext(context)}; result != RESULT_SUCCESS) { + return result; + } + return Flush(context); +} + +bool SystemClockCore::IsClockSetup(Core::System& system) const { + SystemClockContext value{}; + if (GetClockContext(system, value) == RESULT_SUCCESS) { + const SteadyClockTimePoint steady_clock_time_point{ + steady_clock_core.GetCurrentTimePoint(system)}; + return steady_clock_time_point.clock_source_id == value.steady_time_point.clock_source_id; + } + return {}; +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/system_clock_core.h b/src/core/hle/service/time/system_clock_core.h new file mode 100644 index 000000000..54407a6c5 --- /dev/null +++ b/src/core/hle/service/time/system_clock_core.h @@ -0,0 +1,71 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/time/clock_types.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class SteadyClockCore; +class SystemClockContextUpdateCallback; + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class SystemClockCore { +public: + explicit SystemClockCore(SteadyClockCore& steady_clock_core); + ~SystemClockCore(); + + SteadyClockCore& GetSteadyClockCore() const { + return steady_clock_core; + } + + ResultCode GetCurrentTime(Core::System& system, s64& posix_time) const; + + ResultCode SetCurrentTime(Core::System& system, s64 posix_time); + + virtual ResultCode GetClockContext([[maybe_unused]] Core::System& system, + SystemClockContext& value) const { + value = context; + return RESULT_SUCCESS; + } + + virtual ResultCode SetClockContext(const SystemClockContext& value) { + context = value; + return RESULT_SUCCESS; + } + + virtual ResultCode Flush(const SystemClockContext& context); + + void SetUpdateCallbackInstance(std::shared_ptr<SystemClockContextUpdateCallback> callback) { + system_clock_context_update_callback = std::move(callback); + } + + ResultCode SetSystemClockContext(const SystemClockContext& context); + + bool IsInitialized() const { + return is_initialized; + } + + void MarkAsInitialized() { + is_initialized = true; + } + + bool IsClockSetup(Core::System& system) const; + +private: + SteadyClockCore& steady_clock_core; + SystemClockContext context{}; + bool is_initialized{}; + std::shared_ptr<SystemClockContextUpdateCallback> system_clock_context_update_callback; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp new file mode 100644 index 000000000..c77b98189 --- /dev/null +++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp @@ -0,0 +1,24 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/tick_based_steady_clock_core.h" + +namespace Service::Time::Clock { + +SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) { + const TimeSpanType ticks_time_span{TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + + return {ticks_time_span.ToSeconds(), GetClockSourceId()}; +} + +TimeSpanType TickBasedSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) { + return TimeSpanType::FromSeconds(GetTimePoint(system).time_point); +} + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.h b/src/core/hle/service/time/tick_based_steady_clock_core.h new file mode 100644 index 000000000..1a5a53fd7 --- /dev/null +++ b/src/core/hle/service/time/tick_based_steady_clock_core.h @@ -0,0 +1,29 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" + +namespace Core { +class System; +} + +namespace Service::Time::Clock { + +class TickBasedSteadyClockCore final : public SteadyClockCore { +public: + TimeSpanType GetInternalOffset() const override { + return {}; + } + + void SetInternalOffset(TimeSpanType internal_offset) override {} + + SteadyClockTimePoint GetTimePoint(Core::System& system) override; + + TimeSpanType GetCurrentRawTimePoint(Core::System& system) override; +}; + +} // namespace Service::Time::Clock diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 6ee77c5f9..970aed0bb 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -1,9 +1,7 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2019 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <chrono> -#include <ctime> #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" @@ -11,429 +9,282 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/scheduler.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" #include "core/hle/service/time/time_sharedmemory.h" -#include "core/settings.h" +#include "core/hle/service/time/time_zone_service.h" namespace Service::Time { -static std::chrono::seconds GetSecondsSinceEpoch() { - return std::chrono::duration_cast<std::chrono::seconds>( - std::chrono::system_clock::now().time_since_epoch()) + - Settings::values.custom_rtc_differential; -} - -static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time, - CalendarAdditionalInfo& additional_info, - [[maybe_unused]] const TimeZoneRule& /*rule*/) { - const std::time_t time(posix_time); - const std::tm* tm = std::localtime(&time); - if (tm == nullptr) { - calendar_time = {}; - additional_info = {}; - return; - } - calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900); - calendar_time.month = static_cast<u8>(tm->tm_mon + 1); - calendar_time.day = static_cast<u8>(tm->tm_mday); - calendar_time.hour = static_cast<u8>(tm->tm_hour); - calendar_time.minute = static_cast<u8>(tm->tm_min); - calendar_time.second = static_cast<u8>(tm->tm_sec); - - additional_info.day_of_week = tm->tm_wday; - additional_info.day_of_year = tm->tm_yday; - std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC")); - additional_info.utc_offset = 0; -} - -static u64 CalendarToPosix(const CalendarTime& calendar_time, - [[maybe_unused]] const TimeZoneRule& /*rule*/) { - std::tm time{}; - time.tm_year = calendar_time.year - 1900; - time.tm_mon = calendar_time.month - 1; - time.tm_mday = calendar_time.day; - - time.tm_hour = calendar_time.hour; - time.tm_min = calendar_time.minute; - time.tm_sec = calendar_time.second; - - std::time_t epoch_time = std::mktime(&time); - return static_cast<u64>(epoch_time); -} - -enum class ClockContextType { - StandardSteady, - StandardUserSystem, - StandardNetworkSystem, - StandardLocalSystem, -}; - class ISystemClock final : public ServiceFramework<ISystemClock> { public: - ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory, - ClockContextType clock_type) - : ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) { + ISystemClock(Clock::SystemClockCore& clock_core) + : ServiceFramework("ISystemClock"), clock_core{clock_core} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, {1, nullptr, "SetCurrentTime"}, - {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}, + {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"}, {3, nullptr, "SetSystemClockContext"}, {4, nullptr, "GetOperationEventReadableHandle"}, }; // clang-format on RegisterHandlers(functions); - UpdateSharedMemoryContext(system_clock_context); } private: void GetCurrentTime(Kernel::HLERequestContext& ctx) { - const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; LOG_DEBUG(Service_Time, "called"); + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } + + s64 posix_time{}; + if (const ResultCode result{ + clock_core.GetCurrentTime(Core::System::GetInstance(), posix_time)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(time_since_epoch); + rb.Push<s64>(posix_time); } void GetSystemClockContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + LOG_DEBUG(Service_Time, "called"); - // TODO(ogniK): This should be updated periodically however since we have it stubbed we'll - // only update when we get a new context - UpdateSharedMemoryContext(system_clock_context); + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } - IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; + Clock::SystemClockContext system_clock_context{}; + if (const ResultCode result{ + clock_core.GetClockContext(Core::System::GetInstance(), system_clock_context)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, sizeof(Clock::SystemClockContext) / 4 + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(system_clock_context); } - void UpdateSharedMemoryContext(const SystemClockContext& clock_context) { - switch (clock_type) { - case ClockContextType::StandardLocalSystem: - shared_memory->SetStandardLocalSystemClockContext(clock_context); - break; - case ClockContextType::StandardNetworkSystem: - shared_memory->SetStandardNetworkSystemClockContext(clock_context); - break; - } - } - - SystemClockContext system_clock_context{}; - std::shared_ptr<Service::Time::SharedMemory> shared_memory; - ClockContextType clock_type; + Clock::SystemClockCore& clock_core; }; class ISteadyClock final : public ServiceFramework<ISteadyClock> { public: - ISteadyClock(std::shared_ptr<SharedMemory> shared_memory, Core::System& system) - : ServiceFramework("ISteadyClock"), shared_memory(shared_memory), system(system) { + ISteadyClock(Clock::SteadyClockCore& clock_core) + : ServiceFramework("ISteadyClock"), clock_core{clock_core} { static const FunctionInfo functions[] = { {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, }; RegisterHandlers(functions); - - shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint()); } private: void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - const auto time_point = GetCurrentTimePoint(); - // TODO(ogniK): This should be updated periodically - shared_memory->SetStandardSteadyClockTimepoint(time_point); + if (!clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } - IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; + const Clock::SteadyClockTimePoint time_point{ + clock_core.GetCurrentTimePoint(Core::System::GetInstance())}; + IPC::ResponseBuilder rb{ctx, (sizeof(Clock::SteadyClockTimePoint) / 4) + 2}; rb.Push(RESULT_SUCCESS); rb.PushRaw(time_point); } - SteadyClockTimePoint GetCurrentTimePoint() const { - const auto& core_timing = system.CoreTiming(); - const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); - return {static_cast<u64_le>(ms.count() / 1000), {}}; - } - - std::shared_ptr<SharedMemory> shared_memory; - Core::System& system; + Clock::SteadyClockCore& clock_core; }; -class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { -public: - ITimeZoneService() : ServiceFramework("ITimeZoneService") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, - {1, nullptr, "SetDeviceLocationName"}, - {2, &ITimeZoneService::GetTotalLocationNameCount, "GetTotalLocationNameCount"}, - {3, nullptr, "LoadLocationNameList"}, - {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, - {5, nullptr, "GetTimeZoneRuleVersion"}, - {6, nullptr, "GetDeviceLocationNameAndUpdatedTime"}, - {7, nullptr, "SetDeviceLocationNameWithTimeZoneRule"}, - {8, nullptr, "ParseTimeZoneBinary"}, - {20, nullptr, "GetDeviceLocationNameOperationEventReadableHandle"}, - {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, - {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, - {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"}, - {202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"}, - }; - // clang-format on - - RegisterHandlers(functions); - } +ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal( + Kernel::Thread* thread, Clock::SystemClockContext user_context, + Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) { -private: - LocationName location_name{"UTC"}; - TimeZoneRule my_time_zone_rule{}; + auto& time_manager{module->GetTimeManager()}; - void GetDeviceLocationName(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Time, "called"); + clock_snapshot.is_automatic_correction_enabled = + time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled(); + clock_snapshot.user_context = user_context; + clock_snapshot.network_context = network_context; - IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(location_name); + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().GetDeviceLocationName( + clock_snapshot.location_name)}; + result != RESULT_SUCCESS) { + return result; } - void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + const auto current_time_point{ + time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(Core::System::GetInstance())}; + if (const ResultCode result{Clock::ClockSnapshot::GetCurrentTime( + clock_snapshot.user_time, current_time_point, clock_snapshot.user_context)}; + result != RESULT_SUCCESS) { + return result; } - void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); - - ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule)); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + TimeZone::CalendarInfo userCalendarInfo{}; + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules( + clock_snapshot.user_time, userCalendarInfo)}; + result != RESULT_SUCCESS) { + return result; } - void ToCalendarTime(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); - - TimeZoneRule time_zone_rule{}; - auto buffer = ctx.ReadBuffer(); - std::memcpy(&time_zone_rule, buffer.data(), buffer.size()); - - CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; - CalendarAdditionalInfo additional_info{}; + clock_snapshot.user_calendar_time = userCalendarInfo.time; + clock_snapshot.user_calendar_additional_time = userCalendarInfo.additiona_info; - PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule); - - IPC::ResponseBuilder rb{ctx, 10}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(calendar_time); - rb.PushRaw(additional_info); + if (Clock::ClockSnapshot::GetCurrentTime(clock_snapshot.network_time, current_time_point, + clock_snapshot.network_context) != RESULT_SUCCESS) { + clock_snapshot.network_time = 0; } - void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 posix_time = rp.Pop<u64>(); - LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time); - - CalendarTime calendar_time{2018, 1, 1, 0, 0, 0}; - CalendarAdditionalInfo additional_info{}; - - PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule); - - IPC::ResponseBuilder rb{ctx, 10}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw(calendar_time); - rb.PushRaw(additional_info); - } - - void ToPosixTime(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Figure out how to handle multiple times - LOG_WARNING(Service_Time, "(STUBBED) called"); - - IPC::RequestParser rp{ctx}; - auto calendar_time = rp.PopRaw<CalendarTime>(); - auto posix_time = CalendarToPosix(calendar_time, {}); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u32>(1); // Amount of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(u64)); + TimeZone::CalendarInfo networkCalendarInfo{}; + if (const ResultCode result{ + time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules( + clock_snapshot.network_time, networkCalendarInfo)}; + result != RESULT_SUCCESS) { + return result; } - void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Time, "(STUBBED) called"); + clock_snapshot.network_calendar_time = networkCalendarInfo.time; + clock_snapshot.network_calendar_additional_time = networkCalendarInfo.additiona_info; + clock_snapshot.type = type; - IPC::RequestParser rp{ctx}; - auto calendar_time = rp.PopRaw<CalendarTime>(); - auto posix_time = CalendarToPosix(calendar_time, {}); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u32>(1); // Amount of times we're returning - ctx.WriteBuffer(&posix_time, sizeof(u64)); - } -}; + return RESULT_SUCCESS; +} void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem); + rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore()); } void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem); + rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore()); } void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISteadyClock>(shared_memory, system); + rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore()); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ITimeZoneService>(); + rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager()); } -void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { +void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem); + auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()}; + if (!steady_clock_core.IsInitialized()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_UNINITIALIZED_CLOCK); + return; + } + + IPC::RequestParser rp{ctx}; + const auto context{rp.PopRaw<Clock::SystemClockContext>()}; + const auto current_time_point{ + steady_clock_core.GetCurrentTimePoint(Core::System::GetInstance())}; + + if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) { + const auto ticks{Clock::TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + const s64 base_time_point{context.offset + current_time_point.time_point - + ticks.ToSeconds()}; + IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(base_time_point); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_TIME_MISMATCH); } void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - IPC::RequestParser rp{ctx}; - const auto initial_type = rp.PopRaw<u8>(); + const auto type = rp.PopRaw<u8>(); - const s64 time_since_epoch{GetSecondsSinceEpoch().count()}; - const std::time_t time(time_since_epoch); - const std::tm* tm = std::localtime(&time); - if (tm == nullptr) { - LOG_ERROR(Service_Time, "tm is a nullptr"); + Clock::SystemClockContext user_context{}; + if (const ResultCode result{ + module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext( + Core::System::GetInstance(), user_context)}; + result != RESULT_SUCCESS) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Find appropriate error code + rb.Push(result); + return; + } + Clock::SystemClockContext network_context{}; + if (const ResultCode result{ + module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( + Core::System::GetInstance(), network_context)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); return; } - const auto& core_timing = system.CoreTiming(); - const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); - const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}}; - - CalendarTime calendar_time{}; - calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900); - calendar_time.month = static_cast<u8>(tm->tm_mon + 1); - calendar_time.day = static_cast<u8>(tm->tm_mday); - calendar_time.hour = static_cast<u8>(tm->tm_hour); - calendar_time.minute = static_cast<u8>(tm->tm_min); - calendar_time.second = static_cast<u8>(tm->tm_sec); - - ClockSnapshot clock_snapshot{}; - clock_snapshot.system_posix_time = time_since_epoch; - clock_snapshot.network_posix_time = time_since_epoch; - clock_snapshot.system_calendar_time = calendar_time; - clock_snapshot.network_calendar_time = calendar_time; - - CalendarAdditionalInfo additional_info{}; - PosixToCalendar(time_since_epoch, calendar_time, additional_info, {}); - - clock_snapshot.system_calendar_info = additional_info; - clock_snapshot.network_calendar_info = additional_info; - - clock_snapshot.steady_clock_timepoint = steady_clock_time_point; - clock_snapshot.location_name = LocationName{"UTC"}; - clock_snapshot.clock_auto_adjustment_enabled = 1; - clock_snapshot.type = initial_type; + Clock::ClockSnapshot clock_snapshot{}; + if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal( + &ctx.GetThread(), user_context, network_context, type, clock_snapshot)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot)); -} - -void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser( - Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Time, "called"); - - IPC::RequestParser rp{ctx}; - const auto snapshot_a = rp.PopRaw<ClockSnapshot>(); - const auto snapshot_b = rp.PopRaw<ClockSnapshot>(); - const u64 difference = - snapshot_b.user_clock_context.offset - snapshot_a.user_clock_context.offset; - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(difference); + ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot)); } void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder()); -} - -void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled( - Kernel::HLERequestContext& ctx) { - // ogniK(TODO): When clock contexts are implemented, the value should be read from the context - // instead of our shared memory holder - LOG_DEBUG(Service_Time, "called"); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled()); -} - -void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled( - Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto enabled = rp.Pop<u8>(); - - LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called"); - - // TODO(ogniK): Update clock contexts and correct timespans - - shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder()); } -Module::Interface::Interface(std::shared_ptr<Module> time, - std::shared_ptr<SharedMemory> shared_memory, Core::System& system, - const char* name) - : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)), - system(system) {} +Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) + : ServiceFramework(name), module{std::move(module)}, system{system} {} Module::Interface::~Interface() = default; void InstallInterfaces(Core::System& system) { - auto time = std::make_shared<Module>(); - auto shared_mem = std::make_shared<SharedMemory>(system); - - std::make_shared<Time>(time, shared_mem, system, "time:a") - ->InstallAsService(system.ServiceManager()); - std::make_shared<Time>(time, shared_mem, system, "time:s") - ->InstallAsService(system.ServiceManager()); - std::make_shared<Time>(std::move(time), shared_mem, system, "time:u") - ->InstallAsService(system.ServiceManager()); + auto module = std::make_shared<Module>(system); + std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager()); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index c32d32860..7b77ac7ea 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -4,102 +4,50 @@ #pragma once -#include <array> -#include "common/common_funcs.h" #include "core/hle/service/service.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/time_manager.h" -namespace Service::Time { - -class SharedMemory; - -struct LocationName { - std::array<u8, 0x24> name; -}; -static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size"); - -struct CalendarTime { - u16_le year; - u8 month; // Starts at 1 - u8 day; // Starts at 1 - u8 hour; - u8 minute; - u8 second; -}; -static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size"); - -struct CalendarAdditionalInfo { - u32_le day_of_week; - u32_le day_of_year; - std::array<u8, 8> name; - u8 is_dst; - s32_le utc_offset; -}; -static_assert(sizeof(CalendarAdditionalInfo) == 0x18, - "CalendarAdditionalInfo structure has incorrect size"); - -// TODO(mailwl) RE this structure -struct TimeZoneRule { - INSERT_PADDING_BYTES(0x4000); -}; - -struct SteadyClockTimePoint { - using SourceID = std::array<u8, 16>; +namespace Core { +class System; +} - u64_le value; - SourceID source_id; -}; -static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size"); - -struct SystemClockContext { - u64_le offset; - SteadyClockTimePoint time_point; -}; -static_assert(sizeof(SystemClockContext) == 0x20, - "SystemClockContext structure has incorrect size"); - -struct ClockSnapshot { - SystemClockContext user_clock_context; - SystemClockContext network_clock_context; - s64_le system_posix_time; - s64_le network_posix_time; - CalendarTime system_calendar_time; - CalendarTime network_calendar_time; - CalendarAdditionalInfo system_calendar_info; - CalendarAdditionalInfo network_calendar_info; - SteadyClockTimePoint steady_clock_timepoint; - LocationName location_name; - u8 clock_auto_adjustment_enabled; - u8 type; - u8 version; - INSERT_PADDING_BYTES(1); -}; -static_assert(sizeof(ClockSnapshot) == 0xd0, "ClockSnapshot is an invalid size"); +namespace Service::Time { class Module final { public: + Module(Core::System& system) : time_manager{system} {} + class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> time, - std::shared_ptr<SharedMemory> shared_memory, Core::System& system, - const char* name); + explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); void GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx); void GetStandardSteadyClock(Kernel::HLERequestContext& ctx); void GetTimeZoneService(Kernel::HLERequestContext& ctx); - void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); + void CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx); void GetClockSnapshot(Kernel::HLERequestContext& ctx); - void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx); void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); - void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); - void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); + + private: + ResultCode GetClockSnapshotFromSystemClockContextInternal( + Kernel::Thread* thread, Clock::SystemClockContext user_context, + Clock::SystemClockContext network_context, u8 type, + Clock::ClockSnapshot& cloc_snapshot); protected: - std::shared_ptr<Module> time; - std::shared_ptr<SharedMemory> shared_memory; + std::shared_ptr<Module> module; Core::System& system; }; + + TimeManager& GetTimeManager() { + return time_manager; + } + +private: + TimeManager time_manager; }; /// Registers all Time services with the specified service manager. diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp new file mode 100644 index 000000000..9d6c55865 --- /dev/null +++ b/src/core/hle/service/time/time_manager.cpp @@ -0,0 +1,137 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <ctime> + +#include "core/hle/service/time/ephemeral_network_system_clock_context_writer.h" +#include "core/hle/service/time/local_system_clock_context_writer.h" +#include "core/hle/service/time/network_system_clock_context_writer.h" +#include "core/hle/service/time/time_manager.h" +#include "core/settings.h" + +namespace Service::Time { + +constexpr Clock::TimeSpanType standard_network_clock_accuracy{0x0009356907420000ULL}; + +static std::chrono::seconds GetSecondsSinceEpoch() { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()) + + Settings::values.custom_rtc_differential; +} + +static s64 GetExternalRtcValue() { + return GetSecondsSinceEpoch().count(); +} + +TimeManager::TimeManager(Core::System& system) + : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core}, + standard_network_system_clock_core{standard_steady_clock_core}, + standard_user_system_clock_core{standard_local_system_clock_core, + standard_network_system_clock_core, system}, + ephemeral_network_system_clock_core{tick_based_steady_clock_core}, + local_system_clock_context_writer{ + std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)}, + network_system_clock_context_writer{ + std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)}, + ephemeral_network_system_clock_context_writer{ + std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()}, + time_zone_content_manager{*this, system} { + + const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())}; + SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {}); + SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds()); + SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy); + SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom()); + SetupEphemeralNetworkSystemClock(); +} + +TimeManager::~TimeManager() = default; + +void TimeManager::SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, + u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file) { + if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( + location_name, vfs_file) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point); + time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount( + total_location_name_count); + time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version); + time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized(); +} + +void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, + Clock::TimeSpanType setup_value, + Clock::TimeSpanType internal_offset, + bool is_rtc_reset_detected) { + standard_steady_clock_core.SetClockSourceId(clock_source_id); + standard_steady_clock_core.SetSetupValue(setup_value); + standard_steady_clock_core.SetInternalOffset(internal_offset); + standard_steady_clock_core.MarkAsInitialized(); + + const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)}; + shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point); +} + +void TimeManager::SetupStandardLocalSystemClock(Core::System& system, + Clock::SystemClockContext clock_context, + s64 posix_time) { + standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer); + + const auto current_time_point{ + standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)}; + if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) { + standard_local_system_clock_core.SetSystemClockContext(clock_context); + } else { + if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + } + + standard_local_system_clock_core.MarkAsInitialized(); +} + +void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, + Clock::TimeSpanType sufficient_accuracy) { + standard_network_system_clock_core.SetUpdateCallbackInstance( + network_system_clock_context_writer); + + if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy( + sufficient_accuracy); + standard_network_system_clock_core.MarkAsInitialized(); +} + +void TimeManager::SetupStandardUserSystemClock( + Core::System& system, bool is_automatic_correction_enabled, + Clock::SteadyClockTimePoint steady_clock_time_point) { + if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled( + system, is_automatic_correction_enabled) != RESULT_SUCCESS) { + UNREACHABLE(); + return; + } + + standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point); + standard_user_system_clock_core.MarkAsInitialized(); + shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled); +} + +void TimeManager::SetupEphemeralNetworkSystemClock() { + ephemeral_network_system_clock_core.SetUpdateCallbackInstance( + ephemeral_network_system_clock_context_writer); + ephemeral_network_system_clock_core.MarkAsInitialized(); +} + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h new file mode 100644 index 000000000..8e65f0d22 --- /dev/null +++ b/src/core/hle/service/time/time_manager.h @@ -0,0 +1,117 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/ephemeral_network_system_clock_core.h" +#include "core/hle/service/time/standard_local_system_clock_core.h" +#include "core/hle/service/time/standard_network_system_clock_core.h" +#include "core/hle/service/time/standard_steady_clock_core.h" +#include "core/hle/service/time/standard_user_system_clock_core.h" +#include "core/hle/service/time/tick_based_steady_clock_core.h" +#include "core/hle/service/time/time_sharedmemory.h" +#include "core/hle/service/time/time_zone_content_manager.h" + +namespace Service::Time { + +namespace Clock { +class EphemeralNetworkSystemClockContextWriter; +class LocalSystemClockContextWriter; +class NetworkSystemClockContextWriter; +} // namespace Clock + +// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783). +// This code was released under public domain. + +class TimeManager final { +public: + explicit TimeManager(Core::System& system); + ~TimeManager(); + + Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() { + return standard_steady_clock_core; + } + + const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const { + return standard_steady_clock_core; + } + + Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() { + return standard_local_system_clock_core; + } + + const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const { + return standard_local_system_clock_core; + } + + Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() { + return standard_network_system_clock_core; + } + + const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const { + return standard_network_system_clock_core; + } + + Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() { + return standard_user_system_clock_core; + } + + const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const { + return standard_user_system_clock_core; + } + + TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() { + return time_zone_content_manager; + } + + const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const { + return time_zone_content_manager; + } + + SharedMemory& GetSharedMemory() { + return shared_memory; + } + + const SharedMemory& GetSharedMemory() const { + return shared_memory; + } + + void SetupTimeZoneManager(std::string location_name, + Clock::SteadyClockTimePoint time_zone_updated_time_point, + std::size_t total_location_name_count, u128 time_zone_rule_version, + FileSys::VirtualFile& vfs_file); + +private: + void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id, + Clock::TimeSpanType setup_value, + Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected); + void SetupStandardLocalSystemClock(Core::System& system, + Clock::SystemClockContext clock_context, s64 posix_time); + void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context, + Clock::TimeSpanType sufficient_accuracy); + void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled, + Clock::SteadyClockTimePoint steady_clock_time_point); + void SetupEphemeralNetworkSystemClock(); + + SharedMemory shared_memory; + + Clock::StandardSteadyClockCore standard_steady_clock_core; + Clock::TickBasedSteadyClockCore tick_based_steady_clock_core; + Clock::StandardLocalSystemClockCore standard_local_system_clock_core; + Clock::StandardNetworkSystemClockCore standard_network_system_clock_core; + Clock::StandardUserSystemClockCore standard_user_system_clock_core; + Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core; + + std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer; + std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer; + std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter> + ephemeral_network_system_clock_context_writer; + + TimeZone::TimeZoneContentManager time_zone_content_manager; +}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp index 4035f5072..9b03191bf 100644 --- a/src/core/hle/service/time/time_sharedmemory.cpp +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -3,20 +3,21 @@ // Refer to the license.txt file included. #include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/steady_clock_core.h" #include "core/hle/service/time/time_sharedmemory.h" namespace Service::Time { -const std::size_t SHARED_MEMORY_SIZE = 0x1000; + +static constexpr std::size_t SHARED_MEMORY_SIZE{0x1000}; SharedMemory::SharedMemory(Core::System& system) : system(system) { shared_memory_holder = Kernel::SharedMemory::Create( system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory"); - - // Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash - // if it's set to anything else - shared_memory_format.format_version = 14; - std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format)); + std::memset(shared_memory_holder->GetPointer(), 0, SHARED_MEMORY_SIZE); } SharedMemory::~SharedMemory() = default; @@ -25,44 +26,32 @@ std::shared_ptr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() cons return shared_memory_holder; } -void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) { +void SharedMemory::SetupStandardSteadyClock(Core::System& system, + const Common::UUID& clock_source_id, + Clock::TimeSpanType current_time_point) { + const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks( + Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()), + Core::Timing::CNTFREQ)}; + const Clock::SteadyClockContext context{ + static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), + clock_source_id}; shared_memory_format.standard_steady_clock_timepoint.StoreData( - shared_memory_holder->GetPointer(), timepoint); + shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) { +void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) { shared_memory_format.standard_local_system_clock_context.StoreData( shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) { +void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) { shared_memory_format.standard_network_system_clock_context.StoreData( shared_memory_holder->GetPointer(), context); } -void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) { +void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) { shared_memory_format.standard_user_system_clock_automatic_correction.StoreData( - shared_memory_holder->GetPointer(), enabled); -} - -SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() { - return shared_memory_format.standard_steady_clock_timepoint.ReadData( - shared_memory_holder->GetPointer()); -} - -SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() { - return shared_memory_format.standard_local_system_clock_context.ReadData( - shared_memory_holder->GetPointer()); -} - -SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() { - return shared_memory_format.standard_network_system_clock_context.ReadData( - shared_memory_holder->GetPointer()); -} - -bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() { - return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData( - shared_memory_holder->GetPointer()); + shared_memory_holder->GetPointer(), is_enabled); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h index 904a96430..5976b2046 100644 --- a/src/core/hle/service/time/time_sharedmemory.h +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -5,11 +5,14 @@ #pragma once #include "common/common_types.h" +#include "common/uuid.h" #include "core/hle/kernel/shared_memory.h" -#include "core/hle/service/time/time.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/service/time/clock_types.h" namespace Service::Time { -class SharedMemory { + +class SharedMemory final { public: explicit SharedMemory(Core::System& system); ~SharedMemory(); @@ -17,22 +20,10 @@ public: // Return the shared memory handle std::shared_ptr<Kernel::SharedMemory> GetSharedMemoryHolder() const; - // Set memory barriers in shared memory and update them - void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint); - void SetStandardLocalSystemClockContext(const SystemClockContext& context); - void SetStandardNetworkSystemClockContext(const SystemClockContext& context); - void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled); - - // Pull from memory barriers in the shared memory - SteadyClockTimePoint GetStandardSteadyClockTimepoint(); - SystemClockContext GetStandardLocalSystemClockContext(); - SystemClockContext GetStandardNetworkSystemClockContext(); - bool GetStandardUserSystemClockAutomaticCorrectionEnabled(); - // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this? template <typename T, std::size_t Offset> struct MemoryBarrier { - static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable"); + static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); u32_le read_attempt{}; std::array<T, 2> data{}; @@ -57,16 +48,22 @@ public: // Shared memory format struct Format { - MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint; - MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context; - MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context; + MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint; + MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context; + MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context; MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; u32_le format_version; }; static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); + void SetupStandardSteadyClock(Core::System& system, const Common::UUID& clock_source_id, + Clock::TimeSpanType currentTimePoint); + void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); + void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); + void SetAutomaticCorrectionEnabled(bool is_enabled); + private: - std::shared_ptr<Kernel::SharedMemory> shared_memory_holder{}; + std::shared_ptr<Kernel::SharedMemory> shared_memory_holder; Core::System& system; Format shared_memory_format{}; }; diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp new file mode 100644 index 000000000..57b1a2bca --- /dev/null +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -0,0 +1,125 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <sstream> + +#include "common/logging/log.h" +#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/hle/service/filesystem/filesystem.h" +#include "core/hle/service/time/time_manager.h" +#include "core/hle/service/time/time_zone_content_manager.h" + +namespace Service::Time::TimeZone { + +constexpr u64 time_zone_binary_titleid{0x010000000000080E}; + +static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) { + const auto* nand{system.GetFileSystemController().GetSystemNANDContents()}; + const auto nca{nand->GetEntry(time_zone_binary_titleid, FileSys::ContentRecordType::Data)}; + + FileSys::VirtualFile romfs; + if (nca) { + romfs = nca->GetRomFS(); + } + + if (!romfs) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(time_zone_binary_titleid); + } + + if (!romfs) { + LOG_ERROR(Service_Time, "Failed to find or synthesize {:016X!}", time_zone_binary_titleid); + return {}; + } + + return FileSys::ExtractRomFS(romfs); +} + +static std::vector<std::string> BuildLocationNameCache(Core::System& system) { + const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; + if (!extracted_romfs) { + LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); + return {}; + } + + const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")}; + if (!binary_list) { + LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid); + return {}; + } + + std::vector<char> raw_data(binary_list->GetSize()); + binary_list->ReadBytes<char>(raw_data.data(), binary_list->GetSize()); + + std::stringstream data_stream{raw_data.data()}; + std::string name; + std::vector<std::string> location_name_cache; + while (std::getline(data_stream, name)) { + name.pop_back(); // Remove carriage return + location_name_cache.emplace_back(std::move(name)); + } + return location_name_cache; +} + +TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system) + : system{system}, location_name_cache{BuildLocationNameCache(system)} { + if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile("GMT", vfs_file) == RESULT_SUCCESS) { + const auto time_point{ + time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)}; + time_manager.SetupTimeZoneManager("GMT", time_point, location_name_cache.size(), {}, + vfs_file); + } else { + time_zone_manager.MarkAsInitialized(); + } +} + +ResultCode TimeZoneContentManager::LoadTimeZoneRule(TimeZoneRule& rules, + const std::string& location_name) const { + FileSys::VirtualFile vfs_file; + if (const ResultCode result{GetTimeZoneInfoFile(location_name, vfs_file)}; + result != RESULT_SUCCESS) { + return result; + } + + return time_zone_manager.ParseTimeZoneRuleBinary(rules, vfs_file); +} + +bool TimeZoneContentManager::IsLocationNameValid(const std::string& location_name) const { + return std::find(location_name_cache.begin(), location_name_cache.end(), location_name) != + location_name_cache.end(); +} + +ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_name, + FileSys::VirtualFile& vfs_file) const { + if (!IsLocationNameValid(location_name)) { + return ERROR_TIME_NOT_FOUND; + } + + const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; + if (!extracted_romfs) { + LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); + return ERROR_TIME_NOT_FOUND; + } + + const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")}; + if (!zoneinfo_dir) { + LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid); + return ERROR_TIME_NOT_FOUND; + } + + vfs_file = zoneinfo_dir->GetFile(location_name); + if (!vfs_file) { + LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"!", time_zone_binary_titleid, + location_name); + return ERROR_TIME_NOT_FOUND; + } + + return RESULT_SUCCESS; +} + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h new file mode 100644 index 000000000..4f302c3b9 --- /dev/null +++ b/src/core/hle/service/time/time_zone_content_manager.h @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <vector> + +#include "core/hle/service/time/time_zone_manager.h" + +namespace Core { +class System; +} + +namespace Service::Time { +class TimeManager; +} + +namespace Service::Time::TimeZone { + +class TimeZoneContentManager final { +public: + TimeZoneContentManager(TimeManager& time_manager, Core::System& system); + + TimeZoneManager& GetTimeZoneManager() { + return time_zone_manager; + } + + const TimeZoneManager& GetTimeZoneManager() const { + return time_zone_manager; + } + + ResultCode LoadTimeZoneRule(TimeZoneRule& rules, const std::string& location_name) const; + +private: + bool IsLocationNameValid(const std::string& location_name) const; + ResultCode GetTimeZoneInfoFile(const std::string& location_name, + FileSys::VirtualFile& vfs_file) const; + + Core::System& system; + TimeZoneManager time_zone_manager; + const std::vector<std::string> location_name_cache; +}; + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp new file mode 100644 index 000000000..c06ca538a --- /dev/null +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -0,0 +1,1038 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <climits> + +#include <boost/safe_numerics/safe_integer.hpp> + +#include "common/assert.h" +#include "common/logging/log.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/hle/service/time/time_zone_manager.h" + +namespace Service::Time::TimeZone { + +static constexpr s32 epoch_year{1970}; +static constexpr s32 year_base{1900}; +static constexpr s32 epoch_week_day{4}; +static constexpr s32 seconds_per_minute{60}; +static constexpr s32 minutes_per_hour{60}; +static constexpr s32 hours_per_day{24}; +static constexpr s32 days_per_week{7}; +static constexpr s32 days_per_normal_year{365}; +static constexpr s32 days_per_leap_year{366}; +static constexpr s32 months_per_year{12}; +static constexpr s32 seconds_per_hour{seconds_per_minute * minutes_per_hour}; +static constexpr s32 seconds_per_day{seconds_per_hour * hours_per_day}; +static constexpr s32 years_per_repeat{400}; +static constexpr s64 average_seconds_per_year{31556952}; +static constexpr s64 seconds_per_repeat{years_per_repeat * average_seconds_per_year}; + +struct Rule { + enum class Type : u32 { JulianDay, DayOfYear, MonthNthDayOfWeek }; + Type rule_type{}; + s32 day{}; + s32 week{}; + s32 month{}; + s32 transition_time{}; +}; + +struct CalendarTimeInternal { + s64 year{}; + s8 month{}; + s8 day{}; + s8 hour{}; + s8 minute{}; + s8 second{}; + int Compare(const CalendarTimeInternal& other) const { + if (year != other.year) { + if (year < other.year) { + return -1; + } + return 1; + } + if (month != other.month) { + return month - other.month; + } + if (day != other.day) { + return day - other.day; + } + if (hour != other.hour) { + return hour - other.hour; + } + if (minute != other.minute) { + return minute - other.minute; + } + if (second != other.second) { + return second - other.second; + } + return {}; + } +}; + +template <typename TResult, typename TOperand> +static bool SafeAdd(TResult& result, TOperand op) { + const boost::safe_numerics::safe<TResult> safe_result{result}; + const boost::safe_numerics::safe<TOperand> safe_op{op}; + try { + result = safe_result + safe_op; + } catch (const std::exception&) { + return {}; // Failed with undefined behavior + } + return true; +} + +template <typename TResult, typename TUnit, typename TBase> +static bool SafeNormalize(TResult& result, TUnit& unit, TBase base) { + TUnit delta{}; + if (unit >= 0) { + delta = unit / base; + } else { + delta = -1 - (-1 - unit) / base; + } + unit -= delta * base; + return SafeAdd(result, delta); +} + +template <typename T> +static constexpr bool IsLeapYear(T year) { + return ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0); +} + +template <typename T> +static constexpr T GetYearLengthInDays(T year) { + return IsLeapYear(year) ? days_per_leap_year : days_per_normal_year; +} + +static constexpr s64 GetLeapDaysFromYearPositive(s64 year) { + return year / 4 - year / 100 + year / years_per_repeat; +} + +static constexpr s64 GetLeapDaysFromYear(s64 year) { + if (year < 0) { + return -1 - GetLeapDaysFromYearPositive(-1 - year); + } else { + return GetLeapDaysFromYearPositive(year); + } +} + +static constexpr int GetMonthLength(bool is_leap_year, int month) { + constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + return is_leap_year ? month_lengths_leap[month] : month_lengths[month]; +} + +static constexpr bool IsDigit(char value) { + return value >= '0' && value <= '9'; +} + +static constexpr int GetQZName(const char* name, int offset, char delimiter) { + while (name[offset] != '\0' && name[offset] != delimiter) { + offset++; + } + return offset; +} + +static constexpr int GetTZName(const char* name, int offset) { + for (char value{name[offset]}; + value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+'; + offset++) { + value = name[offset]; + } + return offset; +} + +static constexpr bool GetInteger(const char* name, int& offset, int& value, int min, int max) { + value = 0; + char temp{name[offset]}; + if (!IsDigit(temp)) { + return {}; + } + do { + value = value * 10 + (temp - '0'); + if (value > max) { + return {}; + } + temp = name[offset]; + } while (IsDigit(temp)); + + return value >= min; +} + +static constexpr bool GetSeconds(const char* name, int& offset, int& seconds) { + seconds = 0; + int value{}; + if (!GetInteger(name, offset, value, 0, hours_per_day * days_per_week - 1)) { + return {}; + } + seconds = value * seconds_per_hour; + + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, minutes_per_hour - 1)) { + return {}; + } + seconds += value * seconds_per_minute; + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, seconds_per_minute)) { + return {}; + } + seconds += value; + } + } + return true; +} + +static constexpr bool GetOffset(const char* name, int& offset, int& value) { + bool is_negative{}; + if (name[offset] == '-') { + is_negative = true; + offset++; + } else if (name[offset] == '+') { + offset++; + } + if (!GetSeconds(name, offset, value)) { + return {}; + } + if (is_negative) { + value = -value; + } + return true; +} + +static constexpr bool GetRule(const char* name, int& position, Rule& rule) { + bool is_valid{}; + if (name[position] == 'J') { + position++; + rule.rule_type = Rule::Type::JulianDay; + is_valid = GetInteger(name, position, rule.day, 1, days_per_normal_year); + } else if (name[position] == 'M') { + position++; + rule.rule_type = Rule::Type::MonthNthDayOfWeek; + is_valid = GetInteger(name, position, rule.month, 1, months_per_year); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.week, 1, 5); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.day, 0, days_per_week - 1); + } else if (isdigit(name[position])) { + rule.rule_type = Rule::Type::DayOfYear; + is_valid = GetInteger(name, position, rule.day, 0, days_per_leap_year - 1); + } else { + return {}; + } + if (!is_valid) { + return {}; + } + if (name[position] == '/') { + position++; + return GetOffset(name, position, rule.transition_time); + } else { + rule.transition_time = 2 * seconds_per_hour; + } + return true; +} + +static constexpr int TransitionTime(int year, Rule rule, int offset) { + int value{}; + switch (rule.rule_type) { + case Rule::Type::JulianDay: + value = (rule.day - 1) * seconds_per_day; + if (IsLeapYear(year) && rule.day >= 60) { + value += seconds_per_day; + } + break; + case Rule::Type::DayOfYear: + value = rule.day * seconds_per_day; + break; + case Rule::Type::MonthNthDayOfWeek: { + // Use Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence) to + // calculate the day of the week for any Julian or Gregorian calendar date. + const int m1{(rule.month + 9) % 12 + 1}; + const int yy0{(rule.month <= 2) ? (year - 1) : year}; + const int yy1{yy0 / 100}; + const int yy2{yy0 % 100}; + int day_of_week{((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7}; + + if (day_of_week < 0) { + day_of_week += days_per_week; + } + int day{rule.day - day_of_week}; + if (day < 0) { + day += days_per_week; + } + for (int i{1}; i < rule.week; i++) { + if (day + days_per_week >= GetMonthLength(IsLeapYear(year), rule.month - 1)) { + break; + } + day += days_per_week; + } + + value = day * seconds_per_day; + for (int index{}; index < rule.month - 1; ++index) { + value += GetMonthLength(IsLeapYear(year), index) * seconds_per_day; + } + break; + } + default: + UNREACHABLE(); + } + return value + rule.transition_time + offset; +} + +static bool ParsePosixName(const char* name, TimeZoneRule& rule) { + constexpr char default_rule[]{",M4.1.0,M10.5.0"}; + const char* std_name{name}; + int std_len{}; + int offset{}; + int std_offset{}; + + if (name[offset] == '<') { + offset++; + std_name = name + offset; + const int std_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + std_len = offset - std_name_offset; + offset++; + } else { + offset = GetTZName(name, offset); + std_len = offset; + } + if (!std_len) { + return {}; + } + if (!GetOffset(name, offset, std_offset)) { + return {}; + } + + int char_count{std_len + 1}; + int dest_len{}; + int dest_offset{}; + const char* dest_name{name + offset}; + if (rule.chars.size() < char_count) { + return {}; + } + + if (name[offset] != '\0') { + if (name[offset] == '<') { + dest_name = name + (++offset); + const int dest_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + dest_len = offset - dest_name_offset; + offset++; + } else { + dest_name = name + (offset); + offset = GetTZName(name, offset); + dest_len = offset; + } + if (dest_len == 0) { + return {}; + } + char_count += dest_len + 1; + if (rule.chars.size() < char_count) { + return {}; + } + if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') { + if (!GetOffset(name, offset, dest_offset)) { + return {}; + } + } else { + dest_offset = std_offset - seconds_per_hour; + } + if (name[offset] == '\0') { + name = default_rule; + offset = 0; + } + if (name[offset] == ',' || name[offset] == ';') { + offset++; + + Rule start{}; + if (!GetRule(name, offset, start)) { + return {}; + } + if (name[offset++] != ',') { + return {}; + } + + Rule end{}; + if (!GetRule(name, offset, end)) { + return {}; + } + if (name[offset] != '\0') { + return {}; + } + + rule.type_count = 2; + rule.ttis[0].gmt_offset = -dest_offset; + rule.ttis[0].is_dst = true; + rule.ttis[0].abbreviation_list_index = std_len + 1; + rule.ttis[1].gmt_offset = -std_offset; + rule.ttis[1].is_dst = false; + rule.ttis[1].abbreviation_list_index = 0; + rule.default_type = 0; + + s64 jan_first{}; + int time_count{}; + int jan_offset{}; + int year_beginning{epoch_year}; + do { + const int year_seconds{GetYearLengthInDays(year_beginning - 1) * seconds_per_day}; + year_beginning--; + if (!SafeAdd(jan_first, -year_seconds)) { + jan_offset = -year_seconds; + break; + } + } while (epoch_year - years_per_repeat / 2 < year_beginning); + + int year_limit{year_beginning + years_per_repeat + 1}; + int year{}; + for (year = year_beginning; year < year_limit; year++) { + int start_time{TransitionTime(year, start, std_offset)}; + int end_time{TransitionTime(year, end, dest_offset)}; + const int year_seconds{GetYearLengthInDays(year) * seconds_per_day}; + const bool is_reversed{end_time < start_time}; + if (is_reversed) { + int swap{start_time}; + start_time = end_time; + end_time = swap; + } + + if (is_reversed || + (start_time < end_time && + (end_time - start_time < (year_seconds + (std_offset - dest_offset))))) { + if (rule.ats.size() - 2 < time_count) { + break; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + start_time)) { + rule.types[time_count++] = is_reversed ? 1 : 0; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 1 : 0; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + end_time)) { + rule.types[time_count++] = is_reversed ? 0 : 1; + year_limit = year + years_per_repeat + 1; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 0 : 1; + } + } + if (!SafeAdd(jan_first, jan_offset + year_seconds)) { + break; + } + jan_offset = 0; + } + rule.time_count = time_count; + if (time_count == 0) { + rule.type_count = 1; + } else if (years_per_repeat < year - year_beginning) { + rule.go_back = true; + rule.go_ahead = true; + } + } else { + if (name[offset] == '\0') { + return {}; + } + + s64 their_std_offset{}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + if (rule.ttis[type].is_standard_time_daylight) { + their_std_offset = -rule.ttis[type].gmt_offset; + } + } + + s64 their_offset{their_std_offset}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + rule.types[index] = rule.ttis[type].is_dst ? 1 : 0; + if (!rule.ttis[type].is_gmt) { + if (!rule.ttis[type].is_standard_time_daylight) { + rule.ats[index] += dest_offset - their_std_offset; + } else { + rule.ats[index] += std_offset - their_std_offset; + } + } + their_offset = -rule.ttis[type].gmt_offset; + if (!rule.ttis[type].is_dst) { + their_std_offset = their_offset; + } + } + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + rule.ttis[1].gmt_offset = -dest_offset; + rule.ttis[1].is_dst = true; + rule.ttis[1].abbreviation_list_index = std_len + 1; + rule.type_count = 2; + rule.default_type = 0; + } + } else { + // Default is standard time + rule.type_count = 1; + rule.time_count = 0; + rule.default_type = 0; + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + } + + rule.char_count = char_count; + for (int index{}; index < std_len; ++index) { + rule.chars[index] = std_name[index]; + } + + rule.chars[std_len++] = '\0'; + if (dest_len != 0) { + for (int index{}; index < dest_len; ++index) { + rule.chars[std_len + index] = dest_name[index]; + } + rule.chars[std_len + dest_len] = '\0'; + } + + return true; +} + +static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFile& vfs_file) { + TzifHeader header{}; + if (vfs_file->ReadObject<TzifHeader>(&header) != sizeof(TzifHeader)) { + return {}; + } + + constexpr s32 time_zone_max_leaps{50}; + constexpr s32 time_zone_max_chars{50}; + if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps && + 0 < header.type_count && header.type_count < time_zone_rule.ttis.size() && + 0 <= header.time_count && header.time_count < time_zone_rule.ats.size() && + 0 <= header.char_count && header.char_count < time_zone_max_chars && + (header.ttis_std_count == header.type_count || header.ttis_std_count == 0) && + (header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) { + return {}; + } + time_zone_rule.time_count = header.time_count; + time_zone_rule.type_count = header.type_count; + time_zone_rule.char_count = header.char_count; + + int time_count{}; + u64 read_offset = sizeof(TzifHeader); + for (int index{}; index < time_zone_rule.time_count; ++index) { + s64_be at{}; + vfs_file->ReadObject<s64_be>(&at, read_offset); + time_zone_rule.types[index] = 1; + if (time_count != 0 && at <= time_zone_rule.ats[time_count - 1]) { + if (at < time_zone_rule.ats[time_count - 1]) { + return {}; + } + time_zone_rule.types[index - 1] = 0; + time_count--; + } + time_zone_rule.ats[time_count++] = at; + read_offset += sizeof(s64_be); + } + time_count = 0; + for (int index{}; index < time_zone_rule.time_count; ++index) { + const u8 type{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (time_zone_rule.time_count <= type) { + return {}; + } + if (time_zone_rule.types[index] != 0) { + time_zone_rule.types[time_count++] = type; + } + } + time_zone_rule.time_count = time_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + TimeTypeInfo& ttis{time_zone_rule.ttis[index]}; + u32_be gmt_offset{}; + vfs_file->ReadObject<u32_be>(&gmt_offset, read_offset); + read_offset += sizeof(u32_be); + ttis.gmt_offset = gmt_offset; + + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + ttis.is_dst = dst != 0; + + const s32 abbreviation_list_index{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (abbreviation_list_index >= time_zone_rule.char_count) { + return {}; + } + ttis.abbreviation_list_index = abbreviation_list_index; + } + + vfs_file->ReadArray(time_zone_rule.chars.data(), time_zone_rule.char_count, read_offset); + time_zone_rule.chars[time_zone_rule.char_count] = '\0'; + read_offset += time_zone_rule.char_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_standard_time_daylight = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_standard_time_daylight = dst != 0; + } + } + + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_gmt = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_gmt = dst != 0; + } + } + + const u64 position{(read_offset - sizeof(TzifHeader))}; + const std::size_t bytes_read{vfs_file->GetSize() - sizeof(TzifHeader) - position}; + if (bytes_read < 0) { + return {}; + } + constexpr s32 time_zone_name_max{255}; + if (bytes_read > (time_zone_name_max + 1)) { + return {}; + } + + std::array<char, time_zone_name_max + 1> temp_name{}; + vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset); + if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' && + time_zone_rule.type_count + 2 <= time_zone_rule.ttis.size()) { + temp_name[bytes_read - 1] = '\0'; + + std::array<char, time_zone_name_max> name{}; + std::memcpy(name.data(), temp_name.data() + 1, bytes_read - 1); + + TimeZoneRule temp_rule; + if (ParsePosixName(name.data(), temp_rule)) { + UNIMPLEMENTED(); + } + } + if (time_zone_rule.type_count == 0) { + return {}; + } + if (time_zone_rule.time_count > 1) { + UNIMPLEMENTED(); + } + + s32 default_type{}; + + for (default_type = 0; default_type < time_zone_rule.time_count; default_type++) { + if (time_zone_rule.types[default_type] == 0) { + break; + } + } + + default_type = default_type < time_zone_rule.time_count ? -1 : 0; + if (default_type < 0 && time_zone_rule.time_count > 0 && + time_zone_rule.ttis[time_zone_rule.types[0]].is_dst) { + default_type = time_zone_rule.types[0]; + while (--default_type >= 0) { + if (!time_zone_rule.ttis[default_type].is_dst) { + break; + } + } + } + if (default_type < 0) { + default_type = 0; + while (time_zone_rule.ttis[default_type].is_dst) { + if (++default_type >= time_zone_rule.type_count) { + default_type = 0; + break; + } + } + } + time_zone_rule.default_type = default_type; + return true; +} + +static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + s64 year{epoch_year}; + s64 time_days{time / seconds_per_day}; + s64 remaining_seconds{time % seconds_per_day}; + while (time_days < 0 || time_days >= GetYearLengthInDays(year)) { + s64 delta = time_days / days_per_leap_year; + if (!delta) { + delta = time_days < 0 ? -1 : 1; + } + s64 new_year{year}; + if (!SafeAdd(new_year, delta)) { + return ERROR_OUT_OF_RANGE; + } + time_days -= (new_year - year) * days_per_normal_year; + time_days -= GetLeapDaysFromYear(new_year - 1) - GetLeapDaysFromYear(year - 1); + year = new_year; + } + + s64 day_of_year{time_days}; + remaining_seconds += gmt_offset; + while (remaining_seconds < 0) { + remaining_seconds += seconds_per_day; + day_of_year--; + } + + while (remaining_seconds >= seconds_per_day) { + remaining_seconds -= seconds_per_day; + day_of_year++; + } + + while (day_of_year < 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OUT_OF_RANGE; + } + day_of_year += GetYearLengthInDays(year); + } + + while (day_of_year >= GetYearLengthInDays(year)) { + day_of_year -= GetYearLengthInDays(year); + if (!SafeAdd(year, 1)) { + return ERROR_OUT_OF_RANGE; + } + } + + calendar_time.year = year; + calendar_additional_info.day_of_year = static_cast<u32>(day_of_year); + s64 day_of_week{ + (epoch_week_day + + ((year - epoch_year) % days_per_week) * (days_per_normal_year % days_per_week) + + GetLeapDaysFromYear(year - 1) - GetLeapDaysFromYear(epoch_year - 1) + day_of_year) % + days_per_week}; + if (day_of_week < 0) { + day_of_week += days_per_week; + } + + calendar_additional_info.day_of_week = static_cast<u32>(day_of_week); + calendar_time.hour = static_cast<s8>((remaining_seconds / seconds_per_hour) % seconds_per_hour); + remaining_seconds %= seconds_per_hour; + calendar_time.minute = static_cast<s8>(remaining_seconds / seconds_per_minute); + calendar_time.second = static_cast<s8>(remaining_seconds % seconds_per_minute); + + for (calendar_time.month = 0; + day_of_year >= GetMonthLength(IsLeapYear(year), calendar_time.month); + ++calendar_time.month) { + day_of_year -= GetMonthLength(IsLeapYear(year), calendar_time.month); + } + + calendar_time.day = static_cast<s8>(day_of_year + 1); + calendar_additional_info.is_dst = false; + calendar_additional_info.gmt_offset = gmt_offset; + + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, + CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + if ((rules.go_ahead && time < rules.ats[0]) || + (rules.go_back && time > rules.ats[rules.time_count - 1])) { + s64 seconds{}; + if (time < rules.ats[0]) { + seconds = rules.ats[0] - time; + } else { + seconds = time - rules.ats[rules.time_count - 1]; + } + seconds--; + + const s64 years{(seconds / seconds_per_repeat + 1) * years_per_repeat}; + seconds = years * average_seconds_per_year; + + s64 new_time{time}; + if (time < rules.ats[0]) { + new_time += seconds; + } else { + new_time -= seconds; + } + if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) { + return ERROR_TIME_NOT_FOUND; + } + if (const ResultCode result{ + ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + if (time < rules.ats[0]) { + calendar_time.year -= years; + } else { + calendar_time.year += years; + } + + return RESULT_SUCCESS; + } + + s32 tti_index{}; + if (rules.time_count == 0 || time < rules.ats[0]) { + tti_index = rules.default_type; + } else { + s32 low{1}; + s32 high{rules.time_count}; + while (low < high) { + s32 mid{(low + high) >> 1}; + if (time < rules.ats[mid]) { + high = mid; + } else { + low = mid + 1; + } + } + tti_index = rules.types[low - 1]; + } + + if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset, + calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + + calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; + const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; + for (int index{}; time_zone[index] != '\0'; ++index) { + calendar_additional_info.timezone_name[index] = time_zone[index]; + } + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) { + CalendarTimeInternal calendar_time{}; + const ResultCode result{ + ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)}; + calendar.time.year = static_cast<s16>(calendar_time.year); + calendar.time.month = calendar_time.month; + calendar.time.day = calendar_time.day; + calendar.time.hour = calendar_time.hour; + calendar.time.minute = calendar_time.minute; + calendar.time.second = calendar_time.second; + return result; +} + +TimeZoneManager::TimeZoneManager() = default; +TimeZoneManager::~TimeZoneManager() = default; + +ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time, + CalendarInfo& calendar) const { + return ToCalendarTimeImpl(rules, time, calendar); +} + +ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name, + FileSys::VirtualFile& vfs_file) { + TimeZoneRule rule{}; + if (ParseTimeZoneBinary(rule, vfs_file)) { + device_location_name = location_name; + time_zone_rule = rule; + return RESULT_SUCCESS; + } + return ERROR_TIME_ZONE_CONVERSION_FAILED; +} + +ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) { + time_zone_update_time_point = value; + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const { + if (is_initialized) { + return ToCalendarTime(time_zone_rule, time, calendar); + } else { + return ERROR_UNINITIALIZED_CLOCK; + } +} + +ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules, + FileSys::VirtualFile& vfs_file) const { + if (!ParseTimeZoneBinary(rules, vfs_file)) { + return ERROR_TIME_ZONE_CONVERSION_FAILED; + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules, + const CalendarTime& calendar_time, s64& posix_time) const { + posix_time = 0; + + CalendarTimeInternal internal_time{}; + internal_time.year = calendar_time.year; + internal_time.month = calendar_time.month; + internal_time.day = calendar_time.day; + internal_time.hour = calendar_time.hour; + internal_time.minute = calendar_time.minute; + internal_time.second = calendar_time.second; + + s32 hour{internal_time.hour}; + s32 minute{internal_time.minute}; + if (!SafeNormalize(hour, minute, minutes_per_hour)) { + return ERROR_OVERFLOW; + } + internal_time.minute = static_cast<s8>(minute); + + s32 day{internal_time.day}; + if (!SafeNormalize(day, hour, hours_per_day)) { + return ERROR_OVERFLOW; + } + internal_time.day = static_cast<s8>(day); + internal_time.hour = static_cast<s8>(hour); + + s64 year{internal_time.year}; + s64 month{internal_time.month}; + if (!SafeNormalize(year, month, months_per_year)) { + return ERROR_OVERFLOW; + } + internal_time.month = static_cast<s8>(month); + + if (!SafeAdd(year, year_base)) { + return ERROR_OVERFLOW; + } + + while (day <= 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OVERFLOW; + } + s64 temp_year{year}; + if (1 < internal_time.month) { + ++temp_year; + } + day += static_cast<s32>(GetYearLengthInDays(temp_year)); + } + + while (day > days_per_leap_year) { + s64 temp_year{year}; + if (1 < internal_time.month) { + temp_year++; + } + day -= static_cast<s32>(GetYearLengthInDays(temp_year)); + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + + while (true) { + const s32 month_length{GetMonthLength(IsLeapYear(year), internal_time.month)}; + if (day <= month_length) { + break; + } + day -= month_length; + internal_time.month++; + if (internal_time.month >= months_per_year) { + internal_time.month = 0; + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + } + internal_time.day = static_cast<s8>(day); + + if (!SafeAdd(year, -year_base)) { + return ERROR_OVERFLOW; + } + internal_time.year = year; + + s32 saved_seconds{}; + if (internal_time.second >= 0 && internal_time.second < seconds_per_minute) { + saved_seconds = 0; + } else if (year + year_base < epoch_year) { + s32 second{internal_time.second}; + if (!SafeAdd(second, 1 - seconds_per_minute)) { + return ERROR_OVERFLOW; + } + saved_seconds = second; + internal_time.second = 1 - seconds_per_minute; + } else { + saved_seconds = internal_time.second; + internal_time.second = 0; + } + + s64 low{LLONG_MIN}; + s64 high{LLONG_MAX}; + while (true) { + s64 pivot{low / 2 + high / 2}; + if (pivot < low) { + pivot = low; + } else if (pivot > high) { + pivot = high; + } + s32 direction{}; + CalendarTimeInternal candidate_calendar_time{}; + CalendarAdditionalInfo unused{}; + if (ToCalendarTimeInternal(rules, pivot, candidate_calendar_time, unused) != + RESULT_SUCCESS) { + if (pivot > 0) { + direction = 1; + } else { + direction = -1; + } + } else { + direction = candidate_calendar_time.Compare(internal_time); + } + if (!direction) { + const s64 time_result{pivot + saved_seconds}; + if ((time_result < pivot) != (saved_seconds < 0)) { + return ERROR_OVERFLOW; + } + posix_time = time_result; + break; + } else { + if (pivot == low) { + if (pivot == LLONG_MAX) { + return ERROR_TIME_NOT_FOUND; + } + pivot++; + low++; + } else if (pivot == high) { + if (pivot == LLONG_MIN) { + return ERROR_TIME_NOT_FOUND; + } + pivot--; + high--; + } + if (low > high) { + return ERROR_TIME_NOT_FOUND; + } + if (direction > 0) { + high = pivot; + } else { + low = pivot; + } + } + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + std::memcpy(value.data(), device_location_name.c_str(), device_location_name.size()); + return RESULT_SUCCESS; +} + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h new file mode 100644 index 000000000..7c6f975ae --- /dev/null +++ b/src/core/hle/service/time/time_zone_manager.h @@ -0,0 +1,53 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> + +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time::TimeZone { + +class TimeZoneManager final { +public: + TimeZoneManager(); + ~TimeZoneManager(); + + void SetTotalLocationNameCount(std::size_t value) { + total_location_name_count = value; + } + + void SetTimeZoneRuleVersion(const u128& value) { + time_zone_rule_version = value; + } + + void MarkAsInitialized() { + is_initialized = true; + } + + ResultCode SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name, + FileSys::VirtualFile& vfs_file); + ResultCode SetUpdatedTime(const Clock::SteadyClockTimePoint& value); + ResultCode GetDeviceLocationName(TimeZone::LocationName& value) const; + ResultCode ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const; + ResultCode ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const; + ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const; + ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time, + s64& posix_time) const; + +private: + bool is_initialized{}; + TimeZoneRule time_zone_rule{}; + std::string device_location_name{"GMT"}; + u128 time_zone_rule_version{}; + std::size_t total_location_name_count{}; + Clock::SteadyClockTimePoint time_zone_update_time_point{ + Clock::SteadyClockTimePoint::GetRandom()}; +}; + +} // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp new file mode 100644 index 000000000..1566e778e --- /dev/null +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -0,0 +1,148 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/time/time_zone_content_manager.h" +#include "core/hle/service/time/time_zone_service.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::Time { + +ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_content_manager) + : ServiceFramework("ITimeZoneService"), time_zone_content_manager{time_zone_content_manager} { + static const FunctionInfo functions[] = { + {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, + {1, nullptr, "SetDeviceLocationName"}, + {2, nullptr, "GetTotalLocationNameCount"}, + {3, nullptr, "LoadLocationNameList"}, + {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, + {5, nullptr, "GetTimeZoneRuleVersion"}, + {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, + {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, + {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"}, + {202, nullptr, "ToPosixTimeWithMyRule"}, + }; + RegisterHandlers(functions); +} + +void ITimeZoneService::GetDeviceLocationName(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + TimeZone::LocationName location_name{}; + if (const ResultCode result{ + time_zone_content_manager.GetTimeZoneManager().GetDeviceLocationName(location_name)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, (sizeof(location_name) / 4) + 2}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(location_name); +} + +void ITimeZoneService::LoadTimeZoneRule(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto raw_location_name{rp.PopRaw<std::array<u8, 0x24>>()}; + + std::string location_name; + for (const auto& byte : raw_location_name) { + // Strip extra bytes + if (byte == '\0') { + break; + } + location_name.push_back(byte); + } + + LOG_DEBUG(Service_Time, "called, location_name={}", location_name); + + TimeZone::TimeZoneRule time_zone_rule{}; + if (const ResultCode result{ + time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); + std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); + ctx.WriteBuffer(time_zone_rule_outbuffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ITimeZoneService::ToCalendarTime(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto posix_time{rp.Pop<s64>()}; + + LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time); + + TimeZone::TimeZoneRule time_zone_rule{}; + const auto buffer{ctx.ReadBuffer()}; + std::memcpy(&time_zone_rule, buffer.data(), buffer.size()); + + TimeZone::CalendarInfo calendar_info{}; + if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToCalendarTime( + time_zone_rule, posix_time, calendar_info)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_info); +} + +void ITimeZoneService::ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto posix_time{rp.Pop<s64>()}; + + LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time); + + TimeZone::CalendarInfo calendar_info{}; + if (const ResultCode result{ + time_zone_content_manager.GetTimeZoneManager().ToCalendarTimeWithMyRules( + posix_time, calendar_info)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(calendar_info); +} + +void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + IPC::RequestParser rp{ctx}; + const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()}; + TimeZone::TimeZoneRule time_zone_rule{}; + std::memcpy(&time_zone_rule, ctx.ReadBuffer().data(), sizeof(TimeZone::TimeZoneRule)); + + s64 posix_time{}; + if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToPosixTime( + time_zone_rule, calendar_time, posix_time)}; + result != RESULT_SUCCESS) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + // TODO(bunnei): Handle multiple times + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(1); // Number of times we're returning + ctx.WriteBuffer(&posix_time, sizeof(s64)); +} + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h new file mode 100644 index 000000000..a92b4312b --- /dev/null +++ b/src/core/hle/service/time/time_zone_service.h @@ -0,0 +1,30 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Time { + +namespace TimeZone { +class TimeZoneContentManager; +} + +class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { +public: + explicit ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_manager); + +private: + void GetDeviceLocationName(Kernel::HLERequestContext& ctx); + void LoadTimeZoneRule(Kernel::HLERequestContext& ctx); + void ToCalendarTime(Kernel::HLERequestContext& ctx); + void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx); + void ToPosixTime(Kernel::HLERequestContext& ctx); + +private: + TimeZone::TimeZoneContentManager& time_zone_content_manager; +}; + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h new file mode 100644 index 000000000..9be15b53e --- /dev/null +++ b/src/core/hle/service/time/time_zone_types.h @@ -0,0 +1,87 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::Time::TimeZone { + +using LocationName = std::array<char, 0x24>; + +/// https://switchbrew.org/wiki/Glue_services#ttinfo +struct TimeTypeInfo { + s32 gmt_offset{}; + u8 is_dst{}; + INSERT_PADDING_BYTES(3); + s32 abbreviation_list_index{}; + u8 is_standard_time_daylight{}; + u8 is_gmt{}; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(TimeTypeInfo) == 0x10, "TimeTypeInfo is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#TimeZoneRule +struct TimeZoneRule { + s32 time_count{}; + s32 type_count{}; + s32 char_count{}; + u8 go_back{}; + u8 go_ahead{}; + INSERT_PADDING_BYTES(2); + std::array<s64, 1000> ats{}; + std::array<s8, 1000> types{}; + std::array<TimeTypeInfo, 128> ttis{}; + std::array<char, 512> chars{}; + s32 default_type{}; + INSERT_PADDING_BYTES(0x12C4); +}; +static_assert(sizeof(TimeZoneRule) == 0x4000, "TimeZoneRule is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#CalendarAdditionalInfo +struct CalendarAdditionalInfo { + u32 day_of_week{}; + u32 day_of_year{}; + std::array<char, 8> timezone_name; + u32 is_dst{}; + s32 gmt_offset{}; +}; +static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo is incorrect size"); + +/// https://switchbrew.org/wiki/Glue_services#CalendarTime +struct CalendarTime { + s16 year{}; + s8 month{}; + s8 day{}; + s8 hour{}; + s8 minute{}; + s8 second{}; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime is incorrect size"); + +struct CalendarInfo { + CalendarTime time{}; + CalendarAdditionalInfo additiona_info{}; +}; +static_assert(sizeof(CalendarInfo) == 0x20, "CalendarInfo is incorrect size"); + +struct TzifHeader { + u32_be magic{}; + u8 version{}; + INSERT_PADDING_BYTES(15); + s32_be ttis_gmt_count{}; + s32_be ttis_std_count{}; + s32_be leap_count{}; + s32_be time_count{}; + s32_be type_count{}; + s32_be char_count{}; +}; +static_assert(sizeof(TzifHeader) == 0x2C, "TzifHeader is incorrect size"); + +} // namespace Service::Time::TimeZone |