summaryrefslogtreecommitdiffstats
path: root/src/core/hle
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle')
-rw-r--r--src/core/hle/kernel/physical_memory.h5
-rw-r--r--src/core/hle/kernel/vm_manager.cpp37
-rw-r--r--src/core/hle/service/acc/acc.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp14
-rw-r--r--src/core/hle/service/acc/profile_manager.h27
-rw-r--r--src/core/hle/service/friend/friend.cpp2
-rw-r--r--src/core/hle/service/mii/mii_manager.h156
-rw-r--r--src/core/hle/service/time/clock_types.h103
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h16
-rw-r--r--src/core/hle/service/time/ephemeral_network_system_clock_core.h17
-rw-r--r--src/core/hle/service/time/errors.h22
-rw-r--r--src/core/hle/service/time/interface.cpp19
-rw-r--r--src/core/hle/service/time/interface.h11
-rw-r--r--src/core/hle/service/time/local_system_clock_context_writer.h28
-rw-r--r--src/core/hle/service/time/network_system_clock_context_writer.h28
-rw-r--r--src/core/hle/service/time/standard_local_system_clock_core.h17
-rw-r--r--src/core/hle/service/time/standard_network_system_clock_core.h46
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.cpp26
-rw-r--r--src/core/hle/service/time/standard_steady_clock_core.h42
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.cpp77
-rw-r--r--src/core/hle/service/time/standard_user_system_clock_core.h57
-rw-r--r--src/core/hle/service/time/steady_clock_core.h55
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.cpp55
-rw-r--r--src/core/hle/service/time/system_clock_context_update_callback.h43
-rw-r--r--src/core/hle/service/time/system_clock_core.cpp72
-rw-r--r--src/core/hle/service/time/system_clock_core.h71
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.cpp24
-rw-r--r--src/core/hle/service/time/tick_based_steady_clock_core.h29
-rw-r--r--src/core/hle/service/time/time.cpp474
-rw-r--r--src/core/hle/service/time/time.h101
-rw-r--r--src/core/hle/service/time/time_manager.cpp137
-rw-r--r--src/core/hle/service/time/time_manager.h117
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp53
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h35
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.cpp125
-rw-r--r--src/core/hle/service/time/time_zone_content_manager.h46
-rw-r--r--src/core/hle/service/time/time_zone_manager.cpp1030
-rw-r--r--src/core/hle/service/time/time_zone_manager.h53
-rw-r--r--src/core/hle/service/time/time_zone_service.cpp148
-rw-r--r--src/core/hle/service/time/time_zone_service.h30
-rw-r--r--src/core/hle/service/time/time_zone_types.h87
41 files changed, 2978 insertions, 559 deletions
diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h
index 090565310..b689e8e8b 100644
--- a/src/core/hle/kernel/physical_memory.h
+++ b/src/core/hle/kernel/physical_memory.h
@@ -14,6 +14,9 @@ namespace Kernel {
// - Second to ensure all host backing memory used is aligned to 256 bytes due
// to strict alignment restrictions on GPU memory.
-using PhysicalMemory = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
+using PhysicalMemoryVector = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
+class PhysicalMemory final : public PhysicalMemoryVector {
+ using PhysicalMemoryVector::PhysicalMemoryVector;
+};
} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index a9a20ef76..0b3500fce 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
+#include <cstring>
#include <iterator>
#include <utility>
#include "common/alignment.h"
@@ -269,18 +270,9 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
// If necessary, expand backing vector to cover new heap extents in
// the case of allocating. Otherwise, shrink the backing memory,
// if a smaller heap has been requested.
- const u64 old_heap_size = GetCurrentHeapSize();
- if (size > old_heap_size) {
- const u64 alloc_size = size - old_heap_size;
-
- heap_memory->insert(heap_memory->end(), alloc_size, 0);
- RefreshMemoryBlockMappings(heap_memory.get());
- } else if (size < old_heap_size) {
- heap_memory->resize(size);
- heap_memory->shrink_to_fit();
-
- RefreshMemoryBlockMappings(heap_memory.get());
- }
+ heap_memory->resize(size);
+ heap_memory->shrink_to_fit();
+ RefreshMemoryBlockMappings(heap_memory.get());
heap_end = heap_region_base + size;
ASSERT(GetCurrentHeapSize() == heap_memory->size());
@@ -752,24 +744,20 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre
// Always merge allocated memory blocks, even when they don't share the same backing block.
if (left.type == VMAType::AllocatedMemoryBlock &&
(left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
- const auto right_begin = right.backing_block->begin() + right.offset;
- const auto right_end = right_begin + right.size;
// Check if we can save work.
if (left.offset == 0 && left.size == left.backing_block->size()) {
// Fast case: left is an entire backing block.
- left.backing_block->insert(left.backing_block->end(), right_begin, right_end);
+ left.backing_block->resize(left.size + right.size);
+ std::memcpy(left.backing_block->data() + left.size,
+ right.backing_block->data() + right.offset, right.size);
} else {
// Slow case: make a new memory block for left and right.
- const auto left_begin = left.backing_block->begin() + left.offset;
- const auto left_end = left_begin + left.size;
- const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end));
- const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end));
-
auto new_memory = std::make_shared<PhysicalMemory>();
- new_memory->reserve(left_size + right_size);
- new_memory->insert(new_memory->end(), left_begin, left_end);
- new_memory->insert(new_memory->end(), right_begin, right_end);
+ new_memory->resize(left.size + right.size);
+ std::memcpy(new_memory->data(), left.backing_block->data() + left.offset, left.size);
+ std::memcpy(new_memory->data() + left.size, right.backing_block->data() + right.offset,
+ right.size);
left.backing_block = std::move(new_memory);
left.offset = 0;
@@ -792,8 +780,7 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
memory.UnmapRegion(page_table, vma.base, vma.size);
break;
case VMAType::AllocatedMemoryBlock:
- memory.MapMemoryRegion(page_table, vma.base, vma.size,
- vma.backing_block->data() + vma.offset);
+ memory.MapMemoryRegion(page_table, vma.base, vma.size, *vma.backing_block, vma.offset);
break;
case VMAType::BackingMemory:
memory.MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory);
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index 7e3e311fb..cfac8ca9a 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -211,7 +211,7 @@ protected:
}
ProfileManager& profile_manager;
- Common::UUID user_id; ///< The user id this profile refers to.
+ Common::UUID user_id{Common::INVALID_UUID}; ///< The user id this profile refers to.
};
class IProfile final : public IProfileCommon {
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 3e756e59e..eb8c81645 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -16,17 +16,17 @@ namespace Service::Account {
using Common::UUID;
struct UserRaw {
- UUID uuid;
- UUID uuid2;
- u64 timestamp;
- ProfileUsername username;
- ProfileData extra_data;
+ UUID uuid{Common::INVALID_UUID};
+ UUID uuid2{Common::INVALID_UUID};
+ u64 timestamp{};
+ ProfileUsername username{};
+ ProfileData extra_data{};
};
static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
struct ProfileDataRaw {
INSERT_PADDING_BYTES(0x10);
- std::array<UserRaw, MAX_USERS> users;
+ std::array<UserRaw, MAX_USERS> users{};
};
static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
@@ -238,7 +238,7 @@ UserIDArray ProfileManager::GetOpenUsers() const {
std::transform(profiles.begin(), profiles.end(), output.begin(), [](const ProfileInfo& p) {
if (p.is_open)
return p.user_uuid;
- return UUID{};
+ return UUID{Common::INVALID_UUID};
});
std::stable_partition(output.begin(), output.end(), [](const UUID& uuid) { return uuid; });
return output;
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 5a6d28925..5310637a6 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -13,9 +13,10 @@
#include "core/hle/result.h"
namespace Service::Account {
-constexpr std::size_t MAX_USERS = 8;
-constexpr std::size_t profile_username_size = 32;
+constexpr std::size_t MAX_USERS{8};
+constexpr std::size_t profile_username_size{32};
+
using ProfileUsername = std::array<u8, profile_username_size>;
using UserIDArray = std::array<Common::UUID, MAX_USERS>;
@@ -23,8 +24,8 @@ using UserIDArray = std::array<Common::UUID, MAX_USERS>;
/// TODO: RE this structure
struct ProfileData {
INSERT_PADDING_WORDS(1);
- u32 icon_id;
- u8 bg_color_id;
+ u32 icon_id{};
+ u8 bg_color_id{};
INSERT_PADDING_BYTES(0x7);
INSERT_PADDING_BYTES(0x10);
INSERT_PADDING_BYTES(0x60);
@@ -34,17 +35,17 @@ static_assert(sizeof(ProfileData) == 0x80, "ProfileData structure has incorrect
/// This holds general information about a users profile. This is where we store all the information
/// based on a specific user
struct ProfileInfo {
- Common::UUID user_uuid;
- ProfileUsername username;
- u64 creation_time;
- ProfileData data; // TODO(ognik): Work out what this is
- bool is_open;
+ Common::UUID user_uuid{Common::INVALID_UUID};
+ ProfileUsername username{};
+ u64 creation_time{};
+ ProfileData data{}; // TODO(ognik): Work out what this is
+ bool is_open{};
};
struct ProfileBase {
- Common::UUID user_uuid;
- u64_le timestamp;
- ProfileUsername username;
+ Common::UUID user_uuid{Common::INVALID_UUID};
+ u64_le timestamp{};
+ ProfileUsername username{};
// Zero out all the fields to make the profile slot considered "Empty"
void Invalidate() {
@@ -101,7 +102,7 @@ private:
bool RemoveProfileAtIndex(std::size_t index);
std::array<ProfileInfo, MAX_USERS> profiles{};
- std::size_t user_count = 0;
+ std::size_t user_count{};
Common::UUID last_opened_user{Common::INVALID_UUID};
};
diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp
index 219176c31..6aadb3ea8 100644
--- a/src/core/hle/service/friend/friend.cpp
+++ b/src/core/hle/service/friend/friend.cpp
@@ -241,7 +241,7 @@ private:
bool has_received_friend_request;
};
- Common::UUID uuid;
+ Common::UUID uuid{Common::INVALID_UUID};
Kernel::EventPair notification_event;
std::queue<SizedNotificationInfo> notifications;
States states{};
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 38ad78a0d..fc742816a 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -10,13 +10,13 @@
namespace Service::Mii {
-constexpr std::size_t MAX_MIIS = 100;
-constexpr u32 INVALID_INDEX = 0xFFFFFFFF;
+constexpr std::size_t MAX_MIIS{100};
+constexpr u32 INVALID_INDEX{0xFFFFFFFF};
struct RandomParameters {
- u32 unknown_1;
- u32 unknown_2;
- u32 unknown_3;
+ u32 unknown_1{};
+ u32 unknown_2{};
+ u32 unknown_3{};
};
static_assert(sizeof(RandomParameters) == 0xC, "RandomParameters has incorrect size.");
@@ -30,57 +30,57 @@ enum class Source : u32 {
std::ostream& operator<<(std::ostream& os, Source source);
struct MiiInfo {
- Common::UUID uuid;
- std::array<char16_t, 11> name;
- u8 font_region;
- u8 favorite_color;
- u8 gender;
- u8 height;
- u8 weight;
- u8 mii_type;
- u8 mii_region;
- u8 face_type;
- u8 face_color;
- u8 face_wrinkle;
- u8 face_makeup;
- u8 hair_type;
- u8 hair_color;
- bool hair_flip;
- u8 eye_type;
- u8 eye_color;
- u8 eye_scale;
- u8 eye_aspect_ratio;
- u8 eye_rotate;
- u8 eye_x;
- u8 eye_y;
- u8 eyebrow_type;
- u8 eyebrow_color;
- u8 eyebrow_scale;
- u8 eyebrow_aspect_ratio;
- u8 eyebrow_rotate;
- u8 eyebrow_x;
- u8 eyebrow_y;
- u8 nose_type;
- u8 nose_scale;
- u8 nose_y;
- u8 mouth_type;
- u8 mouth_color;
- u8 mouth_scale;
- u8 mouth_aspect_ratio;
- u8 mouth_y;
- u8 facial_hair_color;
- u8 beard_type;
- u8 mustache_type;
- u8 mustache_scale;
- u8 mustache_y;
- u8 glasses_type;
- u8 glasses_color;
- u8 glasses_scale;
- u8 glasses_y;
- u8 mole_type;
- u8 mole_scale;
- u8 mole_x;
- u8 mole_y;
+ Common::UUID uuid{Common::INVALID_UUID};
+ std::array<char16_t, 11> name{};
+ u8 font_region{};
+ u8 favorite_color{};
+ u8 gender{};
+ u8 height{};
+ u8 weight{};
+ u8 mii_type{};
+ u8 mii_region{};
+ u8 face_type{};
+ u8 face_color{};
+ u8 face_wrinkle{};
+ u8 face_makeup{};
+ u8 hair_type{};
+ u8 hair_color{};
+ bool hair_flip{};
+ u8 eye_type{};
+ u8 eye_color{};
+ u8 eye_scale{};
+ u8 eye_aspect_ratio{};
+ u8 eye_rotate{};
+ u8 eye_x{};
+ u8 eye_y{};
+ u8 eyebrow_type{};
+ u8 eyebrow_color{};
+ u8 eyebrow_scale{};
+ u8 eyebrow_aspect_ratio{};
+ u8 eyebrow_rotate{};
+ u8 eyebrow_x{};
+ u8 eyebrow_y{};
+ u8 nose_type{};
+ u8 nose_scale{};
+ u8 nose_y{};
+ u8 mouth_type{};
+ u8 mouth_color{};
+ u8 mouth_scale{};
+ u8 mouth_aspect_ratio{};
+ u8 mouth_y{};
+ u8 facial_hair_color{};
+ u8 beard_type{};
+ u8 mustache_type{};
+ u8 mustache_scale{};
+ u8 mustache_y{};
+ u8 glasses_type{};
+ u8 glasses_color{};
+ u8 glasses_scale{};
+ u8 glasses_y{};
+ u8 mole_type{};
+ u8 mole_scale{};
+ u8 mole_x{};
+ u8 mole_y{};
INSERT_PADDING_BYTES(1);
std::u16string Name() const;
@@ -94,14 +94,14 @@ bool operator!=(const MiiInfo& lhs, const MiiInfo& rhs);
#pragma pack(push, 4)
struct MiiInfoElement {
- MiiInfo info;
- Source source;
+ MiiInfo info{};
+ Source source{};
};
static_assert(sizeof(MiiInfoElement) == 0x5C, "MiiInfoElement has incorrect size.");
struct MiiStoreBitFields {
union {
- u32 word_0;
+ u32 word_0{};
BitField<24, 8, u32> hair_type;
BitField<23, 1, u32> mole_type;
@@ -112,7 +112,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_1;
+ u32 word_1{};
BitField<31, 1, u32> gender;
BitField<24, 7, u32> eye_color;
@@ -122,7 +122,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_2;
+ u32 word_2{};
BitField<31, 1, u32> mii_type;
BitField<24, 7, u32> glasses_color;
@@ -135,7 +135,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_3;
+ u32 word_3{};
BitField<29, 3, u32> mustache_type;
BitField<24, 5, u32> eyebrow_type;
@@ -148,7 +148,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_4;
+ u32 word_4{};
BitField<29, 3, u32> eye_rotate;
BitField<24, 5, u32> mustache_y;
@@ -160,7 +160,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_5;
+ u32 word_5{};
BitField<24, 5, u32> glasses_type;
BitField<20, 4, u32> face_type;
@@ -172,7 +172,7 @@ struct MiiStoreBitFields {
};
union {
- u32 word_6;
+ u32 word_6{};
BitField<28, 4, u32> eyebrow_rotate;
BitField<24, 4, u32> eyebrow_scale;
@@ -192,30 +192,30 @@ struct MiiStoreData {
// This corresponds to the above structure MiiStoreBitFields. I did it like this because the
// BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
// not suitable for our uses.
- std::array<u8, 0x1C> data;
+ std::array<u8, 0x1C> data{};
static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
- std::array<char16_t, 10> name;
- Common::UUID uuid;
- u16 crc_1;
- u16 crc_2;
+ std::array<char16_t, 10> name{};
+ Common::UUID uuid{Common::INVALID_UUID};
+ u16 crc_1{};
+ u16 crc_2{};
std::u16string Name() const;
};
static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
struct MiiStoreDataElement {
- MiiStoreData data;
- Source source;
+ MiiStoreData data{};
+ Source source{};
};
static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
struct MiiDatabase {
- u32 magic; // 'NFDB'
- std::array<MiiStoreData, MAX_MIIS> miis;
+ u32 magic{}; // 'NFDB'
+ std::array<MiiStoreData, MAX_MIIS> miis{};
INSERT_PADDING_BYTES(1);
- u8 count;
- u16 crc;
+ u8 count{};
+ u16 crc{};
};
static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
#pragma pack(pop)
@@ -266,8 +266,8 @@ private:
void EnsureDatabasePartition();
MiiDatabase database;
- bool updated_flag = false;
- bool is_test_mode_enabled = false;
+ bool updated_flag{};
+ bool is_test_mode_enabled{};
};
}; // namespace Service::Mii
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
new file mode 100644
index 000000000..72e1921ec
--- /dev/null
+++ b/src/core/hle/service/time/clock_types.h
@@ -0,0 +1,103 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/uuid.h"
+#include "core/hle/service/time/errors.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace Service::Time::Clock {
+
+/// https://switchbrew.org/wiki/Glue_services#SteadyClockTimePoint
+struct SteadyClockTimePoint {
+ s64 time_point;
+ Common::UUID clock_source_id;
+
+ ResultCode GetSpanBetween(SteadyClockTimePoint other, s64& span) const {
+ span = 0;
+
+ if (clock_source_id != other.clock_source_id) {
+ return ERROR_TIME_MISMATCH;
+ }
+
+ span = other.time_point - time_point;
+
+ return RESULT_SUCCESS;
+ }
+
+ static SteadyClockTimePoint GetRandom() {
+ return {0, Common::UUID::Generate()};
+ }
+};
+static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
+static_assert(std::is_trivially_copyable_v<SteadyClockTimePoint>,
+ "SteadyClockTimePoint must be trivially copyable");
+
+struct SteadyClockContext {
+ u64 internal_offset;
+ Common::UUID steady_time_point;
+};
+static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size");
+static_assert(std::is_trivially_copyable_v<SteadyClockContext>,
+ "SteadyClockContext must be trivially copyable");
+
+struct SystemClockContext {
+ s64 offset;
+ SteadyClockTimePoint steady_time_point;
+};
+static_assert(sizeof(SystemClockContext) == 0x20, "SystemClockContext is incorrect size");
+static_assert(std::is_trivially_copyable_v<SystemClockContext>,
+ "SystemClockContext must be trivially copyable");
+
+/// https://switchbrew.org/wiki/Glue_services#TimeSpanType
+struct TimeSpanType {
+ s64 nanoseconds{};
+ static constexpr s64 ns_per_second{1000000000ULL};
+
+ s64 ToSeconds() const {
+ return nanoseconds / ns_per_second;
+ }
+
+ static TimeSpanType FromSeconds(s64 seconds) {
+ return {seconds * ns_per_second};
+ }
+
+ static TimeSpanType FromTicks(u64 ticks, u64 frequency) {
+ return FromSeconds(static_cast<s64>(ticks) / static_cast<s64>(frequency));
+ }
+};
+static_assert(sizeof(TimeSpanType) == 8, "TimeSpanType is incorrect size");
+
+struct ClockSnapshot {
+ SystemClockContext user_context{};
+ SystemClockContext network_context{};
+ s64 user_time{};
+ s64 network_time{};
+ TimeZone::CalendarTime user_calendar_time{};
+ TimeZone::CalendarTime network_calendar_time{};
+ TimeZone::CalendarAdditionalInfo user_calendar_additional_time{};
+ TimeZone::CalendarAdditionalInfo network_calendar_additional_time{};
+ SteadyClockTimePoint steady_clock_time_point{};
+ TimeZone::LocationName location_name{};
+ u8 is_automatic_correction_enabled{};
+ u8 type{};
+ INSERT_PADDING_BYTES(0x2);
+
+ static ResultCode GetCurrentTime(s64& current_time,
+ const SteadyClockTimePoint& steady_clock_time_point,
+ const SystemClockContext& context) {
+ if (steady_clock_time_point.clock_source_id != context.steady_time_point.clock_source_id) {
+ current_time = 0;
+ return ERROR_TIME_MISMATCH;
+ }
+ current_time = steady_clock_time_point.time_point + context.offset;
+ return RESULT_SUCCESS;
+ }
+};
+static_assert(sizeof(ClockSnapshot) == 0xD0, "ClockSnapshot is incorrect size");
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h
new file mode 100644
index 000000000..42893e3f6
--- /dev/null
+++ b/src/core/hle/service/time/ephemeral_network_system_clock_context_writer.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/system_clock_context_update_callback.h"
+
+namespace Service::Time::Clock {
+
+class EphemeralNetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback {
+public:
+ EphemeralNetworkSystemClockContextWriter() : SystemClockContextUpdateCallback{} {}
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/ephemeral_network_system_clock_core.h b/src/core/hle/service/time/ephemeral_network_system_clock_core.h
new file mode 100644
index 000000000..4c6cdef86
--- /dev/null
+++ b/src/core/hle/service/time/ephemeral_network_system_clock_core.h
@@ -0,0 +1,17 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/system_clock_core.h"
+
+namespace Service::Time::Clock {
+
+class EphemeralNetworkSystemClockCore final : public SystemClockCore {
+public:
+ explicit EphemeralNetworkSystemClockCore(SteadyClockCore& steady_clock_core)
+ : SystemClockCore{steady_clock_core} {}
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/errors.h b/src/core/hle/service/time/errors.h
new file mode 100644
index 000000000..8501a3e8c
--- /dev/null
+++ b/src/core/hle/service/time/errors.h
@@ -0,0 +1,22 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Time {
+
+constexpr ResultCode ERROR_PERMISSION_DENIED{ErrorModule::Time, 1};
+constexpr ResultCode ERROR_TIME_MISMATCH{ErrorModule::Time, 102};
+constexpr ResultCode ERROR_UNINITIALIZED_CLOCK{ErrorModule::Time, 103};
+constexpr ResultCode ERROR_TIME_NOT_FOUND{ErrorModule::Time, 200};
+constexpr ResultCode ERROR_OVERFLOW{ErrorModule::Time, 201};
+constexpr ResultCode ERROR_LOCATION_NAME_TOO_LONG{ErrorModule::Time, 801};
+constexpr ResultCode ERROR_OUT_OF_RANGE{ErrorModule::Time, 902};
+constexpr ResultCode ERROR_TIME_ZONE_CONVERSION_FAILED{ErrorModule::Time, 903};
+constexpr ResultCode ERROR_TIME_ZONE_NOT_FOUND{ErrorModule::Time, 989};
+constexpr ResultCode ERROR_NOT_IMPLEMENTED{ErrorModule::Time, 990};
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp
index bc74f1e1d..1660bbdb8 100644
--- a/src/core/hle/service/time/interface.cpp
+++ b/src/core/hle/service/time/interface.cpp
@@ -1,4 +1,4 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -6,9 +6,8 @@
namespace Service::Time {
-Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
- Core::System& system, const char* name)
- : Module::Interface(std::move(time), std::move(shared_memory), system, name) {
+Time::Time(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : Module::Interface(std::move(module), system, name) {
// clang-format off
static const FunctionInfo functions[] = {
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
@@ -22,15 +21,15 @@ Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_me
{31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"},
{50, nullptr, "SetStandardSteadyClockInternalOffset"},
{51, nullptr, "GetStandardSteadyClockRtcValue"},
- {100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
- {101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
+ {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
{102, nullptr, "GetStandardUserSystemClockInitialYear"},
- {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
+ {200, &Time::IsStandardNetworkSystemClockAccuracySufficient, "IsStandardNetworkSystemClockAccuracySufficient"},
{201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"},
- {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"},
+ {300, &Time::CalculateMonotonicSystemClockBaseTimePoint, "CalculateMonotonicSystemClockBaseTimePoint"},
{400, &Time::GetClockSnapshot, "GetClockSnapshot"},
- {401, nullptr, "GetClockSnapshotFromSystemClockContext"},
- {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, "CalculateStandardUserSystemClockDifferenceByUser"},
+ {401, &Time::GetClockSnapshotFromSystemClockContext, "GetClockSnapshotFromSystemClockContext"},
+ {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"},
{501, nullptr, "CalculateSpanBetween"},
};
// clang-format on
diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h
index 5c63a07f4..4f49e1f07 100644
--- a/src/core/hle/service/time/interface.h
+++ b/src/core/hle/service/time/interface.h
@@ -1,4 +1,4 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -6,14 +6,15 @@
#include "core/hle/service/time/time.h"
-namespace Service::Time {
+namespace Core {
+class System;
+}
-class SharedMemory;
+namespace Service::Time {
class Time final : public Module::Interface {
public:
- explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
- Core::System& system, const char* name);
+ explicit Time(std::shared_ptr<Module> time, Core::System& system, const char* name);
~Time() override;
};
diff --git a/src/core/hle/service/time/local_system_clock_context_writer.h b/src/core/hle/service/time/local_system_clock_context_writer.h
new file mode 100644
index 000000000..7050844c6
--- /dev/null
+++ b/src/core/hle/service/time/local_system_clock_context_writer.h
@@ -0,0 +1,28 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/errors.h"
+#include "core/hle/service/time/system_clock_context_update_callback.h"
+#include "core/hle/service/time/time_sharedmemory.h"
+
+namespace Service::Time::Clock {
+
+class LocalSystemClockContextWriter final : public SystemClockContextUpdateCallback {
+public:
+ explicit LocalSystemClockContextWriter(SharedMemory& shared_memory)
+ : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {}
+
+protected:
+ ResultCode Update() override {
+ shared_memory.UpdateLocalSystemClockContext(context);
+ return RESULT_SUCCESS;
+ }
+
+private:
+ SharedMemory& shared_memory;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/network_system_clock_context_writer.h b/src/core/hle/service/time/network_system_clock_context_writer.h
new file mode 100644
index 000000000..94d8788ff
--- /dev/null
+++ b/src/core/hle/service/time/network_system_clock_context_writer.h
@@ -0,0 +1,28 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/errors.h"
+#include "core/hle/service/time/system_clock_context_update_callback.h"
+#include "core/hle/service/time/time_sharedmemory.h"
+
+namespace Service::Time::Clock {
+
+class NetworkSystemClockContextWriter final : public SystemClockContextUpdateCallback {
+public:
+ explicit NetworkSystemClockContextWriter(SharedMemory& shared_memory)
+ : SystemClockContextUpdateCallback{}, shared_memory{shared_memory} {}
+
+protected:
+ ResultCode Update() override {
+ shared_memory.UpdateNetworkSystemClockContext(context);
+ return RESULT_SUCCESS;
+ }
+
+private:
+ SharedMemory& shared_memory;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_local_system_clock_core.h b/src/core/hle/service/time/standard_local_system_clock_core.h
new file mode 100644
index 000000000..8c1882eb1
--- /dev/null
+++ b/src/core/hle/service/time/standard_local_system_clock_core.h
@@ -0,0 +1,17 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/system_clock_core.h"
+
+namespace Service::Time::Clock {
+
+class StandardLocalSystemClockCore final : public SystemClockCore {
+public:
+ explicit StandardLocalSystemClockCore(SteadyClockCore& steady_clock_core)
+ : SystemClockCore{steady_clock_core} {}
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_network_system_clock_core.h b/src/core/hle/service/time/standard_network_system_clock_core.h
new file mode 100644
index 000000000..3f505c37c
--- /dev/null
+++ b/src/core/hle/service/time/standard_network_system_clock_core.h
@@ -0,0 +1,46 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/steady_clock_core.h"
+#include "core/hle/service/time/system_clock_core.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class StandardNetworkSystemClockCore final : public SystemClockCore {
+public:
+ explicit StandardNetworkSystemClockCore(SteadyClockCore& steady_clock_core)
+ : SystemClockCore{steady_clock_core} {}
+
+ void SetStandardNetworkClockSufficientAccuracy(TimeSpanType value) {
+ standard_network_clock_sufficient_accuracy = value;
+ }
+
+ bool IsStandardNetworkSystemClockAccuracySufficient(Core::System& system) {
+ SystemClockContext context{};
+ if (GetClockContext(system, context) != RESULT_SUCCESS) {
+ return {};
+ }
+
+ s64 span{};
+ if (context.steady_time_point.GetSpanBetween(
+ GetSteadyClockCore().GetCurrentTimePoint(system), span) != RESULT_SUCCESS) {
+ return {};
+ }
+
+ return TimeSpanType{span}.nanoseconds <
+ standard_network_clock_sufficient_accuracy.nanoseconds;
+ }
+
+private:
+ TimeSpanType standard_network_clock_sufficient_accuracy{};
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_steady_clock_core.cpp b/src/core/hle/service/time/standard_steady_clock_core.cpp
new file mode 100644
index 000000000..ca1a783fc
--- /dev/null
+++ b/src/core/hle/service/time/standard_steady_clock_core.cpp
@@ -0,0 +1,26 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hle/service/time/standard_steady_clock_core.h"
+
+namespace Service::Time::Clock {
+
+TimeSpanType StandardSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) {
+ const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
+ Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
+ Core::Timing::CNTFREQ)};
+ TimeSpanType raw_time_point{setup_value.nanoseconds + ticks_time_span.nanoseconds};
+
+ if (raw_time_point.nanoseconds < cached_raw_time_point.nanoseconds) {
+ raw_time_point.nanoseconds = cached_raw_time_point.nanoseconds;
+ }
+
+ cached_raw_time_point = raw_time_point;
+ return raw_time_point;
+}
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_steady_clock_core.h b/src/core/hle/service/time/standard_steady_clock_core.h
new file mode 100644
index 000000000..f56f3fd95
--- /dev/null
+++ b/src/core/hle/service/time/standard_steady_clock_core.h
@@ -0,0 +1,42 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/steady_clock_core.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class StandardSteadyClockCore final : public SteadyClockCore {
+public:
+ SteadyClockTimePoint GetTimePoint(Core::System& system) override {
+ return {GetCurrentRawTimePoint(system).ToSeconds(), GetClockSourceId()};
+ }
+
+ TimeSpanType GetInternalOffset() const override {
+ return internal_offset;
+ }
+
+ void SetInternalOffset(TimeSpanType value) override {
+ internal_offset = value;
+ }
+
+ TimeSpanType GetCurrentRawTimePoint(Core::System& system) override;
+
+ void SetSetupValue(TimeSpanType value) {
+ setup_value = value;
+ }
+
+private:
+ TimeSpanType setup_value{};
+ TimeSpanType internal_offset{};
+ TimeSpanType cached_raw_time_point{};
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.cpp b/src/core/hle/service/time/standard_user_system_clock_core.cpp
new file mode 100644
index 000000000..8af17091c
--- /dev/null
+++ b/src/core/hle/service/time/standard_user_system_clock_core.cpp
@@ -0,0 +1,77 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/core.h"
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/time/standard_local_system_clock_core.h"
+#include "core/hle/service/time/standard_network_system_clock_core.h"
+#include "core/hle/service/time/standard_user_system_clock_core.h"
+
+namespace Service::Time::Clock {
+
+StandardUserSystemClockCore::StandardUserSystemClockCore(
+ StandardLocalSystemClockCore& local_system_clock_core,
+ StandardNetworkSystemClockCore& network_system_clock_core, Core::System& system)
+ : SystemClockCore(local_system_clock_core.GetSteadyClockCore()),
+ local_system_clock_core{local_system_clock_core},
+ network_system_clock_core{network_system_clock_core}, auto_correction_enabled{},
+ auto_correction_time{SteadyClockTimePoint::GetRandom()},
+ auto_correction_event{Kernel::WritableEvent::CreateEventPair(
+ system.Kernel(), "StandardUserSystemClockCore:AutoCorrectionEvent")} {}
+
+ResultCode StandardUserSystemClockCore::SetAutomaticCorrectionEnabled(Core::System& system,
+ bool value) {
+ if (const ResultCode result{ApplyAutomaticCorrection(system, value)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ auto_correction_enabled = value;
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode StandardUserSystemClockCore::GetClockContext(Core::System& system,
+ SystemClockContext& context) const {
+ if (const ResultCode result{ApplyAutomaticCorrection(system, false)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ return local_system_clock_core.GetClockContext(system, context);
+}
+
+ResultCode StandardUserSystemClockCore::Flush(const SystemClockContext& context) {
+ UNREACHABLE();
+ return ERROR_NOT_IMPLEMENTED;
+}
+
+ResultCode StandardUserSystemClockCore::SetClockContext(const SystemClockContext& context) {
+ UNREACHABLE();
+ return ERROR_NOT_IMPLEMENTED;
+}
+
+ResultCode StandardUserSystemClockCore::ApplyAutomaticCorrection(Core::System& system,
+ bool value) const {
+ if (auto_correction_enabled == value) {
+ return RESULT_SUCCESS;
+ }
+
+ if (!network_system_clock_core.IsClockSetup(system)) {
+ return ERROR_UNINITIALIZED_CLOCK;
+ }
+
+ SystemClockContext context{};
+ if (const ResultCode result{network_system_clock_core.GetClockContext(system, context)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ local_system_clock_core.SetClockContext(context);
+
+ return RESULT_SUCCESS;
+}
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/standard_user_system_clock_core.h b/src/core/hle/service/time/standard_user_system_clock_core.h
new file mode 100644
index 000000000..ef3d468b7
--- /dev/null
+++ b/src/core/hle/service/time/standard_user_system_clock_core.h
@@ -0,0 +1,57 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/system_clock_core.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class StandardLocalSystemClockCore;
+class StandardNetworkSystemClockCore;
+
+class StandardUserSystemClockCore final : public SystemClockCore {
+public:
+ StandardUserSystemClockCore(StandardLocalSystemClockCore& local_system_clock_core,
+ StandardNetworkSystemClockCore& network_system_clock_core,
+ Core::System& system);
+
+ ResultCode SetAutomaticCorrectionEnabled(Core::System& system, bool value);
+
+ ResultCode GetClockContext(Core::System& system, SystemClockContext& context) const override;
+
+ bool IsAutomaticCorrectionEnabled() const {
+ return auto_correction_enabled;
+ }
+
+ void SetAutomaticCorrectionUpdatedTime(SteadyClockTimePoint steady_clock_time_point) {
+ auto_correction_time = steady_clock_time_point;
+ }
+
+protected:
+ ResultCode Flush(const SystemClockContext& context) override;
+
+ ResultCode SetClockContext(const SystemClockContext&) override;
+
+ ResultCode ApplyAutomaticCorrection(Core::System& system, bool value) const;
+
+ const SteadyClockTimePoint& GetAutomaticCorrectionUpdatedTime() const {
+ return auto_correction_time;
+ }
+
+private:
+ StandardLocalSystemClockCore& local_system_clock_core;
+ StandardNetworkSystemClockCore& network_system_clock_core;
+ bool auto_correction_enabled{};
+ SteadyClockTimePoint auto_correction_time;
+ Kernel::EventPair auto_correction_event;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/steady_clock_core.h b/src/core/hle/service/time/steady_clock_core.h
new file mode 100644
index 000000000..84af3d105
--- /dev/null
+++ b/src/core/hle/service/time/steady_clock_core.h
@@ -0,0 +1,55 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/uuid.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class SteadyClockCore {
+public:
+ SteadyClockCore() = default;
+
+ const Common::UUID& GetClockSourceId() const {
+ return clock_source_id;
+ }
+
+ void SetClockSourceId(const Common::UUID& value) {
+ clock_source_id = value;
+ }
+
+ virtual TimeSpanType GetInternalOffset() const = 0;
+
+ virtual void SetInternalOffset(TimeSpanType internal_offset) = 0;
+
+ virtual SteadyClockTimePoint GetTimePoint(Core::System& system) = 0;
+
+ virtual TimeSpanType GetCurrentRawTimePoint(Core::System& system) = 0;
+
+ SteadyClockTimePoint GetCurrentTimePoint(Core::System& system) {
+ SteadyClockTimePoint result{GetTimePoint(system)};
+ result.time_point += GetInternalOffset().ToSeconds();
+ return result;
+ }
+
+ bool IsInitialized() const {
+ return is_initialized;
+ }
+
+ void MarkAsInitialized() {
+ is_initialized = true;
+ }
+
+private:
+ Common::UUID clock_source_id{Common::UUID::Generate()};
+ bool is_initialized{};
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.cpp b/src/core/hle/service/time/system_clock_context_update_callback.cpp
new file mode 100644
index 000000000..5cdb80703
--- /dev/null
+++ b/src/core/hle/service/time/system_clock_context_update_callback.cpp
@@ -0,0 +1,55 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/writable_event.h"
+#include "core/hle/service/time/errors.h"
+#include "core/hle/service/time/system_clock_context_update_callback.h"
+
+namespace Service::Time::Clock {
+
+SystemClockContextUpdateCallback::SystemClockContextUpdateCallback() = default;
+SystemClockContextUpdateCallback::~SystemClockContextUpdateCallback() = default;
+
+bool SystemClockContextUpdateCallback::NeedUpdate(const SystemClockContext& value) const {
+ if (has_context) {
+ return context.offset != value.offset ||
+ context.steady_time_point.clock_source_id != value.steady_time_point.clock_source_id;
+ }
+
+ return true;
+}
+
+void SystemClockContextUpdateCallback::RegisterOperationEvent(
+ std::shared_ptr<Kernel::WritableEvent>&& writable_event) {
+ operation_event_list.emplace_back(std::move(writable_event));
+}
+
+void SystemClockContextUpdateCallback::BroadcastOperationEvent() {
+ for (const auto& writable_event : operation_event_list) {
+ writable_event->Signal();
+ }
+}
+
+ResultCode SystemClockContextUpdateCallback::Update(const SystemClockContext& value) {
+ ResultCode result{RESULT_SUCCESS};
+
+ if (NeedUpdate(value)) {
+ context = value;
+ has_context = true;
+
+ result = Update();
+
+ if (result == RESULT_SUCCESS) {
+ BroadcastOperationEvent();
+ }
+ }
+
+ return result;
+}
+
+ResultCode SystemClockContextUpdateCallback::Update() {
+ return RESULT_SUCCESS;
+}
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/system_clock_context_update_callback.h b/src/core/hle/service/time/system_clock_context_update_callback.h
new file mode 100644
index 000000000..6260de6c3
--- /dev/null
+++ b/src/core/hle/service/time/system_clock_context_update_callback.h
@@ -0,0 +1,43 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "core/hle/service/time/clock_types.h"
+
+namespace Kernel {
+class WritableEvent;
+}
+
+namespace Service::Time::Clock {
+
+// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783).
+// This code was released under public domain.
+
+class SystemClockContextUpdateCallback {
+public:
+ SystemClockContextUpdateCallback();
+ ~SystemClockContextUpdateCallback();
+
+ bool NeedUpdate(const SystemClockContext& value) const;
+
+ void RegisterOperationEvent(std::shared_ptr<Kernel::WritableEvent>&& writable_event);
+
+ void BroadcastOperationEvent();
+
+ ResultCode Update(const SystemClockContext& value);
+
+protected:
+ virtual ResultCode Update();
+
+ SystemClockContext context{};
+
+private:
+ bool has_context{};
+ std::vector<std::shared_ptr<Kernel::WritableEvent>> operation_event_list;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/system_clock_core.cpp b/src/core/hle/service/time/system_clock_core.cpp
new file mode 100644
index 000000000..1a3ab8cfa
--- /dev/null
+++ b/src/core/hle/service/time/system_clock_core.cpp
@@ -0,0 +1,72 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/time/steady_clock_core.h"
+#include "core/hle/service/time/system_clock_context_update_callback.h"
+#include "core/hle/service/time/system_clock_core.h"
+
+namespace Service::Time::Clock {
+
+SystemClockCore::SystemClockCore(SteadyClockCore& steady_clock_core)
+ : steady_clock_core{steady_clock_core}, is_initialized{} {
+ context.steady_time_point.clock_source_id = steady_clock_core.GetClockSourceId();
+}
+
+SystemClockCore ::~SystemClockCore() = default;
+
+ResultCode SystemClockCore::GetCurrentTime(Core::System& system, s64& posix_time) const {
+ posix_time = 0;
+
+ const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)};
+
+ SystemClockContext clock_context{};
+ if (const ResultCode result{GetClockContext(system, clock_context)}; result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ if (current_time_point.clock_source_id != clock_context.steady_time_point.clock_source_id) {
+ return ERROR_TIME_MISMATCH;
+ }
+
+ posix_time = clock_context.offset + current_time_point.time_point;
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode SystemClockCore::SetCurrentTime(Core::System& system, s64 posix_time) {
+ const SteadyClockTimePoint current_time_point{steady_clock_core.GetCurrentTimePoint(system)};
+ const SystemClockContext clock_context{posix_time - current_time_point.time_point,
+ current_time_point};
+
+ if (const ResultCode result{SetClockContext(clock_context)}; result != RESULT_SUCCESS) {
+ return result;
+ }
+ return Flush(clock_context);
+}
+
+ResultCode SystemClockCore::Flush(const SystemClockContext& context) {
+ if (!system_clock_context_update_callback) {
+ return RESULT_SUCCESS;
+ }
+ return system_clock_context_update_callback->Update(context);
+}
+
+ResultCode SystemClockCore::SetSystemClockContext(const SystemClockContext& context) {
+ if (const ResultCode result{SetClockContext(context)}; result != RESULT_SUCCESS) {
+ return result;
+ }
+ return Flush(context);
+}
+
+bool SystemClockCore::IsClockSetup(Core::System& system) const {
+ SystemClockContext value{};
+ if (GetClockContext(system, value) == RESULT_SUCCESS) {
+ const SteadyClockTimePoint steady_clock_time_point{
+ steady_clock_core.GetCurrentTimePoint(system)};
+ return steady_clock_time_point.clock_source_id == value.steady_time_point.clock_source_id;
+ }
+ return {};
+}
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/system_clock_core.h b/src/core/hle/service/time/system_clock_core.h
new file mode 100644
index 000000000..54407a6c5
--- /dev/null
+++ b/src/core/hle/service/time/system_clock_core.h
@@ -0,0 +1,71 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class SteadyClockCore;
+class SystemClockContextUpdateCallback;
+
+// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783).
+// This code was released under public domain.
+
+class SystemClockCore {
+public:
+ explicit SystemClockCore(SteadyClockCore& steady_clock_core);
+ ~SystemClockCore();
+
+ SteadyClockCore& GetSteadyClockCore() const {
+ return steady_clock_core;
+ }
+
+ ResultCode GetCurrentTime(Core::System& system, s64& posix_time) const;
+
+ ResultCode SetCurrentTime(Core::System& system, s64 posix_time);
+
+ virtual ResultCode GetClockContext([[maybe_unused]] Core::System& system,
+ SystemClockContext& value) const {
+ value = context;
+ return RESULT_SUCCESS;
+ }
+
+ virtual ResultCode SetClockContext(const SystemClockContext& value) {
+ context = value;
+ return RESULT_SUCCESS;
+ }
+
+ virtual ResultCode Flush(const SystemClockContext& context);
+
+ void SetUpdateCallbackInstance(std::shared_ptr<SystemClockContextUpdateCallback> callback) {
+ system_clock_context_update_callback = std::move(callback);
+ }
+
+ ResultCode SetSystemClockContext(const SystemClockContext& context);
+
+ bool IsInitialized() const {
+ return is_initialized;
+ }
+
+ void MarkAsInitialized() {
+ is_initialized = true;
+ }
+
+ bool IsClockSetup(Core::System& system) const;
+
+private:
+ SteadyClockCore& steady_clock_core;
+ SystemClockContext context{};
+ bool is_initialized{};
+ std::shared_ptr<SystemClockContextUpdateCallback> system_clock_context_update_callback;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.cpp b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
new file mode 100644
index 000000000..c77b98189
--- /dev/null
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.cpp
@@ -0,0 +1,24 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hle/service/time/tick_based_steady_clock_core.h"
+
+namespace Service::Time::Clock {
+
+SteadyClockTimePoint TickBasedSteadyClockCore::GetTimePoint(Core::System& system) {
+ const TimeSpanType ticks_time_span{TimeSpanType::FromTicks(
+ Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
+ Core::Timing::CNTFREQ)};
+
+ return {ticks_time_span.ToSeconds(), GetClockSourceId()};
+}
+
+TimeSpanType TickBasedSteadyClockCore::GetCurrentRawTimePoint(Core::System& system) {
+ return TimeSpanType::FromSeconds(GetTimePoint(system).time_point);
+}
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/tick_based_steady_clock_core.h b/src/core/hle/service/time/tick_based_steady_clock_core.h
new file mode 100644
index 000000000..1a5a53fd7
--- /dev/null
+++ b/src/core/hle/service/time/tick_based_steady_clock_core.h
@@ -0,0 +1,29 @@
+// Copyright 2020 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/steady_clock_core.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time::Clock {
+
+class TickBasedSteadyClockCore final : public SteadyClockCore {
+public:
+ TimeSpanType GetInternalOffset() const override {
+ return {};
+ }
+
+ void SetInternalOffset(TimeSpanType internal_offset) override {}
+
+ SteadyClockTimePoint GetTimePoint(Core::System& system) override;
+
+ TimeSpanType GetCurrentRawTimePoint(Core::System& system) override;
+};
+
+} // namespace Service::Time::Clock
diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp
index 6ee77c5f9..8ef4efcef 100644
--- a/src/core/hle/service/time/time.cpp
+++ b/src/core/hle/service/time/time.cpp
@@ -1,9 +1,7 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <chrono>
-#include <ctime>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -11,429 +9,321 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/service/time/interface.h"
#include "core/hle/service/time/time.h"
#include "core/hle/service/time/time_sharedmemory.h"
-#include "core/settings.h"
+#include "core/hle/service/time/time_zone_service.h"
namespace Service::Time {
-static std::chrono::seconds GetSecondsSinceEpoch() {
- return std::chrono::duration_cast<std::chrono::seconds>(
- std::chrono::system_clock::now().time_since_epoch()) +
- Settings::values.custom_rtc_differential;
-}
-
-static void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time,
- CalendarAdditionalInfo& additional_info,
- [[maybe_unused]] const TimeZoneRule& /*rule*/) {
- const std::time_t time(posix_time);
- const std::tm* tm = std::localtime(&time);
- if (tm == nullptr) {
- calendar_time = {};
- additional_info = {};
- return;
- }
- calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900);
- calendar_time.month = static_cast<u8>(tm->tm_mon + 1);
- calendar_time.day = static_cast<u8>(tm->tm_mday);
- calendar_time.hour = static_cast<u8>(tm->tm_hour);
- calendar_time.minute = static_cast<u8>(tm->tm_min);
- calendar_time.second = static_cast<u8>(tm->tm_sec);
-
- additional_info.day_of_week = tm->tm_wday;
- additional_info.day_of_year = tm->tm_yday;
- std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC"));
- additional_info.utc_offset = 0;
-}
-
-static u64 CalendarToPosix(const CalendarTime& calendar_time,
- [[maybe_unused]] const TimeZoneRule& /*rule*/) {
- std::tm time{};
- time.tm_year = calendar_time.year - 1900;
- time.tm_mon = calendar_time.month - 1;
- time.tm_mday = calendar_time.day;
-
- time.tm_hour = calendar_time.hour;
- time.tm_min = calendar_time.minute;
- time.tm_sec = calendar_time.second;
-
- std::time_t epoch_time = std::mktime(&time);
- return static_cast<u64>(epoch_time);
-}
-
-enum class ClockContextType {
- StandardSteady,
- StandardUserSystem,
- StandardNetworkSystem,
- StandardLocalSystem,
-};
-
class ISystemClock final : public ServiceFramework<ISystemClock> {
public:
- ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory,
- ClockContextType clock_type)
- : ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) {
+ ISystemClock(Clock::SystemClockCore& clock_core)
+ : ServiceFramework("ISystemClock"), clock_core{clock_core} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
{1, nullptr, "SetCurrentTime"},
- {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"},
+ {2, &ISystemClock::GetSystemClockContext, "GetSystemClockContext"},
{3, nullptr, "SetSystemClockContext"},
{4, nullptr, "GetOperationEventReadableHandle"},
};
// clang-format on
RegisterHandlers(functions);
- UpdateSharedMemoryContext(system_clock_context);
}
private:
void GetCurrentTime(Kernel::HLERequestContext& ctx) {
- const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
LOG_DEBUG(Service_Time, "called");
+ if (!clock_core.IsInitialized()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_UNINITIALIZED_CLOCK);
+ return;
+ }
+
+ s64 posix_time{};
+ if (const ResultCode result{
+ clock_core.GetCurrentTime(Core::System::GetInstance(), posix_time)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(time_since_epoch);
+ rb.Push<s64>(posix_time);
}
void GetSystemClockContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
+ LOG_DEBUG(Service_Time, "called");
+
+ if (!clock_core.IsInitialized()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_UNINITIALIZED_CLOCK);
+ return;
+ }
- // TODO(ogniK): This should be updated periodically however since we have it stubbed we'll
- // only update when we get a new context
- UpdateSharedMemoryContext(system_clock_context);
+ Clock::SystemClockContext system_clock_context{};
+ if (const ResultCode result{
+ clock_core.GetClockContext(Core::System::GetInstance(), system_clock_context)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
- IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2};
+ IPC::ResponseBuilder rb{ctx, sizeof(Clock::SystemClockContext) / 4 + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(system_clock_context);
}
- void UpdateSharedMemoryContext(const SystemClockContext& clock_context) {
- switch (clock_type) {
- case ClockContextType::StandardLocalSystem:
- shared_memory->SetStandardLocalSystemClockContext(clock_context);
- break;
- case ClockContextType::StandardNetworkSystem:
- shared_memory->SetStandardNetworkSystemClockContext(clock_context);
- break;
- }
- }
-
- SystemClockContext system_clock_context{};
- std::shared_ptr<Service::Time::SharedMemory> shared_memory;
- ClockContextType clock_type;
+ Clock::SystemClockCore& clock_core;
};
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
public:
- ISteadyClock(std::shared_ptr<SharedMemory> shared_memory, Core::System& system)
- : ServiceFramework("ISteadyClock"), shared_memory(shared_memory), system(system) {
+ ISteadyClock(Clock::SteadyClockCore& clock_core)
+ : ServiceFramework("ISteadyClock"), clock_core{clock_core} {
static const FunctionInfo functions[] = {
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
};
RegisterHandlers(functions);
-
- shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint());
}
private:
void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
- const auto time_point = GetCurrentTimePoint();
- // TODO(ogniK): This should be updated periodically
- shared_memory->SetStandardSteadyClockTimepoint(time_point);
+ if (!clock_core.IsInitialized()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_UNINITIALIZED_CLOCK);
+ return;
+ }
- IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
+ const Clock::SteadyClockTimePoint time_point{
+ clock_core.GetCurrentTimePoint(Core::System::GetInstance())};
+ IPC::ResponseBuilder rb{ctx, (sizeof(Clock::SteadyClockTimePoint) / 4) + 2};
rb.Push(RESULT_SUCCESS);
rb.PushRaw(time_point);
}
- SteadyClockTimePoint GetCurrentTimePoint() const {
- const auto& core_timing = system.CoreTiming();
- const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
- return {static_cast<u64_le>(ms.count() / 1000), {}};
- }
-
- std::shared_ptr<SharedMemory> shared_memory;
- Core::System& system;
+ Clock::SteadyClockCore& clock_core;
};
-class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
-public:
- ITimeZoneService() : ServiceFramework("ITimeZoneService") {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"},
- {1, nullptr, "SetDeviceLocationName"},
- {2, &ITimeZoneService::GetTotalLocationNameCount, "GetTotalLocationNameCount"},
- {3, nullptr, "LoadLocationNameList"},
- {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"},
- {5, nullptr, "GetTimeZoneRuleVersion"},
- {6, nullptr, "GetDeviceLocationNameAndUpdatedTime"},
- {7, nullptr, "SetDeviceLocationNameWithTimeZoneRule"},
- {8, nullptr, "ParseTimeZoneBinary"},
- {20, nullptr, "GetDeviceLocationNameOperationEventReadableHandle"},
- {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
- {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
- {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"},
- {202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
+ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal(
+ Kernel::Thread* thread, Clock::SystemClockContext user_context,
+ Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) {
-private:
- LocationName location_name{"UTC"};
- TimeZoneRule my_time_zone_rule{};
+ auto& time_manager{module->GetTimeManager()};
- void GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_Time, "called");
+ clock_snapshot.is_automatic_correction_enabled =
+ time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled();
+ clock_snapshot.user_context = user_context;
+ clock_snapshot.network_context = network_context;
- IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw(location_name);
+ if (const ResultCode result{
+ time_manager.GetTimeZoneContentManager().GetTimeZoneManager().GetDeviceLocationName(
+ clock_snapshot.location_name)};
+ result != RESULT_SUCCESS) {
+ return result;
}
- void GetTotalLocationNameCount(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ const auto current_time_point{
+ time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(Core::System::GetInstance())};
+ if (const ResultCode result{Clock::ClockSnapshot::GetCurrentTime(
+ clock_snapshot.user_time, current_time_point, clock_snapshot.user_context)};
+ result != RESULT_SUCCESS) {
+ return result;
}
- void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
-
- ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule));
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ TimeZone::CalendarInfo userCalendarInfo{};
+ if (const ResultCode result{
+ time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules(
+ clock_snapshot.user_time, userCalendarInfo)};
+ result != RESULT_SUCCESS) {
+ return result;
}
- void ToCalendarTime(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 posix_time = rp.Pop<u64>();
- LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
-
- TimeZoneRule time_zone_rule{};
- auto buffer = ctx.ReadBuffer();
- std::memcpy(&time_zone_rule, buffer.data(), buffer.size());
+ clock_snapshot.user_calendar_time = userCalendarInfo.time;
+ clock_snapshot.user_calendar_additional_time = userCalendarInfo.additiona_info;
- CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
- CalendarAdditionalInfo additional_info{};
-
- PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule);
-
- IPC::ResponseBuilder rb{ctx, 10};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw(calendar_time);
- rb.PushRaw(additional_info);
- }
-
- void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 posix_time = rp.Pop<u64>();
- LOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
-
- CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
- CalendarAdditionalInfo additional_info{};
-
- PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule);
-
- IPC::ResponseBuilder rb{ctx, 10};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw(calendar_time);
- rb.PushRaw(additional_info);
+ if (Clock::ClockSnapshot::GetCurrentTime(clock_snapshot.network_time, current_time_point,
+ clock_snapshot.network_context) != RESULT_SUCCESS) {
+ clock_snapshot.network_time = 0;
}
- void ToPosixTime(Kernel::HLERequestContext& ctx) {
- // TODO(ogniK): Figure out how to handle multiple times
- LOG_WARNING(Service_Time, "(STUBBED) called");
-
- IPC::RequestParser rp{ctx};
- auto calendar_time = rp.PopRaw<CalendarTime>();
- auto posix_time = CalendarToPosix(calendar_time, {});
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u32>(1); // Amount of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(u64));
+ TimeZone::CalendarInfo networkCalendarInfo{};
+ if (const ResultCode result{
+ time_manager.GetTimeZoneContentManager().GetTimeZoneManager().ToCalendarTimeWithMyRules(
+ clock_snapshot.network_time, networkCalendarInfo)};
+ result != RESULT_SUCCESS) {
+ return result;
}
- void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_Time, "(STUBBED) called");
-
- IPC::RequestParser rp{ctx};
- auto calendar_time = rp.PopRaw<CalendarTime>();
- auto posix_time = CalendarToPosix(calendar_time, {});
+ clock_snapshot.network_calendar_time = networkCalendarInfo.time;
+ clock_snapshot.network_calendar_additional_time = networkCalendarInfo.additiona_info;
+ clock_snapshot.type = type;
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u32>(1); // Amount of times we're returning
- ctx.WriteBuffer(&posix_time, sizeof(u64));
- }
-};
+ return RESULT_SUCCESS;
+}
void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem);
+ rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardUserSystemClockCore());
}
void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem);
+ rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardNetworkSystemClockCore());
}
void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISteadyClock>(shared_memory, system);
+ rb.PushIpcInterface<ISteadyClock>(module->GetTimeManager().GetStandardSteadyClockCore());
}
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ITimeZoneService>();
+ rb.PushIpcInterface<ITimeZoneService>(module->GetTimeManager().GetTimeZoneContentManager());
}
void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem);
+ rb.PushIpcInterface<ISystemClock>(module->GetTimeManager().GetStandardLocalSystemClockCore());
}
-void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
+void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient(
+ Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
+ auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()};
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system));
+}
- IPC::RequestParser rp{ctx};
- const auto initial_type = rp.PopRaw<u8>();
+void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
- const s64 time_since_epoch{GetSecondsSinceEpoch().count()};
- const std::time_t time(time_since_epoch);
- const std::tm* tm = std::localtime(&time);
- if (tm == nullptr) {
- LOG_ERROR(Service_Time, "tm is a nullptr");
+ auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()};
+ if (!steady_clock_core.IsInitialized()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_UNKNOWN); // TODO(ogniK): Find appropriate error code
+ rb.Push(ERROR_UNINITIALIZED_CLOCK);
return;
}
- const auto& core_timing = system.CoreTiming();
- const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
- const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}};
-
- CalendarTime calendar_time{};
- calendar_time.year = static_cast<u16_le>(tm->tm_year + 1900);
- calendar_time.month = static_cast<u8>(tm->tm_mon + 1);
- calendar_time.day = static_cast<u8>(tm->tm_mday);
- calendar_time.hour = static_cast<u8>(tm->tm_hour);
- calendar_time.minute = static_cast<u8>(tm->tm_min);
- calendar_time.second = static_cast<u8>(tm->tm_sec);
+ IPC::RequestParser rp{ctx};
+ const auto context{rp.PopRaw<Clock::SystemClockContext>()};
+ const auto current_time_point{
+ steady_clock_core.GetCurrentTimePoint(Core::System::GetInstance())};
+
+ if (current_time_point.clock_source_id == context.steady_time_point.clock_source_id) {
+ const auto ticks{Clock::TimeSpanType::FromTicks(
+ Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
+ Core::Timing::CNTFREQ)};
+ const s64 base_time_point{context.offset + current_time_point.time_point -
+ ticks.ToSeconds()};
+ IPC::ResponseBuilder rb{ctx, (sizeof(s64) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(base_time_point);
+ return;
+ }
- ClockSnapshot clock_snapshot{};
- clock_snapshot.system_posix_time = time_since_epoch;
- clock_snapshot.network_posix_time = time_since_epoch;
- clock_snapshot.system_calendar_time = calendar_time;
- clock_snapshot.network_calendar_time = calendar_time;
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERROR_TIME_MISMATCH);
+}
- CalendarAdditionalInfo additional_info{};
- PosixToCalendar(time_since_epoch, calendar_time, additional_info, {});
+void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+ IPC::RequestParser rp{ctx};
+ const auto type{rp.PopRaw<u8>()};
- clock_snapshot.system_calendar_info = additional_info;
- clock_snapshot.network_calendar_info = additional_info;
+ Clock::SystemClockContext user_context{};
+ if (const ResultCode result{
+ module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(
+ Core::System::GetInstance(), user_context)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+ Clock::SystemClockContext network_context{};
+ if (const ResultCode result{
+ module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext(
+ Core::System::GetInstance(), network_context)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
- clock_snapshot.steady_clock_timepoint = steady_clock_time_point;
- clock_snapshot.location_name = LocationName{"UTC"};
- clock_snapshot.clock_auto_adjustment_enabled = 1;
- clock_snapshot.type = initial_type;
+ Clock::ClockSnapshot clock_snapshot{};
+ if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal(
+ &ctx.GetThread(), user_context, network_context, type, clock_snapshot)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot));
+ ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
}
-void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
- Kernel::HLERequestContext& ctx) {
+void Module::Interface::GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
-
IPC::RequestParser rp{ctx};
- const auto snapshot_a = rp.PopRaw<ClockSnapshot>();
- const auto snapshot_b = rp.PopRaw<ClockSnapshot>();
- const u64 difference =
- snapshot_b.user_clock_context.offset - snapshot_a.user_clock_context.offset;
+ const auto type{rp.PopRaw<u8>()};
+ rp.AlignWithPadding();
+
+ const Clock::SystemClockContext user_context{rp.PopRaw<Clock::SystemClockContext>()};
+ const Clock::SystemClockContext network_context{rp.PopRaw<Clock::SystemClockContext>()};
- IPC::ResponseBuilder rb{ctx, 4};
+ Clock::ClockSnapshot clock_snapshot{};
+ if (const ResultCode result{GetClockSnapshotFromSystemClockContextInternal(
+ &ctx.GetThread(), user_context, network_context, type, clock_snapshot)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.PushRaw<u64>(difference);
+ ctx.WriteBuffer(&clock_snapshot, sizeof(Clock::ClockSnapshot));
}
void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder());
-}
-
-void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled(
- Kernel::HLERequestContext& ctx) {
- // ogniK(TODO): When clock contexts are implemented, the value should be read from the context
- // instead of our shared memory holder
- LOG_DEBUG(Service_Time, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(RESULT_SUCCESS);
- rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled());
-}
-
-void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled(
- Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto enabled = rp.Pop<u8>();
-
- LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called");
-
- // TODO(ogniK): Update clock contexts and correct timespans
-
- shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder());
}
-Module::Interface::Interface(std::shared_ptr<Module> time,
- std::shared_ptr<SharedMemory> shared_memory, Core::System& system,
- const char* name)
- : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)),
- system(system) {}
+Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name)
+ : ServiceFramework(name), module{std::move(module)}, system{system} {}
Module::Interface::~Interface() = default;
void InstallInterfaces(Core::System& system) {
- auto time = std::make_shared<Module>();
- auto shared_mem = std::make_shared<SharedMemory>(system);
-
- std::make_shared<Time>(time, shared_mem, system, "time:a")
- ->InstallAsService(system.ServiceManager());
- std::make_shared<Time>(time, shared_mem, system, "time:s")
- ->InstallAsService(system.ServiceManager());
- std::make_shared<Time>(std::move(time), shared_mem, system, "time:u")
- ->InstallAsService(system.ServiceManager());
+ auto module{std::make_shared<Module>(system)};
+ std::make_shared<Time>(module, system, "time:a")->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(module, system, "time:s")->InstallAsService(system.ServiceManager());
+ std::make_shared<Time>(module, system, "time:u")->InstallAsService(system.ServiceManager());
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h
index c32d32860..aadc2df60 100644
--- a/src/core/hle/service/time/time.h
+++ b/src/core/hle/service/time/time.h
@@ -4,84 +4,23 @@
#pragma once
-#include <array>
-#include "common/common_funcs.h"
#include "core/hle/service/service.h"
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/time_manager.h"
-namespace Service::Time {
-
-class SharedMemory;
-
-struct LocationName {
- std::array<u8, 0x24> name;
-};
-static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size");
-
-struct CalendarTime {
- u16_le year;
- u8 month; // Starts at 1
- u8 day; // Starts at 1
- u8 hour;
- u8 minute;
- u8 second;
-};
-static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size");
-
-struct CalendarAdditionalInfo {
- u32_le day_of_week;
- u32_le day_of_year;
- std::array<u8, 8> name;
- u8 is_dst;
- s32_le utc_offset;
-};
-static_assert(sizeof(CalendarAdditionalInfo) == 0x18,
- "CalendarAdditionalInfo structure has incorrect size");
-
-// TODO(mailwl) RE this structure
-struct TimeZoneRule {
- INSERT_PADDING_BYTES(0x4000);
-};
-
-struct SteadyClockTimePoint {
- using SourceID = std::array<u8, 16>;
+namespace Core {
+class System;
+}
- u64_le value;
- SourceID source_id;
-};
-static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
-
-struct SystemClockContext {
- u64_le offset;
- SteadyClockTimePoint time_point;
-};
-static_assert(sizeof(SystemClockContext) == 0x20,
- "SystemClockContext structure has incorrect size");
-
-struct ClockSnapshot {
- SystemClockContext user_clock_context;
- SystemClockContext network_clock_context;
- s64_le system_posix_time;
- s64_le network_posix_time;
- CalendarTime system_calendar_time;
- CalendarTime network_calendar_time;
- CalendarAdditionalInfo system_calendar_info;
- CalendarAdditionalInfo network_calendar_info;
- SteadyClockTimePoint steady_clock_timepoint;
- LocationName location_name;
- u8 clock_auto_adjustment_enabled;
- u8 type;
- u8 version;
- INSERT_PADDING_BYTES(1);
-};
-static_assert(sizeof(ClockSnapshot) == 0xd0, "ClockSnapshot is an invalid size");
+namespace Service::Time {
class Module final {
public:
+ Module(Core::System& system) : time_manager{system} {}
+
class Interface : public ServiceFramework<Interface> {
public:
- explicit Interface(std::shared_ptr<Module> time,
- std::shared_ptr<SharedMemory> shared_memory, Core::System& system,
- const char* name);
+ explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name);
~Interface() override;
void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
@@ -89,17 +28,29 @@ public:
void GetStandardSteadyClock(Kernel::HLERequestContext& ctx);
void GetTimeZoneService(Kernel::HLERequestContext& ctx);
void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx);
+ void IsStandardNetworkSystemClockAccuracySufficient(Kernel::HLERequestContext& ctx);
+ void CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx);
void GetClockSnapshot(Kernel::HLERequestContext& ctx);
- void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx);
+ void GetClockSnapshotFromSystemClockContext(Kernel::HLERequestContext& ctx);
void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
- void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
- void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
+
+ private:
+ ResultCode GetClockSnapshotFromSystemClockContextInternal(
+ Kernel::Thread* thread, Clock::SystemClockContext user_context,
+ Clock::SystemClockContext network_context, u8 type,
+ Clock::ClockSnapshot& cloc_snapshot);
protected:
- std::shared_ptr<Module> time;
- std::shared_ptr<SharedMemory> shared_memory;
+ std::shared_ptr<Module> module;
Core::System& system;
};
+
+ TimeManager& GetTimeManager() {
+ return time_manager;
+ }
+
+private:
+ TimeManager time_manager;
};
/// Registers all Time services with the specified service manager.
diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp
new file mode 100644
index 000000000..9d6c55865
--- /dev/null
+++ b/src/core/hle/service/time/time_manager.cpp
@@ -0,0 +1,137 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <ctime>
+
+#include "core/hle/service/time/ephemeral_network_system_clock_context_writer.h"
+#include "core/hle/service/time/local_system_clock_context_writer.h"
+#include "core/hle/service/time/network_system_clock_context_writer.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/settings.h"
+
+namespace Service::Time {
+
+constexpr Clock::TimeSpanType standard_network_clock_accuracy{0x0009356907420000ULL};
+
+static std::chrono::seconds GetSecondsSinceEpoch() {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()) +
+ Settings::values.custom_rtc_differential;
+}
+
+static s64 GetExternalRtcValue() {
+ return GetSecondsSinceEpoch().count();
+}
+
+TimeManager::TimeManager(Core::System& system)
+ : shared_memory{system}, standard_local_system_clock_core{standard_steady_clock_core},
+ standard_network_system_clock_core{standard_steady_clock_core},
+ standard_user_system_clock_core{standard_local_system_clock_core,
+ standard_network_system_clock_core, system},
+ ephemeral_network_system_clock_core{tick_based_steady_clock_core},
+ local_system_clock_context_writer{
+ std::make_shared<Clock::LocalSystemClockContextWriter>(shared_memory)},
+ network_system_clock_context_writer{
+ std::make_shared<Clock::NetworkSystemClockContextWriter>(shared_memory)},
+ ephemeral_network_system_clock_context_writer{
+ std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()},
+ time_zone_content_manager{*this, system} {
+
+ const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())};
+ SetupStandardSteadyClock(system, Common::UUID::Generate(), system_time, {}, {});
+ SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds());
+ SetupStandardNetworkSystemClock({}, standard_network_clock_accuracy);
+ SetupStandardUserSystemClock(system, {}, Clock::SteadyClockTimePoint::GetRandom());
+ SetupEphemeralNetworkSystemClock();
+}
+
+TimeManager::~TimeManager() = default;
+
+void TimeManager::SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count,
+ u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file) {
+ if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule(
+ location_name, vfs_file) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point);
+ time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount(
+ total_location_name_count);
+ time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion(time_zone_rule_version);
+ time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized();
+}
+
+void TimeManager::SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
+ Clock::TimeSpanType setup_value,
+ Clock::TimeSpanType internal_offset,
+ bool is_rtc_reset_detected) {
+ standard_steady_clock_core.SetClockSourceId(clock_source_id);
+ standard_steady_clock_core.SetSetupValue(setup_value);
+ standard_steady_clock_core.SetInternalOffset(internal_offset);
+ standard_steady_clock_core.MarkAsInitialized();
+
+ const auto current_time_point{standard_steady_clock_core.GetCurrentRawTimePoint(system)};
+ shared_memory.SetupStandardSteadyClock(system, clock_source_id, current_time_point);
+}
+
+void TimeManager::SetupStandardLocalSystemClock(Core::System& system,
+ Clock::SystemClockContext clock_context,
+ s64 posix_time) {
+ standard_local_system_clock_core.SetUpdateCallbackInstance(local_system_clock_context_writer);
+
+ const auto current_time_point{
+ standard_local_system_clock_core.GetSteadyClockCore().GetCurrentTimePoint(system)};
+ if (current_time_point.clock_source_id == clock_context.steady_time_point.clock_source_id) {
+ standard_local_system_clock_core.SetSystemClockContext(clock_context);
+ } else {
+ if (standard_local_system_clock_core.SetCurrentTime(system, posix_time) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+ }
+
+ standard_local_system_clock_core.MarkAsInitialized();
+}
+
+void TimeManager::SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
+ Clock::TimeSpanType sufficient_accuracy) {
+ standard_network_system_clock_core.SetUpdateCallbackInstance(
+ network_system_clock_context_writer);
+
+ if (standard_network_system_clock_core.SetSystemClockContext(clock_context) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_network_system_clock_core.SetStandardNetworkClockSufficientAccuracy(
+ sufficient_accuracy);
+ standard_network_system_clock_core.MarkAsInitialized();
+}
+
+void TimeManager::SetupStandardUserSystemClock(
+ Core::System& system, bool is_automatic_correction_enabled,
+ Clock::SteadyClockTimePoint steady_clock_time_point) {
+ if (standard_user_system_clock_core.SetAutomaticCorrectionEnabled(
+ system, is_automatic_correction_enabled) != RESULT_SUCCESS) {
+ UNREACHABLE();
+ return;
+ }
+
+ standard_user_system_clock_core.SetAutomaticCorrectionUpdatedTime(steady_clock_time_point);
+ standard_user_system_clock_core.MarkAsInitialized();
+ shared_memory.SetAutomaticCorrectionEnabled(is_automatic_correction_enabled);
+}
+
+void TimeManager::SetupEphemeralNetworkSystemClock() {
+ ephemeral_network_system_clock_core.SetUpdateCallbackInstance(
+ ephemeral_network_system_clock_context_writer);
+ ephemeral_network_system_clock_core.MarkAsInitialized();
+}
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h
new file mode 100644
index 000000000..8e65f0d22
--- /dev/null
+++ b/src/core/hle/service/time/time_manager.h
@@ -0,0 +1,117 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/ephemeral_network_system_clock_core.h"
+#include "core/hle/service/time/standard_local_system_clock_core.h"
+#include "core/hle/service/time/standard_network_system_clock_core.h"
+#include "core/hle/service/time/standard_steady_clock_core.h"
+#include "core/hle/service/time/standard_user_system_clock_core.h"
+#include "core/hle/service/time/tick_based_steady_clock_core.h"
+#include "core/hle/service/time/time_sharedmemory.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+
+namespace Service::Time {
+
+namespace Clock {
+class EphemeralNetworkSystemClockContextWriter;
+class LocalSystemClockContextWriter;
+class NetworkSystemClockContextWriter;
+} // namespace Clock
+
+// Parts of this implementation were based on Ryujinx (https://github.com/Ryujinx/Ryujinx/pull/783).
+// This code was released under public domain.
+
+class TimeManager final {
+public:
+ explicit TimeManager(Core::System& system);
+ ~TimeManager();
+
+ Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() {
+ return standard_steady_clock_core;
+ }
+
+ const Clock::StandardSteadyClockCore& GetStandardSteadyClockCore() const {
+ return standard_steady_clock_core;
+ }
+
+ Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() {
+ return standard_local_system_clock_core;
+ }
+
+ const Clock::StandardLocalSystemClockCore& GetStandardLocalSystemClockCore() const {
+ return standard_local_system_clock_core;
+ }
+
+ Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() {
+ return standard_network_system_clock_core;
+ }
+
+ const Clock::StandardNetworkSystemClockCore& GetStandardNetworkSystemClockCore() const {
+ return standard_network_system_clock_core;
+ }
+
+ Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() {
+ return standard_user_system_clock_core;
+ }
+
+ const Clock::StandardUserSystemClockCore& GetStandardUserSystemClockCore() const {
+ return standard_user_system_clock_core;
+ }
+
+ TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() {
+ return time_zone_content_manager;
+ }
+
+ const TimeZone::TimeZoneContentManager& GetTimeZoneContentManager() const {
+ return time_zone_content_manager;
+ }
+
+ SharedMemory& GetSharedMemory() {
+ return shared_memory;
+ }
+
+ const SharedMemory& GetSharedMemory() const {
+ return shared_memory;
+ }
+
+ void SetupTimeZoneManager(std::string location_name,
+ Clock::SteadyClockTimePoint time_zone_updated_time_point,
+ std::size_t total_location_name_count, u128 time_zone_rule_version,
+ FileSys::VirtualFile& vfs_file);
+
+private:
+ void SetupStandardSteadyClock(Core::System& system, Common::UUID clock_source_id,
+ Clock::TimeSpanType setup_value,
+ Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected);
+ void SetupStandardLocalSystemClock(Core::System& system,
+ Clock::SystemClockContext clock_context, s64 posix_time);
+ void SetupStandardNetworkSystemClock(Clock::SystemClockContext clock_context,
+ Clock::TimeSpanType sufficient_accuracy);
+ void SetupStandardUserSystemClock(Core::System& system, bool is_automatic_correction_enabled,
+ Clock::SteadyClockTimePoint steady_clock_time_point);
+ void SetupEphemeralNetworkSystemClock();
+
+ SharedMemory shared_memory;
+
+ Clock::StandardSteadyClockCore standard_steady_clock_core;
+ Clock::TickBasedSteadyClockCore tick_based_steady_clock_core;
+ Clock::StandardLocalSystemClockCore standard_local_system_clock_core;
+ Clock::StandardNetworkSystemClockCore standard_network_system_clock_core;
+ Clock::StandardUserSystemClockCore standard_user_system_clock_core;
+ Clock::EphemeralNetworkSystemClockCore ephemeral_network_system_clock_core;
+
+ std::shared_ptr<Clock::LocalSystemClockContextWriter> local_system_clock_context_writer;
+ std::shared_ptr<Clock::NetworkSystemClockContextWriter> network_system_clock_context_writer;
+ std::shared_ptr<Clock::EphemeralNetworkSystemClockContextWriter>
+ ephemeral_network_system_clock_context_writer;
+
+ TimeZone::TimeZoneContentManager time_zone_content_manager;
+};
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index 4035f5072..9b03191bf 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -3,20 +3,21 @@
// Refer to the license.txt file included.
#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/steady_clock_core.h"
#include "core/hle/service/time/time_sharedmemory.h"
namespace Service::Time {
-const std::size_t SHARED_MEMORY_SIZE = 0x1000;
+
+static constexpr std::size_t SHARED_MEMORY_SIZE{0x1000};
SharedMemory::SharedMemory(Core::System& system) : system(system) {
shared_memory_holder = Kernel::SharedMemory::Create(
system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory");
-
- // Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash
- // if it's set to anything else
- shared_memory_format.format_version = 14;
- std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format));
+ std::memset(shared_memory_holder->GetPointer(), 0, SHARED_MEMORY_SIZE);
}
SharedMemory::~SharedMemory() = default;
@@ -25,44 +26,32 @@ std::shared_ptr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() cons
return shared_memory_holder;
}
-void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) {
+void SharedMemory::SetupStandardSteadyClock(Core::System& system,
+ const Common::UUID& clock_source_id,
+ Clock::TimeSpanType current_time_point) {
+ const Clock::TimeSpanType ticks_time_span{Clock::TimeSpanType::FromTicks(
+ Core::Timing::CpuCyclesToClockCycles(system.CoreTiming().GetTicks()),
+ Core::Timing::CNTFREQ)};
+ const Clock::SteadyClockContext context{
+ static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),
+ clock_source_id};
shared_memory_format.standard_steady_clock_timepoint.StoreData(
- shared_memory_holder->GetPointer(), timepoint);
+ shared_memory_holder->GetPointer(), context);
}
-void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) {
+void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) {
shared_memory_format.standard_local_system_clock_context.StoreData(
shared_memory_holder->GetPointer(), context);
}
-void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) {
+void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) {
shared_memory_format.standard_network_system_clock_context.StoreData(
shared_memory_holder->GetPointer(), context);
}
-void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) {
+void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) {
shared_memory_format.standard_user_system_clock_automatic_correction.StoreData(
- shared_memory_holder->GetPointer(), enabled);
-}
-
-SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() {
- return shared_memory_format.standard_steady_clock_timepoint.ReadData(
- shared_memory_holder->GetPointer());
-}
-
-SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() {
- return shared_memory_format.standard_local_system_clock_context.ReadData(
- shared_memory_holder->GetPointer());
-}
-
-SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() {
- return shared_memory_format.standard_network_system_clock_context.ReadData(
- shared_memory_holder->GetPointer());
-}
-
-bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() {
- return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData(
- shared_memory_holder->GetPointer());
+ shared_memory_holder->GetPointer(), is_enabled);
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
index 904a96430..5976b2046 100644
--- a/src/core/hle/service/time/time_sharedmemory.h
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -5,11 +5,14 @@
#pragma once
#include "common/common_types.h"
+#include "common/uuid.h"
#include "core/hle/kernel/shared_memory.h"
-#include "core/hle/service/time/time.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/service/time/clock_types.h"
namespace Service::Time {
-class SharedMemory {
+
+class SharedMemory final {
public:
explicit SharedMemory(Core::System& system);
~SharedMemory();
@@ -17,22 +20,10 @@ public:
// Return the shared memory handle
std::shared_ptr<Kernel::SharedMemory> GetSharedMemoryHolder() const;
- // Set memory barriers in shared memory and update them
- void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint);
- void SetStandardLocalSystemClockContext(const SystemClockContext& context);
- void SetStandardNetworkSystemClockContext(const SystemClockContext& context);
- void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled);
-
- // Pull from memory barriers in the shared memory
- SteadyClockTimePoint GetStandardSteadyClockTimepoint();
- SystemClockContext GetStandardLocalSystemClockContext();
- SystemClockContext GetStandardNetworkSystemClockContext();
- bool GetStandardUserSystemClockAutomaticCorrectionEnabled();
-
// TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
template <typename T, std::size_t Offset>
struct MemoryBarrier {
- static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable");
+ static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
u32_le read_attempt{};
std::array<T, 2> data{};
@@ -57,16 +48,22 @@ public:
// Shared memory format
struct Format {
- MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint;
- MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context;
- MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context;
+ MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint;
+ MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context;
+ MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context;
MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
u32_le format_version;
};
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
+ void SetupStandardSteadyClock(Core::System& system, const Common::UUID& clock_source_id,
+ Clock::TimeSpanType currentTimePoint);
+ void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);
+ void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);
+ void SetAutomaticCorrectionEnabled(bool is_enabled);
+
private:
- std::shared_ptr<Kernel::SharedMemory> shared_memory_holder{};
+ std::shared_ptr<Kernel::SharedMemory> shared_memory_holder;
Core::System& system;
Format shared_memory_format{};
};
diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp
new file mode 100644
index 000000000..57b1a2bca
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_content_manager.cpp
@@ -0,0 +1,125 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <sstream>
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/system_archive.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+
+namespace Service::Time::TimeZone {
+
+constexpr u64 time_zone_binary_titleid{0x010000000000080E};
+
+static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) {
+ const auto* nand{system.GetFileSystemController().GetSystemNANDContents()};
+ const auto nca{nand->GetEntry(time_zone_binary_titleid, FileSys::ContentRecordType::Data)};
+
+ FileSys::VirtualFile romfs;
+ if (nca) {
+ romfs = nca->GetRomFS();
+ }
+
+ if (!romfs) {
+ romfs = FileSys::SystemArchive::SynthesizeSystemArchive(time_zone_binary_titleid);
+ }
+
+ if (!romfs) {
+ LOG_ERROR(Service_Time, "Failed to find or synthesize {:016X!}", time_zone_binary_titleid);
+ return {};
+ }
+
+ return FileSys::ExtractRomFS(romfs);
+}
+
+static std::vector<std::string> BuildLocationNameCache(Core::System& system) {
+ const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
+ if (!extracted_romfs) {
+ LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
+ return {};
+ }
+
+ const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")};
+ if (!binary_list) {
+ LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid);
+ return {};
+ }
+
+ std::vector<char> raw_data(binary_list->GetSize());
+ binary_list->ReadBytes<char>(raw_data.data(), binary_list->GetSize());
+
+ std::stringstream data_stream{raw_data.data()};
+ std::string name;
+ std::vector<std::string> location_name_cache;
+ while (std::getline(data_stream, name)) {
+ name.pop_back(); // Remove carriage return
+ location_name_cache.emplace_back(std::move(name));
+ }
+ return location_name_cache;
+}
+
+TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system)
+ : system{system}, location_name_cache{BuildLocationNameCache(system)} {
+ if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile("GMT", vfs_file) == RESULT_SUCCESS) {
+ const auto time_point{
+ time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)};
+ time_manager.SetupTimeZoneManager("GMT", time_point, location_name_cache.size(), {},
+ vfs_file);
+ } else {
+ time_zone_manager.MarkAsInitialized();
+ }
+}
+
+ResultCode TimeZoneContentManager::LoadTimeZoneRule(TimeZoneRule& rules,
+ const std::string& location_name) const {
+ FileSys::VirtualFile vfs_file;
+ if (const ResultCode result{GetTimeZoneInfoFile(location_name, vfs_file)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ return time_zone_manager.ParseTimeZoneRuleBinary(rules, vfs_file);
+}
+
+bool TimeZoneContentManager::IsLocationNameValid(const std::string& location_name) const {
+ return std::find(location_name_cache.begin(), location_name_cache.end(), location_name) !=
+ location_name_cache.end();
+}
+
+ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) const {
+ if (!IsLocationNameValid(location_name)) {
+ return ERROR_TIME_NOT_FOUND;
+ }
+
+ const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)};
+ if (!extracted_romfs) {
+ LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid);
+ return ERROR_TIME_NOT_FOUND;
+ }
+
+ const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")};
+ if (!zoneinfo_dir) {
+ LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid);
+ return ERROR_TIME_NOT_FOUND;
+ }
+
+ vfs_file = zoneinfo_dir->GetFile(location_name);
+ if (!vfs_file) {
+ LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"!", time_zone_binary_titleid,
+ location_name);
+ return ERROR_TIME_NOT_FOUND;
+ }
+
+ return RESULT_SUCCESS;
+}
+
+} // namespace Service::Time::TimeZone
diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h
new file mode 100644
index 000000000..4f302c3b9
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_content_manager.h
@@ -0,0 +1,46 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "core/hle/service/time/time_zone_manager.h"
+
+namespace Core {
+class System;
+}
+
+namespace Service::Time {
+class TimeManager;
+}
+
+namespace Service::Time::TimeZone {
+
+class TimeZoneContentManager final {
+public:
+ TimeZoneContentManager(TimeManager& time_manager, Core::System& system);
+
+ TimeZoneManager& GetTimeZoneManager() {
+ return time_zone_manager;
+ }
+
+ const TimeZoneManager& GetTimeZoneManager() const {
+ return time_zone_manager;
+ }
+
+ ResultCode LoadTimeZoneRule(TimeZoneRule& rules, const std::string& location_name) const;
+
+private:
+ bool IsLocationNameValid(const std::string& location_name) const;
+ ResultCode GetTimeZoneInfoFile(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) const;
+
+ Core::System& system;
+ TimeZoneManager time_zone_manager;
+ const std::vector<std::string> location_name_cache;
+};
+
+} // namespace Service::Time::TimeZone
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
new file mode 100644
index 000000000..4db6d7ad3
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -0,0 +1,1030 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <climits>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/system_archive.h"
+#include "core/hle/service/time/time_zone_manager.h"
+
+namespace Service::Time::TimeZone {
+
+static constexpr s32 epoch_year{1970};
+static constexpr s32 year_base{1900};
+static constexpr s32 epoch_week_day{4};
+static constexpr s32 seconds_per_minute{60};
+static constexpr s32 minutes_per_hour{60};
+static constexpr s32 hours_per_day{24};
+static constexpr s32 days_per_week{7};
+static constexpr s32 days_per_normal_year{365};
+static constexpr s32 days_per_leap_year{366};
+static constexpr s32 months_per_year{12};
+static constexpr s32 seconds_per_hour{seconds_per_minute * minutes_per_hour};
+static constexpr s32 seconds_per_day{seconds_per_hour * hours_per_day};
+static constexpr s32 years_per_repeat{400};
+static constexpr s64 average_seconds_per_year{31556952};
+static constexpr s64 seconds_per_repeat{years_per_repeat * average_seconds_per_year};
+
+struct Rule {
+ enum class Type : u32 { JulianDay, DayOfYear, MonthNthDayOfWeek };
+ Type rule_type{};
+ s32 day{};
+ s32 week{};
+ s32 month{};
+ s32 transition_time{};
+};
+
+struct CalendarTimeInternal {
+ s64 year{};
+ s8 month{};
+ s8 day{};
+ s8 hour{};
+ s8 minute{};
+ s8 second{};
+ int Compare(const CalendarTimeInternal& other) const {
+ if (year != other.year) {
+ if (year < other.year) {
+ return -1;
+ }
+ return 1;
+ }
+ if (month != other.month) {
+ return month - other.month;
+ }
+ if (day != other.day) {
+ return day - other.day;
+ }
+ if (hour != other.hour) {
+ return hour - other.hour;
+ }
+ if (minute != other.minute) {
+ return minute - other.minute;
+ }
+ if (second != other.second) {
+ return second - other.second;
+ }
+ return {};
+ }
+};
+
+template <typename TResult, typename TOperand>
+static bool SafeAdd(TResult& result, TOperand op) {
+ result = result + op;
+ return true;
+}
+
+template <typename TResult, typename TUnit, typename TBase>
+static bool SafeNormalize(TResult& result, TUnit& unit, TBase base) {
+ TUnit delta{};
+ if (unit >= 0) {
+ delta = unit / base;
+ } else {
+ delta = -1 - (-1 - unit) / base;
+ }
+ unit -= delta * base;
+ return SafeAdd(result, delta);
+}
+
+template <typename T>
+static constexpr bool IsLeapYear(T year) {
+ return ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0);
+}
+
+template <typename T>
+static constexpr T GetYearLengthInDays(T year) {
+ return IsLeapYear(year) ? days_per_leap_year : days_per_normal_year;
+}
+
+static constexpr s64 GetLeapDaysFromYearPositive(s64 year) {
+ return year / 4 - year / 100 + year / years_per_repeat;
+}
+
+static constexpr s64 GetLeapDaysFromYear(s64 year) {
+ if (year < 0) {
+ return -1 - GetLeapDaysFromYearPositive(-1 - year);
+ } else {
+ return GetLeapDaysFromYearPositive(year);
+ }
+}
+
+static constexpr int GetMonthLength(bool is_leap_year, int month) {
+ constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31};
+ return is_leap_year ? month_lengths_leap[month] : month_lengths[month];
+}
+
+static constexpr bool IsDigit(char value) {
+ return value >= '0' && value <= '9';
+}
+
+static constexpr int GetQZName(const char* name, int offset, char delimiter) {
+ while (name[offset] != '\0' && name[offset] != delimiter) {
+ offset++;
+ }
+ return offset;
+}
+
+static constexpr int GetTZName(const char* name, int offset) {
+ for (char value{name[offset]};
+ value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+';
+ offset++) {
+ value = name[offset];
+ }
+ return offset;
+}
+
+static constexpr bool GetInteger(const char* name, int& offset, int& value, int min, int max) {
+ value = 0;
+ char temp{name[offset]};
+ if (!IsDigit(temp)) {
+ return {};
+ }
+ do {
+ value = value * 10 + (temp - '0');
+ if (value > max) {
+ return {};
+ }
+ temp = name[offset];
+ } while (IsDigit(temp));
+
+ return value >= min;
+}
+
+static constexpr bool GetSeconds(const char* name, int& offset, int& seconds) {
+ seconds = 0;
+ int value{};
+ if (!GetInteger(name, offset, value, 0, hours_per_day * days_per_week - 1)) {
+ return {};
+ }
+ seconds = value * seconds_per_hour;
+
+ if (name[offset] == ':') {
+ offset++;
+ if (!GetInteger(name, offset, value, 0, minutes_per_hour - 1)) {
+ return {};
+ }
+ seconds += value * seconds_per_minute;
+ if (name[offset] == ':') {
+ offset++;
+ if (!GetInteger(name, offset, value, 0, seconds_per_minute)) {
+ return {};
+ }
+ seconds += value;
+ }
+ }
+ return true;
+}
+
+static constexpr bool GetOffset(const char* name, int& offset, int& value) {
+ bool is_negative{};
+ if (name[offset] == '-') {
+ is_negative = true;
+ offset++;
+ } else if (name[offset] == '+') {
+ offset++;
+ }
+ if (!GetSeconds(name, offset, value)) {
+ return {};
+ }
+ if (is_negative) {
+ value = -value;
+ }
+ return true;
+}
+
+static constexpr bool GetRule(const char* name, int& position, Rule& rule) {
+ bool is_valid{};
+ if (name[position] == 'J') {
+ position++;
+ rule.rule_type = Rule::Type::JulianDay;
+ is_valid = GetInteger(name, position, rule.day, 1, days_per_normal_year);
+ } else if (name[position] == 'M') {
+ position++;
+ rule.rule_type = Rule::Type::MonthNthDayOfWeek;
+ is_valid = GetInteger(name, position, rule.month, 1, months_per_year);
+ if (!is_valid) {
+ return {};
+ }
+ if (name[position++] != '.') {
+ return {};
+ }
+ is_valid = GetInteger(name, position, rule.week, 1, 5);
+ if (!is_valid) {
+ return {};
+ }
+ if (name[position++] != '.') {
+ return {};
+ }
+ is_valid = GetInteger(name, position, rule.day, 0, days_per_week - 1);
+ } else if (isdigit(name[position])) {
+ rule.rule_type = Rule::Type::DayOfYear;
+ is_valid = GetInteger(name, position, rule.day, 0, days_per_leap_year - 1);
+ } else {
+ return {};
+ }
+ if (!is_valid) {
+ return {};
+ }
+ if (name[position] == '/') {
+ position++;
+ return GetOffset(name, position, rule.transition_time);
+ } else {
+ rule.transition_time = 2 * seconds_per_hour;
+ }
+ return true;
+}
+
+static constexpr int TransitionTime(int year, Rule rule, int offset) {
+ int value{};
+ switch (rule.rule_type) {
+ case Rule::Type::JulianDay:
+ value = (rule.day - 1) * seconds_per_day;
+ if (IsLeapYear(year) && rule.day >= 60) {
+ value += seconds_per_day;
+ }
+ break;
+ case Rule::Type::DayOfYear:
+ value = rule.day * seconds_per_day;
+ break;
+ case Rule::Type::MonthNthDayOfWeek: {
+ // Use Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence) to
+ // calculate the day of the week for any Julian or Gregorian calendar date.
+ const int m1{(rule.month + 9) % 12 + 1};
+ const int yy0{(rule.month <= 2) ? (year - 1) : year};
+ const int yy1{yy0 / 100};
+ const int yy2{yy0 % 100};
+ int day_of_week{((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7};
+
+ if (day_of_week < 0) {
+ day_of_week += days_per_week;
+ }
+ int day{rule.day - day_of_week};
+ if (day < 0) {
+ day += days_per_week;
+ }
+ for (int i{1}; i < rule.week; i++) {
+ if (day + days_per_week >= GetMonthLength(IsLeapYear(year), rule.month - 1)) {
+ break;
+ }
+ day += days_per_week;
+ }
+
+ value = day * seconds_per_day;
+ for (int index{}; index < rule.month - 1; ++index) {
+ value += GetMonthLength(IsLeapYear(year), index) * seconds_per_day;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return value + rule.transition_time + offset;
+}
+
+static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
+ constexpr char default_rule[]{",M4.1.0,M10.5.0"};
+ const char* std_name{name};
+ int std_len{};
+ int offset{};
+ int std_offset{};
+
+ if (name[offset] == '<') {
+ offset++;
+ std_name = name + offset;
+ const int std_name_offset{offset};
+ offset = GetQZName(name, offset, '>');
+ if (name[offset] != '>') {
+ return {};
+ }
+ std_len = offset - std_name_offset;
+ offset++;
+ } else {
+ offset = GetTZName(name, offset);
+ std_len = offset;
+ }
+ if (!std_len) {
+ return {};
+ }
+ if (!GetOffset(name, offset, std_offset)) {
+ return {};
+ }
+
+ int char_count{std_len + 1};
+ int dest_len{};
+ int dest_offset{};
+ const char* dest_name{name + offset};
+ if (rule.chars.size() < char_count) {
+ return {};
+ }
+
+ if (name[offset] != '\0') {
+ if (name[offset] == '<') {
+ dest_name = name + (++offset);
+ const int dest_name_offset{offset};
+ offset = GetQZName(name, offset, '>');
+ if (name[offset] != '>') {
+ return {};
+ }
+ dest_len = offset - dest_name_offset;
+ offset++;
+ } else {
+ dest_name = name + (offset);
+ offset = GetTZName(name, offset);
+ dest_len = offset;
+ }
+ if (dest_len == 0) {
+ return {};
+ }
+ char_count += dest_len + 1;
+ if (rule.chars.size() < char_count) {
+ return {};
+ }
+ if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') {
+ if (!GetOffset(name, offset, dest_offset)) {
+ return {};
+ }
+ } else {
+ dest_offset = std_offset - seconds_per_hour;
+ }
+ if (name[offset] == '\0') {
+ name = default_rule;
+ offset = 0;
+ }
+ if (name[offset] == ',' || name[offset] == ';') {
+ offset++;
+
+ Rule start{};
+ if (!GetRule(name, offset, start)) {
+ return {};
+ }
+ if (name[offset++] != ',') {
+ return {};
+ }
+
+ Rule end{};
+ if (!GetRule(name, offset, end)) {
+ return {};
+ }
+ if (name[offset] != '\0') {
+ return {};
+ }
+
+ rule.type_count = 2;
+ rule.ttis[0].gmt_offset = -dest_offset;
+ rule.ttis[0].is_dst = true;
+ rule.ttis[0].abbreviation_list_index = std_len + 1;
+ rule.ttis[1].gmt_offset = -std_offset;
+ rule.ttis[1].is_dst = false;
+ rule.ttis[1].abbreviation_list_index = 0;
+ rule.default_type = 0;
+
+ s64 jan_first{};
+ int time_count{};
+ int jan_offset{};
+ int year_beginning{epoch_year};
+ do {
+ const int year_seconds{GetYearLengthInDays(year_beginning - 1) * seconds_per_day};
+ year_beginning--;
+ if (!SafeAdd(jan_first, -year_seconds)) {
+ jan_offset = -year_seconds;
+ break;
+ }
+ } while (epoch_year - years_per_repeat / 2 < year_beginning);
+
+ int year_limit{year_beginning + years_per_repeat + 1};
+ int year{};
+ for (year = year_beginning; year < year_limit; year++) {
+ int start_time{TransitionTime(year, start, std_offset)};
+ int end_time{TransitionTime(year, end, dest_offset)};
+ const int year_seconds{GetYearLengthInDays(year) * seconds_per_day};
+ const bool is_reversed{end_time < start_time};
+ if (is_reversed) {
+ int swap{start_time};
+ start_time = end_time;
+ end_time = swap;
+ }
+
+ if (is_reversed ||
+ (start_time < end_time &&
+ (end_time - start_time < (year_seconds + (std_offset - dest_offset))))) {
+ if (rule.ats.size() - 2 < time_count) {
+ break;
+ }
+
+ rule.ats[time_count] = jan_first;
+ if (SafeAdd(rule.ats[time_count], jan_offset + start_time)) {
+ rule.types[time_count++] = is_reversed ? 1 : 0;
+ } else if (jan_offset != 0) {
+ rule.default_type = is_reversed ? 1 : 0;
+ }
+
+ rule.ats[time_count] = jan_first;
+ if (SafeAdd(rule.ats[time_count], jan_offset + end_time)) {
+ rule.types[time_count++] = is_reversed ? 0 : 1;
+ year_limit = year + years_per_repeat + 1;
+ } else if (jan_offset != 0) {
+ rule.default_type = is_reversed ? 0 : 1;
+ }
+ }
+ if (!SafeAdd(jan_first, jan_offset + year_seconds)) {
+ break;
+ }
+ jan_offset = 0;
+ }
+ rule.time_count = time_count;
+ if (time_count == 0) {
+ rule.type_count = 1;
+ } else if (years_per_repeat < year - year_beginning) {
+ rule.go_back = true;
+ rule.go_ahead = true;
+ }
+ } else {
+ if (name[offset] == '\0') {
+ return {};
+ }
+
+ s64 their_std_offset{};
+ for (int index{}; index < rule.time_count; ++index) {
+ const s8 type{rule.types[index]};
+ if (rule.ttis[type].is_standard_time_daylight) {
+ their_std_offset = -rule.ttis[type].gmt_offset;
+ }
+ }
+
+ s64 their_offset{their_std_offset};
+ for (int index{}; index < rule.time_count; ++index) {
+ const s8 type{rule.types[index]};
+ rule.types[index] = rule.ttis[type].is_dst ? 1 : 0;
+ if (!rule.ttis[type].is_gmt) {
+ if (!rule.ttis[type].is_standard_time_daylight) {
+ rule.ats[index] += dest_offset - their_std_offset;
+ } else {
+ rule.ats[index] += std_offset - their_std_offset;
+ }
+ }
+ their_offset = -rule.ttis[type].gmt_offset;
+ if (!rule.ttis[type].is_dst) {
+ their_std_offset = their_offset;
+ }
+ }
+ rule.ttis[0].gmt_offset = -std_offset;
+ rule.ttis[0].is_dst = false;
+ rule.ttis[0].abbreviation_list_index = 0;
+ rule.ttis[1].gmt_offset = -dest_offset;
+ rule.ttis[1].is_dst = true;
+ rule.ttis[1].abbreviation_list_index = std_len + 1;
+ rule.type_count = 2;
+ rule.default_type = 0;
+ }
+ } else {
+ // Default is standard time
+ rule.type_count = 1;
+ rule.time_count = 0;
+ rule.default_type = 0;
+ rule.ttis[0].gmt_offset = -std_offset;
+ rule.ttis[0].is_dst = false;
+ rule.ttis[0].abbreviation_list_index = 0;
+ }
+
+ rule.char_count = char_count;
+ for (int index{}; index < std_len; ++index) {
+ rule.chars[index] = std_name[index];
+ }
+
+ rule.chars[std_len++] = '\0';
+ if (dest_len != 0) {
+ for (int index{}; index < dest_len; ++index) {
+ rule.chars[std_len + index] = dest_name[index];
+ }
+ rule.chars[std_len + dest_len] = '\0';
+ }
+
+ return true;
+}
+
+static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFile& vfs_file) {
+ TzifHeader header{};
+ if (vfs_file->ReadObject<TzifHeader>(&header) != sizeof(TzifHeader)) {
+ return {};
+ }
+
+ constexpr s32 time_zone_max_leaps{50};
+ constexpr s32 time_zone_max_chars{50};
+ if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps &&
+ 0 < header.type_count && header.type_count < time_zone_rule.ttis.size() &&
+ 0 <= header.time_count && header.time_count < time_zone_rule.ats.size() &&
+ 0 <= header.char_count && header.char_count < time_zone_max_chars &&
+ (header.ttis_std_count == header.type_count || header.ttis_std_count == 0) &&
+ (header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) {
+ return {};
+ }
+ time_zone_rule.time_count = header.time_count;
+ time_zone_rule.type_count = header.type_count;
+ time_zone_rule.char_count = header.char_count;
+
+ int time_count{};
+ u64 read_offset = sizeof(TzifHeader);
+ for (int index{}; index < time_zone_rule.time_count; ++index) {
+ s64_be at{};
+ vfs_file->ReadObject<s64_be>(&at, read_offset);
+ time_zone_rule.types[index] = 1;
+ if (time_count != 0 && at <= time_zone_rule.ats[time_count - 1]) {
+ if (at < time_zone_rule.ats[time_count - 1]) {
+ return {};
+ }
+ time_zone_rule.types[index - 1] = 0;
+ time_count--;
+ }
+ time_zone_rule.ats[time_count++] = at;
+ read_offset += sizeof(s64_be);
+ }
+ time_count = 0;
+ for (int index{}; index < time_zone_rule.time_count; ++index) {
+ const u8 type{*vfs_file->ReadByte(read_offset)};
+ read_offset += sizeof(u8);
+ if (time_zone_rule.time_count <= type) {
+ return {};
+ }
+ if (time_zone_rule.types[index] != 0) {
+ time_zone_rule.types[time_count++] = type;
+ }
+ }
+ time_zone_rule.time_count = time_count;
+ for (int index{}; index < time_zone_rule.type_count; ++index) {
+ TimeTypeInfo& ttis{time_zone_rule.ttis[index]};
+ u32_be gmt_offset{};
+ vfs_file->ReadObject<u32_be>(&gmt_offset, read_offset);
+ read_offset += sizeof(u32_be);
+ ttis.gmt_offset = gmt_offset;
+
+ const u8 dst{*vfs_file->ReadByte(read_offset)};
+ read_offset += sizeof(u8);
+ if (dst >= 2) {
+ return {};
+ }
+ ttis.is_dst = dst != 0;
+
+ const s32 abbreviation_list_index{*vfs_file->ReadByte(read_offset)};
+ read_offset += sizeof(u8);
+ if (abbreviation_list_index >= time_zone_rule.char_count) {
+ return {};
+ }
+ ttis.abbreviation_list_index = abbreviation_list_index;
+ }
+
+ vfs_file->ReadArray(time_zone_rule.chars.data(), time_zone_rule.char_count, read_offset);
+ time_zone_rule.chars[time_zone_rule.char_count] = '\0';
+ read_offset += time_zone_rule.char_count;
+ for (int index{}; index < time_zone_rule.type_count; ++index) {
+ if (header.ttis_std_count == 0) {
+ time_zone_rule.ttis[index].is_standard_time_daylight = false;
+ } else {
+ const u8 dst{*vfs_file->ReadByte(read_offset)};
+ read_offset += sizeof(u8);
+ if (dst >= 2) {
+ return {};
+ }
+ time_zone_rule.ttis[index].is_standard_time_daylight = dst != 0;
+ }
+ }
+
+ for (int index{}; index < time_zone_rule.type_count; ++index) {
+ if (header.ttis_std_count == 0) {
+ time_zone_rule.ttis[index].is_gmt = false;
+ } else {
+ const u8 dst{*vfs_file->ReadByte(read_offset)};
+ read_offset += sizeof(u8);
+ if (dst >= 2) {
+ return {};
+ }
+ time_zone_rule.ttis[index].is_gmt = dst != 0;
+ }
+ }
+
+ const u64 position{(read_offset - sizeof(TzifHeader))};
+ const std::size_t bytes_read{vfs_file->GetSize() - sizeof(TzifHeader) - position};
+ if (bytes_read < 0) {
+ return {};
+ }
+ constexpr s32 time_zone_name_max{255};
+ if (bytes_read > (time_zone_name_max + 1)) {
+ return {};
+ }
+
+ std::array<char, time_zone_name_max + 1> temp_name{};
+ vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset);
+ if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' &&
+ time_zone_rule.type_count + 2 <= time_zone_rule.ttis.size()) {
+ temp_name[bytes_read - 1] = '\0';
+
+ std::array<char, time_zone_name_max> name{};
+ std::memcpy(name.data(), temp_name.data() + 1, bytes_read - 1);
+
+ TimeZoneRule temp_rule;
+ if (ParsePosixName(name.data(), temp_rule)) {
+ UNIMPLEMENTED();
+ }
+ }
+ if (time_zone_rule.type_count == 0) {
+ return {};
+ }
+ if (time_zone_rule.time_count > 1) {
+ UNIMPLEMENTED();
+ }
+
+ s32 default_type{};
+
+ for (default_type = 0; default_type < time_zone_rule.time_count; default_type++) {
+ if (time_zone_rule.types[default_type] == 0) {
+ break;
+ }
+ }
+
+ default_type = default_type < time_zone_rule.time_count ? -1 : 0;
+ if (default_type < 0 && time_zone_rule.time_count > 0 &&
+ time_zone_rule.ttis[time_zone_rule.types[0]].is_dst) {
+ default_type = time_zone_rule.types[0];
+ while (--default_type >= 0) {
+ if (!time_zone_rule.ttis[default_type].is_dst) {
+ break;
+ }
+ }
+ }
+ if (default_type < 0) {
+ default_type = 0;
+ while (time_zone_rule.ttis[default_type].is_dst) {
+ if (++default_type >= time_zone_rule.type_count) {
+ default_type = 0;
+ break;
+ }
+ }
+ }
+ time_zone_rule.default_type = default_type;
+ return true;
+}
+
+static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time,
+ CalendarAdditionalInfo& calendar_additional_info) {
+ s64 year{epoch_year};
+ s64 time_days{time / seconds_per_day};
+ s64 remaining_seconds{time % seconds_per_day};
+ while (time_days < 0 || time_days >= GetYearLengthInDays(year)) {
+ s64 delta = time_days / days_per_leap_year;
+ if (!delta) {
+ delta = time_days < 0 ? -1 : 1;
+ }
+ s64 new_year{year};
+ if (!SafeAdd(new_year, delta)) {
+ return ERROR_OUT_OF_RANGE;
+ }
+ time_days -= (new_year - year) * days_per_normal_year;
+ time_days -= GetLeapDaysFromYear(new_year - 1) - GetLeapDaysFromYear(year - 1);
+ year = new_year;
+ }
+
+ s64 day_of_year{time_days};
+ remaining_seconds += gmt_offset;
+ while (remaining_seconds < 0) {
+ remaining_seconds += seconds_per_day;
+ day_of_year--;
+ }
+
+ while (remaining_seconds >= seconds_per_day) {
+ remaining_seconds -= seconds_per_day;
+ day_of_year++;
+ }
+
+ while (day_of_year < 0) {
+ if (!SafeAdd(year, -1)) {
+ return ERROR_OUT_OF_RANGE;
+ }
+ day_of_year += GetYearLengthInDays(year);
+ }
+
+ while (day_of_year >= GetYearLengthInDays(year)) {
+ day_of_year -= GetYearLengthInDays(year);
+ if (!SafeAdd(year, 1)) {
+ return ERROR_OUT_OF_RANGE;
+ }
+ }
+
+ calendar_time.year = year;
+ calendar_additional_info.day_of_year = static_cast<u32>(day_of_year);
+ s64 day_of_week{
+ (epoch_week_day +
+ ((year - epoch_year) % days_per_week) * (days_per_normal_year % days_per_week) +
+ GetLeapDaysFromYear(year - 1) - GetLeapDaysFromYear(epoch_year - 1) + day_of_year) %
+ days_per_week};
+ if (day_of_week < 0) {
+ day_of_week += days_per_week;
+ }
+
+ calendar_additional_info.day_of_week = static_cast<u32>(day_of_week);
+ calendar_time.hour = static_cast<s8>((remaining_seconds / seconds_per_hour) % seconds_per_hour);
+ remaining_seconds %= seconds_per_hour;
+ calendar_time.minute = static_cast<s8>(remaining_seconds / seconds_per_minute);
+ calendar_time.second = static_cast<s8>(remaining_seconds % seconds_per_minute);
+
+ for (calendar_time.month = 0;
+ day_of_year >= GetMonthLength(IsLeapYear(year), calendar_time.month);
+ ++calendar_time.month) {
+ day_of_year -= GetMonthLength(IsLeapYear(year), calendar_time.month);
+ }
+
+ calendar_time.day = static_cast<s8>(day_of_year + 1);
+ calendar_additional_info.is_dst = false;
+ calendar_additional_info.gmt_offset = gmt_offset;
+
+ return RESULT_SUCCESS;
+}
+
+static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
+ CalendarTimeInternal& calendar_time,
+ CalendarAdditionalInfo& calendar_additional_info) {
+ if ((rules.go_ahead && time < rules.ats[0]) ||
+ (rules.go_back && time > rules.ats[rules.time_count - 1])) {
+ s64 seconds{};
+ if (time < rules.ats[0]) {
+ seconds = rules.ats[0] - time;
+ } else {
+ seconds = time - rules.ats[rules.time_count - 1];
+ }
+ seconds--;
+
+ const s64 years{(seconds / seconds_per_repeat + 1) * years_per_repeat};
+ seconds = years * average_seconds_per_year;
+
+ s64 new_time{time};
+ if (time < rules.ats[0]) {
+ new_time += seconds;
+ } else {
+ new_time -= seconds;
+ }
+ if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) {
+ return ERROR_TIME_NOT_FOUND;
+ }
+ if (const ResultCode result{
+ ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+ if (time < rules.ats[0]) {
+ calendar_time.year -= years;
+ } else {
+ calendar_time.year += years;
+ }
+
+ return RESULT_SUCCESS;
+ }
+
+ s32 tti_index{};
+ if (rules.time_count == 0 || time < rules.ats[0]) {
+ tti_index = rules.default_type;
+ } else {
+ s32 low{1};
+ s32 high{rules.time_count};
+ while (low < high) {
+ s32 mid{(low + high) >> 1};
+ if (time < rules.ats[mid]) {
+ high = mid;
+ } else {
+ low = mid + 1;
+ }
+ }
+ tti_index = rules.types[low - 1];
+ }
+
+ if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset,
+ calendar_time, calendar_additional_info)};
+ result != RESULT_SUCCESS) {
+ return result;
+ }
+
+ calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst;
+ const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]};
+ for (int index{}; time_zone[index] != '\0'; ++index) {
+ calendar_additional_info.timezone_name[index] = time_zone[index];
+ }
+ return RESULT_SUCCESS;
+}
+
+static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) {
+ CalendarTimeInternal calendar_time{};
+ const ResultCode result{
+ ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)};
+ calendar.time.year = static_cast<s16>(calendar_time.year);
+ calendar.time.month = calendar_time.month;
+ calendar.time.day = calendar_time.day;
+ calendar.time.hour = calendar_time.hour;
+ calendar.time.minute = calendar_time.minute;
+ calendar.time.second = calendar_time.second;
+ return result;
+}
+
+TimeZoneManager::TimeZoneManager() = default;
+TimeZoneManager::~TimeZoneManager() = default;
+
+ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time,
+ CalendarInfo& calendar) const {
+ return ToCalendarTimeImpl(rules, time, calendar);
+}
+
+ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file) {
+ TimeZoneRule rule{};
+ if (ParseTimeZoneBinary(rule, vfs_file)) {
+ device_location_name = location_name;
+ time_zone_rule = rule;
+ return RESULT_SUCCESS;
+ }
+ return ERROR_TIME_ZONE_CONVERSION_FAILED;
+}
+
+ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) {
+ time_zone_update_time_point = value;
+ return RESULT_SUCCESS;
+}
+
+ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const {
+ if (is_initialized) {
+ return ToCalendarTime(time_zone_rule, time, calendar);
+ } else {
+ return ERROR_UNINITIALIZED_CLOCK;
+ }
+}
+
+ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules,
+ FileSys::VirtualFile& vfs_file) const {
+ if (!ParseTimeZoneBinary(rules, vfs_file)) {
+ return ERROR_TIME_ZONE_CONVERSION_FAILED;
+ }
+ return RESULT_SUCCESS;
+}
+
+ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
+ const CalendarTime& calendar_time, s64& posix_time) const {
+ posix_time = 0;
+
+ CalendarTimeInternal internal_time{};
+ internal_time.year = calendar_time.year;
+ internal_time.month = calendar_time.month;
+ internal_time.day = calendar_time.day;
+ internal_time.hour = calendar_time.hour;
+ internal_time.minute = calendar_time.minute;
+ internal_time.second = calendar_time.second;
+
+ s32 hour{internal_time.hour};
+ s32 minute{internal_time.minute};
+ if (!SafeNormalize(hour, minute, minutes_per_hour)) {
+ return ERROR_OVERFLOW;
+ }
+ internal_time.minute = static_cast<s8>(minute);
+
+ s32 day{internal_time.day};
+ if (!SafeNormalize(day, hour, hours_per_day)) {
+ return ERROR_OVERFLOW;
+ }
+ internal_time.day = static_cast<s8>(day);
+ internal_time.hour = static_cast<s8>(hour);
+
+ s64 year{internal_time.year};
+ s64 month{internal_time.month};
+ if (!SafeNormalize(year, month, months_per_year)) {
+ return ERROR_OVERFLOW;
+ }
+ internal_time.month = static_cast<s8>(month);
+
+ if (!SafeAdd(year, year_base)) {
+ return ERROR_OVERFLOW;
+ }
+
+ while (day <= 0) {
+ if (!SafeAdd(year, -1)) {
+ return ERROR_OVERFLOW;
+ }
+ s64 temp_year{year};
+ if (1 < internal_time.month) {
+ ++temp_year;
+ }
+ day += static_cast<s32>(GetYearLengthInDays(temp_year));
+ }
+
+ while (day > days_per_leap_year) {
+ s64 temp_year{year};
+ if (1 < internal_time.month) {
+ temp_year++;
+ }
+ day -= static_cast<s32>(GetYearLengthInDays(temp_year));
+ if (!SafeAdd(year, 1)) {
+ return ERROR_OVERFLOW;
+ }
+ }
+
+ while (true) {
+ const s32 month_length{GetMonthLength(IsLeapYear(year), internal_time.month)};
+ if (day <= month_length) {
+ break;
+ }
+ day -= month_length;
+ internal_time.month++;
+ if (internal_time.month >= months_per_year) {
+ internal_time.month = 0;
+ if (!SafeAdd(year, 1)) {
+ return ERROR_OVERFLOW;
+ }
+ }
+ }
+ internal_time.day = static_cast<s8>(day);
+
+ if (!SafeAdd(year, -year_base)) {
+ return ERROR_OVERFLOW;
+ }
+ internal_time.year = year;
+
+ s32 saved_seconds{};
+ if (internal_time.second >= 0 && internal_time.second < seconds_per_minute) {
+ saved_seconds = 0;
+ } else if (year + year_base < epoch_year) {
+ s32 second{internal_time.second};
+ if (!SafeAdd(second, 1 - seconds_per_minute)) {
+ return ERROR_OVERFLOW;
+ }
+ saved_seconds = second;
+ internal_time.second = 1 - seconds_per_minute;
+ } else {
+ saved_seconds = internal_time.second;
+ internal_time.second = 0;
+ }
+
+ s64 low{LLONG_MIN};
+ s64 high{LLONG_MAX};
+ while (true) {
+ s64 pivot{low / 2 + high / 2};
+ if (pivot < low) {
+ pivot = low;
+ } else if (pivot > high) {
+ pivot = high;
+ }
+ s32 direction{};
+ CalendarTimeInternal candidate_calendar_time{};
+ CalendarAdditionalInfo unused{};
+ if (ToCalendarTimeInternal(rules, pivot, candidate_calendar_time, unused) !=
+ RESULT_SUCCESS) {
+ if (pivot > 0) {
+ direction = 1;
+ } else {
+ direction = -1;
+ }
+ } else {
+ direction = candidate_calendar_time.Compare(internal_time);
+ }
+ if (!direction) {
+ const s64 time_result{pivot + saved_seconds};
+ if ((time_result < pivot) != (saved_seconds < 0)) {
+ return ERROR_OVERFLOW;
+ }
+ posix_time = time_result;
+ break;
+ } else {
+ if (pivot == low) {
+ if (pivot == LLONG_MAX) {
+ return ERROR_TIME_NOT_FOUND;
+ }
+ pivot++;
+ low++;
+ } else if (pivot == high) {
+ if (pivot == LLONG_MIN) {
+ return ERROR_TIME_NOT_FOUND;
+ }
+ pivot--;
+ high--;
+ }
+ if (low > high) {
+ return ERROR_TIME_NOT_FOUND;
+ }
+ if (direction > 0) {
+ high = pivot;
+ } else {
+ low = pivot;
+ }
+ }
+ }
+ return RESULT_SUCCESS;
+}
+
+ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
+ if (!is_initialized) {
+ return ERROR_UNINITIALIZED_CLOCK;
+ }
+ std::memcpy(value.data(), device_location_name.c_str(), device_location_name.size());
+ return RESULT_SUCCESS;
+}
+
+} // namespace Service::Time::TimeZone
diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h
new file mode 100644
index 000000000..7c6f975ae
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_manager.h
@@ -0,0 +1,53 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/hle/service/time/clock_types.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace Service::Time::TimeZone {
+
+class TimeZoneManager final {
+public:
+ TimeZoneManager();
+ ~TimeZoneManager();
+
+ void SetTotalLocationNameCount(std::size_t value) {
+ total_location_name_count = value;
+ }
+
+ void SetTimeZoneRuleVersion(const u128& value) {
+ time_zone_rule_version = value;
+ }
+
+ void MarkAsInitialized() {
+ is_initialized = true;
+ }
+
+ ResultCode SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
+ FileSys::VirtualFile& vfs_file);
+ ResultCode SetUpdatedTime(const Clock::SteadyClockTimePoint& value);
+ ResultCode GetDeviceLocationName(TimeZone::LocationName& value) const;
+ ResultCode ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const;
+ ResultCode ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const;
+ ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;
+ ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
+ s64& posix_time) const;
+
+private:
+ bool is_initialized{};
+ TimeZoneRule time_zone_rule{};
+ std::string device_location_name{"GMT"};
+ u128 time_zone_rule_version{};
+ std::size_t total_location_name_count{};
+ Clock::SteadyClockTimePoint time_zone_update_time_point{
+ Clock::SteadyClockTimePoint::GetRandom()};
+};
+
+} // namespace Service::Time::TimeZone
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
new file mode 100644
index 000000000..1566e778e
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -0,0 +1,148 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/hle/service/time/time_zone_service.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace Service::Time {
+
+ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_content_manager)
+ : ServiceFramework("ITimeZoneService"), time_zone_content_manager{time_zone_content_manager} {
+ static const FunctionInfo functions[] = {
+ {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"},
+ {1, nullptr, "SetDeviceLocationName"},
+ {2, nullptr, "GetTotalLocationNameCount"},
+ {3, nullptr, "LoadLocationNameList"},
+ {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"},
+ {5, nullptr, "GetTimeZoneRuleVersion"},
+ {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
+ {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
+ {201, &ITimeZoneService::ToPosixTime, "ToPosixTime"},
+ {202, nullptr, "ToPosixTimeWithMyRule"},
+ };
+ RegisterHandlers(functions);
+}
+
+void ITimeZoneService::GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+
+ TimeZone::LocationName location_name{};
+ if (const ResultCode result{
+ time_zone_content_manager.GetTimeZoneManager().GetDeviceLocationName(location_name)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, (sizeof(location_name) / 4) + 2};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(location_name);
+}
+
+void ITimeZoneService::LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto raw_location_name{rp.PopRaw<std::array<u8, 0x24>>()};
+
+ std::string location_name;
+ for (const auto& byte : raw_location_name) {
+ // Strip extra bytes
+ if (byte == '\0') {
+ break;
+ }
+ location_name.push_back(byte);
+ }
+
+ LOG_DEBUG(Service_Time, "called, location_name={}", location_name);
+
+ TimeZone::TimeZoneRule time_zone_rule{};
+ if (const ResultCode result{
+ time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule));
+ std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule));
+ ctx.WriteBuffer(time_zone_rule_outbuffer);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void ITimeZoneService::ToCalendarTime(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto posix_time{rp.Pop<s64>()};
+
+ LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time);
+
+ TimeZone::TimeZoneRule time_zone_rule{};
+ const auto buffer{ctx.ReadBuffer()};
+ std::memcpy(&time_zone_rule, buffer.data(), buffer.size());
+
+ TimeZone::CalendarInfo calendar_info{};
+ if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToCalendarTime(
+ time_zone_rule, posix_time, calendar_info)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(calendar_info);
+}
+
+void ITimeZoneService::ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto posix_time{rp.Pop<s64>()};
+
+ LOG_DEBUG(Service_Time, "called, posix_time=0x{:016X}", posix_time);
+
+ TimeZone::CalendarInfo calendar_info{};
+ if (const ResultCode result{
+ time_zone_content_manager.GetTimeZoneManager().ToCalendarTimeWithMyRules(
+ posix_time, calendar_info)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2 + (sizeof(TimeZone::CalendarInfo) / 4)};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw(calendar_info);
+}
+
+void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_Time, "called");
+
+ IPC::RequestParser rp{ctx};
+ const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()};
+ TimeZone::TimeZoneRule time_zone_rule{};
+ std::memcpy(&time_zone_rule, ctx.ReadBuffer().data(), sizeof(TimeZone::TimeZoneRule));
+
+ s64 posix_time{};
+ if (const ResultCode result{time_zone_content_manager.GetTimeZoneManager().ToPosixTime(
+ time_zone_rule, calendar_time, posix_time)};
+ result != RESULT_SUCCESS) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ // TODO(bunnei): Handle multiple times
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u32>(1); // Number of times we're returning
+ ctx.WriteBuffer(&posix_time, sizeof(s64));
+}
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h
new file mode 100644
index 000000000..a92b4312b
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_service.h
@@ -0,0 +1,30 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::Time {
+
+namespace TimeZone {
+class TimeZoneContentManager;
+}
+
+class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
+public:
+ explicit ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_manager);
+
+private:
+ void GetDeviceLocationName(Kernel::HLERequestContext& ctx);
+ void LoadTimeZoneRule(Kernel::HLERequestContext& ctx);
+ void ToCalendarTime(Kernel::HLERequestContext& ctx);
+ void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx);
+ void ToPosixTime(Kernel::HLERequestContext& ctx);
+
+private:
+ TimeZone::TimeZoneContentManager& time_zone_content_manager;
+};
+
+} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_zone_types.h b/src/core/hle/service/time/time_zone_types.h
new file mode 100644
index 000000000..9be15b53e
--- /dev/null
+++ b/src/core/hle/service/time/time_zone_types.h
@@ -0,0 +1,87 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Service::Time::TimeZone {
+
+using LocationName = std::array<char, 0x24>;
+
+/// https://switchbrew.org/wiki/Glue_services#ttinfo
+struct TimeTypeInfo {
+ s32 gmt_offset{};
+ u8 is_dst{};
+ INSERT_PADDING_BYTES(3);
+ s32 abbreviation_list_index{};
+ u8 is_standard_time_daylight{};
+ u8 is_gmt{};
+ INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(TimeTypeInfo) == 0x10, "TimeTypeInfo is incorrect size");
+
+/// https://switchbrew.org/wiki/Glue_services#TimeZoneRule
+struct TimeZoneRule {
+ s32 time_count{};
+ s32 type_count{};
+ s32 char_count{};
+ u8 go_back{};
+ u8 go_ahead{};
+ INSERT_PADDING_BYTES(2);
+ std::array<s64, 1000> ats{};
+ std::array<s8, 1000> types{};
+ std::array<TimeTypeInfo, 128> ttis{};
+ std::array<char, 512> chars{};
+ s32 default_type{};
+ INSERT_PADDING_BYTES(0x12C4);
+};
+static_assert(sizeof(TimeZoneRule) == 0x4000, "TimeZoneRule is incorrect size");
+
+/// https://switchbrew.org/wiki/Glue_services#CalendarAdditionalInfo
+struct CalendarAdditionalInfo {
+ u32 day_of_week{};
+ u32 day_of_year{};
+ std::array<char, 8> timezone_name;
+ u32 is_dst{};
+ s32 gmt_offset{};
+};
+static_assert(sizeof(CalendarAdditionalInfo) == 0x18, "CalendarAdditionalInfo is incorrect size");
+
+/// https://switchbrew.org/wiki/Glue_services#CalendarTime
+struct CalendarTime {
+ s16 year{};
+ s8 month{};
+ s8 day{};
+ s8 hour{};
+ s8 minute{};
+ s8 second{};
+ INSERT_PADDING_BYTES(1);
+};
+static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime is incorrect size");
+
+struct CalendarInfo {
+ CalendarTime time{};
+ CalendarAdditionalInfo additiona_info{};
+};
+static_assert(sizeof(CalendarInfo) == 0x20, "CalendarInfo is incorrect size");
+
+struct TzifHeader {
+ u32_be magic{};
+ u8 version{};
+ INSERT_PADDING_BYTES(15);
+ s32_be ttis_gmt_count{};
+ s32_be ttis_std_count{};
+ s32_be leap_count{};
+ s32_be time_count{};
+ s32_be type_count{};
+ s32_be char_count{};
+};
+static_assert(sizeof(TzifHeader) == 0x2C, "TzifHeader is incorrect size");
+
+} // namespace Service::Time::TimeZone