// Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include "common/archives.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/settings.h" #include "common/string_util.h" #include "common/swap.h" #include "core/core.h" #include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/errors.h" #include "core/file_sys/file_backend.h" #include "core/hle/ipc_helpers.h" #include "core/hle/result.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg_defaults.h" #include "core/hle/service/cfg/cfg_i.h" #include "core/hle/service/cfg/cfg_nor.h" #include "core/hle/service/cfg/cfg_s.h" #include "core/hle/service/cfg/cfg_u.h" #include "core/loader/loader.h" SERVICE_CONSTRUCT_IMPL(Service::CFG::Module) SERIALIZE_EXPORT_IMPL(Service::CFG::Module) namespace Service::CFG { template void Module::serialize(Archive& ar, const unsigned int) { ar& cfg_config_file_buffer; ar& cfg_system_save_data_archive; ar& preferred_region_code; ar& preferred_region_chosen; } SERIALIZE_IMPL(Module) /// The maximum number of block entries that can exist in the config file constexpr u32 CONFIG_FILE_MAX_BLOCK_ENTRIES = 1479; namespace { /// Block header in the config savedata file struct SaveConfigBlockEntry { u32 block_id; ///< The id of the current block 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 u16 size; ///< The size of the block AccessFlag access_flags; ///< The access control flags of the block }; /** * The header of the config savedata file, * contains information about the blocks in the file */ struct SaveFileConfig { u16 total_entries; ///< The total number of set entries in the config file u16 data_entries_offset; ///< The offset where the data for the blocks start, this is hardcoded /// to 0x455C as per hardware SaveConfigBlockEntry block_entries[CONFIG_FILE_MAX_BLOCK_ENTRIES]; ///< The block headers, the /// maximum possible value is /// 1479 as per hardware u32 unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware }; static_assert(sizeof(SaveFileConfig) == 0x455C, "SaveFileConfig header must be exactly 0x455C bytes"); } // namespace static constexpr u16 C(const char code[2]) { return code[0] | (code[1] << 8); } static const std::array country_codes = {{ 0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 C("DO"), C("EC"), C("SV"), C("GF"), C("GD"), C("GP"), C("GT"), C("GY"), // 24-31 C("HT"), C("HN"), C("JM"), C("MQ"), C("MX"), C("MS"), C("AN"), C("NI"), // 32-39 C("PA"), C("PY"), C("PE"), C("KN"), C("LC"), C("VC"), C("SR"), C("TT"), // 40-47 C("TC"), C("US"), C("UY"), C("VI"), C("VE"), 0, 0, 0, // 48-55 0, 0, 0, 0, 0, 0, 0, 0, // 56-63 C("AL"), C("AU"), C("AT"), C("BE"), C("BA"), C("BW"), C("BG"), C("HR"), // 64-71 C("CY"), C("CZ"), C("DK"), C("EE"), C("FI"), C("FR"), C("DE"), C("GR"), // 72-79 C("HU"), C("IS"), C("IE"), C("IT"), C("LV"), C("LS"), C("LI"), C("LT"), // 80-87 C("LU"), C("MK"), C("MT"), C("ME"), C("MZ"), C("NA"), C("NL"), C("NZ"), // 88-95 C("NO"), C("PL"), C("PT"), C("RO"), C("RU"), C("RS"), C("SK"), C("SI"), // 96-103 C("ZA"), C("ES"), C("SZ"), C("SE"), C("CH"), C("TR"), C("GB"), C("ZM"), // 104-111 C("ZW"), C("AZ"), C("MR"), C("ML"), C("NE"), C("TD"), C("SD"), C("ER"), // 112-119 C("DJ"), C("SO"), C("AD"), C("GI"), C("GG"), C("IM"), C("JE"), C("MC"), // 120-127 C("TW"), 0, 0, 0, 0, 0, 0, 0, // 128-135 C("KR"), 0, 0, 0, 0, 0, 0, 0, // 136-143 C("HK"), C("MO"), 0, 0, 0, 0, 0, 0, // 144-151 C("ID"), C("SG"), C("TH"), C("PH"), C("MY"), 0, 0, 0, // 152-159 C("CN"), 0, 0, 0, 0, 0, 0, 0, // 160-167 C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 C("SM"), C("VA"), C("BM"), // 184-186 }}; // Based on PKHeX's lists of subregions at // https://github.com/kwsch/PKHeX/tree/master/PKHeX.Core/Resources/text/locale3DS/subregions static const std::array default_subregion = {{ 0, 2, 0, 0, 0, 0, 0, 0, // 0-7 1, 2, 2, 1, 1, 1, 2, 2, // 8-15 2, 1, 2, 1, 2, 2, 2, 1, // 16-23 2, 2, 2, 1, 1, 1, 2, 2, // 24-31 2, 2, 2, 1, 2, 1, 1, 2, // 32-39 2, 2, 2, 2, 1, 1, 2, 2, // 40-47 1, 2, 2, 1, 2, 0, 0, 0, // 48-55 0, 0, 0, 0, 0, 0, 0, 0, // 56-63 2, 2, 2, 2, 2, 1, 2, 6, // 64-71 1, 2, 18, 1, 8, 2, 2, 2, // 72-79 2, 1, 2, 2, 1, 2, 1, 2, // 80-87 1, 1, 1, 1, 1, 1, 2, 2, // 88-95 7, 2, 2, 2, 9, 1, 2, 1, // 96-103 2, 2, 2, 2, 2, 2, 2, 1, // 104-111 1, 1, 1, 1, 1, 1, 1, 1, // 112-119 1, 1, 1, 1, 1, 1, 1, 1, // 120-127 2, 0, 0, 0, 0, 0, 0, 0, // 128-135 2, 0, 0, 0, 0, 0, 0, 0, // 136-143 1, 0, 0, 0, 0, 0, 0, 0, // 144-151 0, 1, 0, 0, 2, 0, 0, 0, // 152-159 2, 0, 0, 0, 0, 0, 0, 0, // 160-167 2, 2, 0, 0, 0, 0, 2, 0, // 168-175 0, 0, 0, 0, 0, 0, 0, 0, // 176-183 1, 1, 1, // 184-186 }}; std::array unknown; constexpr std::array cfg_system_savedata_id{ 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, }; Module::Interface::Interface(std::shared_ptr cfg, const char* name, u32 max_session) : ServiceFramework(name, max_session), cfg(std::move(cfg)) {} Module::Interface::~Interface() = default; void Module::Interface::GetCountryCodeString(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u16 country_code_id = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (country_code_id >= country_codes.size() || 0 == country_codes[country_code_id]) { LOG_ERROR(Service_CFG, "requested country code id={} is invalid", country_code_id); rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent)); rb.Skip(1, false); return; } rb.Push(ResultSuccess); // the real CFG service copies only three bytes (including the null-terminator) here rb.Push(country_codes[country_code_id]); } std::shared_ptr Module::Interface::Interface::GetModule() const { return cfg; } void Module::Interface::GetCountryCodeID(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u16 country_code = rp.Pop(); u16 country_code_id = 0; // The following algorithm will fail if the first country code isn't 0. DEBUG_ASSERT(country_codes[0] == 0); for (u16 id = 0; id < country_codes.size(); ++id) { if (country_codes[id] == country_code) { country_code_id = id; break; } } IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); if (0 == country_code_id) { LOG_ERROR(Service_CFG, "requested country code name={}{} is invalid", static_cast(country_code & 0xff), static_cast(country_code >> 8)); rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent)); rb.Push(0x00FF); return; } rb.Push(ResultSuccess); rb.Push(country_code_id); } u32 Module::GetRegionValue() { if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT) { UpdatePreferredRegionCode(); return preferred_region_code; } return Settings::values.region_value.GetValue(); } void Module::Interface::GetRegion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(static_cast(cfg->GetRegionValue())); } void Module::Interface::SecureInfoGetByte101(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); LOG_DEBUG(Service_CFG, "(STUBBED) called"); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); // According to 3dbrew this is normally 0. rb.Push(0); } void Module::Interface::SetUUIDClockSequence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); cfg->mcu_data.clock_sequence = rp.Pop(); cfg->SaveMCUConfig(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } void Module::Interface::GetUUIDClockSequence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(static_cast(cfg->mcu_data.clock_sequence)); } void Module::Interface::GetTransferableId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 app_id_salt = rp.Pop() & 0x000FFFFF; IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); std::array buffer; const Result result = cfg->GetConfigBlock(ConsoleUniqueID2BlockID, 8, AccessFlag::SystemRead, buffer.data()); rb.Push(result); if (result.IsSuccess()) { std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); std::array hash; CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); u32 low, high; std::memcpy(&low, &hash[hash.size() - 8], sizeof(u32)); std::memcpy(&high, &hash[hash.size() - 4], sizeof(u32)); rb.Push(low); rb.Push(high); } else { rb.Push(0); rb.Push(0); } LOG_DEBUG(Service_CFG, "called app_id_salt=0x{:X}", app_id_salt); } void Module::Interface::IsCoppacsSupported(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); u8 canada_or_usa = 1; if (canada_or_usa == cfg->GetRegionValue()) { rb.Push(true); } else { rb.Push(false); } } void Module::Interface::GetSystemModel(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); u32 data{}; // TODO(Subv): Find out the correct error codes rb.Push(cfg->GetConfigBlock(ConsoleModelBlockID, 4, AccessFlag::SystemRead, reinterpret_cast(&data))); ConsoleModelInfo model; std::memcpy(&model, &data, 4); if ((model.model == NINTENDO_3DS || model.model == NINTENDO_3DS_XL || model.model == NINTENDO_2DS) && Settings::values.is_new_3ds) { model.model = NEW_NINTENDO_3DS_XL; } else if ((model.model == NEW_NINTENDO_3DS || model.model == NEW_NINTENDO_3DS_XL || model.model == NEW_NINTENDO_2DS_XL) && !Settings::values.is_new_3ds) { model.model = NINTENDO_3DS_XL; } std::memcpy(&data, &model, 4); rb.Push(data & 0xFF); } void Module::Interface::GetModelNintendo2DS(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); u32 data{}; // TODO(Subv): Find out the correct error codes rb.Push(cfg->GetConfigBlock(ConsoleModelBlockID, 4, AccessFlag::SystemRead, reinterpret_cast(&data))); u8 model = data & 0xFF; rb.Push(model != Service::CFG::NINTENDO_2DS); } void Module::Interface::GetConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 size = rp.Pop(); u32 block_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); std::vector data(size); rb.Push(cfg->GetConfigBlock(block_id, size, AccessFlag::UserRead, data.data())); buffer.Write(data.data(), 0, data.size()); rb.PushMappedBuffer(buffer); } void Module::Interface::GetSystemConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 size = rp.Pop(); u32 block_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); std::vector data(size); rb.Push(cfg->GetConfigBlock(block_id, size, AccessFlag::SystemRead, data.data())); buffer.Write(data.data(), 0, data.size()); rb.PushMappedBuffer(buffer); } void Module::Interface::SetSystemConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 block_id = rp.Pop(); u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); std::vector data(size); buffer.Read(data.data(), 0, data.size()); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(cfg->SetConfigBlock(block_id, size, AccessFlag::SystemWrite, data.data())); rb.PushMappedBuffer(buffer); } void Module::Interface::UpdateConfigNANDSavegame(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(cfg->UpdateConfigNANDSavegame()); } void Module::Interface::FormatConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(cfg->FormatConfig()); } ResultVal Module::GetConfigBlockPointer(u32 block_id, u32 size, AccessFlag accesss_flag) { // Read the header auto config = reinterpret_cast(cfg_config_file_buffer.data()); auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries), [&](const SaveConfigBlockEntry& entry) { return entry.block_id == block_id; }); if (itr == std::end(config->block_entries)) { if (HasDefaultConfigBlock(static_cast(block_id))) { LOG_WARNING(Service_CFG, "Config block 0x{:X} with flags {} and size {} was not found, creating " "from defaults.", block_id, accesss_flag, size); const auto& default_block = GetDefaultConfigBlock(static_cast(block_id)); auto result = CreateConfigBlock(block_id, static_cast(default_block.data.size()), default_block.access_flags, default_block.data.data()); if (!result.IsSuccess()) { LOG_ERROR(Service_CFG, "Failed to create config block 0x{:X} from defaults: 0x{:08X}", block_id, result.raw); return result; } result = UpdateConfigNANDSavegame(); if (!result.IsSuccess()) { LOG_ERROR(Service_CFG, "Failed to save updated config savegame: 0x{:08X}", result.raw); return result; } itr = std::find_if( std::begin(config->block_entries), std::end(config->block_entries), [&](const SaveConfigBlockEntry& entry) { return entry.block_id == block_id; }); } else { LOG_ERROR(Service_CFG, "Config block 0x{:X} with flags {} and size {} was not found, and no default " "exists.", block_id, accesss_flag, size); return Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent); } } if (False(itr->access_flags & accesss_flag)) { LOG_ERROR(Service_CFG, "Invalid access flag {:X} for config block 0x{:X} with size {}", accesss_flag, block_id, size); return Result(ErrorDescription::NotAuthorized, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent); } if (itr->size != size) { LOG_ERROR(Service_CFG, "Invalid size {} for config block 0x{:X} with flags {}", size, block_id, accesss_flag); return Result(ErrorDescription::InvalidSize, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent); } void* pointer; // The data is located in the block header itself if the size is less than 4 bytes if (itr->size <= 4) pointer = &itr->offset_or_data; else pointer = &cfg_config_file_buffer[itr->offset_or_data]; return pointer; } Result Module::GetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, void* output) { void* pointer = nullptr; CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag)); std::memcpy(output, pointer, size); return ResultSuccess; } Result Module::SetConfigBlock(u32 block_id, u32 size, AccessFlag accesss_flag, const void* input) { void* pointer = nullptr; CASCADE_RESULT(pointer, GetConfigBlockPointer(block_id, size, accesss_flag)); std::memcpy(pointer, input, size); return ResultSuccess; } Result Module::CreateConfigBlock(u32 block_id, u16 size, AccessFlag access_flags, const void* data) { SaveFileConfig* config = reinterpret_cast(cfg_config_file_buffer.data()); if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES) return ResultUnknown; // TODO(Subv): Find the right error code // Insert the block header with offset 0 for now config->block_entries[config->total_entries] = {block_id, 0, size, access_flags}; if (size > 4) { u32 offset = config->data_entries_offset; // Perform a search to locate the next offset for the new data // use the offset and size of the previous block to determine the new position for (int i = config->total_entries - 1; i >= 0; --i) { // Ignore the blocks that don't have a separate data offset if (config->block_entries[i].size > 4) { offset = config->block_entries[i].offset_or_data + config->block_entries[i].size; break; } } config->block_entries[config->total_entries].offset_or_data = offset; // Write the data at the new offset std::memcpy(&cfg_config_file_buffer[offset], data, size); } else { // The offset_or_data field in the header contains the data itself if it's 4 bytes or less std::memcpy(&config->block_entries[config->total_entries].offset_or_data, data, size); } ++config->total_entries; return ResultSuccess; } Result Module::DeleteConfigNANDSaveFile() { FileSys::Path path("/config"); return cfg_system_save_data_archive->DeleteFile(path); } Result Module::UpdateConfigNANDSavegame() { FileSys::Mode mode = {}; mode.write_flag.Assign(1); mode.create_flag.Assign(1); FileSys::Path path("/config"); auto config_result = cfg_system_save_data_archive->OpenFile(path, mode); ASSERT_MSG(config_result.Succeeded(), "could not open file"); auto config = std::move(config_result).Unwrap(); config->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); return ResultSuccess; } Result Module::FormatConfig() { Result res = DeleteConfigNANDSaveFile(); // The delete command fails if the file doesn't exist, so we have to check that too if (!res.IsSuccess() && res != FileSys::ResultFileNotFound) { return res; } // Delete the old data cfg_config_file_buffer.fill(0); // Create the header SaveFileConfig* config = reinterpret_cast(cfg_config_file_buffer.data()); // This value is hardcoded, taken from 3dbrew, verified by hardware, it's always the same value config->data_entries_offset = 0x455C; // Fill the config with default block data. auto default_blocks = GetDefaultConfigBlocks(); for (auto& entry : default_blocks) { res = CreateConfigBlock(entry.first, static_cast(entry.second.data.size()), entry.second.access_flags, entry.second.data.data()); if (!res.IsSuccess()) { return res; } } // Generate a new console unique ID. const auto [random_number, console_id] = GenerateConsoleUniqueId(); SetConsoleUniqueId(random_number, console_id); // Save the buffer to the file res = UpdateConfigNANDSavegame(); if (!res.IsSuccess()) { return res; } return ResultSuccess; } Result Module::LoadConfigNANDSaveFile() { const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory); // Open the SystemSaveData archive 0x00010017 FileSys::Path archive_path(cfg_system_savedata_id); auto archive_result = systemsavedata_factory.Open(archive_path, 0); // If the archive didn't exist, create the files inside if (archive_result.Code() == FileSys::ResultNotFound) { // Format the archive to create the directories systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); // Open it again to get a valid archive now that the folder exists cfg_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap(); } else { ASSERT_MSG(archive_result.Succeeded(), "Could not open the CFG SystemSaveData archive!"); cfg_system_save_data_archive = std::move(archive_result).Unwrap(); } FileSys::Path config_path("/config"); FileSys::Mode open_mode = {}; open_mode.read_flag.Assign(1); auto config_result = cfg_system_save_data_archive->OpenFile(config_path, open_mode); // Read the file if it already exists if (config_result.Succeeded()) { auto config = std::move(config_result).Unwrap(); config->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); return ResultSuccess; } return FormatConfig(); } void Module::LoadMCUConfig() { FileUtil::IOFile mcu_data_file( fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb"); if (mcu_data_file.IsOpen() && mcu_data_file.GetSize() >= sizeof(MCUData) && mcu_data_file.ReadBytes(&mcu_data, sizeof(MCUData)) == sizeof(MCUData)) { if (mcu_data.IsValid()) { return; } } mcu_data_file.Close(); mcu_data = MCUData(); SaveMCUConfig(); } void Module::SaveMCUConfig() { FileUtil::IOFile mcu_data_file( fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "wb"); if (mcu_data_file.IsOpen()) { mcu_data_file.WriteBytes(&mcu_data, sizeof(MCUData)); } } Module::Module(Core::System& system_) : system(system_) { LoadConfigNANDSaveFile(); LoadMCUConfig(); // Check the config savegame EULA Version and update it to 0x7F7F if necessary // so users will never get a prompt to accept EULA auto version = GetEULAVersion(); auto& default_version = GetDefaultEULAVersion(); if (version.major != default_version.major || version.minor != default_version.minor) { LOG_INFO(Service_CFG, "Updating accepted EULA version to {}.{}", default_version.major, default_version.minor); SetEULAVersion(default_version); UpdateConfigNANDSavegame(); } } Module::~Module() = default; /// Checks if the language is available in the chosen region, and returns a proper one static std::tuple AdjustLanguageInfoBlock( std::span region_code, SystemLanguage language) { static const std::array, 7> region_languages{{ // JPN {LANGUAGE_JP}, // USA {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT}, // EUR {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, LANGUAGE_RU}, // AUS {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, LANGUAGE_RU}, // CHN {LANGUAGE_ZH}, // KOR {LANGUAGE_KO}, // TWN {LANGUAGE_TW}, }}; // Check if any available region supports the languages for (u32 region : region_code) { const auto& available = region_languages[region]; if (std::find(available.begin(), available.end(), language) != available.end()) { // found a proper region, so return this region - language pair return {region, language}; } } // The language is not available in any available region, so default to the first region and // language const u32 default_region = region_code[0]; return {default_region, region_languages[default_region][0]}; } void Module::UpdatePreferredRegionCode() { if (preferred_region_chosen || !system.IsPoweredOn()) { return; } preferred_region_chosen = true; const auto preferred_regions = system.GetAppLoader().GetPreferredRegions(); if (preferred_regions.empty()) { return; } const auto current_language = GetRawSystemLanguage(); const auto [region, adjusted_language] = AdjustLanguageInfoBlock(preferred_regions, current_language); preferred_region_code = region; LOG_INFO(Service_CFG, "Preferred region code set to {}", preferred_region_code); if (current_language != adjusted_language) { LOG_WARNING(Service_CFG, "System language {} does not fit the region. Adjusted to {}", current_language, adjusted_language); SetSystemLanguage(adjusted_language); } } void Module::SetUsername(const std::u16string& name) { ASSERT(name.size() <= 10); UsernameBlock block{}; name.copy(block.username.data(), name.size()); SetConfigBlock(UsernameBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } std::u16string Module::GetUsername() { UsernameBlock block; GetConfigBlock(UsernameBlockID, sizeof(block), AccessFlag::SystemRead, &block); // the username string in the block isn't null-terminated, // so we need to find the end manually. std::u16string username(block.username.data(), std::size(block.username)); const std::size_t pos = username.find(u'\0'); if (pos != std::u16string::npos) username.erase(pos); return username; } void Module::SetBirthday(u8 month, u8 day) { BirthdayBlock block = {month, day}; SetConfigBlock(BirthdayBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } std::tuple Module::GetBirthday() { BirthdayBlock block; GetConfigBlock(BirthdayBlockID, sizeof(block), AccessFlag::SystemRead, &block); return std::make_tuple(block.month, block.day); } void Module::SetSystemLanguage(SystemLanguage language) { u8 block = language; SetConfigBlock(LanguageBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } SystemLanguage Module::GetSystemLanguage() { if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT) { UpdatePreferredRegionCode(); } return GetRawSystemLanguage(); } SystemLanguage Module::GetRawSystemLanguage() { u8 block{}; GetConfigBlock(LanguageBlockID, sizeof(block), AccessFlag::SystemRead, &block); return static_cast(block); } void Module::SetSoundOutputMode(SoundOutputMode mode) { u8 block = mode; SetConfigBlock(SoundOutputModeBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } SoundOutputMode Module::GetSoundOutputMode() { u8 block{}; GetConfigBlock(SoundOutputModeBlockID, sizeof(block), AccessFlag::SystemRead, &block); return static_cast(block); } void Module::SetCountryCode(u8 country_code) { ConsoleCountryInfo block = {{0, 0}, default_subregion[country_code], country_code}; SetConfigBlock(CountryInfoBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } u8 Module::GetCountryCode() { ConsoleCountryInfo block{}; GetConfigBlock(CountryInfoBlockID, sizeof(block), AccessFlag::SystemRead, &block); return block.country_code; } void Module::SetCountryInfo(u8 country_code, u8 state_code) { ConsoleCountryInfo block = {{0, 0}, state_code, country_code}; SetConfigBlock(CountryInfoBlockID, sizeof(block), AccessFlag::SystemWrite, &block); } u8 Module::GetStateCode() { ConsoleCountryInfo block{}; GetConfigBlock(CountryInfoBlockID, sizeof(block), AccessFlag::SystemRead, &block); return block.state_code; } std::pair Module::GenerateConsoleUniqueId() const { CryptoPP::AutoSeededRandomPool rng; const u32 random_number = rng.GenerateWord32(0, 0xFFFF); u64_le local_friend_code_seed; rng.GenerateBlock(reinterpret_cast(&local_friend_code_seed), sizeof(local_friend_code_seed)); const u64 console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast(random_number) << 48); return std::make_pair(random_number, console_id); } Result Module::SetConsoleUniqueId(u32 random_number, u64 console_id) { u64_le console_id_le = console_id; Result res = SetConfigBlock(ConsoleUniqueID1BlockID, sizeof(console_id_le), AccessFlag::Global, &console_id_le); if (!res.IsSuccess()) return res; res = SetConfigBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), AccessFlag::Global, &console_id_le); if (!res.IsSuccess()) return res; u32_le random_number_le = random_number; res = SetConfigBlock(ConsoleUniqueID3BlockID, sizeof(random_number_le), AccessFlag::Global, &random_number_le); if (!res.IsSuccess()) return res; return ResultSuccess; } u64 Module::GetConsoleUniqueId() { u64_le console_id_le{}; GetConfigBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), AccessFlag::Global, &console_id_le); return console_id_le; } EULAVersion Module::GetEULAVersion() { u32_le data{}; GetConfigBlock(EULAVersionBlockID, sizeof(data), AccessFlag::Global, &data); EULAVersion version; version.minor = data & 0xFF; version.major = (data >> 8) & 0xFF; return version; } void Module::SetEULAVersion(const EULAVersion& version) { SetConfigBlock(EULAVersionBlockID, sizeof(version), AccessFlag::Global, &version); } void Module::SetSystemSetupNeeded(bool setup_needed) { u32 block = setup_needed ? 0 : 1; SetConfigBlock(SystemSetupRequiredBlockID, sizeof(block), AccessFlag::System, &block); } bool Module::IsSystemSetupNeeded() { u32 block{}; GetConfigBlock(SystemSetupRequiredBlockID, sizeof(block), AccessFlag::System, &block); return (block & 0xFFFF) == 0; } std::shared_ptr GetModule(Core::System& system) { if (system.IsPoweredOn()) { auto cfg = system.ServiceManager().GetService("cfg:u"); if (cfg) { return cfg->GetModule(); } } // If the system is not running or the module is missing, // create an ad-hoc module instance to read data from. return std::make_shared(system); } void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); auto cfg = std::make_shared(system); std::make_shared(cfg)->InstallAsService(service_manager); std::make_shared(cfg)->InstallAsService(service_manager); std::make_shared(cfg)->InstallAsService(service_manager); std::make_shared()->InstallAsService(service_manager); } std::string GetConsoleIdHash(Core::System& system) { u64_le console_id = GetModule(system)->GetConsoleUniqueId(); std::array buffer; std::memcpy(buffer.data(), &console_id, sizeof(console_id)); std::array hash; CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), "")); } } // namespace Service::CFG