diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 29462c982d375f15a8208a8abaf8b62ad6f71f1a..98f09325808abc7077a9279fed50d7c0b52455cc 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -89,7 +89,8 @@ void Config::ReadValues() { // System 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 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info"); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 001b18ac2e0aaf0fa29f5a66be98dd2c04e24253..50c4a28129c4d7508d2d9a77e8b0ee36bf7d5138 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -101,7 +101,7 @@ use_virtual_sd = is_new_3ds = # 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 = [Miscellaneous] diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 06a4e9d25c9f5304d84344227935dab1c48b35df..c904c4b0006e1c99fd67bfbf2adc36d7e5eb9048 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -72,7 +72,8 @@ void Config::ReadValues() { qt_config->beginGroup("System"); 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->beginGroup("Miscellaneous"); diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp index 03cd8835bac0e62524f007c2f079d3ddb2db0ddd..ac90a6df4726d21c4acd468ede15cdfdb4e0ad4e 100644 --- a/src/citra_qt/configure_general.cpp +++ b/src/citra_qt/configure_general.cpp @@ -23,13 +23,15 @@ void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 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() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->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::Apply(); } diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui index 81688113f27e7e9d6e160cb7d92830271330198f..342954e4159132dc7ffa38879c65de86349cec2f 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configure_general.ui @@ -82,6 +82,11 @@ </item> <item> <widget class="QComboBox" name="region_combobox"> + <item> + <property name="text"> + <string>Auto-select</string> + </property> + </item> <item> <property name="text"> <string notr="true">JPN</string> diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui index 6a906b61bd1ad093a48ecaeb5b32d9e39c43bfd5..cc54fa37f192b6a7c6f5c3fd1c3c50df66e26366 100644 --- a/src/citra_qt/configure_system.ui +++ b/src/citra_qt/configure_system.ui @@ -129,6 +129,9 @@ </item> <item row="2" column="1"> <widget class="QComboBox" name="combo_language"> + <property name="toolTip"> + <string>Note: this can be overridden when region setting is auto-select</string> + </property> <item> <property name="text"> <string>Japanese (日本語)</string> diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 65655f45dc93d2131db5a641b0ab545dc885a10d..0bf59eb76cd20bd3e4e14176afb807889fadcf4d 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -115,6 +115,8 @@ static const std::vector<u8> cfg_system_savedata_id = { 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, }; +static u32 preferred_region_code = 0; + void GetCountryCodeString(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 country_code_id = cmd_buff[1]; @@ -160,11 +162,18 @@ void GetCountryCodeID(Service::Interface* self) { 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) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = Settings::values.region_value; + cmd_buff[2] = GetRegionValue(); } void GenHashConsoleUnique(Service::Interface* self) { @@ -184,7 +193,7 @@ void GetRegionCanadaUSA(Service::Interface* self) { cmd_buff[1] = RESULT_SUCCESS.raw; u8 canada_or_usa = 1; - if (canada_or_usa == Settings::values.region_value) { + if (canada_or_usa == GetRegionValue()) { cmd_buff[2] = 1; } else { cmd_buff[2] = 0; @@ -314,10 +323,47 @@ static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 fl 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) { void* pointer; CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); 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; } @@ -535,10 +581,17 @@ void Init() { AddService(new CFG_U); LoadConfigNANDSaveFile(); + + preferred_region_code = 0; } 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) { ASSERT(name.size() <= 10); UsernameBlock block{}; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index fb47c2aa558da4dd90e59d42a0e3f82891d05568..618c9647e66d8eb06ce711ab44d3bba74786d449 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -282,6 +282,13 @@ void Init(); /// Shutdown the config service 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. // Note: before calling these functions, LoadConfigNANDSaveFile should be called, // and UpdateConfigNANDSavegame should be called after making changes to config data. diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 6f216442868314931cd0a2248e16c654c48c1b1d..a204dc33628786c2bcea526eeefa1f280b3146cf 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -11,8 +11,10 @@ #include "core/file_sys/archive_romfs.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "core/loader/ncch.h" +#include "core/loader/smdh.h" #include "core/memory.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { 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() { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() { Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS); + + ParseRegionLockoutInfo(); + return ResultStatus::Success; } diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 6afc171a50f2e6ddc648c1ce04d3cff2c528aceb..fe08f5b45af1ff4ab97f5ece18679e16e15ca208 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -229,6 +229,9 @@ private: */ 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_compressed = false; diff --git a/src/core/settings.h b/src/core/settings.h index db4c8fadaa28a94aa58d20ad6b615061d7947085..4e7a4b1be27f7db59dec0397d6ad8de1333bd94c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -110,5 +110,9 @@ struct Values { u16 gdbstub_port; } 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(); }