From ba418976b089e6791ad926d71b6a2eafb26749e6 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Wed, 3 Mar 2021 20:42:51 +0000 Subject: [PATCH] Implement full timezone service support This serves as an extension to the initial time commit and combined they provide a complete implementation of everything application facing in time. psc:ITimeZoneService and glue:ITimeZoneService are used to convert between POSIX and calendar times according to the device location. Timezone binaries are used during the conversion, details of them can be read about in the previous commit. This is based off my own glue RE and Thog's time RE. --- app/CMakeLists.txt | 2 + app/src/main/cpp/emu_jni.cpp | 28 +++- app/src/main/cpp/skyline/os.cpp | 2 +- app/src/main/cpp/skyline/os.h | 3 +- .../skyline/services/glue/IStaticService.cpp | 9 +- .../skyline/services/glue/IStaticService.h | 3 +- .../services/glue/ITimeZoneService.cpp | 102 +++++++++++++ .../skyline/services/glue/ITimeZoneService.h | 74 ++++++++++ .../main/cpp/skyline/services/serviceman.cpp | 6 +- .../services/timesrv/IStaticService.cpp | 8 +- .../skyline/services/timesrv/IStaticService.h | 8 +- .../services/timesrv/ITimeZoneService.cpp | 137 +++++++++++++++--- .../services/timesrv/ITimeZoneService.h | 63 +++++++- .../cpp/skyline/services/timesrv/common.h | 12 +- .../cpp/skyline/services/timesrv/core.cpp | 41 +++++- .../main/cpp/skyline/services/timesrv/core.h | 5 + .../cpp/skyline/services/timesrv/results.h | 1 + .../services/timesrv/time_manager_server.cpp | 4 + .../services/timesrv/time_manager_server.h | 9 +- .../services/timesrv/timezone_manager.cpp | 130 +++++++++++++++++ .../services/timesrv/timezone_manager.h | 82 +++++++++++ 21 files changed, 679 insertions(+), 50 deletions(-) create mode 100644 app/src/main/cpp/skyline/services/glue/ITimeZoneService.cpp create mode 100644 app/src/main/cpp/skyline/services/glue/ITimeZoneService.h create mode 100644 app/src/main/cpp/skyline/services/timesrv/timezone_manager.cpp create mode 100644 app/src/main/cpp/skyline/services/timesrv/timezone_manager.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 08fd883f..425b786a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -128,12 +128,14 @@ add_library(skyline SHARED ${source_DIR}/skyline/services/timesrv/common.cpp ${source_DIR}/skyline/services/timesrv/core.cpp ${source_DIR}/skyline/services/timesrv/time_shared_memory.cpp + ${source_DIR}/skyline/services/timesrv/timezone_manager.cpp ${source_DIR}/skyline/services/timesrv/time_manager_server.cpp ${source_DIR}/skyline/services/timesrv/IStaticService.cpp ${source_DIR}/skyline/services/timesrv/ISystemClock.cpp ${source_DIR}/skyline/services/timesrv/ISteadyClock.cpp ${source_DIR}/skyline/services/timesrv/ITimeZoneService.cpp ${source_DIR}/skyline/services/glue/IStaticService.cpp + ${source_DIR}/skyline/services/glue/ITimeZoneService.cpp ${source_DIR}/skyline/services/fssrv/IFileSystemProxy.cpp ${source_DIR}/skyline/services/fssrv/IFileSystem.cpp ${source_DIR}/skyline/services/fssrv/IFile.cpp diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 3fdf3e19..f15567ab 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "skyline/common.h" #include "skyline/common/signal.h" #include "skyline/common/settings.h" @@ -23,6 +24,31 @@ std::weak_ptr OsWeak; std::weak_ptr GpuWeak; std::weak_ptr InputWeak; +// https://cs.android.com/android/platform/superproject/+/master:bionic/libc/tzcode/bionic.cpp;l=43;drc=master;bpv=1;bpt=1 +static std::string GetTimeZoneName() { + const char* nameEnv = getenv("TZ"); + if (nameEnv) + return std::string(nameEnv); + + char propBuf[PROP_VALUE_MAX]; + if (__system_property_get("persist.sys.timezone", propBuf)) { + std::string nameProp(propBuf); + + // Flip -/+, see https://cs.android.com/android/platform/superproject/+/master:bionic/libc/tzcode/bionic.cpp;l=53;drc=master;bpv=1;bpt=1 + if (nameProp.size() > 2) { + if (nameProp[2] == '-') + nameProp[2] = '+'; + else if (nameProp[2] == '+') + nameProp[2] = '-'; + } + + return nameProp; + } + + // Fallback to GMT + return "GMT"; +} + extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring, jobject assetManager) { skyline::signal::ScopedStackBlocker stackBlocker; // We do not want anything to unwind past JNI code as there are invalid stack frames which can lead to a segmentation fault Fps = FrameTime = 0; @@ -39,7 +65,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( auto start{std::chrono::steady_clock::now()}; try { - auto os{std::make_shared(jvmManager, logger, settings, std::string(appFilesPath), std::make_shared(AAssetManager_fromJava(env, assetManager)))}; + auto os{std::make_shared(jvmManager, logger, settings, std::string(appFilesPath), GetTimeZoneName(), std::make_shared(AAssetManager_fromJava(env, assetManager)))}; OsWeak = os; GpuWeak = os->state.gpu; InputWeak = os->state.input; diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index 095e4403..228f37e5 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -13,7 +13,7 @@ #include "os.h" namespace skyline::kernel { - OS::OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::shared_ptr assetFileSystem) : state(this, jvmManager, settings, logger), appFilesPath(std::move(appFilesPath)), assetFileSystem(std::move(assetFileSystem)), serviceManager(state) {} + OS::OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::string deviceTimeZone, std::shared_ptr assetFileSystem) : state(this, jvmManager, settings, logger), appFilesPath(std::move(appFilesPath)), deviceTimeZone(std::move(deviceTimeZone)), assetFileSystem(std::move(assetFileSystem)), serviceManager(state) {} void OS::Execute(int romFd, loader::RomFormat romType) { auto romFile{std::make_shared(romFd)}; diff --git a/app/src/main/cpp/skyline/os.h b/app/src/main/cpp/skyline/os.h index 9cef2ee7..c00396ef 100644 --- a/app/src/main/cpp/skyline/os.h +++ b/app/src/main/cpp/skyline/os.h @@ -15,6 +15,7 @@ namespace skyline::kernel { public: DeviceState state; std::string appFilesPath; //!< The full path to the app's files directory + std::string deviceTimeZone; //!< The timezone name (e.g. Europe/London) std::shared_ptr assetFileSystem; //!< A filesystem to be used for accessing emulator assets (like tzdata) service::ServiceManager serviceManager; @@ -23,7 +24,7 @@ namespace skyline::kernel { * @param settings An instance of the Settings class * @param window The ANativeWindow object to draw the screen to */ - OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::shared_ptr); + OS(std::shared_ptr &jvmManager, std::shared_ptr &logger, std::shared_ptr &settings, std::string appFilesPath, std::string deviceTimeZone, std::shared_ptr); /** * @brief Execute a particular ROM file diff --git a/app/src/main/cpp/skyline/services/glue/IStaticService.cpp b/app/src/main/cpp/skyline/services/glue/IStaticService.cpp index 68e8fbf0..b21b8d39 100644 --- a/app/src/main/cpp/skyline/services/glue/IStaticService.cpp +++ b/app/src/main/cpp/skyline/services/glue/IStaticService.cpp @@ -2,12 +2,12 @@ // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #include -#include #include +#include "ITimeZoneService.h" #include "IStaticService.h" namespace skyline::service::glue { - IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::StaticServicePermissions permissions) : BaseService(state, manager), core(std::move(core)), permissions(permissions) {} + IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::core::TimeServiceObject ×rvCore, timesrv::StaticServicePermissions permissions) : BaseService(state, manager), core(std::move(core)), timesrvCore(timesrvCore), permissions(permissions) {} Result IStaticService::GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { return core->GetStandardUserSystemClock(session, request, response); @@ -20,9 +20,10 @@ namespace skyline::service::glue { Result IStaticService::GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { return core->GetStandardSteadyClock(session, request, response); } + Result IStaticService::GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - // STUFFF - return core->GetTimeZoneService(session, request, response); + manager.RegisterService(std::make_shared(state, manager, core->GetTimeZoneService(state, manager), timesrvCore, true), session, response); + return {}; } Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { diff --git a/app/src/main/cpp/skyline/services/glue/IStaticService.h b/app/src/main/cpp/skyline/services/glue/IStaticService.h index ef0cd4d5..f6ecae7c 100644 --- a/app/src/main/cpp/skyline/services/glue/IStaticService.h +++ b/app/src/main/cpp/skyline/services/glue/IStaticService.h @@ -14,10 +14,11 @@ namespace skyline::service::glue { class IStaticService : public BaseService { private: std::shared_ptr core; + timesrv::core::TimeServiceObject ×rvCore; timesrv::StaticServicePermissions permissions; public: - IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::StaticServicePermissions permissions); + IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::core::TimeServiceObject ×rvCore, timesrv::StaticServicePermissions permissions); Result GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); diff --git a/app/src/main/cpp/skyline/services/glue/ITimeZoneService.cpp b/app/src/main/cpp/skyline/services/glue/ITimeZoneService.cpp new file mode 100644 index 00000000..a57a34a2 --- /dev/null +++ b/app/src/main/cpp/skyline/services/glue/ITimeZoneService.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include +#include +#include +#include "ITimeZoneService.h" + +namespace skyline::service::glue { + ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::core::TimeServiceObject ×rvCore, bool writeable) : BaseService(state, manager), core(std::move(core)), timesrvCore(timesrvCore), locationNameUpdateEvent(std::make_shared(state, false)), writeable(writeable) {} + + Result ITimeZoneService::GetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetDeviceLocationName(session, request, response); + } + + Result ITimeZoneService::SetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!writeable) + return timesrv::result::PermissionDenied; + + auto locationName{span(request.Pop()).as_string(true)}; + + // TODO Checked + auto timeZoneBinaryFile{state.os->assetFileSystem->OpenFile(fmt::format("tzdata/zoneinfo/{}", locationName))}; + std::vector timeZoneBinaryBuffer(timeZoneBinaryFile->size); + timeZoneBinaryFile->Read(timeZoneBinaryBuffer); + auto ret{core->SetDeviceLocationNameWithTimeZoneBinary(span(locationName).as_string(true), timeZoneBinaryBuffer)}; + if (ret) + return ret; + + locationNameUpdateEvent->Signal(); + return {}; + } + + Result ITimeZoneService::GetTotalLocationNameCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetTotalLocationNameCount(session, request, response); + } + + Result ITimeZoneService::LoadLocationNameList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto outList{request.outputBuf.at(0).cast()}; + auto offset{request.Pop()}; + + outList.copy_from(span(timesrvCore.locationNameList).subspan(offset, outList.size())); + + response.Push(outList.size()); + return {}; + } + + Result ITimeZoneService::LoadTimeZoneRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto locationName{span(request.Pop()).as_string(true)}; + + // TODO Checked + auto timeZoneBinaryFile{state.os->assetFileSystem->OpenFile(fmt::format("tzdata/zoneinfo/{}", locationName))}; + std::vector timeZoneBinaryBuffer(timeZoneBinaryFile->size); + timeZoneBinaryFile->Read(timeZoneBinaryBuffer); + return core->ParseTimeZoneBinary(timeZoneBinaryBuffer, request.outputBuf.at(0)); + } + + Result ITimeZoneService::GetTimeZoneRuleVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->GetTimeZoneRuleVersion(session, request, response); + } + + Result ITimeZoneService::GetDeviceLocationNameAndUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return timesrv::result::Unimplemented; + } + + Result ITimeZoneService::SetDeviceLocationNameWithTimeZoneBinary(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!writeable) + return timesrv::result::PermissionDenied; + + return timesrv::result::Unimplemented; + } + + Result ITimeZoneService::ParseTimeZoneBinary(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return timesrv::result::Unimplemented; + } + + Result ITimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto handle{state.process->InsertItem(locationNameUpdateEvent)}; + state.logger->Debug("Location Name Update Event Handle: 0x{:X}", handle); + response.copyHandles.push_back(handle); + return {}; + } + + Result ITimeZoneService::ToCalendarTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->ToCalendarTime(session, request, response); + } + + Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->ToCalendarTimeWithMyRule(session, request, response); + } + + Result ITimeZoneService::ToPosixTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->ToPosixTime(session, request, response); + } + + Result ITimeZoneService::ToPosixTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core->ToPosixTimeWithMyRule(session, request, response); + } +} diff --git a/app/src/main/cpp/skyline/services/glue/ITimeZoneService.h b/app/src/main/cpp/skyline/services/glue/ITimeZoneService.h new file mode 100644 index 00000000..3d2b0a71 --- /dev/null +++ b/app/src/main/cpp/skyline/services/glue/ITimeZoneService.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include + +namespace skyline::service::timesrv { + class ITimeZoneService; + + namespace core { + struct TimeServiceObject; + } +} +namespace skyline::service::glue { + + + class ITimeZoneService : public BaseService { + private: + std::shared_ptr core; + timesrv::core::TimeServiceObject ×rvCore; + std::shared_ptr locationNameUpdateEvent; //!< N uses a list here but a single event should be fine + bool writeable; //!< If this instance is allowed to set the device timezone + + public: + ITimeZoneService(const DeviceState &state, ServiceManager &manager, std::shared_ptr core, timesrv::core::TimeServiceObject ×rvCore, bool writeable); + + Result GetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetTotalLocationNameCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result LoadLocationNameList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result LoadTimeZoneRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetTimeZoneRuleVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetDeviceLocationNameAndUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetDeviceLocationNameWithTimeZoneBinary(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ParseTimeZoneBinary(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetDeviceLocationNameOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToCalendarTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToPosixTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToPosixTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + SERVICE_DECL( + SFUNC(0x0, ITimeZoneService, GetDeviceLocationName), + SFUNC(0x1, ITimeZoneService, SetDeviceLocationName), + SFUNC(0x2, ITimeZoneService, GetTotalLocationNameCount), + SFUNC(0x3, ITimeZoneService, LoadLocationNameList), + SFUNC(0x4, ITimeZoneService, LoadTimeZoneRule), + SFUNC(0x5, ITimeZoneService, GetTimeZoneRuleVersion), + SFUNC(0x6, ITimeZoneService, GetDeviceLocationNameAndUpdatedTime), + SFUNC(0x7, ITimeZoneService, SetDeviceLocationNameWithTimeZoneBinary), + SFUNC(0x8, ITimeZoneService, ParseTimeZoneBinary), + SFUNC(0x9, ITimeZoneService, GetDeviceLocationNameOperationEventReadableHandle), + SFUNC(0x64, ITimeZoneService, ToCalendarTime), + SFUNC(0x65, ITimeZoneService, ToCalendarTimeWithMyRule), + SFUNC(0xC9, ITimeZoneService, ToPosixTime), + SFUNC(0xCA, ITimeZoneService, ToPosixTimeWithMyRule) + ) + }; +} diff --git a/app/src/main/cpp/skyline/services/serviceman.cpp b/app/src/main/cpp/skyline/services/serviceman.cpp index 219c5a01..beae4696 100644 --- a/app/src/main/cpp/skyline/services/serviceman.cpp +++ b/app/src/main/cpp/skyline/services/serviceman.cpp @@ -64,9 +64,9 @@ namespace skyline::service { SERVICE_CASE(hid::IHidServer, "hid") SERVICE_CASE(timesrv::IStaticService, "time:s", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemPermissions) // Both of these would be registered after TimeServiceManager::Setup normally but we call that in the GlobalServiceState constructor so can just list them here directly SERVICE_CASE(timesrv::IStaticService, "time:su", globalServiceState->timesrv, timesrv::constant::StaticServiceSystemUpdatePermissions) - SERVICE_CASE(glue::IStaticService, "time:a", globalServiceState->timesrv.managerServer.GetAdminStaticService(state, *this), timesrv::constant::StaticServiceAdminPermissions) - SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetRepairStaticService(state, *this), timesrv::constant::StaticServiceRepairPermissions) - SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetUserStaticService(state, *this), timesrv::constant::StaticServiceUserPermissions) + SERVICE_CASE(glue::IStaticService, "time:a", globalServiceState->timesrv.managerServer.GetAdminStaticService(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceAdminPermissions) + SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetRepairStaticService(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions) + SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetUserStaticService(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions) SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv") SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv") SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a") diff --git a/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp b/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp index 1a830ca5..3363cefa 100644 --- a/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/IStaticService.cpp @@ -27,11 +27,15 @@ namespace skyline::service::timesrv { return {}; } - Result IStaticService::GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - manager.RegisterService(std::make_shared(state, manager), session, response); + Result IStaticService::GetTimeZoneServiceIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.RegisterService(std::make_shared(state, manager, core, permissions.writeTimezone), session, response); return {}; } + std::shared_ptr IStaticService::GetTimeZoneService(const DeviceState &state, ServiceManager &manager) { + return std::make_shared(state, manager, core, permissions.writeTimezone); + } + Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { manager.RegisterService(std::make_shared(state, manager, core.localSystemClock, permissions.writeLocalSystemClock, permissions.ignoreUninitializedChecks), session, response); return {}; diff --git a/app/src/main/cpp/skyline/services/timesrv/IStaticService.h b/app/src/main/cpp/skyline/services/timesrv/IStaticService.h index eaf22b82..f3e9f4df 100644 --- a/app/src/main/cpp/skyline/services/timesrv/IStaticService.h +++ b/app/src/main/cpp/skyline/services/timesrv/IStaticService.h @@ -46,6 +46,8 @@ namespace skyline::service::timesrv { } + class ITimeZoneService; + namespace core { struct TimeServiceObject; } @@ -70,7 +72,9 @@ namespace skyline::service::timesrv { Result GetStandardSteadyClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - Result GetTimeZoneService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result GetTimeZoneServiceIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + std::shared_ptr GetTimeZoneService(const DeviceState &state, ServiceManager &manager); Result GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); @@ -121,7 +125,7 @@ namespace skyline::service::timesrv { SFUNC(0x0, IStaticService, GetStandardUserSystemClock), SFUNC(0x1, IStaticService, GetStandardNetworkSystemClock), SFUNC(0x2, IStaticService, GetStandardSteadyClock), - SFUNC(0x3, IStaticService, GetTimeZoneService), + SFUNC(0x3, IStaticService, GetTimeZoneServiceIpc), SFUNC(0x4, IStaticService, GetStandardLocalSystemClock), SFUNC(0x5, IStaticService, GetEphemeralNetworkSystemClock), SFUNC(0x14, IStaticService, GetSharedMemoryNativeHandle), diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp index 9027bb21..9460fba9 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp @@ -2,32 +2,131 @@ // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) #include "common.h" +#include "core.h" #include "ITimeZoneService.h" namespace skyline::service::timesrv { - ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} + ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager, core::TimeServiceObject &core, bool writeable) : BaseService(state, manager), core(core), writeable(writeable) {} + + Result ITimeZoneService::GetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto locationName{core.timeZoneManager.GetLocationName()}; + if (locationName) + response.Push(*locationName); + + return locationName; + } + + Result ITimeZoneService::SetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + if (!writeable) + return result::PermissionDenied; + + return result::Unimplemented; + } + + Result ITimeZoneService::GetTotalLocationNameCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto count{core.timeZoneManager.GetLocationCount()}; + if (count) + response.Push(*count); + + return count; + } + + Result ITimeZoneService::LoadLocationNameList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return result::Unimplemented; + } + + Result ITimeZoneService::LoadTimeZoneRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return result::Unimplemented; + } + + Result ITimeZoneService::GetTimeZoneRuleVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto version{core.timeZoneManager.GetBinaryVersion()}; + if (version) + response.Push(*version); + + return version; + } + + Result ITimeZoneService::GetDeviceLocationNameAndUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto locationName{core.timeZoneManager.GetLocationName()}; + if (!locationName) + return locationName; + + auto updateTime{core.timeZoneManager.GetUpdateTime()}; + if (!updateTime) + return updateTime; + + response.Push(*locationName); + response.Push(0); + response.Push(*updateTime); + + return {}; + } + + Result ITimeZoneService::SetDeviceLocationNameWithTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto locationName{request.Pop()}; + + return SetDeviceLocationNameWithTimeZoneBinary(span(locationName).as_string(true), request.inputBuf.at(0)); + } + + Result ITimeZoneService::SetDeviceLocationNameWithTimeZoneBinary(std::string_view locationName, span binary) { + if (!writeable) + return result::PermissionDenied; + + return core.timeZoneManager.SetNewLocation(locationName, binary); + } + + Result ITimeZoneService::ParseTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return core.timeZoneManager.ParseTimeZoneBinary(request.inputBuf.at(0), request.outputBuf.at(0)); + } + + Result ITimeZoneService::ParseTimeZoneBinary(span binary, span rule) { + return core.timeZoneManager.ParseTimeZoneBinary(binary, rule); + } + + Result ITimeZoneService::GetDeviceLocationNameOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + return result::Unimplemented; + } + + Result ITimeZoneService::ToCalendarTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto posixTime{request.Pop()}; + auto calendarTime{core.timeZoneManager.ToCalendarTime(reinterpret_cast(request.inputBuf.at(0).data()), posixTime)}; + + if (calendarTime) + response.Push(*calendarTime); + + return calendarTime; + } Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto time{static_cast(request.Pop())}; - auto calender{*std::gmtime(&time)}; + auto posixTime{request.Pop()}; + auto calendarTime{core.timeZoneManager.ToCalendarTimeWithMyRule(posixTime)}; - CalendarTime calendarTime{ - .year = static_cast(calender.tm_year), - .month = static_cast(calender.tm_mon + 1), - .day = static_cast(calender.tm_hour), - .minute = static_cast(calender.tm_min), - .second = static_cast(calender.tm_sec), - }; - response.Push(calendarTime); + if (calendarTime) + response.Push(*calendarTime); - CalendarAdditionalInfo calendarInfo{ - .dayOfWeek = static_cast(calender.tm_wday), - .dayOfYear = static_cast(calender.tm_yday), - .timezoneName = "GMT", - .dst = static_cast(calender.tm_isdst), - .gmtOffset = static_cast(calender.tm_gmtoff), - }; - response.Push(calendarInfo); + return calendarTime; + } + + Result ITimeZoneService::ToPosixTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto calendarTime{request.Pop()}; + auto posixTime{core.timeZoneManager.ToPosixTime(reinterpret_cast(request.inputBuf.at(0).data()), calendarTime)}; + if (!posixTime) + return posixTime; + + request.outputBuf.at(0).as() = *posixTime; + response.Push(1); + return {}; + } + + Result ITimeZoneService::ToPosixTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto calendarTime{request.Pop()}; + auto posixTime{core.timeZoneManager.ToPosixTimeWithMyRule(calendarTime)}; + if (!posixTime) + return posixTime; + + request.outputBuf.at(0).as() = *posixTime; + response.Push(1); return {}; } } diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h index 49bc3a01..8ab560cb 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h @@ -6,22 +6,69 @@ #include namespace skyline::service::timesrv { + namespace core { + struct TimeServiceObject; + } + /** - * @brief ITimeZoneService is used to retrieve and set time + * @brief ITimeZoneService is used to retrieve and set timezone info and convert between times and dates * @url https://switchbrew.org/wiki/PSC_services#ITimeZoneService */ class ITimeZoneService : public BaseService { - public: - ITimeZoneService(const DeviceState &state, ServiceManager &manager); + private: + core::TimeServiceObject &core; + bool writeable; //!< If this instance is allowed to set the device timezone + + public: + ITimeZoneService(const DeviceState &state, ServiceManager &manager, core::TimeServiceObject &core, bool writeable); + + Result GetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetDeviceLocationName(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetTotalLocationNameCount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result LoadLocationNameList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result LoadTimeZoneRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetTimeZoneRuleVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result GetDeviceLocationNameAndUpdatedTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetDeviceLocationNameWithTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result SetDeviceLocationNameWithTimeZoneBinary(std::string_view locationName, span rule); + + Result ParseTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ParseTimeZoneBinary(span binary, span rule); + + Result GetDeviceLocationNameOperationEventReadableHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToCalendarTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); - /** - * @brief Receives a u64 #PosixTime (https://switchbrew.org/wiki/PSC_services#PosixTime), and returns a #CalendarTime (https://switchbrew.org/wiki/PSC_services#CalendarTime), #CalendarAdditionalInfo - * @url https://switchbrew.org/wiki/PSC_services#CalendarAdditionalInfo - */ Result ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + Result ToPosixTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + Result ToPosixTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + SERVICE_DECL( - SFUNC(0x65, ITimeZoneService, ToCalendarTimeWithMyRule) + SFUNC(0x0, ITimeZoneService, GetDeviceLocationName), + SFUNC(0x1, ITimeZoneService, SetDeviceLocationName), + SFUNC(0x2, ITimeZoneService, GetTotalLocationNameCount), + SFUNC(0x3, ITimeZoneService, LoadLocationNameList), + SFUNC(0x4, ITimeZoneService, LoadTimeZoneRule), + SFUNC(0x5, ITimeZoneService, GetTimeZoneRuleVersion), + SFUNC(0x6, ITimeZoneService, GetDeviceLocationNameAndUpdatedTime), + SFUNC(0x7, ITimeZoneService, SetDeviceLocationNameWithTimeZoneBinaryIpc), + SFUNC(0x8, ITimeZoneService, ParseTimeZoneBinaryIpc), + SFUNC(0x9, ITimeZoneService, GetDeviceLocationNameOperationEventReadableHandle), + SFUNC(0x64, ITimeZoneService, ToCalendarTime), + SFUNC(0x65, ITimeZoneService, ToCalendarTimeWithMyRule), + SFUNC(0xC9, ITimeZoneService, ToPosixTime), + SFUNC(0xCA, ITimeZoneService, ToPosixTimeWithMyRule) ) }; } diff --git a/app/src/main/cpp/skyline/services/timesrv/common.h b/app/src/main/cpp/skyline/services/timesrv/common.h index 3a89a25b..32f6d0e4 100644 --- a/app/src/main/cpp/skyline/services/timesrv/common.h +++ b/app/src/main/cpp/skyline/services/timesrv/common.h @@ -5,6 +5,7 @@ namespace skyline::service::timesrv { using PosixTime = i64; //!< Unit for time in seconds since the epoch + using LocationName = std::array; /** * @brief Stores a quantity of time with nanosecond accuracy and provides helper functions to convert it to other units @@ -97,12 +98,21 @@ namespace skyline::service::timesrv { struct CalendarAdditionalInfo { u32 dayOfWeek; //!< 0-6 u32 dayOfYear; //!< 0-365 - std::array timezoneName; + std::array timeZoneName; u32 dst; //!< If DST is in effect or not i32 gmtOffset; //!< Offset of the time from GMT in seconds }; static_assert(sizeof(CalendarAdditionalInfo) == 0x18); + /** + * @brief Returned by ToCalendarTime and contains all details about a time + */ + struct FullCalendarTime { + CalendarTime calendarTime; + CalendarAdditionalInfo additionalInfo; + }; + static_assert(sizeof(FullCalendarTime) == 0x20); + /** * @brief A snapshot of all clocks in the system */ diff --git a/app/src/main/cpp/skyline/services/timesrv/core.cpp b/app/src/main/cpp/skyline/services/timesrv/core.cpp index b1dd6b0b..d688f4cc 100644 --- a/app/src/main/cpp/skyline/services/timesrv/core.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/core.cpp @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include +#include +#include #include "core.h" #include "time_manager_server.h" @@ -220,7 +223,6 @@ namespace skyline::service::timesrv::core { } TimeServiceObject::TimeServiceObject(const DeviceState &state) : timeSharedMemory(state), localSystemClockContextWriter(timeSharedMemory), networkSystemClockContextWriter(timeSharedMemory), localSystemClock(standardSteadyClock), networkSystemClock(standardSteadyClock), userSystemClock(state, standardSteadyClock, localSystemClock, networkSystemClock, timeSharedMemory), empheralSystemClock(tickBasedSteadyClock), managerServer(*this) { - // Setup time service: // A new rtc UUID is generated every time glue inits time auto rtcId{UUID::GenerateUuidV4()}; @@ -256,5 +258,42 @@ namespace skyline::service::timesrv::core { // Initialise the user system clock with automatic correction disabled as we don't emulate the automatic correction thread managerServer.SetupStandardUserSystemClock(false, SteadyClockTimePoint{.clockSourceId = UUID::GenerateUuidV4()}); managerServer.SetupEphemeralSystemClock(); + + // Timezone init - normally done in glue + + // Act as if we just updated the current timezone + auto timezoneUpdateTime{standardSteadyClock.GetTimePoint()}; + if (!timezoneUpdateTime) + throw exception("Failed to create a timezone updated timepoint!"); + + // TODO Checked + auto timeZoneBinaryListFile{state.os->assetFileSystem->OpenFile("tzdata/binaryList.txt")}; + std::vector buffer(timeZoneBinaryListFile->size); + timeZoneBinaryListFile->Read(buffer); + + // Parse binaryList.txt into a vector + auto prev{buffer.begin()}; + for (auto it{buffer.begin()}; it != buffer.end(); it++) { + if (*it == '\n' && prev != it) { + timesrv::LocationName name{}; + span(prev.base(), std::distance(prev, std::prev(it))).as_string().copy(name.data(), name.size()); + locationNameList.push_back(name); + + if (std::next(it) != buffer.end()) + prev = std::next(it); + } + } + + // TODO Checked + auto timeZoneBinaryVersionFile{state.os->assetFileSystem->OpenFile("tzdata/version.txt")}; + std::array timeZoneBinaryVersion{}; + timeZoneBinaryVersionFile->Read(timeZoneBinaryVersion); + + // TODO Checked + auto timeZoneBinaryFile{state.os->assetFileSystem->OpenFile(fmt::format("tzdata/zoneinfo/{}", state.os->deviceTimeZone))}; + buffer.resize(timeZoneBinaryFile->size); + timeZoneBinaryFile->Read(buffer); + + managerServer.SetupTimeZoneManager(state.os->deviceTimeZone, *timezoneUpdateTime, locationNameList.size(), timeZoneBinaryVersion, buffer); } } \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/core.h b/app/src/main/cpp/skyline/services/timesrv/core.h index 50241da6..75de0588 100644 --- a/app/src/main/cpp/skyline/services/timesrv/core.h +++ b/app/src/main/cpp/skyline/services/timesrv/core.h @@ -8,8 +8,10 @@ #include #include #include +#include #include "time_shared_memory.h" #include "time_manager_server.h" +#include "timezone_manager.h" #include "results.h" namespace skyline::service::timesrv::core { @@ -308,6 +310,9 @@ namespace skyline::service::timesrv::core { StandardUserSystemClockCore userSystemClock; EphemeralNetworkSystemClockCore empheralSystemClock; + TimeZoneManager timeZoneManager; + std::vector locationNameList; //!< N stores in glue but we are fine putting it here + TimeManagerServer managerServer; /** diff --git a/app/src/main/cpp/skyline/services/timesrv/results.h b/app/src/main/cpp/skyline/services/timesrv/results.h index 6de56df2..e797f3cf 100644 --- a/app/src/main/cpp/skyline/services/timesrv/results.h +++ b/app/src/main/cpp/skyline/services/timesrv/results.h @@ -11,5 +11,6 @@ namespace skyline::service::timesrv::result { constexpr Result ClockUninitialized(116, 103); constexpr Result InvalidComparison(116, 200); constexpr Result CompareOverflow(116, 201); + constexpr Result RuleConversionFailed(116, 903); constexpr Result Unimplemented(116, 990); } \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp index 397d6c5f..209bda6f 100644 --- a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.cpp @@ -33,6 +33,10 @@ namespace skyline::service::timesrv { return {}; } + Result TimeManagerServer::SetupTimeZoneManager(std::string_view locationName, const SteadyClockTimePoint &updateTime, size_t locationCount, std::array binaryVersion, span binary) { + return core.timeZoneManager.Setup(locationName, updateTime, locationCount, binaryVersion, binary); + } + Result TimeManagerServer::SetupStandardLocalSystemClock(const SystemClockContext &context, PosixTime posixTime) { core.localSystemClock.SetUpdateCallback(&core.localSystemClockContextWriter); core.localSystemClock.Setup(context, posixTime); diff --git a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h index 792d3d53..6facfc3b 100644 --- a/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h +++ b/app/src/main/cpp/skyline/services/timesrv/time_manager_server.h @@ -31,6 +31,9 @@ namespace skyline::service::timesrv { Result SetupStandardSteadyClock(UUID rtcId, TimeSpanType rtcOffset, TimeSpanType internalOffset, TimeSpanType testOffset, bool rtcResetDetected); + + Result SetupTimeZoneManager(std::string_view locationName, const SteadyClockTimePoint &updateTime, size_t locationCount, std::array binaryVersion, span binary); + Result SetupStandardLocalSystemClock(const SystemClockContext &context, PosixTime posixTime); Result SetupStandardNetworkSystemClock(const SystemClockContext &context, TimeSpanType sufficientAccuracy); @@ -42,11 +45,5 @@ namespace skyline::service::timesrv { std::shared_ptr GetStandardUserSystemClockAutomaticCorrectionEvent(); Result SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset); - - /* - Result SetupTimeZoneManager - */ - - }; } diff --git a/app/src/main/cpp/skyline/services/timesrv/timezone_manager.cpp b/app/src/main/cpp/skyline/services/timesrv/timezone_manager.cpp new file mode 100644 index 00000000..12be9eac --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/timezone_manager.cpp @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "results.h" +#include "timezone_manager.h" + +namespace skyline::service::timesrv::core { + Result TimeZoneManager::Setup(std::string_view pLocationName, const SteadyClockTimePoint &pUpdateTime, int pLocationCount, std::array pBinaryVersion, span binary) { + SetNewLocation(pLocationName, binary); + SetUpdateTime(pUpdateTime); + SetLocationCount(pLocationCount); + SetBinaryVersion(pBinaryVersion); + + MarkInitialized(); + return {}; + } + + ResultValue TimeZoneManager::GetLocationName() { + std::lock_guard lock(mutex); + + if (!initialized) + return result::ClockUninitialized; + + return locationName; + } + + Result TimeZoneManager::SetNewLocation(std::string_view pLocationName, span binary) { + std::lock_guard lock(mutex); + + rule = tz_tzalloc(binary.data(), binary.size()); + if (!rule) + return result::RuleConversionFailed; + + span(locationName).copy_from(pLocationName); + + return {}; + } + + ResultValue TimeZoneManager::GetUpdateTime() { + std::lock_guard lock(mutex); + + if (!initialized) + return result::ClockUninitialized; + + return updateTime; + } + + void TimeZoneManager::SetUpdateTime(const SteadyClockTimePoint &pUpdateTime) { + std::lock_guard lock(mutex); + updateTime = pUpdateTime; + } + + ResultValue TimeZoneManager::GetLocationCount() { + std::lock_guard lock(mutex); + + if (!initialized) + return result::ClockUninitialized; + + return locationCount; + } + + void TimeZoneManager::SetLocationCount(int pLocationCount) { + std::lock_guard lock(mutex); + locationCount = pLocationCount; + } + + ResultValue> TimeZoneManager::GetBinaryVersion() { + std::lock_guard lock(mutex); + + if (!initialized) + return result::ClockUninitialized; + + return binaryVersion; + } + + void TimeZoneManager::SetBinaryVersion(std::array pBinaryVersion) { + std::lock_guard lock(mutex); + binaryVersion = pBinaryVersion; + } + + Result TimeZoneManager::ParseTimeZoneBinary(span binary, span ruleOut) { + auto ruleObj{tz_tzalloc(binary.data(), binary.size())}; + if (!ruleObj) + return result::RuleConversionFailed; + + memcpy(ruleOut.data(), ruleObj, ruleOut.size_bytes()); + tz_tzfree(ruleObj); + return {}; + } + + ResultValue TimeZoneManager::ToCalendarTime(tz_timezone_t pRule, PosixTime posixTime) { + struct tm tmp{}; + auto posixCalendarTime{tz_localtime_rz(pRule, &posixTime, &tmp)}; + if (!posixCalendarTime) + return result::PermissionDenied; // Not the proper error here but *fine* + + FullCalendarTime out{ + .calendarTime{ + .year = static_cast(posixCalendarTime->tm_year), + .month = static_cast(posixCalendarTime->tm_mon + 1), + .day = static_cast(posixCalendarTime->tm_mday), + .hour = static_cast(posixCalendarTime->tm_hour), + .minute = static_cast(posixCalendarTime->tm_min), + .second = static_cast(posixCalendarTime->tm_sec), + }, + .additionalInfo{ + .dayOfWeek = static_cast(posixCalendarTime->tm_wday), + .dayOfYear = static_cast(posixCalendarTime->tm_yday), + .dst = static_cast(posixCalendarTime->tm_isdst), + .gmtOffset = static_cast(posixCalendarTime->tm_gmtoff), + }, + }; + + std::string_view(posixCalendarTime->tm_zone).copy(out.additionalInfo.timeZoneName.data(), strlen(posixCalendarTime->tm_zone)); + return out; + } + + ResultValue TimeZoneManager::ToPosixTime(tz_timezone_t pRule, CalendarTime calendarTime) { + struct tm posixCalendarTime{ + .tm_year = calendarTime.year, + .tm_mon = calendarTime.month - 1, + .tm_mday = calendarTime.day, + .tm_min = calendarTime.minute, + .tm_sec = calendarTime.second, + }; + + // Nintendo optionally two times here, presumably to deal with DST correction but we are probably fine without it + return static_cast(tz_mktime_z(pRule, &posixCalendarTime)); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/services/timesrv/timezone_manager.h b/app/src/main/cpp/skyline/services/timesrv/timezone_manager.h new file mode 100644 index 00000000..c86c797e --- /dev/null +++ b/app/src/main/cpp/skyline/services/timesrv/timezone_manager.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include "common.h" + +namespace skyline::service::timesrv::core { + /** + * @brief TimeZoneManager handles converting POSIX times to calendar times and vice-versa by using a rule struct + */ + class TimeZoneManager { + private: + bool initialized{}; + std::mutex mutex; + tz_timezone_t rule{}; //!< Rule corresponding to the timezone that is currently in use + SteadyClockTimePoint updateTime{}; //!< Time when the rule was last updated + int locationCount{}; //!< The number of possible timezone binary locations + std::array binaryVersion{}; //!< The version of the tzdata package + LocationName locationName{}; //!< Name of the currently selected location + + void MarkInitialized() { + initialized = true; + } + + public: + /** + * @brief Initialises the manager, setting the initial timezone so it is ready for use by applications + */ + Result Setup(std::string_view pLocationName, const SteadyClockTimePoint &pUpdateTime, int pLocationCount, std::array pBinaryVersion, span binary); + + ResultValue GetLocationName(); + + /** + * @brief Parses the given binary into rule and sets the appropriate location name + */ + Result SetNewLocation(std::string_view pLocationName, span binary); + + ResultValue GetUpdateTime(); + + void SetUpdateTime(const SteadyClockTimePoint &pUpdateTime); + + ResultValue GetLocationCount(); + + void SetLocationCount(int pLocationCount); + + ResultValue> GetBinaryVersion(); + + void SetBinaryVersion(std::array pBinaryVersion); + + /** + * @brief Parses a raw TZIF2 file into a timezone rule that can be passed to other functions + */ + Result ParseTimeZoneBinary(span binary, span ruleOut); + + /** + * @brief Converts a POSIX time to a calendar time using the given rule + */ + ResultValue ToCalendarTime(tz_timezone_t pRule, PosixTime posixTime); + + /** + * @brief Converts a POSIX to a calendar time using the current location's rule + */ + ResultValue ToCalendarTimeWithMyRule(PosixTime posixTime) { + return ToCalendarTime(rule, posixTime); + } + + /** + * @brief Converts a calendar time to a POSIX time using the given rule + */ + ResultValue ToPosixTime(tz_timezone_t pRule, CalendarTime calendarTime); + + /** + * @brief Converts a calendar time to a POSIX time using the current location's rule + */ + ResultValue ToPosixTimeWithMyRule(CalendarTime calendarTime) { + return ToPosixTime(rule, calendarTime); + } + }; +}