forked from suyu/suyu
Config: auto-select region and language
This commit is contained in:
parent
8d529a5cda
commit
84e78790ab
11 changed files with 108 additions and 7 deletions
|
@ -89,7 +89,8 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
// System
|
// System
|
||||||
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
|
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
|
||||||
Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1);
|
Settings::values.region_value =
|
||||||
|
sdl2_config->GetInteger("System", "region_value", Settings::REGION_VALUE_AUTO_SELECT);
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");
|
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");
|
||||||
|
|
|
@ -101,7 +101,7 @@ use_virtual_sd =
|
||||||
is_new_3ds =
|
is_new_3ds =
|
||||||
|
|
||||||
# The system region that Citra will use during emulation
|
# The system region that Citra will use during emulation
|
||||||
# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
||||||
region_value =
|
region_value =
|
||||||
|
|
||||||
[Miscellaneous]
|
[Miscellaneous]
|
||||||
|
|
|
@ -72,7 +72,8 @@ void Config::ReadValues() {
|
||||||
|
|
||||||
qt_config->beginGroup("System");
|
qt_config->beginGroup("System");
|
||||||
Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool();
|
Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool();
|
||||||
Settings::values.region_value = qt_config->value("region_value", 1).toInt();
|
Settings::values.region_value =
|
||||||
|
qt_config->value("region_value", Settings::REGION_VALUE_AUTO_SELECT).toInt();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("Miscellaneous");
|
qt_config->beginGroup("Miscellaneous");
|
||||||
|
|
|
@ -23,13 +23,15 @@ void ConfigureGeneral::setConfiguration() {
|
||||||
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
|
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
|
||||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||||
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||||
ui->region_combobox->setCurrentIndex(Settings::values.region_value);
|
|
||||||
|
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
|
||||||
|
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureGeneral::applyConfiguration() {
|
void ConfigureGeneral::applyConfiguration() {
|
||||||
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
||||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||||
Settings::values.region_value = ui->region_combobox->currentIndex();
|
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
|
||||||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,11 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="region_combobox">
|
<widget class="QComboBox" name="region_combobox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto-select</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string notr="true">JPN</string>
|
<string notr="true">JPN</string>
|
||||||
|
|
|
@ -129,6 +129,9 @@
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QComboBox" name="combo_language">
|
<widget class="QComboBox" name="combo_language">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Note: this can be overridden when region setting is auto-select</string>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Japanese (日本語)</string>
|
<string>Japanese (日本語)</string>
|
||||||
|
|
|
@ -114,6 +114,8 @@ static const std::vector<u8> cfg_system_savedata_id = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static u32 preferred_region_code = 0;
|
||||||
|
|
||||||
void GetCountryCodeString(Service::Interface* self) {
|
void GetCountryCodeString(Service::Interface* self) {
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||||
u32 country_code_id = cmd_buff[1];
|
u32 country_code_id = cmd_buff[1];
|
||||||
|
@ -159,11 +161,18 @@ void GetCountryCodeID(Service::Interface* self) {
|
||||||
cmd_buff[2] = country_code_id;
|
cmd_buff[2] = country_code_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u32 GetRegionValue() {
|
||||||
|
if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT)
|
||||||
|
return preferred_region_code;
|
||||||
|
|
||||||
|
return Settings::values.region_value;
|
||||||
|
}
|
||||||
|
|
||||||
void SecureInfoGetRegion(Service::Interface* self) {
|
void SecureInfoGetRegion(Service::Interface* self) {
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
u32* cmd_buff = Kernel::GetCommandBuffer();
|
||||||
|
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||||
cmd_buff[2] = Settings::values.region_value;
|
cmd_buff[2] = GetRegionValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenHashConsoleUnique(Service::Interface* self) {
|
void GenHashConsoleUnique(Service::Interface* self) {
|
||||||
|
@ -183,7 +192,7 @@ void GetRegionCanadaUSA(Service::Interface* self) {
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw;
|
cmd_buff[1] = RESULT_SUCCESS.raw;
|
||||||
|
|
||||||
u8 canada_or_usa = 1;
|
u8 canada_or_usa = 1;
|
||||||
if (canada_or_usa == Settings::values.region_value) {
|
if (canada_or_usa == GetRegionValue()) {
|
||||||
cmd_buff[2] = 1;
|
cmd_buff[2] = 1;
|
||||||
} else {
|
} else {
|
||||||
cmd_buff[2] = 0;
|
cmd_buff[2] = 0;
|
||||||
|
@ -313,10 +322,47 @@ static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 fl
|
||||||
return MakeResult<void*>(pointer);
|
return MakeResult<void*>(pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the language is available in the chosen region, and returns a proper one
|
||||||
|
static u8 AdjustLanguageInfoBlock(u32 region, u8 language) {
|
||||||
|
static const std::array<std::vector<u8>, 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},
|
||||||
|
}};
|
||||||
|
const auto& available = region_languages[region];
|
||||||
|
if (std::find(available.begin(), available.end(), language) == available.end()) {
|
||||||
|
return available[0];
|
||||||
|
}
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
|
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
|
||||||
void* pointer;
|
void* pointer;
|
||||||
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
|
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
|
||||||
memcpy(output, pointer, size);
|
memcpy(output, pointer, size);
|
||||||
|
|
||||||
|
// override the language setting if the region setting is auto
|
||||||
|
if (block_id == LanguageBlockID &&
|
||||||
|
Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) {
|
||||||
|
u8 language;
|
||||||
|
memcpy(&language, output, sizeof(u8));
|
||||||
|
language = AdjustLanguageInfoBlock(preferred_region_code, language);
|
||||||
|
memcpy(output, &language, sizeof(u8));
|
||||||
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,10 +579,17 @@ void Init() {
|
||||||
AddService(new CFG_U_Interface);
|
AddService(new CFG_U_Interface);
|
||||||
|
|
||||||
LoadConfigNANDSaveFile();
|
LoadConfigNANDSaveFile();
|
||||||
|
|
||||||
|
preferred_region_code = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {}
|
void Shutdown() {}
|
||||||
|
|
||||||
|
void SetPreferredRegionCode(u32 region_code) {
|
||||||
|
preferred_region_code = region_code;
|
||||||
|
LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code);
|
||||||
|
}
|
||||||
|
|
||||||
void SetUsername(const std::u16string& name) {
|
void SetUsername(const std::u16string& name) {
|
||||||
ASSERT(name.size() <= 10);
|
ASSERT(name.size() <= 10);
|
||||||
UsernameBlock block{};
|
UsernameBlock block{};
|
||||||
|
|
|
@ -282,6 +282,13 @@ void Init();
|
||||||
/// Shutdown the config service
|
/// Shutdown the config service
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the region code preferred by the game so that CFG will adjust to it when the region setting
|
||||||
|
* is auto.
|
||||||
|
* @param region_code the preferred region code to set
|
||||||
|
*/
|
||||||
|
void SetPreferredRegionCode(u32 region_code);
|
||||||
|
|
||||||
// Utilities for frontend to set config data.
|
// Utilities for frontend to set config data.
|
||||||
// Note: before calling these functions, LoadConfigNANDSaveFile should be called,
|
// Note: before calling these functions, LoadConfigNANDSaveFile should be called,
|
||||||
// and UpdateConfigNANDSavegame should be called after making changes to config data.
|
// and UpdateConfigNANDSavegame should be called after making changes to config data.
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
#include "core/file_sys/archive_romfs.h"
|
#include "core/file_sys/archive_romfs.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/kernel/resource_limit.h"
|
#include "core/hle/kernel/resource_limit.h"
|
||||||
|
#include "core/hle/service/cfg/cfg.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
#include "core/loader/ncch.h"
|
#include "core/loader/ncch.h"
|
||||||
|
#include "core/loader/smdh.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() {
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AppLoader_NCCH::ParseRegionLockoutInfo() {
|
||||||
|
std::vector<u8> smdh_buffer;
|
||||||
|
if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) {
|
||||||
|
SMDH smdh;
|
||||||
|
memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH));
|
||||||
|
u32 region_lockout = smdh.region_lockout;
|
||||||
|
constexpr u32 REGION_COUNT = 7;
|
||||||
|
for (u32 region = 0; region < REGION_COUNT; ++region) {
|
||||||
|
if (region_lockout & 1) {
|
||||||
|
Service::CFG::SetPreferredRegionCode(region);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
region_lockout >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ResultStatus AppLoader_NCCH::Load() {
|
ResultStatus AppLoader_NCCH::Load() {
|
||||||
if (is_loaded)
|
if (is_loaded)
|
||||||
return ResultStatus::ErrorAlreadyLoaded;
|
return ResultStatus::ErrorAlreadyLoaded;
|
||||||
|
@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() {
|
||||||
|
|
||||||
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this),
|
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this),
|
||||||
Service::FS::ArchiveIdCode::RomFS);
|
Service::FS::ArchiveIdCode::RomFS);
|
||||||
|
|
||||||
|
ParseRegionLockoutInfo();
|
||||||
|
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -250,6 +250,9 @@ private:
|
||||||
*/
|
*/
|
||||||
ResultStatus LoadExeFS();
|
ResultStatus LoadExeFS();
|
||||||
|
|
||||||
|
/// Reads the region lockout info in the SMDH and send it to CFG service
|
||||||
|
void ParseRegionLockoutInfo();
|
||||||
|
|
||||||
bool is_exefs_loaded = false;
|
bool is_exefs_loaded = false;
|
||||||
bool is_compressed = false;
|
bool is_compressed = false;
|
||||||
|
|
||||||
|
|
|
@ -110,5 +110,9 @@ struct Values {
|
||||||
u16 gdbstub_port;
|
u16 gdbstub_port;
|
||||||
} extern values;
|
} extern values;
|
||||||
|
|
||||||
|
// a special value for Values::region_value indicating that citra will automatically select a region
|
||||||
|
// value to fit the region lockout info of the game
|
||||||
|
static constexpr int REGION_VALUE_AUTO_SELECT = -1;
|
||||||
|
|
||||||
void Apply();
|
void Apply();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue