846 lines
32 KiB
C++
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
|