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.
This commit is contained in:
Billy Laws 2021-03-03 20:42:51 +00:00 committed by ◱ Mark
parent 34cb0b49e8
commit ba418976b0
21 changed files with 679 additions and 50 deletions

View File

@ -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

View File

@ -6,6 +6,7 @@
#include <unistd.h>
#include <android/log.h>
#include <android/asset_manager_jni.h>
#include <sys/system_properties.h>
#include "skyline/common.h"
#include "skyline/common/signal.h"
#include "skyline/common/settings.h"
@ -23,6 +24,31 @@ std::weak_ptr<skyline::kernel::OS> OsWeak;
std::weak_ptr<skyline::gpu::GPU> GpuWeak;
std::weak_ptr<skyline::input::Input> 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<skyline::kernel::OS>(jvmManager, logger, settings, std::string(appFilesPath), std::make_shared<skyline::vfs::AndroidAssetFileSystem>(AAssetManager_fromJava(env, assetManager)))};
auto os{std::make_shared<skyline::kernel::OS>(jvmManager, logger, settings, std::string(appFilesPath), GetTimeZoneName(), std::make_shared<skyline::vfs::AndroidAssetFileSystem>(AAssetManager_fromJava(env, assetManager)))};
OsWeak = os;
GpuWeak = os->state.gpu;
InputWeak = os->state.input;

View File

@ -13,7 +13,7 @@
#include "os.h"
namespace skyline::kernel {
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, std::string appFilesPath, std::shared_ptr<vfs::FileSystem> assetFileSystem) : state(this, jvmManager, settings, logger), appFilesPath(std::move(appFilesPath)), assetFileSystem(std::move(assetFileSystem)), serviceManager(state) {}
OS::OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, std::string appFilesPath, std::string deviceTimeZone, std::shared_ptr<vfs::FileSystem> 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<vfs::OsBacking>(romFd)};

View File

