diff options
| author | 2014-12-21 21:47:25 -0500 | |
|---|---|---|
| committer | 2014-12-21 21:47:25 -0500 | |
| commit | ec1ddc17e29d81b4e5e061ebb1cf0c4044fc03a4 (patch) | |
| tree | 7703d352371e3334388eea0008d2966ba0b56d15 /src | |
| parent | Merge pull request #324 from lioncash/dync (diff) | |
| parent | CFG: Fixed some warnings and errors in Clang (diff) | |
| download | yuzu-ec1ddc17e29d81b4e5e061ebb1cf0c4044fc03a4.tar.gz yuzu-ec1ddc17e29d81b4e5e061ebb1cf0c4044fc03a4.tar.xz yuzu-ec1ddc17e29d81b4e5e061ebb1cf0c4044fc03a4.zip | |
Merge pull request #312 from Subv/still_more_savedata_stuff
CFG: Implemented the GetConfigInfoBlk2 function.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/core/file_sys/archive_backend.h | 5 | ||||
| -rw-r--r-- | src/core/file_sys/archive_systemsavedata.cpp | 10 | ||||
| -rw-r--r-- | src/core/file_sys/archive_systemsavedata.h | 2 | ||||
| -rw-r--r-- | src/core/file_sys/disk_archive.h | 1 | ||||
| -rw-r--r-- | src/core/hle/hle.cpp | 3 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg.cpp | 202 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg.h | 144 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg_i.cpp (renamed from src/core/hle/service/cfg_i.cpp) | 70 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg_i.h (renamed from src/core/hle/service/cfg_i.h) | 0 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg_u.cpp (renamed from src/core/hle/service/cfg_u.cpp) | 78 | ||||
| -rw-r--r-- | src/core/hle/service/cfg/cfg_u.h (renamed from src/core/hle/service/cfg_u.h) | 0 | ||||
| -rw-r--r-- | src/core/hle/service/fs/archive.cpp | 9 | ||||
| -rw-r--r-- | src/core/hle/service/service.cpp | 4 |
14 files changed, 508 insertions, 30 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3381524e3..c00fc3493 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -37,8 +37,9 @@ set(SRCS | |||
| 37 | hle/service/apt_u.cpp | 37 | hle/service/apt_u.cpp |
| 38 | hle/service/boss_u.cpp | 38 | hle/service/boss_u.cpp |
| 39 | hle/service/cecd_u.cpp | 39 | hle/service/cecd_u.cpp |
| 40 | hle/service/cfg_i.cpp | 40 | hle/service/cfg/cfg.cpp |
| 41 | hle/service/cfg_u.cpp | 41 | hle/service/cfg/cfg_i.cpp |
| 42 | hle/service/cfg/cfg_u.cpp | ||
| 42 | hle/service/csnd_snd.cpp | 43 | hle/service/csnd_snd.cpp |
| 43 | hle/service/dsp_dsp.cpp | 44 | hle/service/dsp_dsp.cpp |
| 44 | hle/service/err_f.cpp | 45 | hle/service/err_f.cpp |
| @@ -122,8 +123,9 @@ set(HEADERS | |||
| 122 | hle/service/apt_u.h | 123 | hle/service/apt_u.h |
| 123 | hle/service/boss_u.h | 124 | hle/service/boss_u.h |
| 124 | hle/service/cecd_u.h | 125 | hle/service/cecd_u.h |
| 125 | hle/service/cfg_i.h | 126 | hle/service/cfg/cfg.h |
| 126 | hle/service/cfg_u.h | 127 | hle/service/cfg/cfg_i.h |
| 128 | hle/service/cfg/cfg_u.h | ||
| 127 | hle/service/csnd_snd.h | 129 | hle/service/csnd_snd.h |
| 128 | hle/service/dsp_dsp.h | 130 | hle/service/dsp_dsp.h |
| 129 | hle/service/err_f.h | 131 | hle/service/err_f.h |
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index eb1fdaa1f..e2979be17 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h | |||
| @@ -45,6 +45,11 @@ public: | |||
| 45 | { | 45 | { |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | Path(const char* path): | ||
| 49 | type(Char), string(path) | ||
| 50 | { | ||
| 51 | } | ||
| 52 | |||
| 48 | Path(LowPathType type, u32 size, u32 pointer): | 53 | Path(LowPathType type, u32 size, u32 pointer): |
| 49 | type(type) | 54 | type(type) |
| 50 | { | 55 | { |
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp index 5da1ec946..0da32d510 100644 --- a/src/core/file_sys/archive_systemsavedata.cpp +++ b/src/core/file_sys/archive_systemsavedata.cpp | |||
| @@ -16,8 +16,14 @@ | |||
| 16 | 16 | ||
| 17 | namespace FileSys { | 17 | namespace FileSys { |
| 18 | 18 | ||
| 19 | Archive_SystemSaveData::Archive_SystemSaveData(const std::string& mount_point) | 19 | static std::string GetSystemSaveDataPath(const std::string& mount_point, u64 save_id) { |
| 20 | : DiskArchive(mount_point) { | 20 | u32 save_high = static_cast<u32>((save_id >> 32) & 0xFFFFFFFF); |
| 21 | u32 save_low = static_cast<u32>(save_id & 0xFFFFFFFF); | ||
| 22 | return Common::StringFromFormat("%s%08X/%08X/", mount_point.c_str(), save_low, save_high); | ||
| 23 | } | ||
| 24 | |||
| 25 | Archive_SystemSaveData::Archive_SystemSaveData(const std::string& mount_point, u64 save_id) | ||
| 26 | : DiskArchive(GetSystemSaveDataPath(mount_point, save_id)) { | ||
| 21 | LOG_INFO(Service_FS, "Directory %s set as SystemSaveData.", this->mount_point.c_str()); | 27 | LOG_INFO(Service_FS, "Directory %s set as SystemSaveData.", this->mount_point.c_str()); |
| 22 | } | 28 | } |
| 23 | 29 | ||
diff --git a/src/core/file_sys/archive_systemsavedata.h b/src/core/file_sys/archive_systemsavedata.h index c3ebb7c99..443e27091 100644 --- a/src/core/file_sys/archive_systemsavedata.h +++ b/src/core/file_sys/archive_systemsavedata.h | |||
| @@ -19,7 +19,7 @@ namespace FileSys { | |||
| 19 | /// specifically nand:/data/<ID0>/sysdata/<SaveID-Low>/<SaveID-High> | 19 | /// specifically nand:/data/<ID0>/sysdata/<SaveID-Low>/<SaveID-High> |
| 20 | class Archive_SystemSaveData final : public DiskArchive { | 20 | class Archive_SystemSaveData final : public DiskArchive { |
| 21 | public: | 21 | public: |
| 22 | Archive_SystemSaveData(const std::string& mount_point); | 22 | Archive_SystemSaveData(const std::string& mount_point, u64 save_id); |
| 23 | 23 | ||
| 24 | /** | 24 | /** |
| 25 | * Initialize the archive. | 25 | * Initialize the archive. |
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h index c1784e870..6c9b689e0 100644 --- a/src/core/file_sys/disk_archive.h +++ b/src/core/file_sys/disk_archive.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | #include "common/file_util.h" | ||
| 8 | 9 | ||
| 9 | #include "core/file_sys/archive_backend.h" | 10 | #include "core/file_sys/archive_backend.h" |
| 10 | #include "core/loader/loader.h" | 11 | #include "core/loader/loader.h" |
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp index 98f3bb922..2d314a4cf 100644 --- a/src/core/hle/hle.cpp +++ b/src/core/hle/hle.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "core/hle/kernel/thread.h" | 9 | #include "core/hle/kernel/thread.h" |
| 10 | #include "core/hle/service/service.h" | 10 | #include "core/hle/service/service.h" |
| 11 | #include "core/hle/service/fs/archive.h" | 11 | #include "core/hle/service/fs/archive.h" |
| 12 | #include "core/hle/service/cfg/cfg.h" | ||
| 12 | 13 | ||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 14 | 15 | ||
| @@ -58,6 +59,7 @@ void RegisterAllModules() { | |||
| 58 | void Init() { | 59 | void Init() { |
| 59 | Service::Init(); | 60 | Service::Init(); |
| 60 | Service::FS::ArchiveInit(); | 61 | Service::FS::ArchiveInit(); |
| 62 | Service::CFG::CFGInit(); | ||
| 61 | 63 | ||
| 62 | RegisterAllModules(); | 64 | RegisterAllModules(); |
| 63 | 65 | ||
| @@ -65,6 +67,7 @@ void Init() { | |||
| 65 | } | 67 | } |
| 66 | 68 | ||
| 67 | void Shutdown() { | 69 | void Shutdown() { |
| 70 | Service::CFG::CFGShutdown(); | ||
| 68 | Service::FS::ArchiveShutdown(); | 71 | Service::FS::ArchiveShutdown(); |
| 69 | Service::Shutdown(); | 72 | Service::Shutdown(); |
| 70 | 73 | ||
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp new file mode 100644 index 000000000..161aa8531 --- /dev/null +++ b/src/core/hle/service/cfg/cfg.cpp | |||
| @@ -0,0 +1,202 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include "common/log.h" | ||
| 7 | #include "common/make_unique.h" | ||
| 8 | #include "core/file_sys/archive_systemsavedata.h" | ||
| 9 | #include "core/hle/service/cfg/cfg.h" | ||
| 10 | |||
| 11 | namespace Service { | ||
| 12 | namespace CFG { | ||
| 13 | |||
| 14 | const u64 CFG_SAVE_ID = 0x00010017; | ||
| 15 | const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; | ||
| 16 | const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } }; | ||
| 17 | const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; | ||
| 18 | const char CONSOLE_USERNAME[0x14] = "CITRA"; | ||
| 19 | /// This will be initialized in CFGInit, and will be used when creating the block | ||
| 20 | UsernameBlock CONSOLE_USERNAME_BLOCK; | ||
| 21 | /// TODO(Subv): Find out what this actually is | ||
| 22 | const u8 SOUND_OUTPUT_MODE = 2; | ||
| 23 | const u8 UNITED_STATES_COUNTRY_ID = 49; | ||
| 24 | /// TODO(Subv): Find what the other bytes are | ||
| 25 | const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUNTRY_ID }; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, | ||
| 29 | * for example Nintendo Zone | ||
| 30 | * Thanks Normmatt for providing this information | ||
| 31 | */ | ||
| 32 | const std::array<float, 8> STEREO_CAMERA_SETTINGS = { | ||
| 33 | 62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, | ||
| 34 | 10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f | ||
| 35 | }; | ||
| 36 | |||
| 37 | static const u32 CONFIG_SAVEFILE_SIZE = 0x8000; | ||
| 38 | static std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer; | ||
| 39 | |||
| 40 | static std::unique_ptr<FileSys::Archive_SystemSaveData> cfg_system_save_data; | ||
| 41 | |||
| 42 | ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) { | ||
| 43 | // Read the header | ||
| 44 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 45 | |||
| 46 | auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries), | ||
| 47 | [&](const SaveConfigBlockEntry& entry) { | ||
| 48 | return entry.block_id == block_id && entry.size == size && (entry.flags & flag); | ||
| 49 | }); | ||
| 50 | |||
| 51 | if (itr == std::end(config->block_entries)) { | ||
| 52 | LOG_ERROR(Service_CFG, "Config block %u with size %u and flags %u not found", block_id, size, flag); | ||
| 53 | return ResultCode(-1); // TODO(Subv): Find the correct error code | ||
| 54 | } | ||
| 55 | |||
| 56 | // The data is located in the block header itself if the size is less than 4 bytes | ||
| 57 | if (itr->size <= 4) | ||
| 58 | memcpy(output, &itr->offset_or_data, itr->size); | ||
| 59 | else | ||
| 60 | memcpy(output, &cfg_config_file_buffer[itr->offset_or_data], itr->size); | ||
| 61 | |||
| 62 | return RESULT_SUCCESS; | ||
| 63 | } | ||
| 64 | |||
| 65 | ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const u8* data) { | ||
| 66 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 67 | if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES) | ||
| 68 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 69 | |||
| 70 | // Insert the block header with offset 0 for now | ||
| 71 | config->block_entries[config->total_entries] = { block_id, 0, size, flags }; | ||
| 72 | if (size > 4) { | ||
| 73 | u32 offset = config->data_entries_offset; | ||
| 74 | // Perform a search to locate the next offset for the new data | ||
| 75 | // use the offset and size of the previous block to determine the new position | ||
| 76 | for (int i = config->total_entries - 1; i >= 0; --i) { | ||
| 77 | // Ignore the blocks that don't have a separate data offset | ||
| 78 | if (config->block_entries[i].size > 4) { | ||
| 79 | offset = config->block_entries[i].offset_or_data + | ||
| 80 | config->block_entries[i].size; | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | config->block_entries[config->total_entries].offset_or_data = offset; | ||
| 86 | |||
| 87 | // Write the data at the new offset | ||
| 88 | memcpy(&cfg_config_file_buffer[offset], data, size); | ||
| 89 | } | ||
| 90 | else { | ||
| 91 | // The offset_or_data field in the header contains the data itself if it's 4 bytes or less | ||
| 92 | memcpy(&config->block_entries[config->total_entries].offset_or_data, data, size); | ||
| 93 | } | ||
| 94 | |||
| 95 | ++config->total_entries; | ||
| 96 | return RESULT_SUCCESS; | ||
| 97 | } | ||
| 98 | |||
| 99 | ResultCode DeleteConfigNANDSaveFile() { | ||
| 100 | FileSys::Path path("config"); | ||
| 101 | if (cfg_system_save_data->DeleteFile(path)) | ||
| 102 | return RESULT_SUCCESS; | ||
| 103 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 104 | } | ||
| 105 | |||
| 106 | ResultCode UpdateConfigNANDSavegame() { | ||
| 107 | FileSys::Mode mode = {}; | ||
| 108 | mode.write_flag = 1; | ||
| 109 | mode.create_flag = 1; | ||
| 110 | FileSys::Path path("config"); | ||
| 111 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 112 | _assert_msg_(Service_CFG, file != nullptr, "could not open file"); | ||
| 113 | file->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); | ||
| 114 | return RESULT_SUCCESS; | ||
| 115 | } | ||
| 116 | |||
| 117 | ResultCode FormatConfig() { | ||
| 118 | ResultCode res = DeleteConfigNANDSaveFile(); | ||
| 119 | if (!res.IsSuccess()) | ||
| 120 | return res; | ||
| 121 | // Delete the old data | ||
| 122 | cfg_config_file_buffer.fill(0); | ||
| 123 | // Create the header | ||
| 124 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 125 | // This value is hardcoded, taken from 3dbrew, verified by hardware, it's always the same value | ||
| 126 | config->data_entries_offset = 0x455C; | ||
| 127 | // Insert the default blocks | ||
| 128 | res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, | ||
| 129 | reinterpret_cast<const u8*>(STEREO_CAMERA_SETTINGS.data())); | ||
| 130 | if (!res.IsSuccess()) | ||
| 131 | return res; | ||
| 132 | res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, | ||
| 133 | reinterpret_cast<const u8*>(&CONSOLE_UNIQUE_ID)); | ||
| 134 | if (!res.IsSuccess()) | ||
| 135 | return res; | ||
| 136 | res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0x8, | ||
| 137 | reinterpret_cast<const u8*>(&CONSOLE_MODEL)); | ||
| 138 | if (!res.IsSuccess()) | ||
| 139 | return res; | ||
| 140 | res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xA, &CONSOLE_LANGUAGE); | ||
| 141 | if (!res.IsSuccess()) | ||
| 142 | return res; | ||
| 143 | res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); | ||
| 144 | if (!res.IsSuccess()) | ||
| 145 | return res; | ||
| 146 | res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, | ||
| 147 | reinterpret_cast<const u8*>(&COUNTRY_INFO)); | ||
| 148 | if (!res.IsSuccess()) | ||
| 149 | return res; | ||
| 150 | res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, | ||
| 151 | reinterpret_cast<const u8*>(&CONSOLE_USERNAME_BLOCK)); | ||
| 152 | if (!res.IsSuccess()) | ||
| 153 | return res; | ||
| 154 | // Save the buffer to the file | ||
| 155 | res = UpdateConfigNANDSavegame(); | ||
| 156 | if (!res.IsSuccess()) | ||
| 157 | return res; | ||
| 158 | return RESULT_SUCCESS; | ||
| 159 | } | ||
| 160 | |||
| 161 | void CFGInit() { | ||
| 162 | // TODO(Subv): In the future we should use the FS service to query this archive, | ||
| 163 | // currently it is not possible because you can only have one open archive of the same type at any time | ||
| 164 | std::string syssavedata_directory = FileUtil::GetUserPath(D_SYSSAVEDATA_IDX); | ||
| 165 | cfg_system_save_data = Common::make_unique<FileSys::Archive_SystemSaveData>( | ||
| 166 | syssavedata_directory, CFG_SAVE_ID); | ||
| 167 | if (!cfg_system_save_data->Initialize()) { | ||
| 168 | LOG_CRITICAL(Service_CFG, "Could not initialize SystemSaveData archive for the CFG:U service"); | ||
| 169 | return; | ||
| 170 | } | ||
| 171 | |||
| 172 | // TODO(Subv): All this code should be moved to cfg:i, | ||
| 173 | // it's only here because we do not currently emulate the lower level code that uses that service | ||
| 174 | // Try to open the file in read-only mode to check its existence | ||
| 175 | FileSys::Mode mode = {}; | ||
| 176 | mode.read_flag = 1; | ||
| 177 | FileSys::Path path("config"); | ||
| 178 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 179 | |||
| 180 | // Load the config if it already exists | ||
| 181 | if (file != nullptr) { | ||
| 182 | file->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); | ||
| 183 | return; | ||
| 184 | } | ||
| 185 | |||
| 186 | // Initialize the Username block | ||
| 187 | // TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals | ||
| 188 | CONSOLE_USERNAME_BLOCK.ng_word = 0; | ||
| 189 | CONSOLE_USERNAME_BLOCK.zero = 0; | ||
| 190 | // Copy string to buffer and pad with zeros at the end | ||
| 191 | auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14); | ||
| 192 | std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size, | ||
| 193 | std::end(CONSOLE_USERNAME_BLOCK.username), 0); | ||
| 194 | FormatConfig(); | ||
| 195 | } | ||
| 196 | |||
| 197 | void CFGShutdown() { | ||
| 198 | |||
| 199 | } | ||
| 200 | |||
| 201 | } // namespace CFG | ||
| 202 | } // namespace Service | ||
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h new file mode 100644 index 000000000..c74527ca4 --- /dev/null +++ b/src/core/hle/service/cfg/cfg.h | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include "core/hle/result.h" | ||
| 9 | |||
| 10 | namespace Service { | ||
| 11 | namespace CFG { | ||
| 12 | |||
| 13 | enum SystemModel { | ||
| 14 | NINTENDO_3DS = 0, | ||
| 15 | NINTENDO_3DS_XL = 1, | ||
| 16 | NEW_NINTENDO_3DS = 2, | ||
| 17 | NINTENDO_2DS = 3, | ||
| 18 | NEW_NINTENDO_3DS_XL = 4 | ||
| 19 | }; | ||
| 20 | |||
| 21 | enum SystemLanguage { | ||
| 22 | LANGUAGE_JP = 0, | ||
| 23 | LANGUAGE_EN = 1, | ||
| 24 | LANGUAGE_FR = 2, | ||
| 25 | LANGUAGE_DE = 3, | ||
| 26 | LANGUAGE_IT = 4, | ||
| 27 | LANGUAGE_ES = 5, | ||
| 28 | LANGUAGE_ZH = 6, | ||
| 29 | LANGUAGE_KO = 7, | ||
| 30 | LANGUAGE_NL = 8, | ||
| 31 | LANGUAGE_PT = 9, | ||
| 32 | LANGUAGE_RU = 10 | ||
| 33 | }; | ||
| 34 | |||
| 35 | /// Block header in the config savedata file | ||
| 36 | struct SaveConfigBlockEntry { | ||
| 37 | u32 block_id; ///< The id of the current block | ||
| 38 | u32 offset_or_data; ///< This is the absolute offset to the block data if the size is greater than 4 bytes, otherwise it contains the data itself | ||
| 39 | u16 size; ///< The size of the block | ||
| 40 | u16 flags; ///< The flags of the block, possibly used for access control | ||
| 41 | }; | ||
| 42 | |||
| 43 | /// The maximum number of block entries that can exist in the config file | ||
| 44 | static const u32 CONFIG_FILE_MAX_BLOCK_ENTRIES = 1479; | ||
| 45 | |||
| 46 | /** | ||
| 47 | * The header of the config savedata file, | ||
| 48 | * contains information about the blocks in the file | ||
| 49 | */ | ||
| 50 | struct SaveFileConfig { | ||
| 51 | u16 total_entries; ///< The total number of set entries in the config file | ||
| 52 | u16 data_entries_offset; ///< The offset where the data for the blocks start, this is hardcoded to 0x455C as per hardware | ||
| 53 | SaveConfigBlockEntry block_entries[CONFIG_FILE_MAX_BLOCK_ENTRIES]; ///< The block headers, the maximum possible value is 1479 as per hardware | ||
| 54 | u32 unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware | ||
| 55 | }; | ||
| 56 | static_assert(sizeof(SaveFileConfig) == 0x455C, "The SaveFileConfig header must be exactly 0x455C bytes"); | ||
| 57 | |||
| 58 | struct UsernameBlock { | ||
| 59 | char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary | ||
| 60 | u32 zero; | ||
| 61 | u32 ng_word; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(UsernameBlock) == 0x1C, "Size of UsernameBlock must be 0x1C"); | ||
| 64 | |||
| 65 | struct ConsoleModelInfo { | ||
| 66 | u8 model; ///< The console model (3DS, 2DS, etc) | ||
| 67 | u8 unknown[3]; ///< Unknown data | ||
| 68 | }; | ||
| 69 | static_assert(sizeof(ConsoleModelInfo) == 4, "ConsoleModelInfo must be exactly 4 bytes"); | ||
| 70 | |||
| 71 | struct ConsoleCountryInfo { | ||
| 72 | u8 unknown[3]; ///< Unknown data | ||
| 73 | u8 country_code; ///< The country code of the console | ||
| 74 | }; | ||
| 75 | static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); | ||
| 76 | |||
| 77 | extern const u64 CFG_SAVE_ID; | ||
| 78 | extern const u64 CONSOLE_UNIQUE_ID; | ||
| 79 | extern const ConsoleModelInfo CONSOLE_MODEL; | ||
| 80 | extern const u8 CONSOLE_LANGUAGE; | ||
| 81 | extern const char CONSOLE_USERNAME[0x14]; | ||
| 82 | /// This will be initialized in the Interface constructor, and will be used when creating the block | ||
| 83 | extern UsernameBlock CONSOLE_USERNAME_BLOCK; | ||
| 84 | /// TODO(Subv): Find out what this actually is | ||
| 85 | extern const u8 SOUND_OUTPUT_MODE; | ||
| 86 | extern const u8 UNITED_STATES_COUNTRY_ID; | ||
| 87 | /// TODO(Subv): Find what the other bytes are | ||
| 88 | extern const ConsoleCountryInfo COUNTRY_INFO; | ||
| 89 | extern const std::array<float, 8> STEREO_CAMERA_SETTINGS; | ||
| 90 | |||
| 91 | static_assert(sizeof(STEREO_CAMERA_SETTINGS) == 0x20, "STEREO_CAMERA_SETTINGS must be exactly 0x20 bytes"); | ||
| 92 | static_assert(sizeof(CONSOLE_UNIQUE_ID) == 8, "CONSOLE_UNIQUE_ID must be exactly 8 bytes"); | ||
| 93 | static_assert(sizeof(CONSOLE_LANGUAGE) == 1, "CONSOLE_LANGUAGE must be exactly 1 byte"); | ||
| 94 | static_assert(sizeof(SOUND_OUTPUT_MODE) == 1, "SOUND_OUTPUT_MODE must be exactly 1 byte"); | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Reads a block with the specified id and flag from the Config savegame buffer | ||
| 98 | * and writes the output to output. | ||
| 99 | * The input size must match exactly the size of the requested block | ||
| 100 | * @param block_id The id of the block we want to read | ||
| 101 | * @param size The size of the block we want to read | ||
| 102 | * @param flag The requested block must have this flag set | ||
| 103 | * @param output A pointer where we will write the read data | ||
| 104 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 105 | */ | ||
| 106 | ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Creates a block with the specified id and writes the input data to the cfg savegame buffer in memory. | ||
| 110 | * The config savegame file in the filesystem is not updated. | ||
| 111 | * @param block_id The id of the block we want to create | ||
| 112 | * @param size The size of the block we want to create | ||
| 113 | * @param flag The flags of the new block | ||
| 114 | * @param data A pointer containing the data we will write to the new block | ||
| 115 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 116 | */ | ||
| 117 | ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const u8* data); | ||
| 118 | |||
| 119 | /** | ||
| 120 | * Deletes the config savegame file from the filesystem, the buffer in memory is not affected | ||
| 121 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 122 | */ | ||
| 123 | ResultCode DeleteConfigNANDSaveFile(); | ||
| 124 | |||
| 125 | /** | ||
| 126 | * Writes the config savegame memory buffer to the config savegame file in the filesystem | ||
| 127 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 128 | */ | ||
| 129 | ResultCode UpdateConfigNANDSavegame(); | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Re-creates the config savegame file in memory and the filesystem with the default blocks | ||
| 133 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 134 | */ | ||
| 135 | ResultCode FormatConfig(); | ||
| 136 | |||
| 137 | /// Initialize the config service | ||
| 138 | void CFGInit(); | ||
| 139 | |||
| 140 | /// Shutdown the config service | ||
| 141 | void CFGShutdown(); | ||
| 142 | |||
| 143 | } // namespace CFG | ||
| 144 | } // namespace Service | ||
diff --git a/src/core/hle/service/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index e886b7c1f..3254cc10e 100644 --- a/src/core/hle/service/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp | |||
| @@ -4,29 +4,83 @@ | |||
| 4 | 4 | ||
| 5 | #include "common/log.h" | 5 | #include "common/log.h" |
| 6 | #include "core/hle/hle.h" | 6 | #include "core/hle/hle.h" |
| 7 | #include "core/hle/service/cfg_i.h" | 7 | #include "core/hle/service/cfg/cfg.h" |
| 8 | #include "core/hle/service/cfg/cfg_i.h" | ||
| 8 | 9 | ||
| 9 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 10 | // Namespace CFG_I | 11 | // Namespace CFG_I |
| 11 | 12 | ||
| 12 | namespace CFG_I { | 13 | namespace CFG_I { |
| 14 | |||
| 15 | /** | ||
| 16 | * CFG_I::GetConfigInfoBlk8 service function | ||
| 17 | * This function is called by two command headers, | ||
| 18 | * there appears to be no difference between them according to 3dbrew | ||
| 19 | * Inputs: | ||
| 20 | * 0 : 0x04010082 / 0x08010082 | ||
| 21 | * 1 : Size | ||
| 22 | * 2 : Block ID | ||
| 23 | * 3 : Descriptor for the output buffer | ||
| 24 | * 4 : Output buffer pointer | ||
| 25 | * Outputs: | ||
| 26 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 27 | */ | ||
| 28 | static void GetConfigInfoBlk8(Service::Interface* self) { | ||
| 29 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 30 | u32 size = cmd_buffer[1]; | ||
| 31 | u32 block_id = cmd_buffer[2]; | ||
| 32 | u8* data_pointer = Memory::GetPointer(cmd_buffer[4]); | ||
| 33 | |||
| 34 | if (data_pointer == nullptr) { | ||
| 35 | cmd_buffer[1] = -1; // TODO(Subv): Find the right error code | ||
| 36 | return; | ||
| 37 | } | ||
| 38 | |||
| 39 | cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data_pointer).raw; | ||
| 40 | } | ||
| 41 | |||
| 42 | /** | ||
| 43 | * CFG_I::UpdateConfigNANDSavegame service function | ||
| 44 | * This function is called by two command headers, | ||
| 45 | * there appears to be no difference between them according to 3dbrew | ||
| 46 | * Inputs: | ||
| 47 | * 0 : 0x04030000 / 0x08030000 | ||
| 48 | * Outputs: | ||
| 49 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 50 | */ | ||
| 51 | static void UpdateConfigNANDSavegame(Service::Interface* self) { | ||
| 52 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 53 | cmd_buffer[1] = Service::CFG::UpdateConfigNANDSavegame().raw; | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * CFG_I::FormatConfig service function | ||
| 58 | * Inputs: | ||
| 59 | * 0 : 0x08060000 | ||
| 60 | * Outputs: | ||
| 61 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 62 | */ | ||
| 63 | static void FormatConfig(Service::Interface* self) { | ||
| 64 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 65 | cmd_buffer[1] = Service::CFG::FormatConfig().raw; | ||
| 66 | } | ||
| 13 | 67 | ||
| 14 | const Interface::FunctionInfo FunctionTable[] = { | 68 | const Interface::FunctionInfo FunctionTable[] = { |
| 15 | {0x04010082, nullptr, "GetConfigInfoBlk8"}, | 69 | {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, |
| 16 | {0x04020082, nullptr, "GetConfigInfoBlk4"}, | 70 | {0x04020082, nullptr, "SetConfigInfoBlk4"}, |
| 17 | {0x04030000, nullptr, "UpdateConfigNANDSavegame"}, | 71 | {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, |
| 18 | {0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, | 72 | {0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, |
| 19 | {0x04050000, nullptr, "GetLocalFriendCodeSeed"}, | 73 | {0x04050000, nullptr, "GetLocalFriendCodeSeed"}, |
| 20 | {0x04060000, nullptr, "SecureInfoGetRegion"}, | 74 | {0x04060000, nullptr, "SecureInfoGetRegion"}, |
| 21 | {0x04070000, nullptr, "SecureInfoGetByte101"}, | 75 | {0x04070000, nullptr, "SecureInfoGetByte101"}, |
| 22 | {0x04080042, nullptr, "SecureInfoGetSerialNo"}, | 76 | {0x04080042, nullptr, "SecureInfoGetSerialNo"}, |
| 23 | {0x04090000, nullptr, "UpdateConfigBlk00040003"}, | 77 | {0x04090000, nullptr, "UpdateConfigBlk00040003"}, |
| 24 | {0x08010082, nullptr, "GetConfigInfoBlk8"}, | 78 | {0x08010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, |
| 25 | {0x08020082, nullptr, "GetConfigInfoBlk4"}, | 79 | {0x08020082, nullptr, "SetConfigInfoBlk4"}, |
| 26 | {0x08030000, nullptr, "UpdateConfigNANDSavegame"}, | 80 | {0x08030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, |
| 27 | {0x080400C2, nullptr, "CreateConfigInfoBlk"}, | 81 | {0x080400C2, nullptr, "CreateConfigInfoBlk"}, |
| 28 | {0x08050000, nullptr, "DeleteConfigNANDSavefile"}, | 82 | {0x08050000, nullptr, "DeleteConfigNANDSavefile"}, |
| 29 | {0x08060000, nullptr, "FormatConfig"}, | 83 | {0x08060000, FormatConfig, "FormatConfig"}, |
| 30 | {0x08070000, nullptr, "Unknown"}, | 84 | {0x08070000, nullptr, "Unknown"}, |
| 31 | {0x08080000, nullptr, "UpdateConfigBlk1"}, | 85 | {0x08080000, nullptr, "UpdateConfigBlk1"}, |
| 32 | {0x08090000, nullptr, "UpdateConfigBlk2"}, | 86 | {0x08090000, nullptr, "UpdateConfigBlk2"}, |
diff --git a/src/core/hle/service/cfg_i.h b/src/core/hle/service/cfg/cfg_i.h index 577aad236..577aad236 100644 --- a/src/core/hle/service/cfg_i.h +++ b/src/core/hle/service/cfg/cfg_i.h | |||
diff --git a/src/core/hle/service/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp index 6fcd1d7f3..59934ea46 100644 --- a/src/core/hle/service/cfg_u.cpp +++ b/src/core/hle/service/cfg/cfg_u.cpp | |||
| @@ -2,9 +2,13 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "common/file_util.h" | ||
| 5 | #include "common/log.h" | 6 | #include "common/log.h" |
| 7 | #include "common/string_util.h" | ||
| 8 | #include "core/file_sys/archive_systemsavedata.h" | ||
| 6 | #include "core/hle/hle.h" | 9 | #include "core/hle/hle.h" |
| 7 | #include "core/hle/service/cfg_u.h" | 10 | #include "core/hle/service/cfg/cfg.h" |
| 11 | #include "core/hle/service/cfg/cfg_u.h" | ||
| 8 | 12 | ||
| 9 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 10 | // Namespace CFG_U | 14 | // Namespace CFG_U |
| @@ -99,13 +103,79 @@ static void GetCountryCodeID(Service::Interface* self) { | |||
| 99 | cmd_buffer[2] = country_code_id; | 103 | cmd_buffer[2] = country_code_id; |
| 100 | } | 104 | } |
| 101 | 105 | ||
| 106 | /** | ||
| 107 | * CFG_User::GetConfigInfoBlk2 service function | ||
| 108 | * Inputs: | ||
| 109 | * 0 : 0x00010082 | ||
| 110 | * 1 : Size | ||
| 111 | * 2 : Block ID | ||
| 112 | * 3 : Descriptor for the output buffer | ||
| 113 | * 4 : Output buffer pointer | ||
| 114 | * Outputs: | ||
| 115 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 116 | */ | ||
| 117 | static void GetConfigInfoBlk2(Service::Interface* self) { | ||
| 118 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 119 | u32 size = cmd_buffer[1]; | ||
| 120 | u32 block_id = cmd_buffer[2]; | ||
| 121 | u8* data_pointer = Memory::GetPointer(cmd_buffer[4]); | ||
| 122 | |||
| 123 | if (data_pointer == nullptr) { | ||
| 124 | cmd_buffer[1] = -1; // TODO(Subv): Find the right error code | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | |||
| 128 | cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw; | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * CFG_User::GetSystemModel service function | ||
| 133 | * Inputs: | ||
| 134 | * 0 : 0x00050000 | ||
| 135 | * Outputs: | ||
| 136 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 137 | * 2 : Model of the console | ||
| 138 | */ | ||
| 139 | static void GetSystemModel(Service::Interface* self) { | ||
| 140 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 141 | u32 data; | ||
| 142 | |||
| 143 | // TODO(Subv): Find out the correct error codes | ||
| 144 | cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(0x000F0004, 4, 0x8, | ||
| 145 | reinterpret_cast<u8*>(&data)).raw; | ||
| 146 | cmd_buffer[2] = data & 0xFF; | ||
| 147 | } | ||
| 148 | |||
| 149 | /** | ||
| 150 | * CFG_User::GetModelNintendo2DS service function | ||
| 151 | * Inputs: | ||
| 152 | * 0 : 0x00060000 | ||
| 153 | * Outputs: | ||
| 154 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 155 | * 2 : 0 if the system is a Nintendo 2DS, 1 otherwise | ||
| 156 | */ | ||
| 157 | static void GetModelNintendo2DS(Service::Interface* self) { | ||
| 158 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 159 | u32 data; | ||
| 160 | |||
| 161 | // TODO(Subv): Find out the correct error codes | ||
| 162 | cmd_buffer[1] = Service::CFG::GetConfigInfoBlock(0x000F0004, 4, 0x8, | ||
| 163 | reinterpret_cast<u8*>(&data)).raw; | ||
| 164 | |||
| 165 | u8 model = data & 0xFF; | ||
| 166 | if (model == Service::CFG::NINTENDO_2DS) | ||
| 167 | cmd_buffer[2] = 0; | ||
| 168 | else | ||
| 169 | cmd_buffer[2] = 1; | ||
| 170 | } | ||
| 171 | |||
| 102 | const Interface::FunctionInfo FunctionTable[] = { | 172 | const Interface::FunctionInfo FunctionTable[] = { |
| 103 | {0x00010082, nullptr, "GetConfigInfoBlk2"}, | 173 | {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, |
| 104 | {0x00020000, nullptr, "SecureInfoGetRegion"}, | 174 | {0x00020000, nullptr, "SecureInfoGetRegion"}, |
| 105 | {0x00030000, nullptr, "GenHashConsoleUnique"}, | 175 | {0x00030000, nullptr, "GenHashConsoleUnique"}, |
| 106 | {0x00040000, nullptr, "GetRegionCanadaUSA"}, | 176 | {0x00040000, nullptr, "GetRegionCanadaUSA"}, |
| 107 | {0x00050000, nullptr, "GetSystemModel"}, | 177 | {0x00050000, GetSystemModel, "GetSystemModel"}, |
| 108 | {0x00060000, nullptr, "GetModelNintendo2DS"}, | 178 | {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, |
| 109 | {0x00070040, nullptr, "unknown"}, | 179 | {0x00070040, nullptr, "unknown"}, |
| 110 | {0x00080080, nullptr, "unknown"}, | 180 | {0x00080080, nullptr, "unknown"}, |
| 111 | {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, | 181 | {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, |
diff --git a/src/core/hle/service/cfg_u.h b/src/core/hle/service/cfg/cfg_u.h index 0136bff53..0136bff53 100644 --- a/src/core/hle/service/cfg_u.h +++ b/src/core/hle/service/cfg/cfg_u.h | |||
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index e2a59a069..98db02f15 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp | |||
| @@ -428,15 +428,6 @@ void ArchiveInit() { | |||
| 428 | CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC); | 428 | CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC); |
| 429 | else | 429 | else |
| 430 | LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); | 430 | LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); |
| 431 | |||
| 432 | std::string systemsavedata_directory = FileUtil::GetUserPath(D_SYSSAVEDATA_IDX); | ||
| 433 | auto systemsavedata_archive = Common::make_unique<FileSys::Archive_SDMC>(systemsavedata_directory); | ||
| 434 | if (systemsavedata_archive->Initialize()) { | ||
| 435 | CreateArchive(std::move(systemsavedata_archive), ArchiveIdCode::SystemSaveData); | ||
| 436 | } else { | ||
| 437 | LOG_ERROR(Service_FS, "Can't instantiate SystemSaveData archive with path %s", | ||
| 438 | systemsavedata_directory.c_str()); | ||
| 439 | } | ||
| 440 | } | 431 | } |
| 441 | 432 | ||
| 442 | /// Shutdown archives | 433 | /// Shutdown archives |
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index ac3f908f8..664f914d6 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -12,8 +12,8 @@ | |||
| 12 | #include "core/hle/service/apt_u.h" | 12 | #include "core/hle/service/apt_u.h" |
| 13 | #include "core/hle/service/boss_u.h" | 13 | #include "core/hle/service/boss_u.h" |
| 14 | #include "core/hle/service/cecd_u.h" | 14 | #include "core/hle/service/cecd_u.h" |
| 15 | #include "core/hle/service/cfg_i.h" | 15 | #include "core/hle/service/cfg/cfg_i.h" |
| 16 | #include "core/hle/service/cfg_u.h" | 16 | #include "core/hle/service/cfg/cfg_u.h" |
| 17 | #include "core/hle/service/csnd_snd.h" | 17 | #include "core/hle/service/csnd_snd.h" |
| 18 | #include "core/hle/service/dsp_dsp.h" | 18 | #include "core/hle/service/dsp_dsp.h" |
| 19 | #include "core/hle/service/err_f.h" | 19 | #include "core/hle/service/err_f.h" |