diff options
| author | 2014-12-21 16:36:18 -0500 | |
|---|---|---|
| committer | 2014-12-21 16:41:06 -0500 | |
| commit | 6115f013a9df6faf66c9799ab25ecb106182b8c2 (patch) | |
| tree | 657e9b7ff1830ffc1568c46da4a89debf1e21933 | |
| parent | CFGU: Some changes (diff) | |
| download | yuzu-6115f013a9df6faf66c9799ab25ecb106182b8c2.tar.gz yuzu-6115f013a9df6faf66c9799ab25ecb106182b8c2.tar.xz yuzu-6115f013a9df6faf66c9799ab25ecb106182b8c2.zip | |
CFG: Create a new subfolder cfg inside service to handle cfg
Moved most of the shared CFG code there, implemented a few CFG:I functions
| -rw-r--r-- | src/core/CMakeLists.txt | 10 | ||||
| -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 | 204 | ||||
| -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) | 72 | ||||
| -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 | 194 | ||||
| -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/cfg_u.cpp | 474 | ||||
| -rw-r--r-- | src/core/hle/service/service.cpp | 4 |
11 files changed, 617 insertions, 489 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/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..d41e15285 --- /dev/null +++ b/src/core/hle/service/cfg/cfg.cpp | |||
| @@ -0,0 +1,204 @@ | |||
| 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 "common/log.h" | ||
| 6 | #include "common/make_unique.h" | ||
| 7 | #include "core/file_sys/archive_systemsavedata.h" | ||
| 8 | #include "core/hle/service/cfg/cfg.h" | ||
| 9 | |||
| 10 | namespace Service { | ||
| 11 | namespace CFG { | ||
| 12 | |||
| 13 | const u64 CFG_SAVE_ID = 0x00010017; | ||
| 14 | const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; | ||
| 15 | const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, 0, 0, 0 }; | ||
| 16 | const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; | ||
| 17 | const char CONSOLE_USERNAME[0x14] = "CITRA"; | ||
| 18 | /// This will be initialized in CFGInit, and will be used when creating the block | ||
| 19 | UsernameBlock CONSOLE_USERNAME_BLOCK; | ||
| 20 | /// TODO(Subv): Find out what this actually is | ||
| 21 | const u8 SOUND_OUTPUT_MODE = 2; | ||
| 22 | const u8 UNITED_STATES_COUNTRY_ID = 49; | ||
| 23 | /// TODO(Subv): Find what the other bytes are | ||
| 24 | const ConsoleCountryInfo COUNTRY_INFO = { 0, 0, 0, UNITED_STATES_COUNTRY_ID }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, | ||
| 28 | * for example Nintendo Zone | ||
| 29 | * Thanks Normmatt for providing this information | ||
| 30 | */ | ||
| 31 | const std::array<float, 8> STEREO_CAMERA_SETTINGS = { | ||
| 32 | 62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, | ||
| 33 | 10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f | ||
| 34 | }; | ||
| 35 | |||
| 36 | const u32 CONFIG_SAVEFILE_SIZE = 0x8000; | ||
| 37 | std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer = {}; | ||
| 38 | |||
| 39 | static std::unique_ptr<FileSys::Archive_SystemSaveData> cfg_system_save_data; | ||
| 40 | |||
| 41 | ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) { | ||
| 42 | // Read the header | ||
| 43 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 44 | |||
| 45 | auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries), | ||
| 46 | [&](const SaveConfigBlockEntry& entry) { | ||
| 47 | return entry.block_id == block_id && entry.size == size && (entry.flags & flag); | ||
| 48 | }); | ||
| 49 | |||
| 50 | if (itr == std::end(config->block_entries)) { | ||
| 51 | LOG_ERROR(Service_CFG, "Config block %u with size %u and flags %u not found", block_id, size, flag); | ||
| 52 | return ResultCode(-1); // TODO(Subv): Find the correct error code | ||
| 53 | } | ||
| 54 | |||
| 55 | // The data is located in the block header itself if the size is less than 4 bytes | ||
| 56 | if (itr->size <= 4) | ||
| 57 | memcpy(output, &itr->offset_or_data, itr->size); | ||
| 58 | else | ||
| 59 | memcpy(output, &cfg_config_file_buffer[itr->offset_or_data], itr->size); | ||
| 60 | |||
| 61 | return RESULT_SUCCESS; | ||
| 62 | } | ||
| 63 | |||
| 64 | ResultCode CreateConfigInfoBlk(u32 block_id, u32 size, u32 flags, const u8* data) { | ||
| 65 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 66 | if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES) | ||
| 67 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 68 | |||
| 69 | // Insert the block header with offset 0 for now | ||
| 70 | config->block_entries[config->total_entries] = { block_id, 0, size, flags }; | ||
| 71 | if (size > 4) { | ||
| 72 | s32 total_entries = config->total_entries - 1; | ||
| 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 | while (total_entries >= 0) { | ||
| 77 | // Ignore the blocks that don't have a separate data offset | ||
| 78 | if (config->block_entries[total_entries].size > 4) { | ||
| 79 | offset = config->block_entries[total_entries].offset_or_data + | ||
| 80 | config->block_entries[total_entries].size; | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | |||
| 84 | --total_entries; | ||
| 85 | } | ||
| 86 | |||
| 87 | config->block_entries[config->total_entries].offset_or_data = offset; | ||
| 88 | |||
| 89 | // Write the data at the new offset | ||
| 90 | memcpy(&cfg_config_file_buffer[offset], data, size); | ||
| 91 | } | ||
| 92 | else { | ||
| 93 | // The offset_or_data field in the header contains the data itself if it's 4 bytes or less | ||
| 94 | memcpy(&config->block_entries[config->total_entries].offset_or_data, data, size); | ||
| 95 | } | ||
| 96 | |||
| 97 | ++config->total_entries; | ||
| 98 | return RESULT_SUCCESS; | ||
| 99 | } | ||
| 100 | |||
| 101 | ResultCode DeleteConfigNANDSaveFile() { | ||
| 102 | FileSys::Path path("config"); | ||
| 103 | if (cfg_system_save_data->DeleteFile(path)) | ||
| 104 | return RESULT_SUCCESS; | ||
| 105 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 106 | } | ||
| 107 | |||
| 108 | ResultCode UpdateConfigNANDSavegame() { | ||
| 109 | FileSys::Mode mode = {}; | ||
| 110 | mode.write_flag = 1; | ||
| 111 | mode.create_flag = 1; | ||
| 112 | FileSys::Path path("config"); | ||
| 113 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 114 | _assert_msg_(Service_CFG, file != nullptr, "could not open file"); | ||
| 115 | file->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); | ||
| 116 | return RESULT_SUCCESS; | ||
| 117 | } | ||
| 118 | |||
| 119 | ResultCode FormatConfig() { | ||
| 120 | ResultCode res = DeleteConfigNANDSaveFile(); | ||
| 121 | if (!res.IsSuccess()) | ||
| 122 | return res; | ||
| 123 | // Delete the old data | ||
| 124 | std::fill(cfg_config_file_buffer.begin(), cfg_config_file_buffer.end(), 0); | ||
| 125 | // Create the header | ||
| 126 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 127 | // This value is hardcoded, taken from 3dbrew, verified by hardware, it's always the same value | ||
| 128 | config->data_entries_offset = 0x455C; | ||
| 129 | // Insert the default blocks | ||
| 130 | res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, | ||
| 131 | reinterpret_cast<const u8*>(STEREO_CAMERA_SETTINGS.data())); | ||
| 132 | if (!res.IsSuccess()) | ||
| 133 | return res; | ||
| 134 | res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, | ||
| 135 | reinterpret_cast<const u8*>(&CONSOLE_UNIQUE_ID)); | ||
| 136 | if (!res.IsSuccess()) | ||
| 137 | return res; | ||
| 138 | res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0x8, | ||
| 139 | reinterpret_cast<const u8*>(&CONSOLE_MODEL)); | ||
| 140 | if (!res.IsSuccess()) | ||
| 141 | return res; | ||
| 142 | res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xA, &CONSOLE_LANGUAGE); | ||
| 143 | if (!res.IsSuccess()) | ||
| 144 | return res; | ||
| 145 | res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); | ||
| 146 | if (!res.IsSuccess()) | ||
| 147 | return res; | ||
| 148 | res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, | ||
| 149 | reinterpret_cast<const u8*>(&COUNTRY_INFO)); | ||
| 150 | if (!res.IsSuccess()) | ||
| 151 | return res; | ||
| 152 | res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, | ||
| 153 | reinterpret_cast<const u8*>(&CONSOLE_USERNAME_BLOCK)); | ||
| 154 | if (!res.IsSuccess()) | ||
| 155 | return res; | ||
| 156 | // Save the buffer to the file | ||
| 157 | res = UpdateConfigNANDSavegame(); | ||
| 158 | if (!res.IsSuccess()) | ||
| 159 | return res; | ||
| 160 | return RESULT_SUCCESS; | ||
| 161 | } | ||
| 162 | |||
| 163 | void CFGInit() { | ||
| 164 | // TODO(Subv): In the future we should use the FS service to query this archive, | ||
| 165 | // currently it is not possible because you can only have one open archive of the same type at any time | ||
| 166 | std::string syssavedata_directory = FileUtil::GetUserPath(D_SYSSAVEDATA_IDX); | ||
| 167 | cfg_system_save_data = Common::make_unique<FileSys::Archive_SystemSaveData>(syssavedata_directory, | ||
| 168 | CFG_SAVE_ID); | ||
| 169 | if (!cfg_system_save_data->Initialize()) { | ||
| 170 | LOG_CRITICAL(Service_CFG, "Could not initialize SystemSaveData archive for the CFG:U service"); | ||
| 171 | return; | ||
| 172 | } | ||
| 173 | |||
| 174 | // TODO(Subv): All this code should be moved to cfg:i, | ||
| 175 | // it's only here because we do not currently emulate the lower level code that uses that service | ||
| 176 | // Try to open the file in read-only mode to check its existence | ||
| 177 | FileSys::Mode mode = {}; | ||
| 178 | mode.read_flag = 1; | ||
| 179 | FileSys::Path path("config"); | ||
| 180 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 181 | |||
| 182 | // Load the config if it already exists | ||
| 183 | if (file != nullptr) { | ||
| 184 | file->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); | ||
| 185 | return; | ||
| 186 | } | ||
| 187 | |||
| 188 | // Initialize the Username block | ||
| 189 | // TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals | ||
| 190 | CONSOLE_USERNAME_BLOCK.ng_word = 0; | ||
| 191 | CONSOLE_USERNAME_BLOCK.zero = 0; | ||
| 192 | // Copy string to buffer and pad with zeros at the end | ||
| 193 | auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14); | ||
| 194 | std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size, | ||
| 195 | std::end(CONSOLE_USERNAME_BLOCK.username), 0); | ||
| 196 | FormatConfig(); | ||
| 197 | } | ||
| 198 | |||
| 199 | void CFGShutdown() { | ||
| 200 | |||
| 201 | } | ||
| 202 | |||
| 203 | } // namespace CFG | ||
| 204 | } // 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..38db5a5ec --- /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, u32 size, u32 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..b3f28f8e5 100644 --- a/src/core/hle/service/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp | |||
| @@ -1,32 +1,86 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | 1 | // Copyright 2014 Citra Emulator Project |
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 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/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp new file mode 100644 index 000000000..439fcdbb9 --- /dev/null +++ b/src/core/hle/service/cfg/cfg_u.cpp | |||
| @@ -0,0 +1,194 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/file_util.h" | ||
| 6 | #include "common/log.h" | ||
| 7 | #include "common/string_util.h" | ||
| 8 | #include "core/file_sys/archive_systemsavedata.h" | ||
| 9 | #include "core/hle/hle.h" | ||
| 10 | #include "core/hle/service/cfg/cfg.h" | ||
| 11 | #include "core/hle/service/cfg/cfg_u.h" | ||
| 12 | |||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 14 | // Namespace CFG_U | ||
| 15 | |||
| 16 | namespace CFG_U { | ||
| 17 | |||
| 18 | // TODO(Link Mauve): use a constexpr once MSVC starts supporting it. | ||
| 19 | #define C(code) ((code)[0] | ((code)[1] << 8)) | ||
| 20 | |||
| 21 | static const std::array<u16, 187> country_codes = { | ||
| 22 | 0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 | ||
| 23 | C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 | ||
| 24 | C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 | ||
| 25 | C("DO"), C("EC"), C("SV"), C("GF"), C("GD"), C("GP"), C("GT"), C("GY"), // 24-31 | ||
| 26 | C("HT"), C("HN"), C("JM"), C("MQ"), C("MX"), C("MS"), C("AN"), C("NI"), // 32-39 | ||
| 27 | C("PA"), C("PY"), C("PE"), C("KN"), C("LC"), C("VC"), C("SR"), C("TT"), // 40-47 | ||
| 28 | C("TC"), C("US"), C("UY"), C("VI"), C("VE"), 0, 0, 0, // 48-55 | ||
| 29 | 0, 0, 0, 0, 0, 0, 0, 0, // 56-63 | ||
| 30 | C("AL"), C("AU"), C("AT"), C("BE"), C("BA"), C("BW"), C("BG"), C("HR"), // 64-71 | ||
| 31 | C("CY"), C("CZ"), C("DK"), C("EE"), C("FI"), C("FR"), C("DE"), C("GR"), // 72-79 | ||
| 32 | C("HU"), C("IS"), C("IE"), C("IT"), C("LV"), C("LS"), C("LI"), C("LT"), // 80-87 | ||
| 33 | C("LU"), C("MK"), C("MT"), C("ME"), C("MZ"), C("NA"), C("NL"), C("NZ"), // 88-95 | ||
| 34 | C("NO"), C("PL"), C("PT"), C("RO"), C("RU"), C("RS"), C("SK"), C("SI"), // 96-103 | ||
| 35 | C("ZA"), C("ES"), C("SZ"), C("SE"), C("CH"), C("TR"), C("GB"), C("ZM"), // 104-111 | ||
| 36 | C("ZW"), C("AZ"), C("MR"), C("ML"), C("NE"), C("TD"), C("SD"), C("ER"), // 112-119 | ||
| 37 | C("DJ"), C("SO"), C("AD"), C("GI"), C("GG"), C("IM"), C("JE"), C("MC"), // 120-127 | ||
| 38 | C("TW"), 0, 0, 0, 0, 0, 0, 0, // 128-135 | ||
| 39 | C("KR"), 0, 0, 0, 0, 0, 0, 0, // 136-143 | ||
| 40 | C("HK"), C("MO"), 0, 0, 0, 0, 0, 0, // 144-151 | ||
| 41 | C("ID"), C("SG"), C("TH"), C("PH"), C("MY"), 0, 0, 0, // 152-159 | ||
| 42 | C("CN"), 0, 0, 0, 0, 0, 0, 0, // 160-167 | ||
| 43 | C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 | ||
| 44 | C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 | ||
| 45 | C("SM"), C("VA"), C("BM") // 184-186 | ||
| 46 | }; | ||
| 47 | |||
| 48 | #undef C | ||
| 49 | |||
| 50 | /** | ||
| 51 | * CFG_User::GetCountryCodeString service function | ||
| 52 | * Inputs: | ||
| 53 | * 1 : Country Code ID | ||
| 54 | * Outputs: | ||
| 55 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 56 | * 2 : Country's 2-char string | ||
| 57 | */ | ||
| 58 | static void GetCountryCodeString(Service::Interface* self) { | ||
| 59 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 60 | u32 country_code_id = cmd_buffer[1]; | ||
| 61 | |||
| 62 | if (country_code_id >= country_codes.size() || 0 == country_codes[country_code_id]) { | ||
| 63 | LOG_ERROR(Service_CFG, "requested country code id=%d is invalid", country_code_id); | ||
| 64 | cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; | ||
| 65 | return; | ||
| 66 | } | ||
| 67 | |||
| 68 | cmd_buffer[1] = 0; | ||
| 69 | cmd_buffer[2] = country_codes[country_code_id]; | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * CFG_User::GetCountryCodeID service function | ||
| 74 | * Inputs: | ||
| 75 | * 1 : Country Code 2-char string | ||
| 76 | * Outputs: | ||
| 77 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 78 | * 2 : Country Code ID | ||
| 79 | */ | ||
| 80 | static void GetCountryCodeID(Service::Interface* self) { | ||
| 81 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 82 | u16 country_code = cmd_buffer[1]; | ||
| 83 | u16 country_code_id = 0; | ||
| 84 | |||
| 85 | // The following algorithm will fail if the first country code isn't 0. | ||
| 86 | _dbg_assert_(Service_CFG, country_codes[0] == 0); | ||
| 87 | |||
| 88 | for (size_t id = 0; id < country_codes.size(); ++id) { | ||
| 89 | if (country_codes[id] == country_code) { | ||
| 90 | country_code_id = id; | ||
| 91 | break; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | if (0 == country_code_id) { | ||
| 96 | LOG_ERROR(Service_CFG, "requested country code name=%c%c is invalid", country_code & 0xff, country_code >> 8); | ||
| 97 | cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; | ||
| 98 | cmd_buffer[2] = 0xFFFF; | ||
| 99 | return; | ||
| 100 | } | ||
| 101 | |||
| 102 | cmd_buffer[1] = 0; | ||
| 103 | cmd_buffer[2] = country_code_id; | ||
| 104 | } | ||
| 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 | |||
| 172 | const Interface::FunctionInfo FunctionTable[] = { | ||
| 173 | {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, | ||
| 174 | {0x00020000, nullptr, "SecureInfoGetRegion"}, | ||
| 175 | {0x00030000, nullptr, "GenHashConsoleUnique"}, | ||
| 176 | {0x00040000, nullptr, "GetRegionCanadaUSA"}, | ||
| 177 | {0x00050000, GetSystemModel, "GetSystemModel"}, | ||
| 178 | {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, | ||
| 179 | {0x00070040, nullptr, "unknown"}, | ||
| 180 | {0x00080080, nullptr, "unknown"}, | ||
| 181 | {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, | ||
| 182 | {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, | ||
| 183 | }; | ||
| 184 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 185 | // Interface class | ||
| 186 | |||
| 187 | Interface::Interface() { | ||
| 188 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); | ||
| 189 | } | ||
| 190 | |||
| 191 | Interface::~Interface() { | ||
| 192 | } | ||
| 193 | |||
| 194 | } // namespace | ||
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/cfg_u.cpp b/src/core/hle/service/cfg_u.cpp deleted file mode 100644 index b97181cbd..000000000 --- a/src/core/hle/service/cfg_u.cpp +++ /dev/null | |||
| @@ -1,474 +0,0 @@ | |||
| 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 "common/file_util.h" | ||
| 6 | #include "common/log.h" | ||
| 7 | #include "common/make_unique.h" | ||
| 8 | #include "common/string_util.h" | ||
| 9 | #include "core/file_sys/archive_systemsavedata.h" | ||
| 10 | #include "core/hle/hle.h" | ||
| 11 | #include "core/hle/service/cfg_u.h" | ||
| 12 | |||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 14 | // Namespace CFG_U | ||
| 15 | |||
| 16 | namespace CFG_U { | ||
| 17 | |||
| 18 | enum SystemModel { | ||
| 19 | NINTENDO_3DS = 0, | ||
| 20 | NINTENDO_3DS_XL = 1, | ||
| 21 | NEW_NINTENDO_3DS = 2, | ||
| 22 | NINTENDO_2DS = 3, | ||
| 23 | NEW_NINTENDO_3DS_XL = 4 | ||
| 24 | }; | ||
| 25 | |||
| 26 | enum SystemLanguage { | ||
| 27 | LANGUAGE_JP = 0, | ||
| 28 | LANGUAGE_EN = 1, | ||
| 29 | LANGUAGE_FR = 2, | ||
| 30 | LANGUAGE_DE = 3, | ||
| 31 | LANGUAGE_IT = 4, | ||
| 32 | LANGUAGE_ES = 5, | ||
| 33 | LANGUAGE_ZH = 6, | ||
| 34 | LANGUAGE_KO = 7, | ||
| 35 | LANGUAGE_NL = 8, | ||
| 36 | LANGUAGE_PT = 9, | ||
| 37 | LANGUAGE_RU = 10 | ||
| 38 | }; | ||
| 39 | |||
| 40 | struct UsernameBlock { | ||
| 41 | char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary | ||
| 42 | u32 zero; | ||
| 43 | u32 ng_word; | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(UsernameBlock) == 0x1C, "Size of UsernameBlock must be 0x1C"); | ||
| 46 | |||
| 47 | struct ConsoleModelInfo { | ||
| 48 | u8 model; ///< The console model (3DS, 2DS, etc) | ||
| 49 | u8 unknown[3]; ///< Unknown data | ||
| 50 | }; | ||
| 51 | static_assert(sizeof(ConsoleModelInfo) == 4, "ConsoleModelInfo must be exactly 4 bytes"); | ||
| 52 | |||
| 53 | struct ConsoleCountryInfo { | ||
| 54 | u8 unknown[3]; ///< Unknown data | ||
| 55 | u8 country_code; ///< The country code of the console | ||
| 56 | }; | ||
| 57 | static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); | ||
| 58 | |||
| 59 | static std::unique_ptr<FileSys::Archive_SystemSaveData> cfg_system_save_data; | ||
| 60 | static const u64 CFG_SAVE_ID = 0x00010017; | ||
| 61 | static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; | ||
| 62 | static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, 0, 0, 0 }; | ||
| 63 | static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; | ||
| 64 | static const char CONSOLE_USERNAME[0x14] = "CITRA"; | ||
| 65 | /// This will be initialized in the Interface constructor, and will be used when creating the block | ||
| 66 | static UsernameBlock CONSOLE_USERNAME_BLOCK; | ||
| 67 | /// TODO(Subv): Find out what this actually is | ||
| 68 | static const u8 SOUND_OUTPUT_MODE = 2; | ||
| 69 | static const u8 UNITED_STATES_COUNTRY_ID = 49; | ||
| 70 | /// TODO(Subv): Find what the other bytes are | ||
| 71 | static const ConsoleCountryInfo COUNTRY_INFO = { 0, 0, 0, UNITED_STATES_COUNTRY_ID }; | ||
| 72 | |||
| 73 | static const u32 CONFIG_SAVEFILE_SIZE = 0x8000; | ||
| 74 | static std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer = { }; | ||
| 75 | |||
| 76 | /** | ||
| 77 | * TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, | ||
| 78 | * for example Nintendo Zone | ||
| 79 | * Thanks Normmatt for providing this information | ||
| 80 | */ | ||
| 81 | static const std::array<float, 8> STEREO_CAMERA_SETTINGS = { | ||
| 82 | 62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, | ||
| 83 | 10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f | ||
| 84 | }; | ||
| 85 | |||
| 86 | static_assert(sizeof(STEREO_CAMERA_SETTINGS) == 0x20, "STEREO_CAMERA_SETTINGS must be exactly 0x20 bytes"); | ||
| 87 | static_assert(sizeof(CONSOLE_UNIQUE_ID) == 8, "CONSOLE_UNIQUE_ID must be exactly 8 bytes"); | ||
| 88 | static_assert(sizeof(CONSOLE_LANGUAGE) == 1, "CONSOLE_LANGUAGE must be exactly 1 byte"); | ||
| 89 | static_assert(sizeof(SOUND_OUTPUT_MODE) == 1, "SOUND_OUTPUT_MODE must be exactly 1 byte"); | ||
| 90 | |||
| 91 | // TODO(Link Mauve): use a constexpr once MSVC starts supporting it. | ||
| 92 | #define C(code) ((code)[0] | ((code)[1] << 8)) | ||
| 93 | |||
| 94 | static const std::array<u16, 187> country_codes = { | ||
| 95 | 0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 | ||
| 96 | C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 | ||
| 97 | C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 | ||
| 98 | C("DO"), C("EC"), C("SV"), C("GF"), C("GD"), C("GP"), C("GT"), C("GY"), // 24-31 | ||
| 99 | C("HT"), C("HN"), C("JM"), C("MQ"), C("MX"), C("MS"), C("AN"), C("NI"), // 32-39 | ||
| 100 | C("PA"), C("PY"), C("PE"), C("KN"), C("LC"), C("VC"), C("SR"), C("TT"), // 40-47 | ||
| 101 | C("TC"), C("US"), C("UY"), C("VI"), C("VE"), 0, 0, 0, // 48-55 | ||
| 102 | 0, 0, 0, 0, 0, 0, 0, 0, // 56-63 | ||
| 103 | C("AL"), C("AU"), C("AT"), C("BE"), C("BA"), C("BW"), C("BG"), C("HR"), // 64-71 | ||
| 104 | C("CY"), C("CZ"), C("DK"), C("EE"), C("FI"), C("FR"), C("DE"), C("GR"), // 72-79 | ||
| 105 | C("HU"), C("IS"), C("IE"), C("IT"), C("LV"), C("LS"), C("LI"), C("LT"), // 80-87 | ||
| 106 | C("LU"), C("MK"), C("MT"), C("ME"), C("MZ"), C("NA"), C("NL"), C("NZ"), // 88-95 | ||
| 107 | C("NO"), C("PL"), C("PT"), C("RO"), C("RU"), C("RS"), C("SK"), C("SI"), // 96-103 | ||
| 108 | C("ZA"), C("ES"), C("SZ"), C("SE"), C("CH"), C("TR"), C("GB"), C("ZM"), // 104-111 | ||
| 109 | C("ZW"), C("AZ"), C("MR"), C("ML"), C("NE"), C("TD"), C("SD"), C("ER"), // 112-119 | ||
| 110 | C("DJ"), C("SO"), C("AD"), C("GI"), C("GG"), C("IM"), C("JE"), C("MC"), // 120-127 | ||
| 111 | C("TW"), 0, 0, 0, 0, 0, 0, 0, // 128-135 | ||
| 112 | C("KR"), 0, 0, 0, 0, 0, 0, 0, // 136-143 | ||
| 113 | C("HK"), C("MO"), 0, 0, 0, 0, 0, 0, // 144-151 | ||
| 114 | C("ID"), C("SG"), C("TH"), C("PH"), C("MY"), 0, 0, 0, // 152-159 | ||
| 115 | C("CN"), 0, 0, 0, 0, 0, 0, 0, // 160-167 | ||
| 116 | C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 | ||
| 117 | C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 | ||
| 118 | C("SM"), C("VA"), C("BM") // 184-186 | ||
| 119 | }; | ||
| 120 | |||
| 121 | #undef C | ||
| 122 | |||
| 123 | /** | ||
| 124 | * CFG_User::GetCountryCodeString service function | ||
| 125 | * Inputs: | ||
| 126 | * 1 : Country Code ID | ||
| 127 | * Outputs: | ||
| 128 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 129 | * 2 : Country's 2-char string | ||
| 130 | */ | ||
| 131 | static void GetCountryCodeString(Service::Interface* self) { | ||
| 132 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 133 | u32 country_code_id = cmd_buffer[1]; | ||
| 134 | |||
| 135 | if (country_code_id >= country_codes.size() || 0 == country_codes[country_code_id]) { | ||
| 136 | LOG_ERROR(Service_CFG, "requested country code id=%d is invalid", country_code_id); | ||
| 137 | cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | |||
| 141 | cmd_buffer[1] = 0; | ||
| 142 | cmd_buffer[2] = country_codes[country_code_id]; | ||
| 143 | } | ||
| 144 | |||
| 145 | /** | ||
| 146 | * CFG_User::GetCountryCodeID service function | ||
| 147 | * Inputs: | ||
| 148 | * 1 : Country Code 2-char string | ||
| 149 | * Outputs: | ||
| 150 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 151 | * 2 : Country Code ID | ||
| 152 | */ | ||
| 153 | static void GetCountryCodeID(Service::Interface* self) { | ||
| 154 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 155 | u16 country_code = cmd_buffer[1]; | ||
| 156 | u16 country_code_id = 0; | ||
| 157 | |||
| 158 | // The following algorithm will fail if the first country code isn't 0. | ||
| 159 | _dbg_assert_(Service_CFG, country_codes[0] == 0); | ||
| 160 | |||
| 161 | for (size_t id = 0; id < country_codes.size(); ++id) { | ||
| 162 | if (country_codes[id] == country_code) { | ||
| 163 | country_code_id = id; | ||
| 164 | break; | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | if (0 == country_code_id) { | ||
| 169 | LOG_ERROR(Service_CFG, "requested country code name=%c%c is invalid", country_code & 0xff, country_code >> 8); | ||
| 170 | cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; | ||
| 171 | cmd_buffer[2] = 0xFFFF; | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | |||
| 175 | cmd_buffer[1] = 0; | ||
| 176 | cmd_buffer[2] = country_code_id; | ||
| 177 | } | ||
| 178 | |||
| 179 | /// Block header in the config savedata file | ||
| 180 | struct SaveConfigBlockEntry { | ||
| 181 | u32 block_id; ///< The id of the current block | ||
| 182 | 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 | ||
| 183 | u16 size; ///< The size of the block | ||
| 184 | u16 flags; ///< The flags of the block, possibly used for access control | ||
| 185 | }; | ||
| 186 | |||
| 187 | /// The maximum number of block entries that can exist in the config file | ||
| 188 | static const u32 CONFIG_FILE_MAX_BLOCK_ENTRIES = 1479; | ||
| 189 | |||
| 190 | /** | ||
| 191 | * The header of the config savedata file, | ||
| 192 | * contains information about the blocks in the file | ||
| 193 | */ | ||
| 194 | struct SaveFileConfig { | ||
| 195 | u16 total_entries; ///< The total number of set entries in the config file | ||
| 196 | u16 data_entries_offset; ///< The offset where the data for the blocks start, this is hardcoded to 0x455C as per hardware | ||
| 197 | SaveConfigBlockEntry block_entries[CONFIG_FILE_MAX_BLOCK_ENTRIES]; ///< The block headers, the maximum possible value is 1479 as per hardware | ||
| 198 | u32 unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware | ||
| 199 | }; | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Reads a block with the specified id and flag from the Config savegame buffer | ||
| 203 | * and writes the output to output. | ||
| 204 | * The input size must match exactly the size of the requested block | ||
| 205 | * TODO(Subv): This should actually be in some file common to the CFG process | ||
| 206 | * @param block_id The id of the block we want to read | ||
| 207 | * @param size The size of the block we want to read | ||
| 208 | * @param flag The requested block must have this flag set | ||
| 209 | * @param output A pointer where we will write the read data | ||
| 210 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 211 | */ | ||
| 212 | ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) { | ||
| 213 | // Read the header | ||
| 214 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 215 | |||
| 216 | auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries), | ||
| 217 | [&](const SaveConfigBlockEntry& entry) { | ||
| 218 | return entry.block_id == block_id && entry.size == size && (entry.flags & flag); | ||
| 219 | }); | ||
| 220 | |||
| 221 | if (itr == std::end(config->block_entries)) { | ||
| 222 | LOG_ERROR(Service_CFG, "Config block %u with size %u and flags %u not found", block_id, size, flag); | ||
| 223 | return ResultCode(-1); // TODO(Subv): Find the correct error code | ||
| 224 | } | ||
| 225 | |||
| 226 | // The data is located in the block header itself if the size is less than 4 bytes | ||
| 227 | if (itr->size <= 4) | ||
| 228 | memcpy(output, &itr->offset_or_data, itr->size); | ||
| 229 | else | ||
| 230 | memcpy(output, &cfg_config_file_buffer[itr->offset_or_data], itr->size); | ||
| 231 | |||
| 232 | return RESULT_SUCCESS; | ||
| 233 | } | ||
| 234 | |||
| 235 | /** | ||
| 236 | * Creates a block with the specified id and writes the input data to the cfg savegame buffer in memory. | ||
| 237 | * The config savegame file in the filesystem is not updated. | ||
| 238 | * TODO(Subv): This should actually be in some file common to the CFG process | ||
| 239 | * @param block_id The id of the block we want to create | ||
| 240 | * @param size The size of the block we want to create | ||
| 241 | * @param flag The flags of the new block | ||
| 242 | * @param data A pointer containing the data we will write to the new block | ||
| 243 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 244 | */ | ||
| 245 | ResultCode CreateConfigInfoBlk(u32 block_id, u32 size, u32 flags, const u8* data) { | ||
| 246 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 247 | if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES) | ||
| 248 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 249 | |||
| 250 | // Insert the block header with offset 0 for now | ||
| 251 | config->block_entries[config->total_entries] = { block_id, 0, size, flags }; | ||
| 252 | if (size > 4) { | ||
| 253 | s32 total_entries = config->total_entries - 1; | ||
| 254 | u32 offset = config->data_entries_offset; | ||
| 255 | // Perform a search to locate the next offset for the new data | ||
| 256 | // use the offset and size of the previous block to determine the new position | ||
| 257 | while (total_entries >= 0) { | ||
| 258 | // Ignore the blocks that don't have a separate data offset | ||
| 259 | if (config->block_entries[total_entries].size > 4) { | ||
| 260 | offset = config->block_entries[total_entries].offset_or_data + | ||
| 261 | config->block_entries[total_entries].size; | ||
| 262 | break; | ||
| 263 | } | ||
| 264 | |||
| 265 | --total_entries; | ||
| 266 | } | ||
| 267 | |||
| 268 | config->block_entries[config->total_entries].offset_or_data = offset; | ||
| 269 | |||
| 270 | // Write the data at the new offset | ||
| 271 | memcpy(&cfg_config_file_buffer[offset], data, size); | ||
| 272 | } else { | ||
| 273 | // The offset_or_data field in the header contains the data itself if it's 4 bytes or less | ||
| 274 | memcpy(&config->block_entries[config->total_entries].offset_or_data, data, size); | ||
| 275 | } | ||
| 276 | |||
| 277 | ++config->total_entries; | ||
| 278 | return RESULT_SUCCESS; | ||
| 279 | } | ||
| 280 | |||
| 281 | /** | ||
| 282 | * Deletes the config savegame file from the filesystem, the buffer in memory is not affected | ||
| 283 | * TODO(Subv): This should actually be in some file common to the CFG process | ||
| 284 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 285 | */ | ||
| 286 | ResultCode DeleteConfigNANDSaveFile() { | ||
| 287 | FileSys::Path path("config"); | ||
| 288 | if (cfg_system_save_data->DeleteFile(path)) | ||
| 289 | return RESULT_SUCCESS; | ||
| 290 | return ResultCode(-1); // TODO(Subv): Find the right error code | ||
| 291 | } | ||
| 292 | |||
| 293 | /** | ||
| 294 | * Writes the config savegame memory buffer to the config savegame file in the filesystem | ||
| 295 | * TODO(Subv): This should actually be in some file common to the CFG process | ||
| 296 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 297 | */ | ||
| 298 | ResultCode UpdateConfigNANDSavegame() { | ||
| 299 | FileSys::Mode mode = {}; | ||
| 300 | mode.write_flag = 1; | ||
| 301 | mode.create_flag = 1; | ||
| 302 | FileSys::Path path("config"); | ||
| 303 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 304 | _assert_msg_(Service_CFG, file != nullptr, "could not open file"); | ||
| 305 | file->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); | ||
| 306 | return RESULT_SUCCESS; | ||
| 307 | } | ||
| 308 | |||
| 309 | /** | ||
| 310 | * Re-creates the config savegame file in memory and the filesystem with the default blocks | ||
| 311 | * TODO(Subv): This should actually be in some file common to the CFG process | ||
| 312 | * @returns ResultCode indicating the result of the operation, 0 on success | ||
| 313 | */ | ||
| 314 | ResultCode FormatConfig() { | ||
| 315 | ResultCode res = DeleteConfigNANDSaveFile(); | ||
| 316 | if (!res.IsSuccess()) | ||
| 317 | return res; | ||
| 318 | // Delete the old data | ||
| 319 | std::fill(cfg_config_file_buffer.begin(), cfg_config_file_buffer.end(), 0); | ||
| 320 | // Create the header | ||
| 321 | SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); | ||
| 322 | // This value is hardcoded, taken from 3dbrew, verified by hardware, it's always the same value | ||
| 323 | config->data_entries_offset = 0x455C; | ||
| 324 | // Insert the default blocks | ||
| 325 | res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, | ||
| 326 | reinterpret_cast<const u8*>(STEREO_CAMERA_SETTINGS.data())); | ||
| 327 | if (!res.IsSuccess()) | ||
| 328 | return res; | ||
| 329 | res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, | ||
| 330 | reinterpret_cast<const u8*>(&CONSOLE_UNIQUE_ID)); | ||
| 331 | if (!res.IsSuccess()) | ||
| 332 | return res; | ||
| 333 | res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0x8, | ||
| 334 | reinterpret_cast<const u8*>(&CONSOLE_MODEL)); | ||
| 335 | if (!res.IsSuccess()) | ||
| 336 | return res; | ||
| 337 | res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xA, &CONSOLE_LANGUAGE); | ||
| 338 | if (!res.IsSuccess()) | ||
| 339 | return res; | ||
| 340 | res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); | ||
| 341 | if (!res.IsSuccess()) | ||
| 342 | return res; | ||
| 343 | res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, | ||
| 344 | reinterpret_cast<const u8*>(&COUNTRY_INFO)); | ||
| 345 | if (!res.IsSuccess()) | ||
| 346 | return res; | ||
| 347 | res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, | ||
| 348 | reinterpret_cast<const u8*>(&CONSOLE_USERNAME_BLOCK)); | ||
| 349 | if (!res.IsSuccess()) | ||
| 350 | return res; | ||
| 351 | // Save the buffer to the file | ||
| 352 | res = UpdateConfigNANDSavegame(); | ||
| 353 | if (!res.IsSuccess()) | ||
| 354 | return res; | ||
| 355 | return RESULT_SUCCESS; | ||
| 356 | } | ||
| 357 | |||
| 358 | /** | ||
| 359 | * CFG_User::GetConfigInfoBlk2 service function | ||
| 360 | * Inputs: | ||
| 361 | * 1 : Size | ||
| 362 | * 2 : Block ID | ||
| 363 | * 3 : Descriptor for the output buffer | ||
| 364 | * 4 : Output buffer pointer | ||
| 365 | * Outputs: | ||
| 366 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 367 | */ | ||
| 368 | static void GetConfigInfoBlk2(Service::Interface* self) { | ||
| 369 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 370 | u32 size = cmd_buffer[1]; | ||
| 371 | u32 block_id = cmd_buffer[2]; | ||
| 372 | u8* data_pointer = Memory::GetPointer(cmd_buffer[4]); | ||
| 373 | |||
| 374 | if (data_pointer == nullptr) { | ||
| 375 | cmd_buffer[1] = -1; // TODO(Subv): Find the right error code | ||
| 376 | return; | ||
| 377 | } | ||
| 378 | |||
| 379 | cmd_buffer[1] = GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw; | ||
| 380 | } | ||
| 381 | |||
| 382 | /** | ||
| 383 | * CFG_User::GetSystemModel service function | ||
| 384 | * Outputs: | ||
| 385 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 386 | * 2 : Model of the console | ||
| 387 | */ | ||
| 388 | static void GetSystemModel(Service::Interface* self) { | ||
| 389 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 390 | u32 data; | ||
| 391 | |||
| 392 | // TODO(Subv): Find out the correct error codes | ||
| 393 | cmd_buffer[1] = GetConfigInfoBlock(0x000F0004, 4, 0x8, | ||
| 394 | reinterpret_cast<u8*>(&data)).raw; | ||
| 395 | cmd_buffer[2] = data & 0xFF; | ||
| 396 | } | ||
| 397 | |||
| 398 | /** | ||
| 399 | * CFG_User::GetModelNintendo2DS service function | ||
| 400 | * Outputs: | ||
| 401 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 402 | * 2 : 0 if the system is a Nintendo 2DS, 1 otherwise | ||
| 403 | */ | ||
| 404 | static void GetModelNintendo2DS(Service::Interface* self) { | ||
| 405 | u32* cmd_buffer = Kernel::GetCommandBuffer(); | ||
| 406 | u32 data; | ||
| 407 | |||
| 408 | // TODO(Subv): Find out the correct error codes | ||
| 409 | cmd_buffer[1] = GetConfigInfoBlock(0x000F0004, 4, 0x8, | ||
| 410 | reinterpret_cast<u8*>(&data)).raw; | ||
| 411 | |||
| 412 | u8 model = data & 0xFF; | ||
| 413 | if (model == NINTENDO_2DS) | ||
| 414 | cmd_buffer[2] = 0; | ||
| 415 | else | ||
| 416 | cmd_buffer[2] = 1; | ||
| 417 | } | ||
| 418 | |||
| 419 | const Interface::FunctionInfo FunctionTable[] = { | ||
| 420 | {0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, | ||
| 421 | {0x00020000, nullptr, "SecureInfoGetRegion"}, | ||
| 422 | {0x00030000, nullptr, "GenHashConsoleUnique"}, | ||
| 423 | {0x00040000, nullptr, "GetRegionCanadaUSA"}, | ||
| 424 | {0x00050000, GetSystemModel, "GetSystemModel"}, | ||
| 425 | {0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, | ||
| 426 | {0x00070040, nullptr, "unknown"}, | ||
| 427 | {0x00080080, nullptr, "unknown"}, | ||
| 428 | {0x00090040, GetCountryCodeString, "GetCountryCodeString"}, | ||
| 429 | {0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, | ||
| 430 | }; | ||
| 431 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 432 | // Interface class | ||
| 433 | |||
| 434 | Interface::Interface() { | ||
| 435 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); | ||
| 436 | // TODO(Subv): In the future we should use the FS service to query this archive, | ||
| 437 | // currently it is not possible because you can only have one open archive of the same type at any time | ||
| 438 | std::string syssavedata_directory = FileUtil::GetUserPath(D_SYSSAVEDATA_IDX); | ||
| 439 | cfg_system_save_data = Common::make_unique<FileSys::Archive_SystemSaveData>(syssavedata_directory, | ||
| 440 | CFG_SAVE_ID); | ||
| 441 | if (!cfg_system_save_data->Initialize()) { | ||
| 442 | LOG_CRITICAL(Service_CFG, "Could not initialize SystemSaveData archive for the CFG:U service"); | ||
| 443 | return; | ||
| 444 | } | ||
| 445 | |||
| 446 | // TODO(Subv): All this code should be moved to cfg:i, | ||
| 447 | // it's only here because we do not currently emulate the lower level code that uses that service | ||
| 448 | // Try to open the file in read-only mode to check its existence | ||
| 449 | FileSys::Mode mode = {}; | ||
| 450 | mode.read_flag = 1; | ||
| 451 | FileSys::Path path("config"); | ||
| 452 | auto file = cfg_system_save_data->OpenFile(path, mode); | ||
| 453 | |||
| 454 | // Load the config if it already exists | ||
| 455 | if (file != nullptr) { | ||
| 456 | file->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); | ||
| 457 | return; | ||
| 458 | } | ||
| 459 | |||
| 460 | // Initialize the Username block | ||
| 461 | // TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals | ||
| 462 | CONSOLE_USERNAME_BLOCK.ng_word = 0; | ||
| 463 | CONSOLE_USERNAME_BLOCK.zero = 0; | ||
| 464 | // Copy string to buffer and pad with zeros at the end | ||
| 465 | auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14); | ||
| 466 | std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size, | ||
| 467 | std::end(CONSOLE_USERNAME_BLOCK.username), 0); | ||
| 468 | FormatConfig(); | ||
| 469 | } | ||
| 470 | |||
| 471 | Interface::~Interface() { | ||
| 472 | } | ||
| 473 | |||
| 474 | } // namespace | ||
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" |