@ -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<vfs::FileSystem> 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> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, std::string appFilesPath, std::shared_ptr<vfs::FileSystem>);
OS(std::shared_ptr<JvmManager> &jvmManager, std::shared_ptr<Logger> &logger, std::shared_ptr<Settings> &settings, std::string appFilesPath, std::string deviceTimeZone, std::shared_ptr<vfs::FileSystem>);
/**
* @brief Execute a particular ROM file

View File

@ -2,12 +2,12 @@
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include <services/timesrv/IStaticService.h>
#include <services/timesrv/results.h>
#include "ITimeZoneService.h"
#include "IStaticService.h"
namespace skyline::service::glue {
IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> core, timesrv::StaticServicePermissions permissions) : BaseService(state, manager), core(std::move(core)), permissions(permissions) {}
IStaticService::IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> core, timesrv::core::TimeServiceObject &timesrvCore, 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<ITimeZoneService>(state, manager, core->GetTimeZoneService(state, manager), timesrvCore, true), session, response);
return {};
}
Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {

View File

@ -14,10 +14,11 @@ namespace skyline::service::glue {
class IStaticService : public BaseService {
private:
std::shared_ptr<timesrv::IStaticService> core;
timesrv::core::TimeServiceObject &timesrvCore;
timesrv::StaticServicePermissions permissions;
public:
IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> core, timesrv::StaticServicePermissions permissions);
IStaticService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::IStaticService> core, timesrv::core::TimeServiceObject &timesrvCore, timesrv::StaticServicePermissions permissions);
Result GetStandardUserSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -0,0 +1,102 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <os.h>
#include <kernel/types/KProcess.h>
#include <services/timesrv/common.h>
#include <services/timesrv/results.h>
#include <services/timesrv/ITimeZoneService.h>
#include <services/timesrv/core.h>
#include "ITimeZoneService.h"
namespace skyline::service::glue {
ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager, std::shared_ptr<timesrv::ITimeZoneService> core, timesrv::core::TimeServiceObject &timesrvCore, bool writeable) : BaseService(state, manager), core(std::move(core)), timesrvCore(timesrvCore), locationNameUpdateEvent(std::make_shared<kernel::type::KEvent>(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<timesrv::LocationName>()).as_string(true)};
// TODO Checked
auto timeZoneBinaryFile{state.os->assetFileSystem->OpenFile(fmt::format("tzdata/zoneinfo/{}", locationName))};
std::vector<u8> 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<timesrv::LocationName>()};
auto offset{request.Pop<u32>()};
outList.copy_from(span(timesrvCore.locationNameList).subspan(offset, outList.size()));
response.Push<u32>(outList.size());
return {};
}
Result ITimeZoneService::LoadTimeZoneRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto locationName{span(request.Pop<timesrv::LocationName>()).as_string(true)};
// TODO Checked
auto timeZoneBinaryFile{state.os->assetFileSystem->OpenFile(fmt::format("tzdata/zoneinfo/{}", locationName))};
std::vector<u8> 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);
}
}

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <kernel/types/KEvent.h>
#include <services/serviceman.h>
namespace skyline::service::timesrv {
class ITimeZoneService;
namespace core {
struct TimeServiceObject;
}
}
namespace skyline::service::glue {
class ITimeZoneService : public BaseService {
private:
std::shared_ptr<timesrv::ITimeZoneService> core;
timesrv::core::TimeServiceObject &timesrvCore;
std::shared_ptr<kernel::type::KEvent> 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<timesrv::ITimeZoneService> core, timesrv::core::TimeServiceObject &timesrvCore, 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)
)
};
}

View File

@ -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")

View File

@ -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<ITimeZoneService>(state, manager), session, response);
Result IStaticService::GetTimeZoneServiceIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(std::make_shared<ITimeZoneService>(state, manager, core, permissions.writeTimezone), session, response);
return {};
}
std::shared_ptr<ITimeZoneService> IStaticService::GetTimeZoneService(const DeviceState &state, ServiceManager &manager) {
return std::make_shared<ITimeZoneService>(state, manager, core, permissions.writeTimezone);
}
Result IStaticService::GetStandardLocalSystemClock(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
manager.RegisterService(std::make_shared<ISystemClock>(state, manager, core.localSystemClock, permissions.writeLocalSystemClock, permissions.ignoreUninitializedChecks), session, response);
return {};

View File

@ -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<ITimeZoneService> 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),

View File

@ -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<i32>(*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<u32>(0);
response.Push(*updateTime);
return {};
}
Result ITimeZoneService::SetDeviceLocationNameWithTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto locationName{request.Pop<LocationName>()};
return SetDeviceLocationNameWithTimeZoneBinary(span(locationName).as_string(true), request.inputBuf.at(0));
}
Result ITimeZoneService::SetDeviceLocationNameWithTimeZoneBinary(std::string_view locationName, span<u8> 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<u8> binary, span<u8> 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<PosixTime>()};
auto calendarTime{core.timeZoneManager.ToCalendarTime(reinterpret_cast<tz_timezone_t>(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<time_t>(request.Pop<PosixTime>())};
auto calender{*std::gmtime(&time)};
auto posixTime{request.Pop<PosixTime>()};
auto calendarTime{core.timeZoneManager.ToCalendarTimeWithMyRule(posixTime)};
CalendarTime calendarTime{
.year = static_cast<u16>(calender.tm_year),
.month = static_cast<u8>(calender.tm_mon + 1),
.day = static_cast<u8>(calender.tm_hour),
.minute = static_cast<u8>(calender.tm_min),
.second = static_cast<u8>(calender.tm_sec),
};
response.Push(calendarTime);
if (calendarTime)
response.Push(*calendarTime);
CalendarAdditionalInfo calendarInfo{
.dayOfWeek = static_cast<u32>(calender.tm_wday),
.dayOfYear = static_cast<u32>(calender.tm_yday),
.timezoneName = "GMT",
.dst = static_cast<u32>(calender.tm_isdst),
.gmtOffset = static_cast<i32>(calender.tm_gmtoff),
};
response.Push(calendarInfo);
return calendarTime;
}
Result ITimeZoneService::ToPosixTime(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto calendarTime{request.Pop<CalendarTime>()};
auto posixTime{core.timeZoneManager.ToPosixTime(reinterpret_cast<tz_timezone_t>(request.inputBuf.at(0).data()), calendarTime)};
if (!posixTime)
return posixTime;
request.outputBuf.at(0).as<PosixTime>() = *posixTime;
response.Push<u32>(1);
return {};
}
Result ITimeZoneService::ToPosixTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto calendarTime{request.Pop<CalendarTime>()};
auto posixTime{core.timeZoneManager.ToPosixTimeWithMyRule(calendarTime)};
if (!posixTime)
return posixTime;
request.outputBuf.at(0).as<PosixTime>() = *posixTime;
response.Push<u32>(1);
return {};
}
}

View File

@ -6,22 +6,69 @@
#include <services/serviceman.h>
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<u8> rule);
Result ParseTimeZoneBinaryIpc(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
Result ParseTimeZoneBinary(span<u8> binary, span<u8> 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)
)
};
}

View File

@ -5,6 +5,7 @@
namespace skyline::service::timesrv {
using PosixTime = i64; //!< Unit for time in seconds since the epoch
using LocationName = std::array<char, 0x18>;
/**
* @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<u8, 8> timezoneName;
std::array<char, 8> 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
*/

View File

@ -1,6 +1,9 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <os.h>
#include <vfs/filesystem.h>
#include <time.h>
#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<u8> 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<u8, 0x10> 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);
}
}

View File

