diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt index 3d7217aee..07904ca41 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsAdapter.kt @@ -73,7 +73,7 @@ import kotlin.math.roundToInt class SettingsAdapter( private val fragmentView: SettingsFragmentView, - private val context: Context + public val context: Context ) : RecyclerView.Adapter(), DialogInterface.OnClickListener { private var settings: ArrayList? = null private var clickedItem: SettingsItem? = null diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 2d95adcf5..293d33bd3 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -13,6 +13,7 @@ import android.hardware.camera2.CameraManager import android.os.Build import android.text.TextUtils import androidx.preference.PreferenceManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlin.math.min import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R @@ -239,6 +240,30 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } } + private var countryCompatibilityChanged = true + + private fun checkCountryCompatibility() { + if (countryCompatibilityChanged) { + countryCompatibilityChanged = false + val compatFlags = SystemSaveGame.getCountryCompatibility(IntSetting.EMULATED_REGION.int) + if (compatFlags != 0) { + var message = "" + if (compatFlags and 1 != 0) { + message += settingsAdapter.context.getString(R.string.region_mismatch_emulated) + } + if (compatFlags and 2 != 0) { + if (message.isNotEmpty()) message += "\n\n" + message += settingsAdapter.context.getString(R.string.region_mismatch_console) + } + MaterialAlertDialogBuilder(settingsAdapter.context) + .setTitle(R.string.region_mismatch) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null) + .show() + } + } + } + @OptIn(ExperimentalStdlibApi::class) private fun addSystemSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system)) @@ -282,51 +307,45 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) ) ) add(HeaderSetting(R.string.profile_settings)) - add( - StringInputSetting( - usernameSetting, - R.string.username, - 0, - "AZAHAR", - 10 - ) - ) + val regionSetting = object : AbstractIntSetting { + override var int: Int + get() { + val ret = IntSetting.EMULATED_REGION.int + checkCountryCompatibility() + return ret + } + set(value) { + IntSetting.EMULATED_REGION.int = value + countryCompatibilityChanged = true + checkCountryCompatibility() + } + override val key = IntSetting.EMULATED_REGION.key + override val section = null + override val isRuntimeEditable = false + override val valueAsString get() = int.toString() + override val defaultValue = IntSetting.EMULATED_REGION.defaultValue + } add( SingleChoiceSetting( - IntSetting.EMULATED_REGION, + regionSetting, R.string.emulated_region, 0, R.array.regionNames, R.array.regionValues, - IntSetting.EMULATED_REGION.key, - IntSetting.EMULATED_REGION.defaultValue ) ) - - val systemLanguageSetting = object : AbstractIntSetting { - override var int: Int - get() = SystemSaveGame.getSystemLanguage() - set(value) = SystemSaveGame.setSystemLanguage(value) - override val key = null - override val section = null - override val isRuntimeEditable = false - override val valueAsString get() = int.toString() - override val defaultValue = 1 - } - add( - SingleChoiceSetting( - systemLanguageSetting, - R.string.emulated_language, - 0, - R.array.languageNames, - R.array.languageValues - ) - ) - val systemCountrySetting = object : AbstractShortSetting { override var short: Short - get() = SystemSaveGame.getCountryCode() - set(value) = SystemSaveGame.setCountryCode(value) + get() { + val ret = SystemSaveGame.getCountryCode() + checkCountryCompatibility() + return ret; + } + set(value) { + SystemSaveGame.setCountryCode(value) + countryCompatibilityChanged = true + checkCountryCompatibility() + } override val key = null override val section = null override val isRuntimeEditable = false @@ -348,7 +367,34 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) countries.map { it.second }.toTypedArray() ) ) - + val systemLanguageSetting = object : AbstractIntSetting { + override var int: Int + get() = SystemSaveGame.getSystemLanguage() + set(value) = SystemSaveGame.setSystemLanguage(value) + override val key = null + override val section = null + override val isRuntimeEditable = false + override val valueAsString get() = int.toString() + override val defaultValue = 1 + } + add( + SingleChoiceSetting( + systemLanguageSetting, + R.string.emulated_language, + 0, + R.array.languageNames, + R.array.languageValues + ) + ) + add( + StringInputSetting( + usernameSetting, + R.string.username, + 0, + "AZAHAR", + 10 + ) + ) val playCoinSettings = object : AbstractIntSetting { override var int: Int get() = SystemSaveGame.getPlayCoins() diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/SystemSaveGame.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/SystemSaveGame.kt index bbba3e16c..812fb580e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/SystemSaveGame.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/SystemSaveGame.kt @@ -48,6 +48,8 @@ object SystemSaveGame { external fun getMac(): String external fun regenerateMac() + + external fun getCountryCompatibility(region: Int): Int } enum class BirthdayMonth(val code: Int, val days: Int) { diff --git a/src/android/app/src/main/jni/system_save_game.cpp b/src/android/app/src/main/jni/system_save_game.cpp index ae6a5d576..ec33fc9b5 100644 --- a/src/android/app/src/main/jni/system_save_game.cpp +++ b/src/android/app/src/main/jni/system_save_game.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "android_common/android_common.h" static bool changes_pending = false; @@ -138,4 +139,21 @@ void Java_org_citra_citra_1emu_utils_SystemSaveGame_regenerateMac(JNIEnv* env, cfg->SaveMacAddress(); } +jint Java_org_citra_citra_1emu_utils_SystemSaveGame_getCountryCompatibility( + JNIEnv* env, [[maybe_unused]] jobject obj, jint region) { + int res = 0; + u8 country = cfg->GetCountryCode(); + if (region != Settings::REGION_VALUE_AUTO_SELECT && + !Service::CFG::Module::IsValidRegionCountry(static_cast(region), country)) { + res |= 1; + } + if (HW::UniqueData::GetSecureInfoA().IsValid()) { + region = static_cast(cfg->GetRegionValue(true)); + if (!Service::CFG::Module::IsValidRegionCountry(static_cast(region), country)) { + res |= 2; + } + } + return res; +} + } // extern "C" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 05188785d..7febc210e 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -200,6 +200,9 @@ Loads 3GX plugins from the emulated SD card if they are available. Allow Applications to Change Plugin Loader State Allows homebrew apps to enable the plugin loader even when it is disabled. + Region Mismatch Warning + The country setting is not valid for the selected emulated region. + The country setting is not valid for the current linked console. Inner Camera diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index cf091ec3c..606414e20 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -121,16 +121,6 @@ void ConfigureGeneral::SetConfiguration() { !UISettings::values.screenshot_path.UsingGlobal()); ConfigurationShared::SetHighlight(ui->emulation_speed_layout, !Settings::values.frame_limit.UsingGlobal()); - ConfigurationShared::SetHighlight(ui->widget_region, - !Settings::values.region_value.UsingGlobal()); - const bool is_region_global = Settings::values.region_value.UsingGlobal(); - ui->region_combobox->setCurrentIndex( - is_region_global ? ConfigurationShared::USE_GLOBAL_INDEX - : static_cast(Settings::values.region_value.GetValue()) + - ConfigurationShared::USE_GLOBAL_OFFSET + 1); - } else { - // 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.GetValue() + 1); } UISettings::values.screenshot_path.SetGlobal(ui->screenshot_combo->currentIndex() == @@ -160,9 +150,6 @@ void ConfigureGeneral::ResetDefaults() { } void ConfigureGeneral::ApplyConfiguration() { - ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_value, ui->region_combobox, - [](s32 index) { return index - 1; }); - ConfigurationShared::ApplyPerGameSetting( &Settings::values.frame_limit, ui->emulation_speed_combo, [this](s32) { const bool is_maximum = ui->frame_limit->value() == ui->frame_limit->maximum(); @@ -191,7 +178,6 @@ void ConfigureGeneral::RetranslateUI() { void ConfigureGeneral::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { - ui->region_combobox->setEnabled(Settings::values.region_value.UsingGlobal()); ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); return; } @@ -212,8 +198,4 @@ void ConfigureGeneral::SetupPerGameUI() { ui->button_reset_defaults->setVisible(false); ui->toggle_gamemode->setVisible(false); ui->toggle_update_checker->setVisible(false); - - ConfigurationShared::SetColoredComboBox( - ui->region_combobox, ui->widget_region, - static_cast(Settings::values.region_value.GetValue(true) + 1)); } diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 4b6b0adf6..aebd12464 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -73,75 +73,6 @@ Emulation - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Region: - - - - - - - - Auto-select - - - - - JPN - - - - - USA - - - - - EUR - - - - - AUS - - - - - CHN - - - - - KOR - - - - - TWN - - - - - - - diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 2f097aaa3..4ce036394 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -240,6 +240,14 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) connect(ui->button_regenerate_mac, &QPushButton::clicked, this, &ConfigureSystem::RefreshMAC); connect(ui->button_linked_console, &QPushButton::clicked, this, &ConfigureSystem::UnlinkConsole); + connect(ui->combo_country, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) { + CheckCountryValid(static_cast(ui->combo_country->itemData(index).toInt())); + }); + connect(ui->region_combobox, qOverload(&QComboBox::currentIndexChanged), this, + [this]([[maybe_unused]] int index) { + CheckCountryValid(static_cast(ui->combo_country->currentData().toInt())); + }); connect(ui->button_secure_info, &QPushButton::clicked, this, [this] { ui->button_secure_info->setEnabled(false); @@ -280,6 +288,8 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) ui->combo_country->addItem(tr(country_names.at(i)), i); } } + ui->label_country_invalid->setVisible(false); + ui->label_country_invalid->setStyleSheet(QStringLiteral("QLabel { color: #ff3333; }")); SetupPerGameUI(); ConfigureTime(); @@ -290,6 +300,19 @@ ConfigureSystem::~ConfigureSystem() = default; void ConfigureSystem::SetConfiguration() { enabled = !system.IsPoweredOn(); + if (!Settings::IsConfiguringGlobal()) { + ConfigurationShared::SetHighlight(ui->region_label, + !Settings::values.region_value.UsingGlobal()); + const bool is_region_global = Settings::values.region_value.UsingGlobal(); + ui->region_combobox->setCurrentIndex( + is_region_global ? ConfigurationShared::USE_GLOBAL_INDEX + : static_cast(Settings::values.region_value.GetValue()) + + ConfigurationShared::USE_GLOBAL_OFFSET + 1); + } else { + // 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.GetValue() + 1); + } + ui->combo_init_clock->setCurrentIndex(static_cast(Settings::values.init_clock.GetValue())); QDateTime date_time; date_time.setSecsSinceEpoch(Settings::values.init_time.GetValue()); @@ -351,6 +374,7 @@ void ConfigureSystem::ReadSystemSettings() { // set the country code country_code = cfg->GetCountryCode(); ui->combo_country->setCurrentIndex(ui->combo_country->findData(country_code)); + CheckCountryValid(country_code); // set whether system setup is needed system_setup = cfg->IsSystemSetupNeeded(); @@ -373,6 +397,10 @@ void ConfigureSystem::ReadSystemSettings() { void ConfigureSystem::ApplyConfiguration() { if (enabled) { + ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_value, + ui->region_combobox, + [](s32 index) { return index - 1; }); + bool modified = false; // apply username @@ -582,6 +610,32 @@ void ConfigureSystem::UnlinkConsole() { RefreshSecureDataStatus(); } +void ConfigureSystem::CheckCountryValid(u8 country) { + // TODO(PabloMK7): Make this per-game compatible + if (!Settings::IsConfiguringGlobal()) + return; + + s32 region = ui->region_combobox->currentIndex() - 1; + QString label_text; + + if (region != Settings::REGION_VALUE_AUTO_SELECT && + !cfg->IsValidRegionCountry(static_cast(region), country)) { + label_text = tr("Invalid country for configured region"); + } + if (HW::UniqueData::GetSecureInfoA().IsValid()) { + region = static_cast(cfg->GetRegionValue(true)); + if (!cfg->IsValidRegionCountry(static_cast(region), country)) { + if (!label_text.isEmpty()) { + label_text += QString::fromStdString("\n"); + } + label_text += tr("Invalid country for console unique data"); + } + } + + ui->label_country_invalid->setText(label_text); + ui->label_country_invalid->setVisible(!label_text.isEmpty()); +} + void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) { std::string from = FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault); @@ -644,6 +698,7 @@ void ConfigureSystem::SetupPerGameUI() { ui->toggle_lle_applets->setEnabled(Settings::values.lle_applets.UsingGlobal()); ui->enable_required_online_lle_modules->setEnabled( Settings::values.enable_required_online_lle_modules.UsingGlobal()); + ui->region_combobox->setEnabled(Settings::values.region_value.UsingGlobal()); return; } @@ -694,4 +749,7 @@ void ConfigureSystem::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->enable_required_online_lle_modules, Settings::values.enable_required_online_lle_modules, required_online_lle_modules); + ConfigurationShared::SetColoredComboBox( + ui->region_combobox, ui->region_label, + static_cast(Settings::values.region_value.GetValue(true) + 1)); } diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index 26d640f01..af7c81200 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -53,6 +53,7 @@ private: void RefreshConsoleID(); void RefreshMAC(); void UnlinkConsole(); + void CheckCountryValid(u8 country); void InstallSecureData(const std::string& from_path, const std::string& to_path); void RefreshSecureDataStatus(); diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index 792a4ace4..e46f90c85 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -64,21 +64,21 @@ System Settings - + Enable New 3DS mode - + Use LLE applets (if installed) - + Enable required LLE modules for @@ -89,6 +89,57 @@ online features (if installed) + + + + Region: + + + + + + + + Auto-select + + + + + JPN + + + + + USA + + + + + EUR + + + + + AUS + + + + + CHN + + + + + KOR + + + + + TWN + + + + @@ -297,14 +348,21 @@ online features (if installed) - + + + + + + + + Clock - + @@ -318,28 +376,28 @@ online features (if installed) - + Startup time - + yyyy-MM-ddTHH:mm:ss - + Offset time - + @@ -363,14 +421,14 @@ online features (if installed) - + Initial System Ticks - + @@ -384,14 +442,14 @@ online features (if installed) - + Initial System Ticks Override - + @@ -404,21 +462,21 @@ online features (if installed) - + Play Coins - + 300 - + <html><head/><body><p>Number of steps per hour reported by the pedometer. Range from 0 to 65,535.</p></body></html> @@ -428,28 +486,28 @@ online features (if installed) - + 9999 - + Run System Setup when Home Menu is launched - + Console ID: - + @@ -465,14 +523,14 @@ online features (if installed) - + MAC: - + @@ -488,21 +546,21 @@ online features (if installed) - + 3GX Plugin Loader: - + Enable 3GX plugin loader - + Allow applications to change plugin loader state diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index cbd2dcea1..4d04af017 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -86,30 +86,227 @@ static constexpr u16 C(const char code[2]) { } 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 + // 0-7 Japan + 0, + C("JP"), + 0, + 0, + 0, + 0, + 0, + 0, + + // 8-15 America + C("AI"), + C("AG"), + C("AR"), + C("AW"), + C("BS"), + C("BB"), + C("BZ"), + C("BO"), + // 16-23 America + C("BR"), + C("VG"), + C("CA"), + C("KY"), + C("CL"), + C("CO"), + C("CR"), + C("DM"), + // 24-31 America + C("DO"), + C("EC"), + C("SV"), + C("GF"), + C("GD"), + C("GP"), + C("GT"), + C("GY"), + // 32-39 America + C("HT"), + C("HN"), + C("JM"), + C("MQ"), + C("MX"), + C("MS"), + C("AN"), + C("NI"), + // 40-47 America + C("PA"), + C("PY"), + C("PE"), + C("KN"), + C("LC"), + C("VC"), + C("SR"), + C("TT"), + // 48-55 America + C("TC"), + C("US"), + C("UY"), + C("VI"), + C("VE"), + 0, + 0, + 0, + + // 56-63 Invalid + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + + // 64-71 Europe + C("AL"), + C("AU"), + C("AT"), + C("BE"), + C("BA"), + C("BW"), + C("BG"), + C("HR"), + // 72-79 Europe + C("CY"), + C("CZ"), + C("DK"), + C("EE"), + C("FI"), + C("FR"), + C("DE"), + C("GR"), + // 80-87 Europe + C("HU"), + C("IS"), + C("IE"), + C("IT"), + C("LV"), + C("LS"), + C("LI"), + C("LT"), + // 88-95 Europe + C("LU"), + C("MK"), + C("MT"), + C("ME"), + C("MZ"), + C("NA"), + C("NL"), + C("NZ"), + // 96-103 Europe + C("NO"), + C("PL"), + C("PT"), + C("RO"), + C("RU"), + C("RS"), + C("SK"), + C("SI"), + // 104-111 Europe + C("ZA"), + C("ES"), + C("SZ"), + C("SE"), + C("CH"), + C("TR"), + C("GB"), + C("ZM"), + // 112-119 Europe + C("ZW"), + C("AZ"), + C("MR"), + C("ML"), + C("NE"), + C("TD"), + C("SD"), + C("ER"), + // 120-127 Europe + C("DJ"), + C("SO"), + C("AD"), + C("GI"), + C("GG"), + C("IM"), + C("JE"), + C("MC"), + + // 128-135 Taiwan + C("TW"), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + + // 136-143 Korea + C("KR"), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + + // 144-151 China? (Hong Kong & Macao) + C("HK"), + C("MO"), + 0, + 0, + 0, + 0, + 0, + 0, + + // 152-159 Southeast Asia + C("ID"), + C("SG"), // USA + C("TH"), + C("PH"), + C("MY"), // USA + 0, + 0, + 0, + + // 160-167 China + C("CN"), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + + // 168-175 Middle East + C("AE"), // USA + C("IN"), // EUR + C("EG"), + C("OM"), + C("QA"), + C("KW"), + C("SA"), // USA + C("SY"), + // 176-183 Middle East + C("BH"), + C("JO"), + 0, + 0, + 0, + 0, + 0, + 0, + + // 184-186 European Microstates + C("SM"), + C("VA"), + C("BM"), }}; // Based on PKHeX's lists of subregions at @@ -218,6 +415,29 @@ u32 Module::GetRegionValue(bool from_secure_info) { return Settings::values.region_value.GetValue(); } +bool Module::IsValidRegionCountry(u32 region, u8 country_code) { + switch (region) { + case 0: // JPN + return country_code == 1; + case 1: // USA + return (country_code >= 8 && country_code <= 52) || country_code == 153 || + country_code == 156 || country_code == 168 || country_code == 174; + case 2: // EUR + case 3: // AUS + return (country_code >= 64 && country_code <= 127) || + (country_code >= 184 && country_code <= 186) || country_code == 169; + case 4: // CHN + return country_code == 144 || country_code == 145 || country_code == 160; + case 5: // KOR + return country_code == 136; + case 6: // TWN + return country_code == 128; + default: + break; + } + return false; +} + void Module::Interface::GetRegion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 63fad1cdb..7536680c0 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -486,6 +486,8 @@ private: public: u32 GetRegionValue(bool from_secure_info); + static bool IsValidRegionCountry(u32 region, u8 country_code); + // Utilities for frontend to set config data. // Note: UpdateConfigNANDSavegame should be called after making changes to config data. diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp index ab2888f7a..9d8aa240c 100644 --- a/src/core/loader/artic.cpp +++ b/src/core/loader/artic.cpp @@ -408,6 +408,7 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { return ResultStatus::ErrorArtic; } + auto cfg = system.ServiceManager().GetService("cfg:u"); // Request console unique data for (int i = 0; i < 6; i++) { std::string path; @@ -471,7 +472,6 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { memcpy(&console_id, resp_buff->first, sizeof(u64)); memcpy(&random_id, reinterpret_cast(resp_buff->first) + sizeof(u64), sizeof(u32)); - auto cfg = system.ServiceManager().GetService("cfg:u"); if (cfg.get()) { auto cfg_module = cfg->GetModule(); cfg_module->SetConsoleUniqueId(random_id, console_id); @@ -480,7 +480,6 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { } else if (i == 5) { std::array mac; memcpy(mac.data(), resp_buff->first, mac.size()); - auto cfg = system.ServiceManager().GetService("cfg:u"); if (cfg.get()) { auto cfg_module = cfg->GetModule(); cfg_module->GetMacAddress() = Service::CFG::MacToString(mac); @@ -494,10 +493,25 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { if (!HW::UniqueData::GetCTCert().IsValid() || !HW::UniqueData::GetMovableSed().IsValid() || !HW::UniqueData::GetSecureInfoA().IsValid() || !HW::UniqueData::GetLocalFriendCodeSeedB().IsValid()) { - LOG_CRITICAL(Loader, "Some console unique data is invalid, aborting..."); + client->LogOnServer(Network::ArticBaseCommon::LogOnServerType::LOG_ERROR, + "Some console unique data is invalid.\n Aborting..."); return ResultStatus::ErrorArtic; } + if (cfg.get()) { + auto cfg_module = cfg->GetModule(); + if (!Service::CFG::Module::IsValidRegionCountry(cfg_module->GetRegionValue(true), + cfg_module->GetCountryCode())) { + // Report mismatch to server. + client->LogOnServer( + Network::ArticBaseCommon::LogOnServerType::LOG_ERROR, + "The country configuration does not match\n the console region. " + "Please select a valid\n country from the emulation settings."); + return ResultStatus::ErrorArtic; + } + cfg_module->SetSystemSetupNeeded(false); + } + // Set deliver arg so that System Settings goes to the update screen directly auto apt = Service::APT::GetModule(system); Service::APT::DeliverArg arg; diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index fe7a0578a..861e23477 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -471,6 +471,14 @@ std::optional Client::Send(Request& request) { return std::optional(std::move(resp.response)); } +void Client::LogOnServer(ArticBaseCommon::LogOnServerType log_type, const std::string& message) { + auto req = NewRequest("__log"); + req.AddParameterS8(static_cast(log_type)); + req.AddParameterBuffer(message.data(), message.size()); + + Send(req); +} + void Client::SignalCommunicationError(const std::string& msg) { StopImpl(true); LOG_CRITICAL(Network, "Communication error"); diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h index a14578f6d..e5992b41e 100644 --- a/src/network/artic_base/artic_base_client.h +++ b/src/network/artic_base/artic_base_client.h @@ -165,6 +165,8 @@ public: ping_enabled = enable; } + void LogOnServer(ArticBaseCommon::LogOnServerType log_type, const std::string& message); + private: static constexpr const int SERVER_VERSION = 2; diff --git a/src/network/artic_base/artic_base_common.h b/src/network/artic_base/artic_base_common.h index 671da6e99..a43920003 100644 --- a/src/network/artic_base/artic_base_common.h +++ b/src/network/artic_base/artic_base_common.h @@ -1,4 +1,4 @@ -// Copyright 2024 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -31,6 +31,12 @@ enum class RequestParameterType : u16 { IN_SMALL_BUFFER = 4, IN_BIG_BUFFER = 5, }; +enum class LogOnServerType : u8 { + LOG_DEBUG = 0, + LOG_INFO = 1, + LOG_WARNING = 2, + LOG_ERROR = 3, +}; struct RequestParameter { RequestParameterType type{}; union {