summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/common/telemetry.cpp15
-rw-r--r--src/common/x64/cpu_detect.cpp68
-rw-r--r--src/common/x64/cpu_detect.h31
-rw-r--r--src/core/CMakeLists.txt30
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.cpp657
-rw-r--r--src/core/file_sys/system_archive/time_zone_binary.h14
-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
-rw-r--r--src/core/loader/elf.cpp3
-rw-r--r--src/core/loader/kip.cpp5
-rw-r--r--src/core/loader/nso.cpp12
-rw-r--r--src/core/memory.cpp11
-rw-r--r--src/core/memory.h16
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/maxwell_3d.h10
-rw-r--r--src/video_core/engines/shader_bytecode.h37
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp44
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_state.h3
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp16
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h72
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp1141
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h252
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp7
-rw-r--r--src/video_core/shader/decode/memory.cpp19
-rw-r--r--src/video_core/shader/node.h2
-rw-r--r--src/yuzu/configuration/configure_gamelist.cpp53
-rw-r--r--src/yuzu/configuration/configure_gamelist.h3
-rw-r--r--src/yuzu/game_list_p.h11
-rw-r--r--src/yuzu/main.cpp16
-rw-r--r--src/yuzu/main.ui3
72 files changed, 5355 insertions, 740 deletions
diff --git a/src/common/telemetry.cpp b/src/common/telemetry.cpp
index f53a8d193..200c6489a 100644
--- a/src/common/telemetry.cpp
+++ b/src/common/telemetry.cpp
@@ -44,20 +44,6 @@ template class Field<std::string>;
template class Field<const char*>;
template class Field<std::chrono::microseconds>;
-#ifdef ARCHITECTURE_x86_64
-static const char* CpuVendorToStr(Common::CPUVendor vendor) {
- switch (vendor) {
- case Common::CPUVendor::INTEL:
- return "Intel";
- case Common::CPUVendor::AMD:
- return "Amd";
- case Common::CPUVendor::OTHER:
- return "Other";
- }
- UNREACHABLE();
-}
-#endif
-
void AppendBuildInfo(FieldCollection& fc) {
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty);
@@ -71,7 +57,6 @@ void AppendCPUInfo(FieldCollection& fc) {
#ifdef ARCHITECTURE_x86_64
fc.AddField(FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
fc.AddField(FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string);
- fc.AddField(FieldType::UserSystem, "CPU_Vendor", CpuVendorToStr(Common::GetCPUCaps().vendor));
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
diff --git a/src/common/x64/cpu_detect.cpp b/src/common/x64/cpu_detect.cpp
index 2dfcd39c8..c9349a6b4 100644
--- a/src/common/x64/cpu_detect.cpp
+++ b/src/common/x64/cpu_detect.cpp
@@ -3,8 +3,6 @@
// Refer to the license.txt file included.
#include <cstring>
-#include <string>
-#include <thread>
#include "common/common_types.h"
#include "common/x64/cpu_detect.h"
@@ -51,8 +49,6 @@ namespace Common {
static CPUCaps Detect() {
CPUCaps caps = {};
- caps.num_cores = std::thread::hardware_concurrency();
-
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
// yuzu at all anyway
@@ -70,12 +66,6 @@ static CPUCaps Detect() {
__cpuid(cpu_id, 0x80000000);
u32 max_ex_fn = cpu_id[0];
- if (!strcmp(caps.brand_string, "GenuineIntel"))
- caps.vendor = CPUVendor::INTEL;
- else if (!strcmp(caps.brand_string, "AuthenticAMD"))
- caps.vendor = CPUVendor::AMD;
- else
- caps.vendor = CPUVendor::OTHER;
// Set reasonable default brand string even if brand string not available
strcpy(caps.cpu_string, caps.brand_string);
@@ -96,15 +86,9 @@ static CPUCaps Detect() {
caps.sse4_1 = true;
if ((cpu_id[2] >> 20) & 1)
caps.sse4_2 = true;
- if ((cpu_id[2] >> 22) & 1)
- caps.movbe = true;
if ((cpu_id[2] >> 25) & 1)
caps.aes = true;
- if ((cpu_id[3] >> 24) & 1) {
- caps.fxsave_fxrstor = true;
- }
-
// AVX support requires 3 separate checks:
// - Is the AVX bit set in CPUID?
// - Is the XSAVE bit set in CPUID?
@@ -129,8 +113,6 @@ static CPUCaps Detect() {
}
}
- caps.flush_to_zero = caps.sse;
-
if (max_ex_fn >= 0x80000004) {
// Extract CPU model string
__cpuid(cpu_id, 0x80000002);
@@ -144,14 +126,8 @@ static CPUCaps Detect() {
if (max_ex_fn >= 0x80000001) {
// Check for more features
__cpuid(cpu_id, 0x80000001);
- if (cpu_id[2] & 1)
- caps.lahf_sahf_64 = true;
- if ((cpu_id[2] >> 5) & 1)
- caps.lzcnt = true;
if ((cpu_id[2] >> 16) & 1)
caps.fma4 = true;
- if ((cpu_id[3] >> 29) & 1)
- caps.long_mode = true;
}
return caps;
@@ -162,48 +138,4 @@ const CPUCaps& GetCPUCaps() {
return caps;
}
-std::string GetCPUCapsString() {
- auto caps = GetCPUCaps();
-
- std::string sum(caps.cpu_string);
- sum += " (";
- sum += caps.brand_string;
- sum += ")";
-
- if (caps.sse)
- sum += ", SSE";
- if (caps.sse2) {
- sum += ", SSE2";
- if (!caps.flush_to_zero)
- sum += " (without DAZ)";
- }
-
- if (caps.sse3)
- sum += ", SSE3";
- if (caps.ssse3)
- sum += ", SSSE3";
- if (caps.sse4_1)
- sum += ", SSE4.1";
- if (caps.sse4_2)
- sum += ", SSE4.2";
- if (caps.avx)
- sum += ", AVX";
- if (caps.avx2)
- sum += ", AVX2";
- if (caps.bmi1)
- sum += ", BMI1";
- if (caps.bmi2)
- sum += ", BMI2";
- if (caps.fma)
- sum += ", FMA";
- if (caps.aes)
- sum += ", AES";
- if (caps.movbe)
- sum += ", MOVBE";
- if (caps.long_mode)
- sum += ", 64-bit support";
-
- return sum;
-}
-
} // namespace Common
diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h
index 0af3a8adb..20f2ba234 100644
--- a/src/common/x64/cpu_detect.h
+++ b/src/common/x64/cpu_detect.h
@@ -4,23 +4,12 @@
#pragma once
-#include <string>
-
namespace Common {
-/// x86/x64 CPU vendors that may be detected by this module
-enum class CPUVendor {
- INTEL,
- AMD,
- OTHER,
-};
-
/// x86/x64 CPU capabilities that may be detected by this module
struct CPUCaps {
- CPUVendor vendor;
char cpu_string[0x21];
char brand_string[0x41];
- int num_cores;
bool sse;
bool sse2;
bool sse3;
@@ -35,20 +24,6 @@ struct CPUCaps {
bool fma;
bool fma4;
bool aes;
-
- // Support for the FXSAVE and FXRSTOR instructions
- bool fxsave_fxrstor;
-
- bool movbe;
-
- // This flag indicates that the hardware supports some mode in which denormal inputs and outputs
- // are automatically set to (signed) zero.
- bool flush_to_zero;
-
- // Support for LAHF and SAHF instructions in 64-bit mode
- bool lahf_sahf_64;
-
- bool long_mode;
};
/**
@@ -57,10 +32,4 @@ struct CPUCaps {
*/
const CPUCaps& GetCPUCaps();
-/**
- * Gets a string summary of the name and supported capabilities of the host CPU
- * @return String summary
- */
-std::string GetCPUCapsString();
-
} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7fd226050..1a3647a67 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -96,6 +96,8 @@ add_library(core STATIC
file_sys/system_archive/system_archive.h
file_sys/system_archive/system_version.cpp
file_sys/system_archive/system_version.h
+ file_sys/system_archive/time_zone_binary.cpp
+ file_sys/system_archive/time_zone_binary.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
@@ -461,12 +463,40 @@ add_library(core STATIC
hle/service/spl/spl.h
hle/service/ssl/ssl.cpp
hle/service/ssl/ssl.h
+ hle/service/time/clock_types.h
+ hle/service/time/ephemeral_network_system_clock_context_writer.h
+ hle/service/time/ephemeral_network_system_clock_core.h
+ hle/service/time/errors.h
hle/service/time/interface.cpp
hle/service/time/interface.h
+ hle/service/time/local_system_clock_context_writer.h
+ hle/service/time/network_system_clock_context_writer.h
+ hle/service/time/standard_local_system_clock_core.h
+ hle/service/time/standard_network_system_clock_core.h
+ hle/service/time/standard_steady_clock_core.cpp
+ hle/service/time/standard_steady_clock_core.h
+ hle/service/time/standard_user_system_clock_core.cpp
+ hle/service/time/standard_user_system_clock_core.h
+ hle/service/time/steady_clock_core.h
+ hle/service/time/system_clock_context_update_callback.cpp
+ hle/service/time/system_clock_context_update_callback.h
+ hle/service/time/system_clock_core.cpp
+ hle/service/time/system_clock_core.h
+ hle/service/time/tick_based_steady_clock_core.cpp
+ hle/service/time/tick_based_steady_clock_core.h
hle/service/time/time.cpp
hle/service/time/time.h
+ hle/service/time/time_manager.cpp
+ hle/service/time/time_manager.h
hle/service/time/time_sharedmemory.cpp
hle/service/time/time_sharedmemory.h
+ hle/service/time/time_zone_content_manager.cpp
+ hle/service/time/time_zone_content_manager.h
+ hle/service/time/time_zone_manager.cpp
+ hle/service/time/time_zone_manager.h
+ hle/service/time/time_zone_service.cpp
+ hle/service/time/time_zone_service.h
+ hle/service/time/time_zone_types.h
hle/service/usb/usb.cpp
hle/service/usb/usb.h
hle/service/vi/display/vi_display.cpp
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index e93d100a5..a6696024e 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -9,6 +9,7 @@
#include "core/file_sys/system_archive/shared_font.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/system_archive/system_version.h"
+#include "core/file_sys/system_archive/time_zone_binary.h"
namespace FileSys::SystemArchive {
@@ -38,7 +39,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI
{0x010000000000080B, "LocalNews", nullptr},
{0x010000000000080C, "Eula", nullptr},
{0x010000000000080D, "UrlBlackList", nullptr},
- {0x010000000000080E, "TimeZoneBinary", nullptr},
+ {0x010000000000080E, "TimeZoneBinary", &TimeZoneBinary},
{0x010000000000080F, "CertStoreCruiser", nullptr},
{0x0100000000000810, "FontNintendoExtension", &FontNintendoExtension},
{0x0100000000000811, "FontStandard", &FontStandard},
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
new file mode 100644
index 000000000..9806bd197
--- /dev/null
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -0,0 +1,657 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/swap.h"
+#include "core/file_sys/system_archive/time_zone_binary.h"
+#include "core/file_sys/vfs_vector.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace FileSys::SystemArchive {
+
+static constexpr std::array<u8, 9633> LOCATION_NAMES{
+ 0x43, 0x45, 0x54, 0x0d, 0x0a, 0x43, 0x53, 0x54, 0x36, 0x43, 0x44, 0x54, 0x0d, 0x0a, 0x43, 0x75,
+ 0x62, 0x61, 0x0d, 0x0a, 0x45, 0x45, 0x54, 0x0d, 0x0a, 0x45, 0x67, 0x79, 0x70, 0x74, 0x0d, 0x0a,
+ 0x45, 0x69, 0x72, 0x65, 0x0d, 0x0a, 0x45, 0x53, 0x54, 0x0d, 0x0a, 0x45, 0x53, 0x54, 0x35, 0x45,
+ 0x44, 0x54, 0x0d, 0x0a, 0x47, 0x42, 0x0d, 0x0a, 0x47, 0x42, 0x2d, 0x45, 0x69, 0x72, 0x65, 0x0d,
+ 0x0a, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x47, 0x4d, 0x54, 0x2b, 0x30, 0x0d, 0x0a, 0x47, 0x4d, 0x54,
+ 0x2d, 0x30, 0x0d, 0x0a, 0x47, 0x4d, 0x54, 0x30, 0x0d, 0x0a, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x77,
+ 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x48, 0x6f, 0x6e, 0x67, 0x6b, 0x6f, 0x6e, 0x67, 0x0d, 0x0a, 0x48,
+ 0x53, 0x54, 0x0d, 0x0a, 0x49, 0x63, 0x65, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x49, 0x72, 0x61,
+ 0x6e, 0x0d, 0x0a, 0x49, 0x73, 0x72, 0x61, 0x65, 0x6c, 0x0d, 0x0a, 0x4a, 0x61, 0x6d, 0x61, 0x69,
+ 0x63, 0x61, 0x0d, 0x0a, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x0d, 0x0a, 0x4b, 0x77, 0x61, 0x6a, 0x61,
+ 0x6c, 0x65, 0x69, 0x6e, 0x0d, 0x0a, 0x4c, 0x69, 0x62, 0x79, 0x61, 0x0d, 0x0a, 0x4d, 0x45, 0x54,
+ 0x0d, 0x0a, 0x4d, 0x53, 0x54, 0x0d, 0x0a, 0x4d, 0x53, 0x54, 0x37, 0x4d, 0x44, 0x54, 0x0d, 0x0a,
+ 0x4e, 0x61, 0x76, 0x61, 0x6a, 0x6f, 0x0d, 0x0a, 0x4e, 0x5a, 0x0d, 0x0a, 0x4e, 0x5a, 0x2d, 0x43,
+ 0x48, 0x41, 0x54, 0x0d, 0x0a, 0x50, 0x6f, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x50, 0x6f, 0x72,
+ 0x74, 0x75, 0x67, 0x61, 0x6c, 0x0d, 0x0a, 0x50, 0x52, 0x43, 0x0d, 0x0a, 0x50, 0x53, 0x54, 0x38,
+ 0x50, 0x44, 0x54, 0x0d, 0x0a, 0x52, 0x4f, 0x43, 0x0d, 0x0a, 0x52, 0x4f, 0x4b, 0x0d, 0x0a, 0x53,
+ 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x0d, 0x0a, 0x54, 0x75, 0x72, 0x6b, 0x65, 0x79,
+ 0x0d, 0x0a, 0x55, 0x43, 0x54, 0x0d, 0x0a, 0x55, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x61, 0x6c,
+ 0x0d, 0x0a, 0x55, 0x54, 0x43, 0x0d, 0x0a, 0x57, 0x2d, 0x53, 0x55, 0x0d, 0x0a, 0x57, 0x45, 0x54,
+ 0x0d, 0x0a, 0x5a, 0x75, 0x6c, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41,
+ 0x62, 0x69, 0x64, 0x6a, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41,
+ 0x63, 0x63, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x64,
+ 0x69, 0x73, 0x5f, 0x41, 0x62, 0x61, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x41, 0x6c, 0x67, 0x69, 0x65, 0x72, 0x73, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x41, 0x73, 0x6d, 0x61, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x41, 0x73, 0x6d, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42,
+ 0x61, 0x6d, 0x61, 0x6b, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61,
+ 0x6e, 0x67, 0x75, 0x69, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x6e,
+ 0x6a, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x73,
+ 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x74,
+ 0x79, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x72, 0x61, 0x7a,
+ 0x7a, 0x61, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x42, 0x75, 0x6a, 0x75, 0x6d, 0x62, 0x75, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x43, 0x61, 0x69, 0x72, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x43, 0x61, 0x73, 0x61, 0x62, 0x6c, 0x61, 0x6e, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x43, 0x65, 0x75, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x43, 0x6f, 0x6e, 0x61, 0x6b, 0x72, 0x79, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x44, 0x61, 0x6b, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44,
+ 0x61, 0x72, 0x5f, 0x65, 0x73, 0x5f, 0x53, 0x61, 0x6c, 0x61, 0x61, 0x6d, 0x0d, 0x0a, 0x41, 0x66,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6a, 0x69, 0x62, 0x6f, 0x75, 0x74, 0x69, 0x0d, 0x0a, 0x41,
+ 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6f, 0x75, 0x61, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x66,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x6c, 0x5f, 0x41, 0x61, 0x69, 0x75, 0x6e, 0x0d, 0x0a, 0x41,
+ 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x72, 0x65, 0x65, 0x74, 0x6f, 0x77, 0x6e, 0x0d, 0x0a,
+ 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x61, 0x62, 0x6f, 0x72, 0x6f, 0x6e, 0x65, 0x0d,
+ 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x72, 0x61, 0x72, 0x65, 0x0d, 0x0a,
+ 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x6f, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x73, 0x62,
+ 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a, 0x75, 0x62, 0x61,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x70, 0x61, 0x6c, 0x61,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x68, 0x61, 0x72, 0x74, 0x6f, 0x75,
+ 0x6d, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x67, 0x61, 0x6c, 0x69,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x69, 0x6e, 0x73, 0x68, 0x61, 0x73,
+ 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x61, 0x67, 0x6f, 0x73, 0x0d,
+ 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x62, 0x72, 0x65, 0x76, 0x69, 0x6c,
+ 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x6d, 0x65, 0x0d,
+ 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x61, 0x6e, 0x64, 0x61, 0x0d, 0x0a,
+ 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x62, 0x75, 0x6d, 0x62, 0x61, 0x73, 0x68,
+ 0x69, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x75, 0x73, 0x61, 0x6b, 0x61,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6c, 0x61, 0x62, 0x6f, 0x0d,
+ 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x70, 0x75, 0x74, 0x6f, 0x0d, 0x0a,
+ 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x73, 0x65, 0x72, 0x75, 0x0d, 0x0a, 0x41,
+ 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x62, 0x61, 0x62, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41,
+ 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x67, 0x61, 0x64, 0x69, 0x73, 0x68, 0x75, 0x0d,
+ 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x72, 0x6f, 0x76, 0x69, 0x61,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x61, 0x69, 0x72, 0x6f, 0x62, 0x69,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x64, 0x6a, 0x61, 0x6d, 0x65, 0x6e,
+ 0x61, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x61, 0x6d, 0x65, 0x79,
+ 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x75, 0x61, 0x6b, 0x63, 0x68,
+ 0x6f, 0x74, 0x74, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x75, 0x61, 0x67,
+ 0x61, 0x64, 0x6f, 0x75, 0x67, 0x6f, 0x75, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x50, 0x6f, 0x72, 0x74, 0x6f, 0x2d, 0x4e, 0x6f, 0x76, 0x6f, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6f, 0x5f, 0x54, 0x6f, 0x6d, 0x65, 0x0d, 0x0a, 0x41, 0x66, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6d, 0x62, 0x75, 0x6b, 0x74, 0x75, 0x0d, 0x0a, 0x41, 0x66,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x72, 0x69, 0x70, 0x6f, 0x6c, 0x69, 0x0d, 0x0a, 0x41, 0x66,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x75, 0x6e, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x66, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x64, 0x68, 0x6f, 0x65, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x64, 0x61, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x67, 0x75, 0x69, 0x6c, 0x6c, 0x61, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x6e, 0x74, 0x69, 0x67, 0x75, 0x61, 0x0d,
+ 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x61, 0x67, 0x75, 0x61, 0x69,
+ 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x75, 0x62,
+ 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x73, 0x75, 0x6e, 0x63,
+ 0x69, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x74, 0x69,
+ 0x6b, 0x6f, 0x6b, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41,
+ 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68,
+ 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x69,
+ 0x61, 0x5f, 0x42, 0x61, 0x6e, 0x64, 0x65, 0x72, 0x61, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x42, 0x61, 0x72, 0x62, 0x61, 0x64, 0x6f, 0x73, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x65, 0x6c, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x65, 0x6c, 0x69, 0x7a, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6c, 0x61, 0x6e, 0x63, 0x2d, 0x53, 0x61, 0x62, 0x6c, 0x6f,
+ 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f, 0x61, 0x5f, 0x56,
+ 0x69, 0x73, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f,
+ 0x67, 0x6f, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x6f,
+ 0x69, 0x73, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x42, 0x75, 0x65,
+ 0x6e, 0x6f, 0x73, 0x5f, 0x41, 0x69, 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x5f, 0x42, 0x61, 0x79,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x6d, 0x70, 0x6f, 0x5f,
+ 0x47, 0x72, 0x61, 0x6e, 0x64, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x43, 0x61, 0x6e, 0x63, 0x75, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x43, 0x61, 0x72, 0x61, 0x63, 0x61, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x43, 0x61, 0x74, 0x61, 0x6d, 0x61, 0x72, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x79, 0x65, 0x6e, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x79, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x63, 0x61, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x68, 0x75, 0x61, 0x68, 0x75, 0x61, 0x0d,
+ 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x6f, 0x72, 0x61, 0x6c, 0x5f, 0x48,
+ 0x61, 0x72, 0x62, 0x6f, 0x75, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x43, 0x6f, 0x72, 0x64, 0x6f, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x43, 0x6f, 0x73, 0x74, 0x61, 0x5f, 0x52, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x69, 0x61, 0x62, 0x61, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x61, 0x63, 0x61, 0x6f, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x6e, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x68,
+ 0x61, 0x76, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x77,
+ 0x73, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x77,
+ 0x73, 0x6f, 0x6e, 0x5f, 0x43, 0x72, 0x65, 0x65, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x44, 0x65, 0x6e, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x44, 0x65, 0x74, 0x72, 0x6f, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x64, 0x6d, 0x6f, 0x6e, 0x74, 0x6f, 0x6e, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x69, 0x72, 0x75, 0x6e, 0x65, 0x70, 0x65,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45, 0x6c, 0x5f, 0x53, 0x61, 0x6c,
+ 0x76, 0x61, 0x64, 0x6f, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x45,
+ 0x6e, 0x73, 0x65, 0x6e, 0x61, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x65, 0x7a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x5f, 0x4e, 0x65, 0x6c, 0x73, 0x6f, 0x6e, 0x0d,
+ 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x46, 0x6f, 0x72, 0x74, 0x5f, 0x57, 0x61,
+ 0x79, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x6c, 0x61,
+ 0x63, 0x65, 0x5f, 0x42, 0x61, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x47, 0x6f, 0x64, 0x74, 0x68, 0x61, 0x62, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x47, 0x6f, 0x6f, 0x73, 0x65, 0x5f, 0x42, 0x61, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x61, 0x6e, 0x64, 0x5f, 0x54, 0x75, 0x72, 0x6b, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x72, 0x65, 0x6e, 0x61, 0x64, 0x61, 0x0d,
+ 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x65, 0x6c, 0x6f,
+ 0x75, 0x70, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x47, 0x75, 0x61,
+ 0x74, 0x65, 0x6d, 0x61, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x47, 0x75, 0x61, 0x79, 0x61, 0x71, 0x75, 0x69, 0x6c, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x47, 0x75, 0x79, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x48, 0x61, 0x6c, 0x69, 0x66, 0x61, 0x78, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x48, 0x61, 0x76, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x48, 0x65, 0x72, 0x6d, 0x6f, 0x73, 0x69, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70,
+ 0x6f, 0x6c, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e,
+ 0x75, 0x76, 0x69, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x71,
+ 0x61, 0x6c, 0x75, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a,
+ 0x61, 0x6d, 0x61, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x4a, 0x75, 0x6a, 0x75, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4a,
+ 0x75, 0x6e, 0x65, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b,
+ 0x6e, 0x6f, 0x78, 0x5f, 0x49, 0x4e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x4b, 0x72, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6a, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x50, 0x61, 0x7a, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x69, 0x6d, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x4c, 0x6f, 0x73, 0x5f, 0x41, 0x6e, 0x67, 0x65, 0x6c, 0x65, 0x73, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x75, 0x69, 0x73, 0x76, 0x69, 0x6c, 0x6c,
+ 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4c, 0x6f, 0x77, 0x65, 0x72,
+ 0x5f, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x65, 0x69, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x75, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x6e, 0x61, 0x75, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x67, 0x6f, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x74, 0x61, 0x6d, 0x6f, 0x72, 0x6f,
+ 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x7a, 0x61, 0x74,
+ 0x6c, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x6e,
+ 0x64, 0x6f, 0x7a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65,
+ 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x65, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x4d, 0x65, 0x72, 0x69, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x4d, 0x65, 0x74, 0x6c, 0x61, 0x6b, 0x61, 0x74, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x5f, 0x43, 0x69, 0x74, 0x79,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x69, 0x71, 0x75, 0x65, 0x6c,
+ 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x63,
+ 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e,
+ 0x74, 0x65, 0x72, 0x72, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x4d, 0x6f, 0x6e, 0x74, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x72, 0x65, 0x61, 0x6c, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x72, 0x61, 0x74,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x61, 0x73, 0x73, 0x61, 0x75,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x5f, 0x59, 0x6f,
+ 0x72, 0x6b, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x69, 0x70, 0x69,
+ 0x67, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x6d,
+ 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x6f, 0x6e,
+ 0x68, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4f, 0x6a, 0x69, 0x6e,
+ 0x61, 0x67, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e,
+ 0x61, 0x6d, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x61, 0x6e,
+ 0x67, 0x6e, 0x69, 0x72, 0x74, 0x75, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x61, 0x72, 0x69, 0x62, 0x6f, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x68, 0x6f, 0x65, 0x6e, 0x69, 0x78, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x2d, 0x61, 0x75, 0x2d, 0x50,
+ 0x72, 0x69, 0x6e, 0x63, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50,
+ 0x6f, 0x72, 0x74, 0x6f, 0x5f, 0x41, 0x63, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x6f, 0x5f, 0x56, 0x65, 0x6c, 0x68, 0x6f, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x66, 0x5f,
+ 0x53, 0x70, 0x61, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x50,
+ 0x75, 0x65, 0x72, 0x74, 0x6f, 0x5f, 0x52, 0x69, 0x63, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x50, 0x75, 0x6e, 0x74, 0x61, 0x5f, 0x41, 0x72, 0x65, 0x6e, 0x61, 0x73,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x61, 0x69, 0x6e, 0x79, 0x5f,
+ 0x52, 0x69, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52,
+ 0x61, 0x6e, 0x6b, 0x69, 0x6e, 0x5f, 0x49, 0x6e, 0x6c, 0x65, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x63, 0x69, 0x66, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x67, 0x69, 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x42, 0x72, 0x61, 0x6e, 0x63,
+ 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x52, 0x6f, 0x73, 0x61, 0x72,
+ 0x69, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74,
+ 0x61, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61,
+ 0x6e, 0x74, 0x61, 0x5f, 0x49, 0x73, 0x61, 0x62, 0x65, 0x6c, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x69, 0x61, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x74, 0x6f, 0x5f, 0x44, 0x6f, 0x6d, 0x69,
+ 0x6e, 0x67, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x61, 0x6f,
+ 0x5f, 0x50, 0x61, 0x75, 0x6c, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x53, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, 0x73, 0x75, 0x6e, 0x64, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x68, 0x69, 0x70, 0x72, 0x6f, 0x63, 0x6b, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x69, 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x42, 0x61, 0x72, 0x74, 0x68, 0x65,
+ 0x6c, 0x65, 0x6d, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74,
+ 0x5f, 0x4a, 0x6f, 0x68, 0x6e, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x53, 0x74, 0x5f, 0x4b, 0x69, 0x74, 0x74, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x4c, 0x75, 0x63, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x54, 0x68, 0x6f, 0x6d, 0x61, 0x73, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x74, 0x5f, 0x56, 0x69, 0x6e, 0x63, 0x65, 0x6e,
+ 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x77, 0x69, 0x66, 0x74,
+ 0x5f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x54, 0x65, 0x67, 0x75, 0x63, 0x69, 0x67, 0x61, 0x6c, 0x70, 0x61, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x68, 0x75, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x68, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x42, 0x61,
+ 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x69, 0x6a, 0x75, 0x61,
+ 0x6e, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x6f, 0x72, 0x6f,
+ 0x6e, 0x74, 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x54, 0x6f, 0x72,
+ 0x74, 0x6f, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x56, 0x61,
+ 0x6e, 0x63, 0x6f, 0x75, 0x76, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x56, 0x69, 0x72, 0x67, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x57, 0x68, 0x69, 0x74, 0x65, 0x68, 0x6f, 0x72, 0x73, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x6e, 0x69, 0x70, 0x65, 0x67, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x61, 0x74, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x59, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6b, 0x6e,
+ 0x69, 0x66, 0x65, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67,
+ 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x42, 0x75, 0x65, 0x6e, 0x6f, 0x73, 0x5f, 0x41, 0x69,
+ 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67,
+ 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x61, 0x74, 0x61, 0x6d, 0x61, 0x72, 0x63, 0x61,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74,
+ 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x52, 0x69, 0x76, 0x61, 0x64, 0x61, 0x76,
+ 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65,
+ 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x43, 0x6f, 0x72, 0x64, 0x6f, 0x62, 0x61, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61,
+ 0x2f, 0x4a, 0x75, 0x6a, 0x75, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x4c, 0x61, 0x5f, 0x52, 0x69, 0x6f,
+ 0x6a, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65,
+ 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x4d, 0x65, 0x6e, 0x64, 0x6f, 0x7a, 0x61, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61,
+ 0x2f, 0x52, 0x69, 0x6f, 0x5f, 0x47, 0x61, 0x6c, 0x6c, 0x65, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61,
+ 0x2f, 0x53, 0x61, 0x6c, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f,
+ 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4a, 0x75,
+ 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65,
+ 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4c, 0x75, 0x69, 0x73, 0x0d, 0x0a,
+ 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e,
+ 0x61, 0x2f, 0x54, 0x75, 0x63, 0x75, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69,
+ 0x63, 0x61, 0x2f, 0x41, 0x72, 0x67, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x61, 0x2f, 0x55, 0x73, 0x68,
+ 0x75, 0x61, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e,
+ 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, 0x6f, 0x6c,
+ 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69,
+ 0x61, 0x6e, 0x61, 0x2f, 0x4b, 0x6e, 0x6f, 0x78, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x4d, 0x61, 0x72, 0x65, 0x6e, 0x67,
+ 0x6f, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61,
+ 0x6e, 0x61, 0x2f, 0x50, 0x65, 0x74, 0x65, 0x72, 0x73, 0x62, 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x54,
+ 0x65, 0x6c, 0x6c, 0x5f, 0x43, 0x69, 0x74, 0x79, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63,
+ 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x56, 0x65, 0x76, 0x61, 0x79, 0x0d,
+ 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61,
+ 0x2f, 0x56, 0x69, 0x6e, 0x63, 0x65, 0x6e, 0x6e, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2f, 0x57, 0x69, 0x6e, 0x61,
+ 0x6d, 0x61, 0x63, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x65, 0x6e,
+ 0x74, 0x75, 0x63, 0x6b, 0x79, 0x2f, 0x4c, 0x6f, 0x75, 0x69, 0x73, 0x76, 0x69, 0x6c, 0x6c, 0x65,
+ 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4b, 0x65, 0x6e, 0x74, 0x75, 0x63,
+ 0x6b, 0x79, 0x2f, 0x4d, 0x6f, 0x6e, 0x74, 0x69, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a, 0x41,
+ 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b,
+ 0x6f, 0x74, 0x61, 0x2f, 0x42, 0x65, 0x75, 0x6c, 0x61, 0x68, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72,
+ 0x69, 0x63, 0x61, 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61,
+ 0x2f, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61,
+ 0x2f, 0x4e, 0x6f, 0x72, 0x74, 0x68, 0x5f, 0x44, 0x61, 0x6b, 0x6f, 0x74, 0x61, 0x2f, 0x4e, 0x65,
+ 0x77, 0x5f, 0x53, 0x61, 0x6c, 0x65, 0x6d, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74,
+ 0x69, 0x63, 0x61, 0x2f, 0x43, 0x61, 0x73, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72,
+ 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x61, 0x76, 0x69, 0x73, 0x0d, 0x0a, 0x41, 0x6e, 0x74,
+ 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x44, 0x75, 0x6d, 0x6f, 0x6e, 0x74, 0x44, 0x55,
+ 0x72, 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69,
+ 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x71, 0x75, 0x61, 0x72, 0x69, 0x65, 0x0d, 0x0a, 0x41, 0x6e,
+ 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x61, 0x77, 0x73, 0x6f, 0x6e, 0x0d,
+ 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x4d, 0x63, 0x4d, 0x75,
+ 0x72, 0x64, 0x6f, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f,
+ 0x50, 0x61, 0x6c, 0x6d, 0x65, 0x72, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69,
+ 0x63, 0x61, 0x2f, 0x52, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61,
+ 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x5f, 0x50, 0x6f, 0x6c,
+ 0x65, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f, 0x53, 0x79,
+ 0x6f, 0x77, 0x61, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63, 0x61, 0x2f,
+ 0x54, 0x72, 0x6f, 0x6c, 0x6c, 0x0d, 0x0a, 0x41, 0x6e, 0x74, 0x61, 0x72, 0x63, 0x74, 0x69, 0x63,
+ 0x61, 0x2f, 0x56, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0d, 0x0a, 0x41, 0x72, 0x63, 0x74, 0x69, 0x63,
+ 0x2f, 0x4c, 0x6f, 0x6e, 0x67, 0x79, 0x65, 0x61, 0x72, 0x62, 0x79, 0x65, 0x6e, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41,
+ 0x6c, 0x6d, 0x61, 0x74, 0x79, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6d, 0x6d, 0x61,
+ 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x6e, 0x61, 0x64, 0x79, 0x72, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x71, 0x74, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x41, 0x71, 0x74, 0x6f, 0x62, 0x65, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x73,
+ 0x68, 0x67, 0x61, 0x62, 0x61, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x73, 0x68,
+ 0x6b, 0x68, 0x61, 0x62, 0x61, 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x41, 0x74, 0x79,
+ 0x72, 0x61, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x67, 0x68, 0x64, 0x61,
+ 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x68, 0x72, 0x61, 0x69, 0x6e, 0x0d,
+ 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x61, 0x6b, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x42, 0x61, 0x6e, 0x67, 0x6b, 0x6f, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42,
+ 0x61, 0x72, 0x6e, 0x61, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x65, 0x69,
+ 0x72, 0x75, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x69, 0x73, 0x68, 0x6b, 0x65,
+ 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x42, 0x72, 0x75, 0x6e, 0x65, 0x69, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x74, 0x74, 0x61, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x43, 0x68, 0x69, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f,
+ 0x43, 0x68, 0x6f, 0x69, 0x62, 0x61, 0x6c, 0x73, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x43, 0x68, 0x6f, 0x6e, 0x67, 0x71, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x43, 0x68, 0x75, 0x6e, 0x67, 0x6b, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x43, 0x6f, 0x6c, 0x6f, 0x6d, 0x62, 0x6f, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44,
+ 0x61, 0x63, 0x63, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x61, 0x6d, 0x61, 0x73,
+ 0x63, 0x75, 0x73, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x68, 0x61, 0x6b, 0x61, 0x0d,
+ 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x69, 0x6c, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x44, 0x75, 0x62, 0x61, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x44, 0x75, 0x73,
+ 0x68, 0x61, 0x6e, 0x62, 0x65, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x46, 0x61, 0x6d, 0x61,
+ 0x67, 0x75, 0x73, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x47, 0x61, 0x7a, 0x61,
+ 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x48, 0x65, 0x62, 0x72, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x48, 0x6f, 0x6e, 0x67, 0x5f, 0x4b, 0x6f, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x48, 0x6f, 0x76, 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x48, 0x6f, 0x5f, 0x43,
+ 0x68, 0x69, 0x5f, 0x4d, 0x69, 0x6e, 0x68, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x49, 0x72,
+ 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x49, 0x73, 0x74, 0x61,
+ 0x6e, 0x62, 0x75, 0x6c, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x61, 0x6b, 0x61, 0x72,
+ 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x61, 0x79, 0x61, 0x70, 0x75, 0x72,
+ 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4a, 0x65, 0x72, 0x75, 0x73, 0x61, 0x6c, 0x65,
+ 0x6d, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x62, 0x75, 0x6c, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x6d, 0x63, 0x68, 0x61, 0x74, 0x6b, 0x61, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x61, 0x72, 0x61, 0x63, 0x68, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69,
+ 0x61, 0x2f, 0x4b, 0x61, 0x73, 0x68, 0x67, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f,
+ 0x4b, 0x61, 0x74, 0x68, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f,
+ 0x4b, 0x61, 0x74, 0x6d, 0x61, 0x6e, 0x64, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b,
+ 0x68, 0x61, 0x6e, 0x64, 0x79, 0x67, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x6f,
+ 0x6c, 0x6b, 0x61, 0x74, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x72, 0x61, 0x73,
+ 0x6e, 0x6f, 0x79, 0x61, 0x72, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b, 0x75,
+ 0x61, 0x6c, 0x61, 0x5f, 0x4c, 0x75, 0x6d, 0x70, 0x75, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x4b, 0x75, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4b,
+ 0x75, 0x77, 0x61, 0x69, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x61,
+ 0x6f, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x63, 0x61, 0x75, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x61, 0x67, 0x61, 0x64, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69,
+ 0x61, 0x2f, 0x4d, 0x61, 0x6b, 0x61, 0x73, 0x73, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x4d, 0x61, 0x6e, 0x69, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4d, 0x75,
+ 0x73, 0x63, 0x61, 0x74, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x69, 0x63, 0x6f, 0x73,
+ 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f, 0x6b, 0x75, 0x7a,
+ 0x6e, 0x65, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4e, 0x6f, 0x76, 0x6f,
+ 0x73, 0x69, 0x62, 0x69, 0x72, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x6d,
+ 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x4f, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x50, 0x68, 0x6e, 0x6f, 0x6d, 0x5f, 0x50, 0x65, 0x6e, 0x68, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x6f, 0x6e, 0x74, 0x69, 0x61, 0x6e, 0x61, 0x6b, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x50, 0x79, 0x6f, 0x6e, 0x67, 0x79, 0x61, 0x6e, 0x67, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x51, 0x61, 0x74, 0x61, 0x72, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x51, 0x79, 0x7a, 0x79, 0x6c, 0x6f, 0x72, 0x64, 0x61, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x52, 0x61, 0x6e, 0x67, 0x6f, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x52,
+ 0x69, 0x79, 0x61, 0x64, 0x68, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x69, 0x67,
+ 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x6b, 0x68, 0x61, 0x6c, 0x69,
+ 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x6b, 0x61, 0x6e,
+ 0x64, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x53, 0x65, 0x6f, 0x75, 0x6c, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x53, 0x68, 0x61, 0x6e, 0x67, 0x68, 0x61, 0x69, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x53, 0x69, 0x6e, 0x67, 0x61, 0x70, 0x6f, 0x72, 0x65, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x53, 0x72, 0x65, 0x64, 0x6e, 0x65, 0x6b, 0x6f, 0x6c, 0x79, 0x6d, 0x73, 0x6b,
+ 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x69, 0x70, 0x65, 0x69, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x68, 0x6b, 0x65, 0x6e, 0x74, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x54, 0x62, 0x69, 0x6c, 0x69, 0x73, 0x69, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61,
+ 0x2f, 0x54, 0x65, 0x68, 0x72, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x65,
+ 0x6c, 0x5f, 0x41, 0x76, 0x69, 0x76, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x68, 0x69,
+ 0x6d, 0x62, 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x68, 0x69, 0x6d, 0x70, 0x68,
+ 0x75, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x54, 0x6f, 0x6b, 0x79, 0x6f, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x54, 0x6f, 0x6d, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f,
+ 0x55, 0x6a, 0x75, 0x6e, 0x67, 0x5f, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x6e, 0x67, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x61, 0x6e, 0x62, 0x61, 0x61, 0x74, 0x61, 0x72, 0x0d,
+ 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x6c, 0x61, 0x6e, 0x5f, 0x42, 0x61, 0x74, 0x6f, 0x72,
+ 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f, 0x55, 0x72, 0x75, 0x6d, 0x71, 0x69, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x55, 0x73, 0x74, 0x2d, 0x4e, 0x65, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x56, 0x69, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x56, 0x6c, 0x61, 0x64, 0x69, 0x76, 0x6f, 0x73, 0x74, 0x6f, 0x6b, 0x0d, 0x0a,
+ 0x41, 0x73, 0x69, 0x61, 0x2f, 0x59, 0x61, 0x6b, 0x75, 0x74, 0x73, 0x6b, 0x0d, 0x0a, 0x41, 0x73,
+ 0x69, 0x61, 0x2f, 0x59, 0x61, 0x6e, 0x67, 0x6f, 0x6e, 0x0d, 0x0a, 0x41, 0x73, 0x69, 0x61, 0x2f,
+ 0x59, 0x65, 0x6b, 0x61, 0x74, 0x65, 0x72, 0x69, 0x6e, 0x62, 0x75, 0x72, 0x67, 0x0d, 0x0a, 0x41,
+ 0x73, 0x69, 0x61, 0x2f, 0x59, 0x65, 0x72, 0x65, 0x76, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x74, 0x6c,
+ 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x41, 0x7a, 0x6f, 0x72, 0x65, 0x73, 0x0d, 0x0a, 0x41, 0x74,
+ 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x42, 0x65, 0x72, 0x6d, 0x75, 0x64, 0x61, 0x0d, 0x0a,
+ 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x0d,
+ 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x43, 0x61, 0x70, 0x65, 0x5f, 0x56,
+ 0x65, 0x72, 0x64, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x46,
+ 0x61, 0x65, 0x72, 0x6f, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f,
+ 0x46, 0x61, 0x72, 0x6f, 0x65, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f,
+ 0x4a, 0x61, 0x6e, 0x5f, 0x4d, 0x61, 0x79, 0x65, 0x6e, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e,
+ 0x74, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x64, 0x65, 0x69, 0x72, 0x61, 0x0d, 0x0a, 0x41, 0x74, 0x6c,
+ 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x52, 0x65, 0x79, 0x6b, 0x6a, 0x61, 0x76, 0x69, 0x6b, 0x0d,
+ 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x5f,
+ 0x47, 0x65, 0x6f, 0x72, 0x67, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x69,
+ 0x63, 0x2f, 0x53, 0x74, 0x61, 0x6e, 0x6c, 0x65, 0x79, 0x0d, 0x0a, 0x41, 0x74, 0x6c, 0x61, 0x6e,
+ 0x74, 0x69, 0x63, 0x2f, 0x53, 0x74, 0x5f, 0x48, 0x65, 0x6c, 0x65, 0x6e, 0x61, 0x0d, 0x0a, 0x41,
+ 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x41, 0x43, 0x54, 0x0d, 0x0a, 0x41, 0x75,
+ 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x41, 0x64, 0x65, 0x6c, 0x61, 0x69, 0x64, 0x65,
+ 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x42, 0x72, 0x69, 0x73,
+ 0x62, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f,
+ 0x42, 0x72, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x48, 0x69, 0x6c, 0x6c, 0x0d, 0x0a, 0x41, 0x75, 0x73,
+ 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x43, 0x61, 0x6e, 0x62, 0x65, 0x72, 0x72, 0x61, 0x0d,
+ 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x43, 0x75, 0x72, 0x72, 0x69,
+ 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x44, 0x61, 0x72,
+ 0x77, 0x69, 0x6e, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x45,
+ 0x75, 0x63, 0x6c, 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f,
+ 0x48, 0x6f, 0x62, 0x61, 0x72, 0x74, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69,
+ 0x61, 0x2f, 0x4c, 0x48, 0x49, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61,
+ 0x2f, 0x4c, 0x69, 0x6e, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72,
+ 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4c, 0x6f, 0x72, 0x64, 0x5f, 0x48, 0x6f, 0x77, 0x65, 0x0d, 0x0a,
+ 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4d, 0x65, 0x6c, 0x62, 0x6f, 0x75,
+ 0x72, 0x6e, 0x65, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x4e,
+ 0x6f, 0x72, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f,
+ 0x4e, 0x53, 0x57, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x50,
+ 0x65, 0x72, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f,
+ 0x51, 0x75, 0x65, 0x65, 0x6e, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74,
+ 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x6f, 0x75, 0x74, 0x68, 0x0d, 0x0a, 0x41, 0x75, 0x73,
+ 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x53, 0x79, 0x64, 0x6e, 0x65, 0x79, 0x0d, 0x0a, 0x41,
+ 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x54, 0x61, 0x73, 0x6d, 0x61, 0x6e, 0x69,
+ 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61, 0x2f, 0x56, 0x69, 0x63,
+ 0x74, 0x6f, 0x72, 0x69, 0x61, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61,
+ 0x2f, 0x57, 0x65, 0x73, 0x74, 0x0d, 0x0a, 0x41, 0x75, 0x73, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x61,
+ 0x2f, 0x59, 0x61, 0x6e, 0x63, 0x6f, 0x77, 0x69, 0x6e, 0x6e, 0x61, 0x0d, 0x0a, 0x42, 0x72, 0x61,
+ 0x7a, 0x69, 0x6c, 0x2f, 0x41, 0x63, 0x72, 0x65, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a, 0x69, 0x6c,
+ 0x2f, 0x44, 0x65, 0x4e, 0x6f, 0x72, 0x6f, 0x6e, 0x68, 0x61, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a,
+ 0x69, 0x6c, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x0d, 0x0a, 0x42, 0x72, 0x61, 0x7a, 0x69, 0x6c, 0x2f,
+ 0x57, 0x65, 0x73, 0x74, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x41, 0x74, 0x6c,
+ 0x61, 0x6e, 0x74, 0x69, 0x63, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x43, 0x65,
+ 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x45, 0x61,
+ 0x73, 0x74, 0x2d, 0x53, 0x61, 0x73, 0x6b, 0x61, 0x74, 0x63, 0x68, 0x65, 0x77, 0x61, 0x6e, 0x0d,
+ 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x0d,
+ 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e,
+ 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x4e, 0x65, 0x77, 0x66, 0x6f, 0x75, 0x6e,
+ 0x64, 0x6c, 0x61, 0x6e, 0x64, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x50, 0x61,
+ 0x63, 0x69, 0x66, 0x69, 0x63, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61, 0x64, 0x61, 0x2f, 0x53, 0x61,
+ 0x73, 0x6b, 0x61, 0x74, 0x63, 0x68, 0x65, 0x77, 0x61, 0x6e, 0x0d, 0x0a, 0x43, 0x61, 0x6e, 0x61,
+ 0x64, 0x61, 0x2f, 0x59, 0x75, 0x6b, 0x6f, 0x6e, 0x0d, 0x0a, 0x43, 0x68, 0x69, 0x6c, 0x65, 0x2f,
+ 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x0d, 0x0a, 0x43, 0x68, 0x69,
+ 0x6c, 0x65, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72, 0x49, 0x73, 0x6c, 0x61, 0x6e, 0x64, 0x0d,
+ 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d,
+ 0x54, 0x2b, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x0d, 0x0a,
+ 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f,
+ 0x47, 0x4d, 0x54, 0x2b, 0x31, 0x31, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b,
+ 0x31, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x32, 0x0d, 0x0a, 0x45,
+ 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x33, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d,
+ 0x54, 0x2b, 0x34, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x35, 0x0d, 0x0a,
+ 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x36, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47,
+ 0x4d, 0x54, 0x2b, 0x37, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x38, 0x0d,
+ 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2b, 0x39, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f,
+ 0x47, 0x4d, 0x54, 0x2d, 0x30, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31,
+ 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x30, 0x0d, 0x0a, 0x45, 0x74,
+ 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x31, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d,
+ 0x54, 0x2d, 0x31, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x33,
+ 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x31, 0x34, 0x0d, 0x0a, 0x45, 0x74,
+ 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x32, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54,
+ 0x2d, 0x33, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x34, 0x0d, 0x0a, 0x45,
+ 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x35, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d,
+ 0x54, 0x2d, 0x36, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x37, 0x0d, 0x0a,
+ 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x2d, 0x38, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47,
+ 0x4d, 0x54, 0x2d, 0x39, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x47, 0x4d, 0x54, 0x30, 0x0d, 0x0a,
+ 0x45, 0x74, 0x63, 0x2f, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x77, 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x45,
+ 0x74, 0x63, 0x2f, 0x55, 0x43, 0x54, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x55, 0x6e, 0x69, 0x76,
+ 0x65, 0x72, 0x73, 0x61, 0x6c, 0x0d, 0x0a, 0x45, 0x74, 0x63, 0x2f, 0x55, 0x54, 0x43, 0x0d, 0x0a,
+ 0x45, 0x74, 0x63, 0x2f, 0x5a, 0x75, 0x6c, 0x75, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x41, 0x6d, 0x73, 0x74, 0x65, 0x72, 0x64, 0x61, 0x6d, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f,
+ 0x70, 0x65, 0x2f, 0x41, 0x6e, 0x64, 0x6f, 0x72, 0x72, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f,
+ 0x70, 0x65, 0x2f, 0x41, 0x73, 0x74, 0x72, 0x61, 0x6b, 0x68, 0x61, 0x6e, 0x0d, 0x0a, 0x45, 0x75,
+ 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x41, 0x74, 0x68, 0x65, 0x6e, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72,
+ 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x6c, 0x66, 0x61, 0x73, 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72,
+ 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x6c, 0x67, 0x72, 0x61, 0x64, 0x65, 0x0d, 0x0a, 0x45, 0x75,
+ 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72,
+ 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x72, 0x61, 0x74, 0x69, 0x73, 0x6c, 0x61, 0x76, 0x61, 0x0d, 0x0a,
+ 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x72, 0x75, 0x73, 0x73, 0x65, 0x6c, 0x73, 0x0d,
+ 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x63, 0x68, 0x61, 0x72, 0x65, 0x73,
+ 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x64, 0x61, 0x70, 0x65,
+ 0x73, 0x74, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x42, 0x75, 0x73, 0x69, 0x6e,
+ 0x67, 0x65, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x43, 0x68, 0x69, 0x73,
+ 0x69, 0x6e, 0x61, 0x75, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x43, 0x6f, 0x70,
+ 0x65, 0x6e, 0x68, 0x61, 0x67, 0x65, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x44, 0x75, 0x62, 0x6c, 0x69, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x47,
+ 0x69, 0x62, 0x72, 0x61, 0x6c, 0x74, 0x61, 0x72, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x47, 0x75, 0x65, 0x72, 0x6e, 0x73, 0x65, 0x79, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70,
+ 0x65, 0x2f, 0x48, 0x65, 0x6c, 0x73, 0x69, 0x6e, 0x6b, 0x69, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f,
+ 0x70, 0x65, 0x2f, 0x49, 0x73, 0x6c, 0x65, 0x5f, 0x6f, 0x66, 0x5f, 0x4d, 0x61, 0x6e, 0x0d, 0x0a,
+ 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x49, 0x73, 0x74, 0x61, 0x6e, 0x62, 0x75, 0x6c, 0x0d,
+ 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4a, 0x65, 0x72, 0x73, 0x65, 0x79, 0x0d, 0x0a,
+ 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x61, 0x6c, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x72,
+ 0x61, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x65, 0x76, 0x0d,
+ 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4b, 0x69, 0x72, 0x6f, 0x76, 0x0d, 0x0a, 0x45,
+ 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x69, 0x73, 0x62, 0x6f, 0x6e, 0x0d, 0x0a, 0x45, 0x75,
+ 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x6a, 0x75, 0x62, 0x6c, 0x6a, 0x61, 0x6e, 0x61, 0x0d, 0x0a,
+ 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x0d, 0x0a, 0x45,
+ 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4c, 0x75, 0x78, 0x65, 0x6d, 0x62, 0x6f, 0x75, 0x72, 0x67,
+ 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x64, 0x72, 0x69, 0x64, 0x0d,
+ 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x6c, 0x74, 0x61, 0x0d, 0x0a, 0x45,
+ 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x61, 0x72, 0x69, 0x65, 0x68, 0x61, 0x6d, 0x6e, 0x0d,
+ 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x73, 0x6b, 0x0d, 0x0a, 0x45,
+ 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x0d, 0x0a, 0x45, 0x75,
+ 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x4d, 0x6f, 0x73, 0x63, 0x6f, 0x77, 0x0d, 0x0a, 0x45, 0x75, 0x72,
+ 0x6f, 0x70, 0x65, 0x2f, 0x4e, 0x69, 0x63, 0x6f, 0x73, 0x69, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72,
+ 0x6f, 0x70, 0x65, 0x2f, 0x4f, 0x73, 0x6c, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x50, 0x61, 0x72, 0x69, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x50,
+ 0x6f, 0x64, 0x67, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x50, 0x72, 0x61, 0x67, 0x75, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x52, 0x69, 0x67, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x52, 0x6f, 0x6d,
+ 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x6d, 0x61, 0x72, 0x61,
+ 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x6e, 0x5f, 0x4d, 0x61, 0x72,
+ 0x69, 0x6e, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72, 0x61,
+ 0x6a, 0x65, 0x76, 0x6f, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x61, 0x72,
+ 0x61, 0x74, 0x6f, 0x76, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x69, 0x6d,
+ 0x66, 0x65, 0x72, 0x6f, 0x70, 0x6f, 0x6c, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x53, 0x6b, 0x6f, 0x70, 0x6a, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53,
+ 0x6f, 0x66, 0x69, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x53, 0x74, 0x6f,
+ 0x63, 0x6b, 0x68, 0x6f, 0x6c, 0x6d, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54,
+ 0x61, 0x6c, 0x6c, 0x69, 0x6e, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54,
+ 0x69, 0x72, 0x61, 0x6e, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x54, 0x69,
+ 0x72, 0x61, 0x73, 0x70, 0x6f, 0x6c, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x55,
+ 0x6c, 0x79, 0x61, 0x6e, 0x6f, 0x76, 0x73, 0x6b, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x55, 0x7a, 0x68, 0x67, 0x6f, 0x72, 0x6f, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70,
+ 0x65, 0x2f, 0x56, 0x61, 0x64, 0x75, 0x7a, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x56, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x56, 0x69, 0x65, 0x6e, 0x6e, 0x61, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56,
+ 0x69, 0x6c, 0x6e, 0x69, 0x75, 0x73, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x56,
+ 0x6f, 0x6c, 0x67, 0x6f, 0x67, 0x72, 0x61, 0x64, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65,
+ 0x2f, 0x57, 0x61, 0x72, 0x73, 0x61, 0x77, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f,
+ 0x5a, 0x61, 0x67, 0x72, 0x65, 0x62, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70, 0x65, 0x2f, 0x5a,
+ 0x61, 0x70, 0x6f, 0x72, 0x6f, 0x7a, 0x68, 0x79, 0x65, 0x0d, 0x0a, 0x45, 0x75, 0x72, 0x6f, 0x70,
+ 0x65, 0x2f, 0x5a, 0x75, 0x72, 0x69, 0x63, 0x68, 0x0d, 0x0a, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e,
+ 0x2f, 0x41, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x6e, 0x61, 0x72, 0x69, 0x76, 0x6f, 0x0d, 0x0a, 0x49,
+ 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x68, 0x61, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x49, 0x6e,
+ 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x68, 0x72, 0x69, 0x73, 0x74, 0x6d, 0x61, 0x73, 0x0d, 0x0a,
+ 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x63, 0x6f, 0x73, 0x0d, 0x0a, 0x49, 0x6e,
+ 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x72, 0x6f, 0x0d, 0x0a, 0x49, 0x6e, 0x64,
+ 0x69, 0x61, 0x6e, 0x2f, 0x4b, 0x65, 0x72, 0x67, 0x75, 0x65, 0x6c, 0x65, 0x6e, 0x0d, 0x0a, 0x49,
+ 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x68, 0x65, 0x0d, 0x0a, 0x49, 0x6e, 0x64, 0x69,
+ 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x6c, 0x64, 0x69, 0x76, 0x65, 0x73, 0x0d, 0x0a, 0x49, 0x6e, 0x64,
+ 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x75, 0x72, 0x69, 0x74, 0x69, 0x75, 0x73, 0x0d, 0x0a, 0x49,
+ 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x4d, 0x61, 0x79, 0x6f, 0x74, 0x74, 0x65, 0x0d, 0x0a, 0x49,
+ 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x2f, 0x52, 0x65, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x0d, 0x0a, 0x4d,
+ 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x42, 0x61, 0x6a, 0x61, 0x4e, 0x6f, 0x72, 0x74, 0x65, 0x0d,
+ 0x0a, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x42, 0x61, 0x6a, 0x61, 0x53, 0x75, 0x72, 0x0d,
+ 0x0a, 0x4d, 0x65, 0x78, 0x69, 0x63, 0x6f, 0x2f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x0d,
+ 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x41, 0x70, 0x69, 0x61, 0x0d, 0x0a, 0x50,
+ 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x41, 0x75, 0x63, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x0d,
+ 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x42, 0x6f, 0x75, 0x67, 0x61, 0x69, 0x6e,
+ 0x76, 0x69, 0x6c, 0x6c, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x43,
+ 0x68, 0x61, 0x74, 0x68, 0x61, 0x6d, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f,
+ 0x43, 0x68, 0x75, 0x75, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45,
+ 0x61, 0x73, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45,
+ 0x66, 0x61, 0x74, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x45, 0x6e,
+ 0x64, 0x65, 0x72, 0x62, 0x75, 0x72, 0x79, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63,
+ 0x2f, 0x46, 0x61, 0x6b, 0x61, 0x6f, 0x66, 0x6f, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2f, 0x46, 0x69, 0x6a, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f,
+ 0x46, 0x75, 0x6e, 0x61, 0x66, 0x75, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2f, 0x47, 0x61, 0x6c, 0x61, 0x70, 0x61, 0x67, 0x6f, 0x73, 0x0d, 0x0a, 0x50, 0x61, 0x63,
+ 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x61, 0x6d, 0x62, 0x69, 0x65, 0x72, 0x0d, 0x0a, 0x50, 0x61,
+ 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x64, 0x61, 0x6c, 0x63, 0x61, 0x6e, 0x61,
+ 0x6c, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x47, 0x75, 0x61, 0x6d, 0x0d,
+ 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x48, 0x6f, 0x6e, 0x6f, 0x6c, 0x75, 0x6c,
+ 0x75, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4a, 0x6f, 0x68, 0x6e, 0x73,
+ 0x74, 0x6f, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4b, 0x69, 0x72,
+ 0x69, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63,
+ 0x2f, 0x4b, 0x6f, 0x73, 0x72, 0x61, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63,
+ 0x2f, 0x4b, 0x77, 0x61, 0x6a, 0x61, 0x6c, 0x65, 0x69, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69,
+ 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x6a, 0x75, 0x72, 0x6f, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69,
+ 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x61, 0x72, 0x71, 0x75, 0x65, 0x73, 0x61, 0x73, 0x0d, 0x0a, 0x50,
+ 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4d, 0x69, 0x64, 0x77, 0x61, 0x79, 0x0d, 0x0a, 0x50,
+ 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x61, 0x75, 0x72, 0x75, 0x0d, 0x0a, 0x50, 0x61,
+ 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x69, 0x75, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69,
+ 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x6f, 0x72, 0x66, 0x6f, 0x6c, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63,
+ 0x69, 0x66, 0x69, 0x63, 0x2f, 0x4e, 0x6f, 0x75, 0x6d, 0x65, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63,
+ 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x67, 0x6f, 0x5f, 0x50, 0x61, 0x67, 0x6f, 0x0d, 0x0a,
+ 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x61, 0x6c, 0x61, 0x75, 0x0d, 0x0a, 0x50,
+ 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x69, 0x74, 0x63, 0x61, 0x69, 0x72, 0x6e, 0x0d,
+ 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x68, 0x6e, 0x70, 0x65, 0x69,
+ 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x6e, 0x61, 0x70, 0x65,
+ 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f, 0x50, 0x6f, 0x72, 0x74, 0x5f, 0x4d,
+ 0x6f, 0x72, 0x65, 0x73, 0x62, 0x79, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63, 0x2f,
+ 0x52, 0x61, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x67, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66,
+ 0x69, 0x63, 0x2f, 0x53, 0x61, 0x69, 0x70, 0x61, 0x6e, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66,
+ 0x69, 0x63, 0x2f, 0x53, 0x61, 0x6d, 0x6f, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2f, 0x54, 0x61, 0x68, 0x69, 0x74, 0x69, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2f, 0x54, 0x61, 0x72, 0x61, 0x77, 0x61, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2f, 0x54, 0x6f, 0x6e, 0x67, 0x61, 0x74, 0x61, 0x70, 0x75, 0x0d, 0x0a, 0x50, 0x61, 0x63,
+ 0x69, 0x66, 0x69, 0x63, 0x2f, 0x54, 0x72, 0x75, 0x6b, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66,
+ 0x69, 0x63, 0x2f, 0x57, 0x61, 0x6b, 0x65, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63,
+ 0x2f, 0x57, 0x61, 0x6c, 0x6c, 0x69, 0x73, 0x0d, 0x0a, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69, 0x63,
+ 0x2f, 0x59, 0x61, 0x70, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x41, 0x6c, 0x61, 0x73, 0x6b, 0x61, 0x0d,
+ 0x0a, 0x55, 0x53, 0x2f, 0x41, 0x6c, 0x65, 0x75, 0x74, 0x69, 0x61, 0x6e, 0x0d, 0x0a, 0x55, 0x53,
+ 0x2f, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x43, 0x65, 0x6e,
+ 0x74, 0x72, 0x61, 0x6c, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x2d, 0x49, 0x6e,
+ 0x64, 0x69, 0x61, 0x6e, 0x61, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x45, 0x61, 0x73, 0x74, 0x65, 0x72,
+ 0x6e, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x48, 0x61, 0x77, 0x61, 0x69, 0x69, 0x0d, 0x0a, 0x55, 0x53,
+ 0x2f, 0x49, 0x6e, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x6b, 0x65, 0x0d,
+ 0x0a, 0x55, 0x53, 0x2f, 0x4d, 0x69, 0x63, 0x68, 0x69, 0x67, 0x61, 0x6e, 0x0d, 0x0a, 0x55, 0x53,
+ 0x2f, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x50, 0x61,
+ 0x63, 0x69, 0x66, 0x69, 0x63, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x50, 0x61, 0x63, 0x69, 0x66, 0x69,
+ 0x63, 0x2d, 0x4e, 0x65, 0x77, 0x0d, 0x0a, 0x55, 0x53, 0x2f, 0x53, 0x61, 0x6d, 0x6f, 0x61, 0x0d,
+ 0x0a};
+
+static VirtualFile GenerateDefaultTimeZoneFile() {
+ struct {
+ s64_be at;
+ INSERT_PADDING_BYTES(7);
+ std::array<char, 4> time_zone_chars;
+ INSERT_PADDING_BYTES(2);
+ std::array<char, 6> time_zone_name;
+ } time_zone_info{};
+
+ const VirtualFile file{std::make_shared<VectorVfsFile>(
+ std::vector<u8>(sizeof(Service::Time::TimeZone::TzifHeader) + sizeof(time_zone_info)),
+ "GMT")};
+
+ Service::Time::TimeZone::TzifHeader header{};
+ header.magic = 0x545a6966;
+ header.version = 0x32;
+ header.ttis_gmt_count = 0x1;
+ header.ttis_std_count = 0x1;
+ header.time_count = 0x1;
+ header.type_count = 0x1;
+ header.char_count = 0x4;
+ file->WriteObject(header, 0);
+
+ time_zone_info.at = 0xf8;
+ time_zone_info.time_zone_chars = {'G', 'M', 'T', '\0'};
+ time_zone_info.time_zone_name = {'\n', 'G', 'M', 'T', '0', '\n'};
+ file->WriteObject(time_zone_info, sizeof(Service::Time::TimeZone::TzifHeader));
+
+ return file;
+}
+
+VirtualDir TimeZoneBinary() {
+ const std::vector<VirtualDir> root_dirs{std::make_shared<VectorVfsDirectory>(
+ std::vector<VirtualFile>{GenerateDefaultTimeZoneFile()}, std::vector<VirtualDir>{},
+ "zoneinfo")};
+ const std::vector<VirtualFile> root_files{
+ std::make_shared<ArrayVfsFile<LOCATION_NAMES.size()>>(LOCATION_NAMES, "binaryList.txt")};
+ return std::make_shared<VectorVfsDirectory>(root_files, root_dirs, "data");
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/time_zone_binary.h b/src/core/file_sys/system_archive/time_zone_binary.h
new file mode 100644
index 000000000..ed2b78227
--- /dev/null
+++ b/src/core/file_sys/system_archive/time_zone_binary.h
@@ -0,0 +1,14 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+VirtualDir TimeZoneBinary();
+
+} // namespace FileSys::SystemArchive
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
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index f1795fdd6..8908e5328 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -335,7 +335,8 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
codeset_segment->addr = segment_addr;
codeset_segment->size = aligned_size;
- memcpy(&program_image[current_image_position], GetSegmentPtr(i), p->p_filesz);
+ std::memcpy(program_image.data() + current_image_position, GetSegmentPtr(i),
+ p->p_filesz);
current_image_position += aligned_size;
}
}
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 474b55cb1..092103abe 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cstring>
#include "core/file_sys/kernel_executable.h"
#include "core/file_sys/program_metadata.h"
#include "core/gdbstub/gdbstub.h"
@@ -76,8 +77,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
segment.addr = offset;
segment.offset = offset;
segment.size = PageAlignSize(static_cast<u32>(data.size()));
- program_image.resize(offset);
- program_image.insert(program_image.end(), data.begin(), data.end());
+ program_image.resize(offset + data.size());
+ std::memcpy(program_image.data() + offset, data.data(), data.size());
};
load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index f629892ae..515c5accb 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
+#include <cstring>
#include <vector>
#include "common/common_funcs.h"
@@ -96,8 +97,9 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
- program_image.resize(nso_header.segments[i].location);
- program_image.insert(program_image.end(), data.begin(), data.end());
+ program_image.resize(nso_header.segments[i].location + data.size());
+ std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
+ data.size());
codeset.segments[i].addr = nso_header.segments[i].location;
codeset.segments[i].offset = nso_header.segments[i].location;
codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
@@ -139,12 +141,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
std::vector<u8> pi_header;
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
- pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
- program_image.end());
+ pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.data(),
+ program_image.data() + program_image.size());
pi_header = pm->PatchNSO(pi_header, file.GetName());
- std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
+ std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
}
// Apply cheats if they exist and the program has a valid title ID
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 3c2a29d9b..f0888327f 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -14,6 +14,7 @@
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
+#include "core/hle/kernel/physical_memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
@@ -38,6 +39,11 @@ struct Memory::Impl {
system.ArmInterface(3).PageTableChanged(*current_page_table, address_space_width);
}
+ void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Kernel::PhysicalMemory& memory, VAddr offset) {
+ MapMemoryRegion(page_table, base, size, memory.data() + offset);
+ }
+
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
@@ -601,6 +607,11 @@ void Memory::SetCurrentPageTable(Kernel::Process& process) {
impl->SetCurrentPageTable(process);
}
+void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Kernel::PhysicalMemory& memory, VAddr offset) {
+ impl->MapMemoryRegion(page_table, base, size, memory, offset);
+}
+
void Memory::MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
impl->MapMemoryRegion(page_table, base, size, target);
}
diff --git a/src/core/memory.h b/src/core/memory.h
index 1428a6d60..8913a9da4 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -19,8 +19,9 @@ class System;
}
namespace Kernel {
+class PhysicalMemory;
class Process;
-}
+} // namespace Kernel
namespace Memory {
@@ -66,6 +67,19 @@ public:
void SetCurrentPageTable(Kernel::Process& process);
/**
+ * Maps an physical buffer onto a region of the emulated process address space.
+ *
+ * @param page_table The page table of the emulated process.
+ * @param base The address to start mapping at. Must be page-aligned.
+ * @param size The amount of bytes to map. Must be page-aligned.
+ * @param memory Physical buffer with the memory backing the mapping. Must be of length
+ * at least `size + offset`.
+ * @param offset The offset within the physical memory. Must be page-aligned.
+ */
+ void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Kernel::PhysicalMemory& memory, VAddr offset);
+
+ /**
* Maps an allocated buffer onto a region of the emulated process address space.
*
* @param page_table The page table of the emulated process.
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 729ee4a01..12c46e86f 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -153,6 +153,7 @@ if (ENABLE_VULKAN)
renderer_vulkan/fixed_pipeline_state.h
renderer_vulkan/maxwell_to_vk.cpp
renderer_vulkan/maxwell_to_vk.h
+ renderer_vulkan/renderer_vulkan.h
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
renderer_vulkan/vk_compute_pass.cpp
@@ -171,6 +172,7 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_memory_manager.h
renderer_vulkan/vk_pipeline_cache.cpp
renderer_vulkan/vk_pipeline_cache.h
+ renderer_vulkan/vk_rasterizer.cpp
renderer_vulkan/vk_rasterizer.h
renderer_vulkan/vk_renderpass_cache.cpp
renderer_vulkan/vk_renderpass_cache.h
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 16f95b77d..ee79260fc 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1018,7 +1018,14 @@ public:
}
} instanced_arrays;
- INSERT_UNION_PADDING_WORDS(0x6);
+ INSERT_UNION_PADDING_WORDS(0x4);
+
+ union {
+ BitField<0, 1, u32> enable;
+ BitField<4, 8, u32> unk4;
+ } vp_point_size;
+
+ INSERT_UNION_PADDING_WORDS(1);
Cull cull;
@@ -1503,6 +1510,7 @@ ASSERT_REG_POSITION(primitive_restart, 0x591);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(polygon_offset_clamp, 0x61F);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
+ASSERT_REG_POSITION(vp_point_size, 0x644);
ASSERT_REG_POSITION(cull, 0x646);
ASSERT_REG_POSITION(pixel_center_integer, 0x649);
ASSERT_REG_POSITION(viewport_transform_enabled, 0x64B);
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 57b57c647..6f98bd827 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -215,6 +215,18 @@ enum class F2fRoundingOp : u64 {
Trunc = 11,
};
+enum class AtomicOp : u64 {
+ Add = 0,
+ Min = 1,
+ Max = 2,
+ Inc = 3,
+ Dec = 4,
+ And = 5,
+ Or = 6,
+ Xor = 7,
+ Exch = 8,
+};
+
enum class UniformType : u64 {
UnsignedByte = 0,
SignedByte = 1,
@@ -236,6 +248,13 @@ enum class StoreType : u64 {
Bits128 = 6,
};
+enum class AtomicType : u64 {
+ U32 = 0,
+ S32 = 1,
+ U64 = 2,
+ S64 = 3,
+};
+
enum class IMinMaxExchange : u64 {
None = 0,
XLo = 1,
@@ -939,6 +958,16 @@ union Instruction {
} stg;
union {
+ BitField<52, 4, AtomicOp> operation;
+ BitField<28, 2, AtomicType> type;
+ BitField<30, 22, s64> offset;
+
+ s32 GetImmediateOffset() const {
+ return static_cast<s32>(offset << 2);
+ }
+ } atoms;
+
+ union {
BitField<32, 1, PhysicalAttributeDirection> direction;
BitField<47, 3, AttributeSize> size;
BitField<20, 11, u64> address;
@@ -1659,9 +1688,10 @@ public:
ST_A,
ST_L,
ST_S,
- ST, // Store in generic memory
- STG, // Store in global memory
- AL2P, // Transforms attribute memory into physical memory
+ ST, // Store in generic memory
+ STG, // Store in global memory
+ ATOMS, // Atomic operation on shared memory
+ AL2P, // Transforms attribute memory into physical memory
TEX,
TEX_B, // Texture Load Bindless
TXQ, // Texture Query
@@ -1964,6 +1994,7 @@ private:
INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
INST("101-------------", Id::ST, Type::Memory, "ST"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
+ INST("11101100--------", Id::ATOMS, Type::Memory, "ATOMS"),
INST("1110111110100---", Id::AL2P, Type::Memory, "AL2P"),
INST("110000----111---", Id::TEX, Type::Texture, "TEX"),
INST("1101111010111---", Id::TEX_B, Type::Texture, "TEX_B"),
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 672051102..c428f06e4 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1272,6 +1272,7 @@ void RasterizerOpenGL::SyncPointState() {
const auto& regs = system.GPU().Maxwell3D().regs;
// Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
// in OpenGL).
+ state.point.program_control = regs.vp_point_size.enable != 0;
state.point.size = std::max(1.0f, regs.point_size);
}
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index f9f7a97b5..2996aaf08 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1856,6 +1856,16 @@ private:
Type::Uint};
}
+ template <const std::string_view& opname, Type type>
+ Expression Atomic(Operation operation) {
+ ASSERT(stage == ShaderType::Compute);
+ auto& smem = std::get<SmemNode>(*operation[0]);
+
+ return {fmt::format("atomic{}(smem[{} >> 2], {})", opname, Visit(smem.GetAddress()).AsInt(),
+ Visit(operation[1]).As(type)),
+ type};
+ }
+
Expression Branch(Operation operation) {
const auto target = std::get_if<ImmediateNode>(&*operation[0]);
UNIMPLEMENTED_IF(!target);
@@ -2194,6 +2204,8 @@ private:
&GLSLDecompiler::AtomicImage<Func::Xor>,
&GLSLDecompiler::AtomicImage<Func::Exchange>,
+ &GLSLDecompiler::Atomic<Func::Add, Type::Uint>,
+
&GLSLDecompiler::Branch,
&GLSLDecompiler::BranchIndirect,
&GLSLDecompiler::PushFlowStack,
@@ -2313,7 +2325,7 @@ public:
explicit ExprDecompiler(GLSLDecompiler& decomp) : decomp{decomp} {}
void operator()(const ExprAnd& expr) {
- inner += "( ";
+ inner += '(';
std::visit(*this, *expr.operand1);
inner += " && ";
std::visit(*this, *expr.operand2);
@@ -2321,7 +2333,7 @@ public:
}
void operator()(const ExprOr& expr) {
- inner += "( ";
+ inner += '(';
std::visit(*this, *expr.operand1);
inner += " || ";
std::visit(*this, *expr.operand2);
@@ -2339,28 +2351,7 @@ public:
}
void operator()(const ExprCondCode& expr) {
- const Node cc = decomp.ir.GetConditionCode(expr.cc);
- std::string target;
-
- if (const auto pred = std::get_if<PredicateNode>(&*cc)) {
- const auto index = pred->GetIndex();
- switch (index) {
- case Tegra::Shader::Pred::NeverExecute:
- target = "false";
- break;
- case Tegra::Shader::Pred::UnusedIndex:
- target = "true";
- break;
- default:
- target = decomp.GetPredicate(index);
- break;
- }
- } else if (const auto flag = std::get_if<InternalFlagNode>(&*cc)) {
- target = decomp.GetInternalFlag(flag->GetFlag());
- } else {
- UNREACHABLE();
- }
- inner += target;
+ inner += decomp.Visit(decomp.ir.GetConditionCode(expr.cc)).AsBool();
}
void operator()(const ExprVar& expr) {
@@ -2372,8 +2363,7 @@ public:
}
void operator()(VideoCommon::Shader::ExprGprEqual& expr) {
- inner +=
- "( ftou(" + decomp.GetRegister(expr.gpr) + ") == " + std::to_string(expr.value) + ')';
+ inner += fmt::format("(ftou({}) == {})", decomp.GetRegister(expr.gpr), expr.value);
}
const std::string& GetResult() const {
@@ -2381,8 +2371,8 @@ public:
}
private:
- std::string inner;
GLSLDecompiler& decomp;
+ std::string inner;
};
class ASTDecompiler {
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index df2e2395a..cc185e9e1 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -127,6 +127,7 @@ void OpenGLState::ApplyClipDistances() {
}
void OpenGLState::ApplyPointSize() {
+ Enable(GL_PROGRAM_POINT_SIZE, cur_state.point.program_control, point.program_control);
if (UpdateValue(cur_state.point.size, point.size)) {
glPointSize(point.size);
}
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index fb180f302..678e5cd89 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -131,7 +131,8 @@ public:
std::array<Viewport, Tegra::Engines::Maxwell3D::Regs::NumViewports> viewports;
struct {
- float size = 1.0f; // GL_POINT_SIZE
+ bool program_control = false; // GL_PROGRAM_POINT_SIZE
+ GLfloat size = 1.0f; // GL_POINT_SIZE
} point;
struct {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index b790b0ef4..e95eb069e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -44,7 +44,7 @@ struct FormatTuple {
constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // ABGR8U
- {GL_RGBA8, GL_RGBA, GL_BYTE, false}, // ABGR8S
+ {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE, false}, // ABGR8S
{GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, false}, // ABGR8UI
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, false}, // B5G6R5U
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, false}, // A2B10G10R10U
@@ -83,9 +83,9 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format
{GL_RGB32F, GL_RGB, GL_FLOAT, false}, // RGB32F
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // RGBA8_SRGB
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, false}, // RG8U
- {GL_RG8, GL_RG, GL_BYTE, false}, // RG8S
+ {GL_RG8_SNORM, GL_RG, GL_BYTE, false}, // RG8S
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, false}, // RG32UI
- {GL_RGB16F, GL_RGBA16, GL_HALF_FLOAT, false}, // RGBX16F
+ {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBX16F
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, false}, // R32UI
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X8
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X5
@@ -253,14 +253,12 @@ void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) {
glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level)));
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level)));
const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level);
+ u8* const mip_data = staging_buffer.data() + mip_offset;
+ const GLsizei size = static_cast<GLsizei>(params.GetHostMipmapSize(level));
if (is_compressed) {
- glGetCompressedTextureImage(texture.handle, level,
- static_cast<GLsizei>(params.GetHostMipmapSize(level)),
- staging_buffer.data() + mip_offset);
+ glGetCompressedTextureImage(texture.handle, level, size, mip_data);
} else {
- glGetTextureImage(texture.handle, level, format, type,
- static_cast<GLsizei>(params.GetHostMipmapSize(level)),
- staging_buffer.data() + mip_offset);
+ glGetTextureImage(texture.handle, level, format, type, size, mip_data);
}
}
}
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
new file mode 100644
index 000000000..a472c5dc9
--- /dev/null
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -0,0 +1,72 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <vector>
+#include "video_core/renderer_base.h"
+#include "video_core/renderer_vulkan/declarations.h"
+
+namespace Core {
+class System;
+}
+
+namespace Vulkan {
+
+class VKBlitScreen;
+class VKDevice;
+class VKFence;
+class VKMemoryManager;
+class VKResourceManager;
+class VKSwapchain;
+class VKScheduler;
+class VKImage;
+
+struct VKScreenInfo {
+ VKImage* image{};
+ u32 width{};
+ u32 height{};
+ bool is_srgb{};
+};
+
+class RendererVulkan final : public VideoCore::RendererBase {
+public:
+ explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system);
+ ~RendererVulkan() override;
+
+ /// Swap buffers (render frame)
+ void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
+
+ /// Initialize the renderer
+ bool Init() override;
+
+ /// Shutdown the renderer
+ void ShutDown() override;
+
+private:
+ std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback(
+ const vk::DispatchLoaderDynamic& dldi);
+
+ bool PickDevices(const vk::DispatchLoaderDynamic& dldi);
+
+ void Report() const;
+
+ Core::System& system;
+
+ vk::Instance instance;
+ vk::SurfaceKHR surface;
+
+ VKScreenInfo screen_info;
+
+ UniqueDebugUtilsMessengerEXT debug_callback;
+ std::unique_ptr<VKDevice> device;
+ std::unique_ptr<VKSwapchain> swapchain;
+ std::unique_ptr<VKMemoryManager> memory_manager;
+ std::unique_ptr<VKResourceManager> resource_manager;
+ std::unique_ptr<VKScheduler> scheduler;
+ std::unique_ptr<VKBlitScreen> blit_screen;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
new file mode 100644
index 000000000..d2c6b1189
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -0,0 +1,1141 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include <boost/container/static_vector.hpp>
+#include <boost/functional/hash.hpp>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/microprofile.h"
+#include "core/core.h"
+#include "core/memory.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
+#include "video_core/renderer_vulkan/renderer_vulkan.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
+#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
+#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
+#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
+#include "video_core/renderer_vulkan/vk_rasterizer.h"
+#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
+#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_sampler_cache.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+
+namespace Vulkan {
+
+using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192));
+MICROPROFILE_DEFINE(Vulkan_Drawing, "Vulkan", "Record drawing", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_Compute, "Vulkan", "Record compute", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_Clearing, "Vulkan", "Record clearing", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_Geometry, "Vulkan", "Setup geometry", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_ConstBuffers, "Vulkan", "Setup constant buffers", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_GlobalBuffers, "Vulkan", "Setup global buffers", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_RenderTargets, "Vulkan", "Setup render targets", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_Textures, "Vulkan", "Setup textures", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_Images, "Vulkan", "Setup images", MP_RGB(192, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_PipelineCache, "Vulkan", "Pipeline cache", MP_RGB(192, 128, 128));
+
+namespace {
+
+constexpr auto ComputeShaderIndex = static_cast<std::size_t>(Tegra::Engines::ShaderType::Compute);
+
+vk::Viewport GetViewportState(const VKDevice& device, const Maxwell& regs, std::size_t index) {
+ const auto& viewport = regs.viewport_transform[index];
+ const float x = viewport.translate_x - viewport.scale_x;
+ const float y = viewport.translate_y - viewport.scale_y;
+ const float width = viewport.scale_x * 2.0f;
+ const float height = viewport.scale_y * 2.0f;
+
+ const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne;
+ float near = viewport.translate_z - viewport.scale_z * reduce_z;
+ float far = viewport.translate_z + viewport.scale_z;
+ if (!device.IsExtDepthRangeUnrestrictedSupported()) {
+ near = std::clamp(near, 0.0f, 1.0f);
+ far = std::clamp(far, 0.0f, 1.0f);
+ }
+
+ return vk::Viewport(x, y, width != 0 ? width : 1.0f, height != 0 ? height : 1.0f, near, far);
+}
+
+constexpr vk::Rect2D GetScissorState(const Maxwell& regs, std::size_t index) {
+ const auto& scissor = regs.scissor_test[index];
+ if (!scissor.enable) {
+ return {{0, 0}, {INT32_MAX, INT32_MAX}};
+ }
+ const u32 width = scissor.max_x - scissor.min_x;
+ const u32 height = scissor.max_y - scissor.min_y;
+ return {{static_cast<s32>(scissor.min_x), static_cast<s32>(scissor.min_y)}, {width, height}};
+}
+
+std::array<GPUVAddr, Maxwell::MaxShaderProgram> GetShaderAddresses(
+ const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) {
+ std::array<GPUVAddr, Maxwell::MaxShaderProgram> addresses;
+ for (std::size_t i = 0; i < std::size(addresses); ++i) {
+ addresses[i] = shaders[i] ? shaders[i]->GetGpuAddr() : 0;
+ }
+ return addresses;
+}
+
+void TransitionImages(const std::vector<ImageView>& views, vk::PipelineStageFlags pipeline_stage,
+ vk::AccessFlags access) {
+ for (auto& [view, layout] : views) {
+ view->Transition(*layout, pipeline_stage, access);
+ }
+}
+
+template <typename Engine, typename Entry>
+Tegra::Texture::FullTextureInfo GetTextureInfo(const Engine& engine, const Entry& entry,
+ std::size_t stage) {
+ const auto stage_type = static_cast<Tegra::Engines::ShaderType>(stage);
+ if (entry.IsBindless()) {
+ const Tegra::Texture::TextureHandle tex_handle =
+ engine.AccessConstBuffer32(stage_type, entry.GetBuffer(), entry.GetOffset());
+ return engine.GetTextureInfo(tex_handle);
+ }
+ if constexpr (std::is_same_v<Engine, Tegra::Engines::Maxwell3D>) {
+ return engine.GetStageTexture(stage_type, entry.GetOffset());
+ } else {
+ return engine.GetTexture(entry.GetOffset());
+ }
+}
+
+} // Anonymous namespace
+
+class BufferBindings final {
+public:
+ void AddVertexBinding(const vk::Buffer* buffer, vk::DeviceSize offset) {
+ vertex.buffer_ptrs[vertex.num_buffers] = buffer;
+ vertex.offsets[vertex.num_buffers] = offset;
+ ++vertex.num_buffers;
+ }
+
+ void SetIndexBinding(const vk::Buffer* buffer, vk::DeviceSize offset, vk::IndexType type) {
+ index.buffer = buffer;
+ index.offset = offset;
+ index.type = type;
+ }
+
+ void Bind(VKScheduler& scheduler) const {
+ // Use this large switch case to avoid dispatching more memory in the record lambda than
+ // what we need. It looks horrible, but it's the best we can do on standard C++.
+ switch (vertex.num_buffers) {
+ case 0:
+ return BindStatic<0>(scheduler);
+ case 1:
+ return BindStatic<1>(scheduler);
+ case 2:
+ return BindStatic<2>(scheduler);
+ case 3:
+ return BindStatic<3>(scheduler);
+ case 4:
+ return BindStatic<4>(scheduler);
+ case 5:
+ return BindStatic<5>(scheduler);
+ case 6:
+ return BindStatic<6>(scheduler);
+ case 7:
+ return BindStatic<7>(scheduler);
+ case 8:
+ return BindStatic<8>(scheduler);
+ case 9:
+ return BindStatic<9>(scheduler);
+ case 10:
+ return BindStatic<10>(scheduler);
+ case 11:
+ return BindStatic<11>(scheduler);
+ case 12:
+ return BindStatic<12>(scheduler);
+ case 13:
+ return BindStatic<13>(scheduler);
+ case 14:
+ return BindStatic<14>(scheduler);
+ case 15:
+ return BindStatic<15>(scheduler);
+ case 16:
+ return BindStatic<16>(scheduler);
+ case 17:
+ return BindStatic<17>(scheduler);
+ case 18:
+ return BindStatic<18>(scheduler);
+ case 19:
+ return BindStatic<19>(scheduler);
+ case 20:
+ return BindStatic<20>(scheduler);
+ case 21:
+ return BindStatic<21>(scheduler);
+ case 22:
+ return BindStatic<22>(scheduler);
+ case 23:
+ return BindStatic<23>(scheduler);
+ case 24:
+ return BindStatic<24>(scheduler);
+ case 25:
+ return BindStatic<25>(scheduler);
+ case 26:
+ return BindStatic<26>(scheduler);
+ case 27:
+ return BindStatic<27>(scheduler);
+ case 28:
+ return BindStatic<28>(scheduler);
+ case 29:
+ return BindStatic<29>(scheduler);
+ case 30:
+ return BindStatic<30>(scheduler);
+ case 31:
+ return BindStatic<31>(scheduler);
+ case 32:
+ return BindStatic<32>(scheduler);
+ }
+ UNREACHABLE();
+ }
+
+private:
+ // Some of these fields are intentionally left uninitialized to avoid initializing them twice.
+ struct {
+ std::size_t num_buffers = 0;
+ std::array<const vk::Buffer*, Maxwell::NumVertexArrays> buffer_ptrs;
+ std::array<vk::DeviceSize, Maxwell::NumVertexArrays> offsets;
+ } vertex;
+
+ struct {
+ const vk::Buffer* buffer = nullptr;
+ vk::DeviceSize offset;
+ vk::IndexType type;
+ } index;
+
+ template <std::size_t N>
+ void BindStatic(VKScheduler& scheduler) const {
+ if (index.buffer != nullptr) {
+ BindStatic<N, true>(scheduler);
+ } else {
+ BindStatic<N, false>(scheduler);
+ }
+ }
+
+ template <std::size_t N, bool is_indexed>
+ void BindStatic(VKScheduler& scheduler) const {
+ static_assert(N <= Maxwell::NumVertexArrays);
+ if constexpr (N == 0) {
+ return;
+ }
+
+ std::array<vk::Buffer, N> buffers;
+ std::transform(vertex.buffer_ptrs.begin(), vertex.buffer_ptrs.begin() + N, buffers.begin(),
+ [](const auto ptr) { return *ptr; });
+
+ std::array<vk::DeviceSize, N> offsets;
+ std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
+
+ if constexpr (is_indexed) {
+ // Indexed draw
+ scheduler.Record([buffers, offsets, index_buffer = *index.buffer,
+ index_offset = index.offset,
+ index_type = index.type](auto cmdbuf, auto& dld) {
+ cmdbuf.bindIndexBuffer(index_buffer, index_offset, index_type, dld);
+ cmdbuf.bindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data(),
+ dld);
+ });
+ } else {
+ // Array draw
+ scheduler.Record([buffers, offsets](auto cmdbuf, auto& dld) {
+ cmdbuf.bindVertexBuffers(0, static_cast<u32>(N), buffers.data(), offsets.data(),
+ dld);
+ });
+ }
+ }
+};
+
+void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf,
+ const vk::DispatchLoaderDynamic& dld) const {
+ if (is_indexed) {
+ cmdbuf.drawIndexed(num_vertices, num_instances, 0, base_vertex, base_instance, dld);
+ } else {
+ cmdbuf.draw(num_vertices, num_instances, base_vertex, base_instance, dld);
+ }
+}
+
+RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer,
+ VKScreenInfo& screen_info, const VKDevice& device,
+ VKResourceManager& resource_manager,
+ VKMemoryManager& memory_manager, VKScheduler& scheduler)
+ : RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer},
+ screen_info{screen_info}, device{device}, resource_manager{resource_manager},
+ memory_manager{memory_manager}, scheduler{scheduler},
+ staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
+ update_descriptor_queue(device, scheduler),
+ quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
+ uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
+ texture_cache(system, *this, device, resource_manager, memory_manager, scheduler,
+ staging_pool),
+ pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue),
+ buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
+ sampler_cache(device) {}
+
+RasterizerVulkan::~RasterizerVulkan() = default;
+
+bool RasterizerVulkan::DrawBatch(bool is_indexed) {
+ Draw(is_indexed, false);
+ return true;
+}
+
+bool RasterizerVulkan::DrawMultiBatch(bool is_indexed) {
+ Draw(is_indexed, true);
+ return true;
+}
+
+void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
+ MICROPROFILE_SCOPE(Vulkan_Drawing);
+
+ FlushWork();
+
+ const auto& gpu = system.GPU().Maxwell3D();
+ GraphicsPipelineCacheKey key{GetFixedPipelineState(gpu.regs)};
+
+ buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
+
+ BufferBindings buffer_bindings;
+ const DrawParameters draw_params =
+ SetupGeometry(key.fixed_state, buffer_bindings, is_indexed, is_instanced);
+
+ update_descriptor_queue.Acquire();
+ sampled_views.clear();
+ image_views.clear();
+
+ const auto shaders = pipeline_cache.GetShaders();
+ key.shaders = GetShaderAddresses(shaders);
+ SetupShaderDescriptors(shaders);
+
+ buffer_cache.Unmap();
+
+ const auto texceptions = UpdateAttachments();
+ SetupImageTransitions(texceptions, color_attachments, zeta_attachment);
+
+ key.renderpass_params = GetRenderPassParams(texceptions);
+
+ auto& pipeline = pipeline_cache.GetGraphicsPipeline(key);
+ scheduler.BindGraphicsPipeline(pipeline.GetHandle());
+
+ const auto renderpass = pipeline.GetRenderPass();
+ const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass);
+ scheduler.RequestRenderpass({renderpass, framebuffer, {{0, 0}, render_area}, 0, nullptr});
+
+ UpdateDynamicStates();
+
+ buffer_bindings.Bind(scheduler);
+
+ if (device.IsNvDeviceDiagnosticCheckpoints()) {
+ scheduler.Record(
+ [&pipeline](auto cmdbuf, auto& dld) { cmdbuf.setCheckpointNV(&pipeline, dld); });
+ }
+
+ const auto pipeline_layout = pipeline.GetLayout();
+ const auto descriptor_set = pipeline.CommitDescriptorSet();
+ scheduler.Record([pipeline_layout, descriptor_set, draw_params](auto cmdbuf, auto& dld) {
+ if (descriptor_set) {
+ cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline_layout,
+ DESCRIPTOR_SET, 1, &descriptor_set, 0, nullptr, dld);
+ }
+ draw_params.Draw(cmdbuf, dld);
+ });
+}
+
+void RasterizerVulkan::Clear() {
+ MICROPROFILE_SCOPE(Vulkan_Clearing);
+
+ const auto& gpu = system.GPU().Maxwell3D();
+ if (!system.GPU().Maxwell3D().ShouldExecute()) {
+ return;
+ }
+
+ const auto& regs = gpu.regs;
+ const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
+ regs.clear_buffers.A;
+ const bool use_depth = regs.clear_buffers.Z;
+ const bool use_stencil = regs.clear_buffers.S;
+ if (!use_color && !use_depth && !use_stencil) {
+ return;
+ }
+ // Clearing images requires to be out of a renderpass
+ scheduler.RequestOutsideRenderPassOperationContext();
+
+ // TODO(Rodrigo): Implement clears rendering a quad or using beginning a renderpass.
+
+ if (use_color) {
+ View color_view;
+ {
+ MICROPROFILE_SCOPE(Vulkan_RenderTargets);
+ color_view = texture_cache.GetColorBufferSurface(regs.clear_buffers.RT.Value(), false);
+ }
+
+ color_view->Transition(vk::ImageLayout::eTransferDstOptimal,
+ vk::PipelineStageFlagBits::eTransfer,
+ vk::AccessFlagBits::eTransferWrite);
+
+ const std::array clear_color = {regs.clear_color[0], regs.clear_color[1],
+ regs.clear_color[2], regs.clear_color[3]};
+ const vk::ClearColorValue clear(clear_color);
+ scheduler.Record([image = color_view->GetImage(),
+ subresource = color_view->GetImageSubresourceRange(),
+ clear](auto cmdbuf, auto& dld) {
+ cmdbuf.clearColorImage(image, vk::ImageLayout::eTransferDstOptimal, clear, subresource,
+ dld);
+ });
+ }
+ if (use_depth || use_stencil) {
+ View zeta_surface;
+ {
+ MICROPROFILE_SCOPE(Vulkan_RenderTargets);
+ zeta_surface = texture_cache.GetDepthBufferSurface(false);
+ }
+
+ zeta_surface->Transition(vk::ImageLayout::eTransferDstOptimal,
+ vk::PipelineStageFlagBits::eTransfer,
+ vk::AccessFlagBits::eTransferWrite);
+
+ const vk::ClearDepthStencilValue clear(regs.clear_depth,
+ static_cast<u32>(regs.clear_stencil));
+ scheduler.Record([image = zeta_surface->GetImage(),
+ subresource = zeta_surface->GetImageSubresourceRange(),
+ clear](auto cmdbuf, auto& dld) {
+ cmdbuf.clearDepthStencilImage(image, vk::ImageLayout::eTransferDstOptimal, clear,
+ subresource, dld);
+ });
+ }
+}
+
+void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
+ MICROPROFILE_SCOPE(Vulkan_Compute);
+ update_descriptor_queue.Acquire();
+ sampled_views.clear();
+ image_views.clear();
+
+ const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ const ComputePipelineCacheKey key{
+ code_addr,
+ launch_desc.shared_alloc,
+ {launch_desc.block_dim_x, launch_desc.block_dim_y, launch_desc.block_dim_z}};
+ auto& pipeline = pipeline_cache.GetComputePipeline(key);
+
+ // Compute dispatches can't be executed inside a renderpass
+ scheduler.RequestOutsideRenderPassOperationContext();
+
+ buffer_cache.Map(CalculateComputeStreamBufferSize());
+
+ const auto& entries = pipeline.GetEntries();
+ SetupComputeConstBuffers(entries);
+ SetupComputeGlobalBuffers(entries);
+ SetupComputeTexelBuffers(entries);
+ SetupComputeTextures(entries);
+ SetupComputeImages(entries);
+
+ buffer_cache.Unmap();
+
+ TransitionImages(sampled_views, vk::PipelineStageFlagBits::eComputeShader,
+ vk::AccessFlagBits::eShaderRead);
+ TransitionImages(image_views, vk::PipelineStageFlagBits::eComputeShader,
+ vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite);
+
+ if (device.IsNvDeviceDiagnosticCheckpoints()) {
+ scheduler.Record(
+ [&pipeline](auto cmdbuf, auto& dld) { cmdbuf.setCheckpointNV(nullptr, dld); });
+ }
+
+ scheduler.Record([grid_x = launch_desc.grid_dim_x, grid_y = launch_desc.grid_dim_y,
+ grid_z = launch_desc.grid_dim_z, pipeline_handle = pipeline.GetHandle(),
+ layout = pipeline.GetLayout(),
+ descriptor_set = pipeline.CommitDescriptorSet()](auto cmdbuf, auto& dld) {
+ cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline_handle, dld);
+ cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, layout, DESCRIPTOR_SET, 1,
+ &descriptor_set, 0, nullptr, dld);
+ cmdbuf.dispatch(grid_x, grid_y, grid_z, dld);
+ });
+}
+
+void RasterizerVulkan::FlushAll() {}
+
+void RasterizerVulkan::FlushRegion(CacheAddr addr, u64 size) {
+ texture_cache.FlushRegion(addr, size);
+ buffer_cache.FlushRegion(addr, size);
+}
+
+void RasterizerVulkan::InvalidateRegion(CacheAddr addr, u64 size) {
+ texture_cache.InvalidateRegion(addr, size);
+ pipeline_cache.InvalidateRegion(addr, size);
+ buffer_cache.InvalidateRegion(addr, size);
+}
+
+void RasterizerVulkan::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
+ FlushRegion(addr, size);
+ InvalidateRegion(addr, size);
+}
+
+void RasterizerVulkan::FlushCommands() {
+ if (draw_counter > 0) {
+ draw_counter = 0;
+ scheduler.Flush();
+ }
+}
+
+void RasterizerVulkan::TickFrame() {
+ draw_counter = 0;
+ update_descriptor_queue.TickFrame();
+ buffer_cache.TickFrame();
+ staging_pool.TickFrame();
+}
+
+bool RasterizerVulkan::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+ const Tegra::Engines::Fermi2D::Config& copy_config) {
+ texture_cache.DoFermiCopy(src, dst, copy_config);
+ return true;
+}
+
+bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
+ VAddr framebuffer_addr, u32 pixel_stride) {
+ if (!framebuffer_addr) {
+ return false;
+ }
+
+ const u8* host_ptr{system.Memory().GetPointer(framebuffer_addr)};
+ const auto surface{texture_cache.TryFindFramebufferSurface(host_ptr)};
+ if (!surface) {
+ return false;
+ }
+
+ // Verify that the cached surface is the same size and format as the requested framebuffer
+ const auto& params{surface->GetSurfaceParams()};
+ const auto& pixel_format{
+ VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)};
+ ASSERT_MSG(params.width == config.width, "Framebuffer width is different");
+ ASSERT_MSG(params.height == config.height, "Framebuffer height is different");
+
+ screen_info.image = &surface->GetImage();
+ screen_info.width = params.width;
+ screen_info.height = params.height;
+ screen_info.is_srgb = surface->GetSurfaceParams().srgb_conversion;
+ return true;
+}
+
+void RasterizerVulkan::FlushWork() {
+ static constexpr u32 DRAWS_TO_DISPATCH = 4096;
+
+ // Only check multiples of 8 draws
+ static_assert(DRAWS_TO_DISPATCH % 8 == 0);
+ if ((++draw_counter & 7) != 7) {
+ return;
+ }
+
+ if (draw_counter < DRAWS_TO_DISPATCH) {
+ // Send recorded tasks to the worker thread
+ scheduler.DispatchWork();
+ return;
+ }
+
+ // Otherwise (every certain number of draws) flush execution.
+ // This submits commands to the Vulkan driver.
+ scheduler.Flush();
+ draw_counter = 0;
+}
+
+RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments() {
+ MICROPROFILE_SCOPE(Vulkan_RenderTargets);
+ auto& dirty = system.GPU().Maxwell3D().dirty;
+ const bool update_rendertargets = dirty.render_settings;
+ dirty.render_settings = false;
+
+ texture_cache.GuardRenderTargets(true);
+
+ Texceptions texceptions;
+ for (std::size_t rt = 0; rt < Maxwell::NumRenderTargets; ++rt) {
+ if (update_rendertargets) {
+ color_attachments[rt] = texture_cache.GetColorBufferSurface(rt, true);
+ }
+ if (color_attachments[rt] && WalkAttachmentOverlaps(*color_attachments[rt])) {
+ texceptions.set(rt);
+ }
+ }
+
+ if (update_rendertargets) {
+ zeta_attachment = texture_cache.GetDepthBufferSurface(true);
+ }
+ if (zeta_attachment && WalkAttachmentOverlaps(*zeta_attachment)) {
+ texceptions.set(ZETA_TEXCEPTION_INDEX);
+ }
+
+ texture_cache.GuardRenderTargets(false);
+
+ return texceptions;
+}
+
+bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachment) {
+ bool overlap = false;
+ for (auto& [view, layout] : sampled_views) {
+ if (!attachment.IsSameSurface(*view)) {
+ continue;
+ }
+ overlap = true;
+ *layout = vk::ImageLayout::eGeneral;
+ }
+ return overlap;
+}
+
+std::tuple<vk::Framebuffer, vk::Extent2D> RasterizerVulkan::ConfigureFramebuffers(
+ vk::RenderPass renderpass) {
+ FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(),
+ std::numeric_limits<u32>::max()};
+
+ const auto MarkAsModifiedAndPush = [&](const View& view) {
+ if (view == nullptr) {
+ return false;
+ }
+ key.views.push_back(view->GetHandle());
+ key.width = std::min(key.width, view->GetWidth());
+ key.height = std::min(key.height, view->GetHeight());
+ return true;
+ };
+
+ for (std::size_t index = 0; index < std::size(color_attachments); ++index) {
+ if (MarkAsModifiedAndPush(color_attachments[index])) {
+ texture_cache.MarkColorBufferInUse(index);
+ }
+ }
+ if (MarkAsModifiedAndPush(zeta_attachment)) {
+ texture_cache.MarkDepthBufferInUse();
+ }
+
+ const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key);
+ auto& framebuffer = fbentry->second;
+ if (is_cache_miss) {
+ const vk::FramebufferCreateInfo framebuffer_ci({}, key.renderpass,
+ static_cast<u32>(key.views.size()),
+ key.views.data(), key.width, key.height, 1);
+ const auto dev = device.GetLogical();
+ const auto& dld = device.GetDispatchLoader();
+ framebuffer = dev.createFramebufferUnique(framebuffer_ci, nullptr, dld);
+ }
+
+ return {*framebuffer, vk::Extent2D{key.width, key.height}};
+}
+
+RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineState& fixed_state,
+ BufferBindings& buffer_bindings,
+ bool is_indexed,
+ bool is_instanced) {
+ MICROPROFILE_SCOPE(Vulkan_Geometry);
+
+ const auto& gpu = system.GPU().Maxwell3D();
+ const auto& regs = gpu.regs;
+
+ SetupVertexArrays(fixed_state.vertex_input, buffer_bindings);
+
+ const u32 base_instance = regs.vb_base_instance;
+ const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
+ const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first;
+ const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count;
+
+ DrawParameters params{base_instance, num_instances, base_vertex, num_vertices, is_indexed};
+ SetupIndexBuffer(buffer_bindings, params, is_indexed);
+
+ return params;
+}
+
+void RasterizerVulkan::SetupShaderDescriptors(
+ const std::array<Shader, Maxwell::MaxShaderProgram>& shaders) {
+ texture_cache.GuardSamplers(true);
+
+ for (std::size_t stage = 0; stage < Maxwell::MaxShaderStage; ++stage) {
+ // Skip VertexA stage
+ const auto& shader = shaders[stage + 1];
+ if (!shader) {
+ continue;
+ }
+ const auto& entries = shader->GetEntries();
+ SetupGraphicsConstBuffers(entries, stage);
+ SetupGraphicsGlobalBuffers(entries, stage);
+ SetupGraphicsTexelBuffers(entries, stage);
+ SetupGraphicsTextures(entries, stage);
+ SetupGraphicsImages(entries, stage);
+ }
+ texture_cache.GuardSamplers(false);
+}
+
+void RasterizerVulkan::SetupImageTransitions(
+ Texceptions texceptions, const std::array<View, Maxwell::NumRenderTargets>& color_attachments,
+ const View& zeta_attachment) {
+ TransitionImages(sampled_views, vk::PipelineStageFlagBits::eAllGraphics,
+ vk::AccessFlagBits::eShaderRead);
+ TransitionImages(image_views, vk::PipelineStageFlagBits::eAllGraphics,
+ vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite);
+
+ for (std::size_t rt = 0; rt < std::size(color_attachments); ++rt) {
+ const auto color_attachment = color_attachments[rt];
+ if (color_attachment == nullptr) {
+ continue;
+ }
+ const auto image_layout =
+ texceptions[rt] ? vk::ImageLayout::eGeneral : vk::ImageLayout::eColorAttachmentOptimal;
+ color_attachment->Transition(
+ image_layout, vk::PipelineStageFlagBits::eColorAttachmentOutput,
+ vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
+ }
+
+ if (zeta_attachment != nullptr) {
+ const auto image_layout = texceptions[ZETA_TEXCEPTION_INDEX]
+ ? vk::ImageLayout::eGeneral
+ : vk::ImageLayout::eDepthStencilAttachmentOptimal;
+ zeta_attachment->Transition(image_layout, vk::PipelineStageFlagBits::eLateFragmentTests,
+ vk::AccessFlagBits::eDepthStencilAttachmentRead |
+ vk::AccessFlagBits::eDepthStencilAttachmentWrite);
+ }
+}
+
+void RasterizerVulkan::UpdateDynamicStates() {
+ auto& gpu = system.GPU().Maxwell3D();
+ UpdateViewportsState(gpu);
+ UpdateScissorsState(gpu);
+ UpdateDepthBias(gpu);
+ UpdateBlendConstants(gpu);
+ UpdateDepthBounds(gpu);
+ UpdateStencilFaces(gpu);
+}
+
+void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
+ BufferBindings& buffer_bindings) {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+
+ for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexAttributes); ++index) {
+ const auto& attrib = regs.vertex_attrib_format[index];
+ if (!attrib.IsValid()) {
+ continue;
+ }
+
+ const auto& buffer = regs.vertex_array[attrib.buffer];
+ ASSERT(buffer.IsEnabled());
+
+ vertex_input.attributes[vertex_input.num_attributes++] =
+ FixedPipelineState::VertexAttribute(index, attrib.buffer, attrib.type, attrib.size,
+ attrib.offset);
+ }
+
+ for (u32 index = 0; index < static_cast<u32>(Maxwell::NumVertexArrays); ++index) {
+ const auto& vertex_array = regs.vertex_array[index];
+ if (!vertex_array.IsEnabled()) {
+ continue;
+ }
+
+ const GPUVAddr start{vertex_array.StartAddress()};
+ const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
+
+ ASSERT(end > start);
+ const std::size_t size{end - start + 1};
+ const auto [buffer, offset] = buffer_cache.UploadMemory(start, size);
+
+ vertex_input.bindings[vertex_input.num_bindings++] = FixedPipelineState::VertexBinding(
+ index, vertex_array.stride,
+ regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0);
+ buffer_bindings.AddVertexBinding(buffer, offset);
+ }
+}
+
+void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params,
+ bool is_indexed) {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ switch (regs.draw.topology) {
+ case Maxwell::PrimitiveTopology::Quads:
+ if (params.is_indexed) {
+ UNIMPLEMENTED();
+ } else {
+ const auto [buffer, offset] =
+ quad_array_pass.Assemble(params.num_vertices, params.base_vertex);
+ buffer_bindings.SetIndexBinding(&buffer, offset, vk::IndexType::eUint32);
+ params.base_vertex = 0;
+ params.num_vertices = params.num_vertices * 6 / 4;
+ params.is_indexed = true;
+ }
+ break;
+ default: {
+ if (!is_indexed) {
+ break;
+ }
+ const GPUVAddr gpu_addr = regs.index_array.IndexStart();
+ auto [buffer, offset] = buffer_cache.UploadMemory(gpu_addr, CalculateIndexBufferSize());
+
+ auto format = regs.index_array.format;
+ const bool is_uint8 = format == Maxwell::IndexFormat::UnsignedByte;
+ if (is_uint8 && !device.IsExtIndexTypeUint8Supported()) {
+ std::tie(buffer, offset) = uint8_pass.Assemble(params.num_vertices, *buffer, offset);
+ format = Maxwell::IndexFormat::UnsignedShort;
+ }
+
+ buffer_bindings.SetIndexBinding(buffer, offset, MaxwellToVK::IndexFormat(device, format));
+ break;
+ }
+ }
+}
+
+void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
+ const auto& gpu = system.GPU().Maxwell3D();
+ const auto& shader_stage = gpu.state.shader_stages[stage];
+ for (const auto& entry : entries.const_buffers) {
+ SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]);
+ }
+}
+
+void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
+ auto& gpu{system.GPU()};
+ const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]};
+
+ for (const auto& entry : entries.global_buffers) {
+ const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset();
+ SetupGlobalBuffer(entry, addr);
+ }
+}
+
+void RasterizerVulkan::SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ const auto& gpu = system.GPU().Maxwell3D();
+ for (const auto& entry : entries.texel_buffers) {
+ const auto image = GetTextureInfo(gpu, entry, stage).tic;
+ SetupTexelBuffer(image, entry);
+ }
+}
+
+void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ const auto& gpu = system.GPU().Maxwell3D();
+ for (const auto& entry : entries.samplers) {
+ const auto texture = GetTextureInfo(gpu, entry, stage);
+ SetupTexture(texture, entry);
+ }
+}
+
+void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) {
+ MICROPROFILE_SCOPE(Vulkan_Images);
+ const auto& gpu = system.GPU().KeplerCompute();
+ for (const auto& entry : entries.images) {
+ const auto tic = GetTextureInfo(gpu, entry, stage).tic;
+ SetupImage(tic, entry);
+ }
+}
+
+void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
+ const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
+ for (const auto& entry : entries.const_buffers) {
+ const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
+ const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
+ Tegra::Engines::ConstBufferInfo buffer;
+ buffer.address = config.Address();
+ buffer.size = config.size;
+ buffer.enabled = mask[entry.GetIndex()];
+ SetupConstBuffer(entry, buffer);
+ }
+}
+
+void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
+ const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config};
+ for (const auto& entry : entries.global_buffers) {
+ const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
+ SetupGlobalBuffer(entry, addr);
+ }
+}
+
+void RasterizerVulkan::SetupComputeTexelBuffers(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ const auto& gpu = system.GPU().KeplerCompute();
+ for (const auto& entry : entries.texel_buffers) {
+ const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ SetupTexelBuffer(image, entry);
+ }
+}
+
+void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_Textures);
+ const auto& gpu = system.GPU().KeplerCompute();
+ for (const auto& entry : entries.samplers) {
+ const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex);
+ SetupTexture(texture, entry);
+ }
+}
+
+void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) {
+ MICROPROFILE_SCOPE(Vulkan_Images);
+ const auto& gpu = system.GPU().KeplerCompute();
+ for (const auto& entry : entries.images) {
+ const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
+ SetupImage(tic, entry);
+ }
+}
+
+void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
+ const Tegra::Engines::ConstBufferInfo& buffer) {
+ // Align the size to avoid bad std140 interactions
+ const std::size_t size =
+ Common::AlignUp(CalculateConstBufferSize(entry, buffer), 4 * sizeof(float));
+ ASSERT(size <= MaxConstbufferSize);
+
+ const auto [buffer_handle, offset] =
+ buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment());
+
+ update_descriptor_queue.AddBuffer(buffer_handle, offset, size);
+}
+
+void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) {
+ auto& memory_manager{system.GPU().MemoryManager()};
+ const auto actual_addr = memory_manager.Read<u64>(address);
+ const auto size = memory_manager.Read<u32>(address + 8);
+
+ if (size == 0) {
+ // Sometimes global memory pointers don't have a proper size. Upload a dummy entry because
+ // Vulkan doesn't like empty buffers.
+ constexpr std::size_t dummy_size = 4;
+ const auto buffer = buffer_cache.GetEmptyBuffer(dummy_size);
+ update_descriptor_queue.AddBuffer(buffer, 0, dummy_size);
+ return;
+ }
+
+ const auto [buffer, offset] = buffer_cache.UploadMemory(
+ actual_addr, size, device.GetStorageBufferAlignment(), entry.IsWritten());
+ update_descriptor_queue.AddBuffer(buffer, offset, size);
+}
+
+void RasterizerVulkan::SetupTexelBuffer(const Tegra::Texture::TICEntry& tic,
+ const TexelBufferEntry& entry) {
+ const auto view = texture_cache.GetTextureSurface(tic, entry);
+ ASSERT(view->IsBufferView());
+
+ update_descriptor_queue.AddTexelBuffer(view->GetBufferView());
+}
+
+void RasterizerVulkan::SetupTexture(const Tegra::Texture::FullTextureInfo& texture,
+ const SamplerEntry& entry) {
+ auto view = texture_cache.GetTextureSurface(texture.tic, entry);
+ ASSERT(!view->IsBufferView());
+
+ const auto image_view = view->GetHandle(texture.tic.x_source, texture.tic.y_source,
+ texture.tic.z_source, texture.tic.w_source);
+ const auto sampler = sampler_cache.GetSampler(texture.tsc);
+ update_descriptor_queue.AddSampledImage(sampler, image_view);
+
+ const auto image_layout = update_descriptor_queue.GetLastImageLayout();
+ *image_layout = vk::ImageLayout::eShaderReadOnlyOptimal;
+ sampled_views.push_back(ImageView{std::move(view), image_layout});
+}
+
+void RasterizerVulkan::SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry) {
+ auto view = texture_cache.GetImageSurface(tic, entry);
+
+ if (entry.IsWritten()) {
+ view->MarkAsModified(texture_cache.Tick());
+ }
+
+ UNIMPLEMENTED_IF(tic.IsBuffer());
+
+ const auto image_view = view->GetHandle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
+ update_descriptor_queue.AddImage(image_view);
+
+ const auto image_layout = update_descriptor_queue.GetLastImageLayout();
+ *image_layout = vk::ImageLayout::eGeneral;
+ image_views.push_back(ImageView{std::move(view), image_layout});
+}
+
+void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.viewport_transform && scheduler.TouchViewports()) {
+ return;
+ }
+ gpu.dirty.viewport_transform = false;
+ const auto& regs = gpu.regs;
+ const std::array viewports{
+ GetViewportState(device, regs, 0), GetViewportState(device, regs, 1),
+ GetViewportState(device, regs, 2), GetViewportState(device, regs, 3),
+ GetViewportState(device, regs, 4), GetViewportState(device, regs, 5),
+ GetViewportState(device, regs, 6), GetViewportState(device, regs, 7),
+ GetViewportState(device, regs, 8), GetViewportState(device, regs, 9),
+ GetViewportState(device, regs, 10), GetViewportState(device, regs, 11),
+ GetViewportState(device, regs, 12), GetViewportState(device, regs, 13),
+ GetViewportState(device, regs, 14), GetViewportState(device, regs, 15)};
+ scheduler.Record([viewports](auto cmdbuf, auto& dld) {
+ cmdbuf.setViewport(0, static_cast<u32>(viewports.size()), viewports.data(), dld);
+ });
+}
+
+void RasterizerVulkan::UpdateScissorsState(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.scissor_test && scheduler.TouchScissors()) {
+ return;
+ }
+ gpu.dirty.scissor_test = false;
+ const auto& regs = gpu.regs;
+ const std::array scissors = {
+ GetScissorState(regs, 0), GetScissorState(regs, 1), GetScissorState(regs, 2),
+ GetScissorState(regs, 3), GetScissorState(regs, 4), GetScissorState(regs, 5),
+ GetScissorState(regs, 6), GetScissorState(regs, 7), GetScissorState(regs, 8),
+ GetScissorState(regs, 9), GetScissorState(regs, 10), GetScissorState(regs, 11),
+ GetScissorState(regs, 12), GetScissorState(regs, 13), GetScissorState(regs, 14),
+ GetScissorState(regs, 15)};
+ scheduler.Record([scissors](auto cmdbuf, auto& dld) {
+ cmdbuf.setScissor(0, static_cast<u32>(scissors.size()), scissors.data(), dld);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthBias(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.polygon_offset && scheduler.TouchDepthBias()) {
+ return;
+ }
+ gpu.dirty.polygon_offset = false;
+ const auto& regs = gpu.regs;
+ scheduler.Record([constant = regs.polygon_offset_units, clamp = regs.polygon_offset_clamp,
+ factor = regs.polygon_offset_factor](auto cmdbuf, auto& dld) {
+ cmdbuf.setDepthBias(constant, clamp, factor / 2.0f, dld);
+ });
+}
+
+void RasterizerVulkan::UpdateBlendConstants(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.blend_state && scheduler.TouchBlendConstants()) {
+ return;
+ }
+ gpu.dirty.blend_state = false;
+ const std::array blend_color = {gpu.regs.blend_color.r, gpu.regs.blend_color.g,
+ gpu.regs.blend_color.b, gpu.regs.blend_color.a};
+ scheduler.Record([blend_color](auto cmdbuf, auto& dld) {
+ cmdbuf.setBlendConstants(blend_color.data(), dld);
+ });
+}
+
+void RasterizerVulkan::UpdateDepthBounds(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.depth_bounds_values && scheduler.TouchDepthBounds()) {
+ return;
+ }
+ gpu.dirty.depth_bounds_values = false;
+ const auto& regs = gpu.regs;
+ scheduler.Record([min = regs.depth_bounds[0], max = regs.depth_bounds[1]](
+ auto cmdbuf, auto& dld) { cmdbuf.setDepthBounds(min, max, dld); });
+}
+
+void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D& gpu) {
+ if (!gpu.dirty.stencil_test && scheduler.TouchStencilValues()) {
+ return;
+ }
+ gpu.dirty.stencil_test = false;
+ const auto& regs = gpu.regs;
+ if (regs.stencil_two_side_enable) {
+ // Separate values per face
+ scheduler.Record(
+ [front_ref = regs.stencil_front_func_ref, front_write_mask = regs.stencil_front_mask,
+ front_test_mask = regs.stencil_front_func_mask, back_ref = regs.stencil_back_func_ref,
+ back_write_mask = regs.stencil_back_mask,
+ back_test_mask = regs.stencil_back_func_mask](auto cmdbuf, auto& dld) {
+ // Front face
+ cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFront, front_ref, dld);
+ cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFront, front_write_mask, dld);
+ cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, front_test_mask, dld);
+
+ // Back face
+ cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eBack, back_ref, dld);
+ cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eBack, back_write_mask, dld);
+ cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eBack, back_test_mask, dld);
+ });
+ } else {
+ // Front face defines both faces
+ scheduler.Record([ref = regs.stencil_back_func_ref, write_mask = regs.stencil_back_mask,
+ test_mask = regs.stencil_back_func_mask](auto cmdbuf, auto& dld) {
+ cmdbuf.setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, ref, dld);
+ cmdbuf.setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, write_mask, dld);
+ cmdbuf.setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, test_mask, dld);
+ });
+ }
+}
+
+std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
+ std::size_t size = CalculateVertexArraysSize();
+ if (is_indexed) {
+ size = Common::AlignUp(size, 4) + CalculateIndexBufferSize();
+ }
+ size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment());
+ return size;
+}
+
+std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const {
+ return Tegra::Engines::KeplerCompute::NumConstBuffers *
+ (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment());
+}
+
+std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+
+ std::size_t size = 0;
+ for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ // This implementation assumes that all attributes are used in the shader.
+ const GPUVAddr start{regs.vertex_array[index].StartAddress()};
+ const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
+ DEBUG_ASSERT(end > start);
+
+ size += (end - start + 1) * regs.vertex_array[index].enable;
+ }
+ return size;
+}
+
+std::size_t RasterizerVulkan::CalculateIndexBufferSize() const {
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ return static_cast<std::size_t>(regs.index_array.count) *
+ static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
+}
+
+std::size_t RasterizerVulkan::CalculateConstBufferSize(
+ const ConstBufferEntry& entry, const Tegra::Engines::ConstBufferInfo& buffer) const {
+ if (entry.IsIndirect()) {
+ // Buffer is accessed indirectly, so upload the entire thing
+ return buffer.size;
+ } else {
+ // Buffer is accessed directly, upload just what we use
+ return entry.GetSize();
+ }
+}
+
+RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const {
+ using namespace VideoCore::Surface;
+
+ const auto& regs = system.GPU().Maxwell3D().regs;
+ RenderPassParams renderpass_params;
+
+ for (std::size_t rt = 0; rt < static_cast<std::size_t>(regs.rt_control.count); ++rt) {
+ const auto& rendertarget = regs.rt[rt];
+ if (rendertarget.Address() == 0 || rendertarget.format == Tegra::RenderTargetFormat::NONE)
+ continue;
+ renderpass_params.color_attachments.push_back(RenderPassParams::ColorAttachment{
+ static_cast<u32>(rt), PixelFormatFromRenderTargetFormat(rendertarget.format),
+ texceptions.test(rt)});
+ }
+
+ renderpass_params.has_zeta = regs.zeta_enable;
+ if (renderpass_params.has_zeta) {
+ renderpass_params.zeta_pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
+ renderpass_params.zeta_texception = texceptions[ZETA_TEXCEPTION_INDEX];
+ }
+
+ return renderpass_params;
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index fc324952b..7be71e734 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -4,10 +4,260 @@
#pragma once
+#include <array>
+#include <bitset>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <boost/container/static_vector.hpp>
+#include <boost/functional/hash.hpp>
+
+#include "common/common_types.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_accelerated.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
+#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/renderer_vulkan/vk_memory_manager.h"
+#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
+#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
+#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_sampler_cache.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/renderer_vulkan/vk_texture_cache.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Frontend {
+class EmuWindow;
+}
+
+namespace Tegra::Engines {
+class Maxwell3D;
+}
+
+namespace Vulkan {
+
+struct VKScreenInfo;
+
+using ImageViewsPack =
+ boost::container::static_vector<vk::ImageView, Maxwell::NumRenderTargets + 1>;
+
+struct FramebufferCacheKey {
+ vk::RenderPass renderpass{};
+ u32 width = 0;
+ u32 height = 0;
+ ImageViewsPack views;
+
+ std::size_t Hash() const noexcept {
+ std::size_t hash = 0;
+ boost::hash_combine(hash, static_cast<VkRenderPass>(renderpass));
+ for (const auto& view : views) {
+ boost::hash_combine(hash, static_cast<VkImageView>(view));
+ }
+ boost::hash_combine(hash, width);
+ boost::hash_combine(hash, height);
+ return hash;
+ }
+
+ bool operator==(const FramebufferCacheKey& rhs) const noexcept {
+ return std::tie(renderpass, views, width, height) ==
+ std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height);
+ }
+};
+
+} // namespace Vulkan
+
+namespace std {
+
+template <>
+struct hash<Vulkan::FramebufferCacheKey> {
+ std::size_t operator()(const Vulkan::FramebufferCacheKey& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
namespace Vulkan {
-class RasterizerVulkan : public VideoCore::RasterizerInterface {};
+class BufferBindings;
+
+struct ImageView {
+ View view;
+ vk::ImageLayout* layout = nullptr;
+};
+
+class RasterizerVulkan : public VideoCore::RasterizerAccelerated {
+public:
+ explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
+ VKScreenInfo& screen_info, const VKDevice& device,
+ VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
+ VKScheduler& scheduler);
+ ~RasterizerVulkan() override;
+
+ bool DrawBatch(bool is_indexed) override;
+ bool DrawMultiBatch(bool is_indexed) override;
+ void Clear() override;
+ void DispatchCompute(GPUVAddr code_addr) override;
+ void FlushAll() override;
+ void FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushCommands() override;
+ void TickFrame() override;
+ bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst,
+ const Tegra::Engines::Fermi2D::Config& copy_config) override;
+ bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
+ u32 pixel_stride) override;
+
+ /// Maximum supported size that a constbuffer can have in bytes.
+ static constexpr std::size_t MaxConstbufferSize = 0x10000;
+ static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0,
+ "The maximum size of a constbuffer must be a multiple of the size of GLvec4");
+
+private:
+ struct DrawParameters {
+ void Draw(vk::CommandBuffer cmdbuf, const vk::DispatchLoaderDynamic& dld) const;
+
+ u32 base_instance = 0;
+ u32 num_instances = 0;
+ u32 base_vertex = 0;
+ u32 num_vertices = 0;
+ bool is_indexed = 0;
+ };
+
+ using Texceptions = std::bitset<Maxwell::NumRenderTargets + 1>;
+
+ static constexpr std::size_t ZETA_TEXCEPTION_INDEX = 8;
+
+ void Draw(bool is_indexed, bool is_instanced);
+
+ void FlushWork();
+
+ Texceptions UpdateAttachments();
+
+ std::tuple<vk::Framebuffer, vk::Extent2D> ConfigureFramebuffers(vk::RenderPass renderpass);
+
+ /// Setups geometry buffers and state.
+ DrawParameters SetupGeometry(FixedPipelineState& fixed_state, BufferBindings& buffer_bindings,
+ bool is_indexed, bool is_instanced);
+
+ /// Setup descriptors in the graphics pipeline.
+ void SetupShaderDescriptors(const std::array<Shader, Maxwell::MaxShaderProgram>& shaders);
+
+ void SetupImageTransitions(Texceptions texceptions,
+ const std::array<View, Maxwell::NumRenderTargets>& color_attachments,
+ const View& zeta_attachment);
+
+ void UpdateDynamicStates();
+
+ bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
+
+ void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
+ BufferBindings& buffer_bindings);
+
+ void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
+
+ /// Setup constant buffers in the graphics pipeline.
+ void SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage);
+
+ /// Setup global buffers in the graphics pipeline.
+ void SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage);
+
+ /// Setup texel buffers in the graphics pipeline.
+ void SetupGraphicsTexelBuffers(const ShaderEntries& entries, std::size_t stage);
+
+ /// Setup textures in the graphics pipeline.
+ void SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage);
+
+ /// Setup images in the graphics pipeline.
+ void SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage);
+
+ /// Setup constant buffers in the compute pipeline.
+ void SetupComputeConstBuffers(const ShaderEntries& entries);
+
+ /// Setup global buffers in the compute pipeline.
+ void SetupComputeGlobalBuffers(const ShaderEntries& entries);
+
+ /// Setup texel buffers in the compute pipeline.
+ void SetupComputeTexelBuffers(const ShaderEntries& entries);
+
+ /// Setup textures in the compute pipeline.
+ void SetupComputeTextures(const ShaderEntries& entries);
+
+ /// Setup images in the compute pipeline.
+ void SetupComputeImages(const ShaderEntries& entries);
+
+ void SetupConstBuffer(const ConstBufferEntry& entry,
+ const Tegra::Engines::ConstBufferInfo& buffer);
+
+ void SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address);
+
+ void SetupTexelBuffer(const Tegra::Texture::TICEntry& image, const TexelBufferEntry& entry);
+
+ void SetupTexture(const Tegra::Texture::FullTextureInfo& texture, const SamplerEntry& entry);
+
+ void SetupImage(const Tegra::Texture::TICEntry& tic, const ImageEntry& entry);
+
+ void UpdateViewportsState(Tegra::Engines::Maxwell3D& gpu);
+ void UpdateScissorsState(Tegra::Engines::Maxwell3D& gpu);
+ void UpdateDepthBias(Tegra::Engines::Maxwell3D& gpu);
+ void UpdateBlendConstants(Tegra::Engines::Maxwell3D& gpu);
+ void UpdateDepthBounds(Tegra::Engines::Maxwell3D& gpu);
+ void UpdateStencilFaces(Tegra::Engines::Maxwell3D& gpu);
+
+ std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
+
+ std::size_t CalculateComputeStreamBufferSize() const;
+
+ std::size_t CalculateVertexArraysSize() const;
+
+ std::size_t CalculateIndexBufferSize() const;
+
+ std::size_t CalculateConstBufferSize(const ConstBufferEntry& entry,
+ const Tegra::Engines::ConstBufferInfo& buffer) const;
+
+ RenderPassParams GetRenderPassParams(Texceptions texceptions) const;
+
+ Core::System& system;
+ Core::Frontend::EmuWindow& render_window;
+ VKScreenInfo& screen_info;
+ const VKDevice& device;
+ VKResourceManager& resource_manager;
+ VKMemoryManager& memory_manager;
+ VKScheduler& scheduler;
+
+ VKStagingBufferPool staging_pool;
+ VKDescriptorPool descriptor_pool;
+ VKUpdateDescriptorQueue update_descriptor_queue;
+ QuadArrayPass quad_array_pass;
+ Uint8Pass uint8_pass;
+
+ VKTextureCache texture_cache;
+ VKPipelineCache pipeline_cache;
+ VKBufferCache buffer_cache;
+ VKSamplerCache sampler_cache;
+
+ std::array<View, Maxwell::NumRenderTargets> color_attachments;
+ View zeta_attachment;
+
+ std::vector<ImageView> sampled_views;
+ std::vector<ImageView> image_views;
+
+ u32 draw_counter = 0;
+
+ // TODO(Rodrigo): Invalidate on image destruction
+ std::unordered_map<FramebufferCacheKey, UniqueFramebuffer> framebuffer_cache;
+};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index 8fe852ce8..0cf97cafa 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -1796,6 +1796,11 @@ private:
return {};
}
+ Expression UAtomicAdd(Operation) {
+ UNIMPLEMENTED();
+ return {};
+ }
+
Expression Branch(Operation operation) {
const auto& target = std::get<ImmediateNode>(*operation[0]);
OpStore(jmp_to, Constant(t_uint, target.GetValue()));
@@ -2373,6 +2378,8 @@ private:
&SPIRVDecompiler::AtomicImageXor,
&SPIRVDecompiler::AtomicImageExchange,
+ &SPIRVDecompiler::UAtomicAdd,
+
&SPIRVDecompiler::Branch,
&SPIRVDecompiler::BranchIndirect,
&SPIRVDecompiler::PushFlowStack,
diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp
index 8cc84e935..7591a715f 100644
--- a/src/video_core/shader/decode/memory.cpp
+++ b/src/video_core/shader/decode/memory.cpp
@@ -16,6 +16,8 @@
namespace VideoCommon::Shader {
+using Tegra::Shader::AtomicOp;
+using Tegra::Shader::AtomicType;
using Tegra::Shader::Attribute;
using Tegra::Shader::Instruction;
using Tegra::Shader::OpCode;
@@ -333,6 +335,23 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) {
}
break;
}
+ case OpCode::Id::ATOMS: {
+ UNIMPLEMENTED_IF_MSG(instr.atoms.operation != AtomicOp::Add, "operation={}",
+ static_cast<int>(instr.atoms.operation.Value()));
+ UNIMPLEMENTED_IF_MSG(instr.atoms.type != AtomicType::U32, "type={}",
+ static_cast<int>(instr.atoms.type.Value()));
+
+ const s32 offset = instr.atoms.GetImmediateOffset();
+ Node address = GetRegister(instr.gpr8);
+ address = Operation(OperationCode::IAdd, std::move(address), Immediate(offset));
+
+ Node memory = GetSharedMemory(std::move(address));
+ Node data = GetRegister(instr.gpr20);
+
+ Node value = Operation(OperationCode::UAtomicAdd, std::move(memory), std::move(data));
+ SetRegister(bb, instr.gpr0, std::move(value));
+ break;
+ }
case OpCode::Id::AL2P: {
// Ignore al2p.direction since we don't care about it.
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 4e155542a..075c7d07c 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -162,6 +162,8 @@ enum class OperationCode {
AtomicImageXor, /// (MetaImage, int[N] coords) -> void
AtomicImageExchange, /// (MetaImage, int[N] coords) -> void
+ UAtomicAdd, /// (smem, uint) -> uint
+
Branch, /// (uint branch_target) -> void
BranchIndirect, /// (uint branch_target) -> void
PushFlowStack, /// (uint branch_target) -> void
diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp
index daedbc33e..e43e84d39 100644
--- a/src/yuzu/configuration/configure_gamelist.cpp
+++ b/src/yuzu/configuration/configure_gamelist.cpp
@@ -21,10 +21,8 @@ constexpr std::array default_icon_sizes{
};
constexpr std::array row_text_names{
- QT_TR_NOOP("Filename"),
- QT_TR_NOOP("Filetype"),
- QT_TR_NOOP("Title ID"),
- QT_TR_NOOP("Title Name"),
+ QT_TR_NOOP("Filename"), QT_TR_NOOP("Filetype"), QT_TR_NOOP("Title ID"),
+ QT_TR_NOOP("Title Name"), QT_TR_NOOP("None"),
};
} // Anonymous namespace
@@ -46,6 +44,12 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)
&ConfigureGameList::RequestGameListUpdate);
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureGameList::RequestGameListUpdate);
+
+ // Update text ComboBoxes after user interaction.
+ connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
+ [=]() { ConfigureGameList::UpdateSecondRowComboBox(); });
+ connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
+ [=]() { ConfigureGameList::UpdateFirstRowComboBox(); });
}
ConfigureGameList::~ConfigureGameList() = default;
@@ -68,10 +72,6 @@ void ConfigureGameList::SetConfiguration() {
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
ui->icon_size_combobox->setCurrentIndex(
ui->icon_size_combobox->findData(UISettings::values.icon_size));
- ui->row_1_text_combobox->setCurrentIndex(
- ui->row_1_text_combobox->findData(UISettings::values.row_1_text_id));
- ui->row_2_text_combobox->setCurrentIndex(
- ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id));
}
void ConfigureGameList::changeEvent(QEvent* event) {
@@ -104,10 +104,43 @@ void ConfigureGameList::InitializeIconSizeComboBox() {
}
void ConfigureGameList::InitializeRowComboBoxes() {
- for (std::size_t i = 0; i < row_text_names.size(); ++i) {
- const QString row_text_name = QString::fromUtf8(row_text_names[i]);
+ UpdateFirstRowComboBox(true);
+ UpdateSecondRowComboBox(true);
+}
+
+void ConfigureGameList::UpdateFirstRowComboBox(bool init) {
+ const int currentIndex =
+ init ? UISettings::values.row_1_text_id
+ : ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData());
+ ui->row_1_text_combobox->clear();
+
+ for (std::size_t i = 0; i < row_text_names.size(); i++) {
+ const QString row_text_name = QString::fromUtf8(row_text_names[i]);
ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
+ }
+
+ ui->row_1_text_combobox->setCurrentIndex(ui->row_1_text_combobox->findData(currentIndex));
+
+ ui->row_1_text_combobox->removeItem(4); // None
+ ui->row_1_text_combobox->removeItem(
+ ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData()));
+}
+
+void ConfigureGameList::UpdateSecondRowComboBox(bool init) {
+ const int currentIndex =
+ init ? UISettings::values.row_2_text_id
+ : ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData());
+
+ ui->row_2_text_combobox->clear();
+
+ for (std::size_t i = 0; i < row_text_names.size(); ++i) {
+ const QString row_text_name = QString::fromUtf8(row_text_names[i]);
ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
}
+
+ ui->row_2_text_combobox->setCurrentIndex(ui->row_2_text_combobox->findData(currentIndex));
+
+ ui->row_2_text_combobox->removeItem(
+ ui->row_2_text_combobox->findData(ui->row_1_text_combobox->currentData()));
}
diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h
index e11822919..ecd3fa174 100644
--- a/src/yuzu/configuration/configure_gamelist.h
+++ b/src/yuzu/configuration/configure_gamelist.h
@@ -31,5 +31,8 @@ private:
void InitializeIconSizeComboBox();
void InitializeRowComboBoxes();
+ void UpdateFirstRowComboBox(bool init = false);
+ void UpdateSecondRowComboBox(bool init = false);
+
std::unique_ptr<Ui::ConfigureGameList> ui;
};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1c2b37afd..7cde72d1b 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -108,11 +108,14 @@ public:
}};
const auto& row1 = row_data.at(UISettings::values.row_1_text_id);
- const auto& row2 = row_data.at(UISettings::values.row_2_text_id);
+ const int row2_id = UISettings::values.row_2_text_id;
- if (row1.isEmpty() || row1 == row2)
- return row2;
- if (row2.isEmpty())
+ if (row2_id == 4) // None
+ return row1;
+
+ const auto& row2 = row_data.at(row2_id);
+
+ if (row1 == row2)
return row1;
return QString(row1 + QStringLiteral("\n ") + row2);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b21fbf826..b5dd3e0d6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -526,19 +526,30 @@ void GMainWindow::InitializeHotkeys() {
const QString main_window = QStringLiteral("Main Window");
const QString load_file = QStringLiteral("Load File");
+ const QString load_amiibo = QStringLiteral("Load Amiibo");
const QString exit_yuzu = QStringLiteral("Exit yuzu");
+ const QString restart_emulation = QStringLiteral("Restart Emulation");
const QString stop_emulation = QStringLiteral("Stop Emulation");
const QString toggle_filter_bar = QStringLiteral("Toggle Filter Bar");
const QString toggle_status_bar = QStringLiteral("Toggle Status Bar");
const QString fullscreen = QStringLiteral("Fullscreen");
+ const QString capture_screenshot = QStringLiteral("Capture Screenshot");
ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file));
ui.action_Load_File->setShortcutContext(
hotkey_registry.GetShortcutContext(main_window, load_file));
+ ui.action_Load_Amiibo->setShortcut(hotkey_registry.GetKeySequence(main_window, load_amiibo));
+ ui.action_Load_Amiibo->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, load_amiibo));
+
ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence(main_window, exit_yuzu));
ui.action_Exit->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, exit_yuzu));
+ ui.action_Restart->setShortcut(hotkey_registry.GetKeySequence(main_window, restart_emulation));
+ ui.action_Restart->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, restart_emulation));
+
ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence(main_window, stop_emulation));
ui.action_Stop->setShortcutContext(
hotkey_registry.GetShortcutContext(main_window, stop_emulation));
@@ -553,6 +564,11 @@ void GMainWindow::InitializeHotkeys() {
ui.action_Show_Status_Bar->setShortcutContext(
hotkey_registry.GetShortcutContext(main_window, toggle_status_bar));
+ ui.action_Capture_Screenshot->setShortcut(
+ hotkey_registry.GetKeySequence(main_window, capture_screenshot));
+ ui.action_Capture_Screenshot->setShortcutContext(
+ hotkey_registry.GetShortcutContext(main_window, capture_screenshot));
+
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this),
&QShortcut::activated, this, &GMainWindow::OnMenuLoadFile);
connect(
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 21f422500..a2c9e4547 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -15,7 +15,7 @@
</property>
<property name="windowIcon">
<iconset>
- <normaloff>src/pcafe/res/icon3_64x64.ico</normaloff>src/pcafe/res/icon3_64x64.ico</iconset>
+ <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
@@ -98,6 +98,7 @@
<addaction name="action_Display_Dock_Widget_Headers"/>
<addaction name="action_Show_Filter_Bar"/>
<addaction name="action_Show_Status_Bar"/>
+ <addaction name="separator"/>
<addaction name="menu_View_Debugging"/>
</widget>
<widget class="QMenu" name="menu_Tools">