citra/src/core/hle/service/cfg/cfg.cpp

846 lines
32 KiB
C++

// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <tuple>
#include <boost/serialization/array.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <cryptopp/osrng.h>
#include <cryptopp/sha.h>
#include <fmt/format.h>
#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"
SERIALIZE_EXPORT_IMPL(Service::CFG::Module)
namespace Service::CFG {
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) {
ar& cfg_config_file_buffer;
ar& cfg_system_save_data_archive;
ar& preferred_region_code;
}
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<u16, 187> 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<u8, 187> 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<u8, 2> unknown;
constexpr std::array<u8, 8> cfg_system_savedata_id{
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
};
Module::Interface::Interface(std::shared_ptr<Module> 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<u16>();
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(ResultCode(ErrorDescription::NotFound, ErrorModule::Config,
ErrorSummary::WrongArgument, ErrorLevel::Permanent));
rb.Skip(1, false);
return;
}
rb.Push(RESULT_SUCCESS);
// the real CFG service copies only three bytes (including the null-terminator) here
rb.Push<u32>(country_codes[country_code_id]);
}
std::shared_ptr<Module> Module::Interface::Interface::GetModule() const {
return cfg;
}
void Module::Interface::GetCountryCodeID(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u16 country_code = rp.Pop<u16>();
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<char>(country_code & 0xff), static_cast<char>(country_code >> 8));
rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Config,
ErrorSummary::WrongArgument, ErrorLevel::Permanent));
rb.Push<u16>(0x00FF);
return;
}
rb.Push(RESULT_SUCCESS);
rb.Push<u16>(country_code_id);
}
u32 Module::GetRegionValue() {
if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT)
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(RESULT_SUCCESS);
rb.Push<u8>(static_cast<u8>(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(RESULT_SUCCESS);
// According to 3dbrew this is normally 0.
rb.Push<u8>(0);
}
void Module::Interface::SetUUIDClockSequence(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
cfg->mcu_data.clock_sequence = rp.Pop<u16>();
cfg->SaveMCUConfig();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
}
void Module::Interface::GetUUIDClockSequence(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.Push<u16>(static_cast<u16>(cfg->mcu_data.clock_sequence));
}
void Module::Interface::GetTransferableId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF;
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
std::array<u8, 12> buffer;
const ResultCode 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<u8, CryptoPP::SHA256::DIGESTSIZE> 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<u32>(0);
rb.Push<u32>(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(RESULT_SUCCESS);
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<u8*>(&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<u8>(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<u8*>(&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>();
u32 block_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
std::vector<u8> 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>();
u32 block_id = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
std::vector<u8> 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>();
u32 size = rp.Pop<u32>();
auto& buffer = rp.PopMappedBuffer();
std::vector<u8> 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<void*> Module::GetConfigBlockPointer(u32 block_id, u32 size, AccessFlag accesss_flag) {
// Read the header
auto config = reinterpret_cast<SaveFileConfig*>(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<ConfigBlockID>(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);
auto default_block = GetDefaultConfigBlock(static_cast<ConfigBlockID>(block_id));
auto result = CreateConfigBlock(block_id, static_cast<u16>(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 ResultCode(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 ResultCode(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 ResultCode(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;
}
ResultCode 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 RESULT_SUCCESS;
}
ResultCode 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 RESULT_SUCCESS;
}
ResultCode Module::CreateConfigBlock(u32 block_id, u16 size, AccessFlag access_flags,
const void* data) {
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data());
if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES)
return ResultCode(-1); // 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 RESULT_SUCCESS;
}
ResultCode Module::DeleteConfigNANDSaveFile() {
FileSys::Path path("/config");
return cfg_system_save_data_archive->DeleteFile(path);
}
ResultCode 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 RESULT_SUCCESS;
}
ResultCode Module::FormatConfig() {
ResultCode res = DeleteConfigNANDSaveFile();
// The delete command fails if the file doesn't exist, so we have to check that too
if (!res.IsSuccess() && res != FileSys::ERROR_FILE_NOT_FOUND) {
return res;
}
// Delete the old data
cfg_config_file_buffer.fill(0);
// Create the header
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(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<u16>(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 RESULT_SUCCESS;
}
ResultCode 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::ERROR_NOT_FOUND) {
// 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 RESULT_SUCCESS;
}
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() {
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<u32 /*region*/, SystemLanguage> AdjustLanguageInfoBlock(
std::span<const u32> region_code, SystemLanguage language) {
static const std::array<std::vector<SystemLanguage>, 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::SetPreferredRegionCodes(std::span<const u32> region_codes) {
const SystemLanguage current_language = GetSystemLanguage();
const auto [region, adjusted_language] =
AdjustLanguageInfoBlock(region_codes, current_language);
preferred_region_code = region;
LOG_INFO(Service_CFG, "Preferred region code set to {}", preferred_region_code);
if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT) {
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<u8, u8> 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() {
u8 block{};
GetConfigBlock(LanguageBlockID, sizeof(block), AccessFlag::SystemRead, &block);
return static_cast<SystemLanguage>(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<SoundOutputMode>(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<u32, u64> Module::GenerateConsoleUniqueId() const {
CryptoPP::AutoSeededRandomPool rng;
const u32 random_number = rng.GenerateWord32(0, 0xFFFF);
u64_le local_friend_code_seed;
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),
sizeof(local_friend_code_seed));
const u64 console_id =
(local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48);
return std::make_pair(random_number, console_id);
}
ResultCode Module::SetConsoleUniqueId(u32 random_number, u64 console_id) {
u64_le console_id_le = console_id;
ResultCode 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 RESULT_SUCCESS;
}
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<Module> GetModule(Core::System& system) {
auto cfg = system.ServiceManager().GetService<Service::CFG::Module::Interface>("cfg:u");
if (!cfg)
return nullptr;
return cfg->GetModule();
}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
auto cfg = std::make_shared<Module>();
std::make_shared<CFG_I>(cfg)->InstallAsService(service_manager);
std::make_shared<CFG_S>(cfg)->InstallAsService(service_manager);
std::make_shared<CFG_U>(cfg)->InstallAsService(service_manager);
std::make_shared<CFG_NOR>()->InstallAsService(service_manager);
}
std::string GetConsoleIdHash(Core::System& system) {
u64_le console_id{};
std::array<u8, sizeof(console_id)> buffer;
if (system.IsPoweredOn()) {
auto cfg = GetModule(system);
ASSERT_MSG(cfg, "CFG Module missing!");
console_id = cfg->GetConsoleUniqueId();
} else {
console_id = std::make_unique<Service::CFG::Module>()->GetConsoleUniqueId();
}
std::memcpy(buffer.data(), &console_id, sizeof(console_id));
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer));
return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), ""));
}
} // namespace Service::CFG