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); + } + }; +}