Check that the country setting is valid for selected region (#847)

* Check that the country setting is valid for selected region

* `SystemSaveGame.checkCountryCompatibility` -> `SystemSaveGame.getCountryCompatibility`

* SettingsFragmentPresenter.kt: Moved `checkCountryCompatibility` definition out of `addSystemSettings`

* SettingsFragmentPresenter.kt: Renamed `compat` value to `compatFlags` for better readability

* configure_system.ui: Corrected indentation

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
PabloMK7 2025-03-29 22:50:00 +01:00 committed by GitHub
parent eda2d6f9fa
commit 5c7622100b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 528 additions and 177 deletions

View File

@ -73,7 +73,7 @@ import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
private val context: Context
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null

View File

@ -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<SettingsItem>) {
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()

View File

@ -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) {

View File

@ -6,6 +6,7 @@
#include <core/core.h>
#include <core/hle/service/cfg/cfg.h>
#include <core/hle/service/ptm/ptm.h>
#include <core/hw/unique_data.h>
#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<u32>(region), country)) {
res |= 1;
}
if (HW::UniqueData::GetSecureInfoA().IsValid()) {
region = static_cast<jint>(cfg->GetRegionValue(true));
if (!Service::CFG::Module::IsValidRegionCountry(static_cast<u32>(region), country)) {
res |= 2;
}
}
return res;
}
} // extern "C"

View File

@ -200,6 +200,9 @@
<string name="plugin_loader_description">Loads 3GX plugins from the emulated SD card if they are available.</string>
<string name="allow_plugin_loader">Allow Applications to Change Plugin Loader State</string>
<string name="allow_plugin_loader_description">Allows homebrew apps to enable the plugin loader even when it is disabled.</string>
<string name="region_mismatch">Region Mismatch Warning</string>
<string name="region_mismatch_emulated">The country setting is not valid for the selected emulated region.</string>
<string name="region_mismatch_console">The country setting is not valid for the current linked console.</string>
<!-- Camera settings strings -->
<string name="inner_camera">Inner Camera</string>

View File

@ -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<int>(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<u32>(Settings::values.region_value.GetValue(true) + 1));
}

View File

@ -73,75 +73,6 @@
<string>Emulation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget_region" native="true">
<layout class="QHBoxLayout" name="region_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="region_label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</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>
</property>
</item>
<item>
<property name="text">
<string notr="true">USA</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">EUR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">AUS</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">CHN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">KOR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">TWN</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="emulation_speed_layout" native="true">
<layout class="QHBoxLayout" name="emulation_speed_layout_inner">

View File