@ -8,8 +8,10 @@
#include <kernel/types/KEvent.h>
#include <kernel/types/KSharedMemory.h>
#include <services/timesrv/common.h>
#include <horizon_time.h>
#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<LocationName> locationNameList; //!< N stores in glue but we are fine putting it here
TimeManagerServer managerServer;
/**

View File

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

View File

@ -33,6 +33,10 @@ namespace skyline::service::timesrv {
return {};
}
Result TimeManagerServer::SetupTimeZoneManager(std::string_view locationName, const SteadyClockTimePoint &updateTime, size_t locationCount, std::array<u8, 0x10> binaryVersion, span<u8> 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);

View File

@ -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<u8, 0x10> binaryVersion, span<u8> 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<kernel::type::KEvent> GetStandardUserSystemClockAutomaticCorrectionEvent();
Result SetStandardSteadyClockRtcOffset(TimeSpanType rtcOffset);
/*
Result SetupTimeZoneManager
*/
};
}

View File

@ -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<u8, 0x10> pBinaryVersion, span<u8> binary) {
SetNewLocation(pLocationName, binary);
SetUpdateTime(pUpdateTime);
SetLocationCount(pLocationCount);
SetBinaryVersion(pBinaryVersion);
MarkInitialized();
return {};
}
ResultValue<LocationName> TimeZoneManager::GetLocationName() {
std::lock_guard lock(mutex);
if (!initialized)
return result::ClockUninitialized;
return locationName;
}
Result TimeZoneManager::SetNewLocation(std::string_view pLocationName, span<u8> 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<SteadyClockTimePoint> 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<int> 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<std::array<u8, 0x10>> TimeZoneManager::GetBinaryVersion() {
std::lock_guard lock(mutex);
if (!initialized)
return result::ClockUninitialized;
return binaryVersion;
}
void TimeZoneManager::SetBinaryVersion(std::array<u8, 0x10> pBinaryVersion) {
std::lock_guard lock(mutex);
binaryVersion = pBinaryVersion;
}
Result TimeZoneManager::ParseTimeZoneBinary(span<u8> binary, span<u8> 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<FullCalendarTime> 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<u16>(posixCalendarTime->tm_year),
.month = static_cast<u8>(posixCalendarTime->tm_mon + 1),
.day = static_cast<u8>(posixCalendarTime->tm_mday),
.hour = static_cast<u8>(posixCalendarTime->tm_hour),
.minute = static_cast<u8>(posixCalendarTime->tm_min),
.second = static_cast<u8>(posixCalendarTime->tm_sec),
},
.additionalInfo{
.dayOfWeek = static_cast<u32>(posixCalendarTime->tm_wday),
.dayOfYear = static_cast<u32>(posixCalendarTime->tm_yday),
.dst = static_cast<u32>(posixCalendarTime->tm_isdst),
.gmtOffset = static_cast<i32>(posixCalendarTime->tm_gmtoff),
},
};
std::string_view(posixCalendarTime->tm_zone).copy(out.additionalInfo.timeZoneName.data(), strlen(posixCalendarTime->tm_zone));
return out;
}
ResultValue<PosixTime> 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<PosixTime>(tz_mktime_z(pRule, &posixCalendarTime));
}
}

View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <horizon_time.h>
#include <common.h>
#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<u8, 0x10> 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<u8, 0x10> pBinaryVersion, span<u8> binary);
ResultValue<LocationName> GetLocationName();
/**
* @brief Parses the given binary into rule and sets the appropriate location name
*/
Result SetNewLocation(std::string_view pLocationName, span<u8> binary);
ResultValue<SteadyClockTimePoint> GetUpdateTime();
void SetUpdateTime(const SteadyClockTimePoint &pUpdateTime);
ResultValue<int> GetLocationCount();
void SetLocationCount(int pLocationCount);
ResultValue<std::array<u8, 0x10>> GetBinaryVersion();
void SetBinaryVersion(std::array<u8, 0x10> pBinaryVersion);
/**
* @brief Parses a raw TZIF2 file into a timezone rule that can be passed to other functions
*/
Result ParseTimeZoneBinary(span<u8> binary, span<u8> ruleOut);
/**
* @brief Converts a POSIX time to a calendar time using the given rule
*/
ResultValue<FullCalendarTime> ToCalendarTime(tz_timezone_t pRule, PosixTime posixTime);
/**
* @brief Converts a POSIX to a calendar time using the current location's rule
*/
ResultValue<FullCalendarTime> ToCalendarTimeWithMyRule(PosixTime posixTime) {
return ToCalendarTime(rule, posixTime);
}
/**
* @brief Converts a calendar time to a POSIX time using the given rule
*/
ResultValue<PosixTime> ToPosixTime(tz_timezone_t pRule, CalendarTime calendarTime);
/**
* @brief Converts a calendar time to a POSIX time using the current location's rule
*/
ResultValue<PosixTime> ToPosixTimeWithMyRule(CalendarTime calendarTime) {
return ToPosixTime(rule, calendarTime);
}
};
}