@ -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<int>(&QComboBox::currentIndexChanged), this,
[this](int index) {
CheckCountryValid(static_cast<u8>(ui->combo_country->itemData(index).toInt()));
});
connect(ui->region_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this]([[maybe_unused]] int index) {
CheckCountryValid(static_cast<u8>(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<int>(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<u8>(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<u32>(region), country)) {
label_text = tr("Invalid country for configured region");
}
if (HW::UniqueData::GetSecureInfoA().IsValid()) {
region = static_cast<u32>(cfg->GetRegionValue(true));
if (!cfg->IsValidRegionCountry(static_cast<u32>(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<u32>(Settings::values.region_value.GetValue(true) + 1));
}

View File

@ -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();

View File

@ -64,21 +64,21 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<item row="0" column="0">
<widget class="QCheckBox" name="toggle_new_3ds">
<property name="text">
<string>Enable New 3DS mode</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QCheckBox" name="toggle_lle_applets">
<property name="text">
<string>Use LLE applets (if installed)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="2" column="0">
<widget class="QCheckBox" name="enable_required_online_lle_modules">
<property name="text">
<string>Enable required LLE modules for
@ -89,6 +89,57 @@ online features (if installed)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="region_label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<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>
</property>
</item>
<item>
<property name="text">
<string notr="true">USA</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">EUR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">AUS</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">CHN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">KOR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">TWN</string>
</property>
</item>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="edit_username">
<property name="sizePolicy">
@ -297,14 +348,21 @@ online features (if installed)</string>
<item row="8" column="1">
<widget class="QComboBox" name="combo_country"/>
</item>
<item row="9" column="0">
<item row="9" column="1">
<widget class="QLabel" name="label_country_invalid">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_init_clock">
<property name="text">
<string>Clock</string>
</property>
</widget>
</item>
<item row="9" column="1">
<item row="10" column="1">
<widget class="QComboBox" name="combo_init_clock">
<item>
<property name="text">
@ -318,28 +376,28 @@ online features (if installed)</string>
</item>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_init_time">
<property name="text">
<string>Startup time</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<widget class="QDateTimeEdit" name="edit_init_time">
<property name="displayFormat">
<string>yyyy-MM-ddTHH:mm:ss</string>
</property>
</widget>
</item>
<item row="11" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_init_time_offset">
<property name="text">
<string>Offset time</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="12" column="1">
<layout class="QGridLayout" name="edit_init_time_offset_grid">
<item row="0" column="0">
<widget class="QSpinBox" name="edit_init_time_offset_days">
@ -363,14 +421,14 @@ online features (if installed)</string>
</item>
</layout>
</item>
<item row="12" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_init_ticks_type">
<property name="text">
<string>Initial System Ticks</string>
</property>
</widget>
</item>
<item row="12" column="1">
<item row="13" column="1">
<widget class="QComboBox" name="combo_init_ticks_type">
<item>
<property name="text">
@ -384,14 +442,14 @@ online features (if installed)</string>
</item>
</widget>
</item>
<item row="13" column="0">
<item row="14" column="0">
<widget class="QLabel" name="label_init_ticks_value">
<property name="text">
<string>Initial System Ticks Override</string>
</property>
</widget>
</item>
<item row="13" column="1">
<item row="14" column="1">
<widget class="QLineEdit" name="edit_init_ticks_value">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -404,21 +462,21 @@ online features (if installed)</string>
</property>
</widget>
</item>
<item row="14" column="0">
<item row="15" column="0">
<widget class="QLabel" name="label_play_coins">
<property name="text">
<string>Play Coins</string>
</property>
</widget>
</item>
<item row="14" column="1">
<item row="15" column="1">
<widget class="QSpinBox" name="spinBox_play_coins">
<property name="maximum">
<number>300</number>
</property>
</widget>
</item>
<item row="15" column="0">
<item row="16" column="0">
<widget class="QLabel" name="label_steps_per_hour">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of steps per hour reported by the pedometer. Range from 0 to 65,535.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -428,28 +486,28 @@ online features (if installed)</string>
</property>
</widget>
</item>
<item row="15" column="1">
<item row="16" column="1">
<widget class="QSpinBox" name="spinBox_steps_per_hour">
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<widget class="QCheckBox" name="toggle_system_setup">
<property name="text">
<string>Run System Setup when Home Menu is launched</string>
</property>
</widget>
</item>
<item row="17" column="0">
<item row="18" column="0">
<widget class="QLabel" name="label_console_id">
<property name="text">
<string>Console ID:</string>
</property>
</widget>
</item>
<item row="17" column="1">
<item row="18" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -465,14 +523,14 @@ online features (if installed)</string>
</property>
</widget>
</item>
<item row="18" column="0">
<item row="19" column="0">
<widget class="QLabel" name="label_mac">
<property name="text">
<string>MAC:</string>
</property>
</widget>
</item>
<item row="18" column="1">
<item row="19" column="1">
<widget class="QPushButton" name="button_regenerate_mac">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -488,21 +546,21 @@ online features (if installed)</string>
</property>
</widget>
</item>
<item row="19" column="0">
<item row="20" column="0">
<widget class="QLabel" name="label_plugin_loader">
<property name="text">
<string>3GX Plugin Loader:</string>
</property>
</widget>
</item>
<item row="19" column="1">
<item row="20" column="1">
<widget class="QCheckBox" name="plugin_loader">
<property name="text">
<string>Enable 3GX plugin loader</string>
</property>
</widget>
</item>
<item row="20" column="1">
<item row="21" column="1">
<widget class="QCheckBox" name="allow_plugin_loader">
<property name="text">
<string>Allow applications to change plugin loader state</string>

View File

@ -86,30 +86,227 @@ static constexpr u16 C(const char code[2]) {
}
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
// 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);

View File

@ -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.

View File

@ -408,6 +408,7 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
return ResultStatus::ErrorArtic;
}
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("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<Kernel::Process>& process) {
memcpy(&console_id, resp_buff->first, sizeof(u64));
memcpy(&random_id, reinterpret_cast<u8*>(resp_buff->first) + sizeof(u64),
sizeof(u32));
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("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<Kernel::Process>& process) {
} else if (i == 5) {
std::array<u8, 6> mac;
memcpy(mac.data(), resp_buff->first, mac.size());
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("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<Kernel::Process>& 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;

View File

@ -471,6 +471,14 @@ std::optional<Client::Response> Client::Send(Request& request) {
return std::optional<Client::Response>(std::move(resp.response));
}
void Client::LogOnServer(ArticBaseCommon::LogOnServerType log_type, const std::string& message) {
auto req = NewRequest("__log");
req.AddParameterS8(static_cast<s8>(log_type));
req.AddParameterBuffer(message.data(), message.size());
Send(req);
}
void Client::SignalCommunicationError(const std::string& msg) {
StopImpl(true);
LOG_CRITICAL(Network, "Communication error");

View File

@ -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;

View File

@ -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 {