Mark console as "linked" when using the azahar artic setup tool (#833)
Some checks failed
citra-format / clang-format (push) Has been cancelled
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / android (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / ios (push) Has been cancelled

* Mark console as "linked" when using the azahar artic setup tool

* Updated strings related to console linking

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
PabloMK7 2025-03-28 12:10:59 +01:00 committed by GitHub
parent dee576bfeb
commit eda2d6f9fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 285 additions and 121 deletions

View file

@ -182,6 +182,10 @@ object NativeLibrary {
external fun uninstallSystemFiles(old3DS: Boolean)
external fun isFullConsoleLinked(): Boolean
external fun unlinkConsole()
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()

View file

@ -4,6 +4,7 @@
package org.citra.citra_emu.fragments
import android.content.DialogInterface
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.Gravity
@ -157,6 +158,22 @@ class SystemFilesFragment : Fragment() {
movementMethod = LinkMovementMethod.getInstance()
}
binding.buttonUnlinkConsoleData.isEnabled = NativeLibrary.isFullConsoleLinked()
binding.buttonUnlinkConsoleData.setOnClickListener {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.delete_system_files)
.setMessage(HtmlCompat.fromHtml(
requireContext().getString(R.string.delete_system_files_description),
HtmlCompat.FROM_HTML_MODE_COMPACT
))
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
NativeLibrary.unlinkConsole()
binding.buttonUnlinkConsoleData.isEnabled = NativeLibrary.isFullConsoleLinked()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
binding.buttonSetUpSystemFiles.setOnClickListener {
val inflater = LayoutInflater.from(context)
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)

View file

@ -36,6 +36,7 @@
#include "core/frontend/camera/factory.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hw/unique_data.h"
#include "core/loader/loader.h"
#include "core/savestate.h"
#include "core/system_titles.h"
@ -772,4 +773,12 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_isFullConsoleLinked(JNIEnv* env, jobject obj) {
return HW::UniqueData::IsFullConsoleLinked();
}
void Java_org_citra_citra_1emu_NativeLibrary_unlinkConsole(JNIEnv* env, jobject obj) {
HW::UniqueData::UnlinkConsole();
}
} // extern "C"

View file

@ -61,6 +61,13 @@
android:layout_height="wrap_content"
android:text="@string/setup_tool_connect" />
<Button
android:id="@+id/button_unlink_console_data"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/delete_system_files" />
<View
android:id="@+id/divider2"
android:layout_width="match_parent"

View file

@ -150,8 +150,10 @@
<string name="setup_system_files">System Files</string>
<string name="setup_system_files_description">Perform system file operations such as installing system files or booting the Home Menu</string>
<string name="setup_tool_connect">Connect to Artic Setup Tool</string>
<string name="setup_system_files_preamble"><![CDATA[Azahar needs files from a real console to be able to use some of its features. You can get such files with the <a href=https://github.com/azahar-emu/ArticSetupTool>Azahar Artic Setup Tool</a>.<br> Notes:<ul><li><b>This operation will install console unique files to Azahar, do not share your user or nand folders<br>after performing the setup process!</b></li><li>Old 3DS setup is needed for the New 3DS setup to work.</li><li>Both setup modes will work regardless of the model of the console running the setup tool.</li></ul>]]></string>
<string name="setup_system_files_preamble"><![CDATA[Azahar needs console unique data and firmware files from a real console to be able to use some of its features. Such files and data can be set up with the <a href=https://github.com/azahar-emu/ArticSetupTool>Azahar Artic Setup Tool</a>.<br>Notes:<ul><li><b>This operation will install console unique data to Azahar, do not share your user or nand folders after performing the setup process!</b></li><li>While doing the setup process, Azahar will link to the console running the setup tool. You can unlink the console later from the System Files tab in the emulator options menu.</li><li>Do not go online with both Azahar and your 3DS console at the same time after setting up system files, as this could cause issues.</li><li>Old 3DS setup is needed for the New 3DS setup to work (setting up both is recommended).</li><li>Both setup modes will work regardless of the model of the console running the setup tool.</li></ul>]]></string>
<string name="setup_system_files_detect">Fetching current system files status, please wait...</string>
<string name="delete_system_files">Unlink Console Unique Data</string>
<string name="delete_system_files_description"><![CDATA[This action will unlink your real console from Azahar, with the following consequences:<br><ul><li>Your OTP, SecureInfo and LocalFriendCodeSeed will be removed from Azahar.</li><li>Your friend list will reset and you will be logged out of your NNID/PNID account.</li><li>System files and eshop titles obtained through Azahar will become inaccessible until the same console is linked again using the setup tool (save data will not be lost).</li></ul><br>Continue?]]></string>
<string name="setup_system_files_o3ds">Old 3DS Setup</string>
<string name="setup_system_files_n3ds">New 3DS Setup</string>
<string name="setup_system_files_possible">Setup is possible.</string>

View file

@ -2112,15 +2112,18 @@ void GMainWindow::OnMenuSetUpSystemFiles() {
QVBoxLayout layout(&dialog);
QLabel label_description(
tr("<p>Azahar needs files from a real console to be able to use some of its features.<br>"
"You can get such files with the <a "
tr("<p>Azahar needs console unique data and firmware files from a real console to be "
"able to use some of its features.<br>Such files and data can be set up with the <a "
"href=https://github.com/azahar-emu/ArticSetupTool>Azahar "
"Artic Setup Tool</a><br> Notes:<ul><li><b>This operation will install console unique "
"files "
"to Azahar, do not share your user or nand folders<br>after performing the setup "
"process!</b></li><li>Old 3DS setup is needed for the New 3DS setup to "
"work.</li><li>Both setup modes will work regardless of the model of the console "
"running the setup tool.</li></ul><hr></p>"),
"Artic Setup Tool</a><br>Notes:<ul><li><b>This operation will install console unique "
"data to Azahar, do not share your user or nand folders<br>after performing the setup "
"process!</b></li><li>While doing the setup process, Azahar will link to the console "
"running the setup tool. You can unlink the<br>console later from the System tab in the "
"emulator configuration menu.</li><li>Do not go online with both Azahar and your 3DS "
"console at the same time after setting up system files,<br>as it could cause "
"issues.</li><li>Old 3DS setup is needed for the New 3DS setup to work (doing both "
"setup modes is recommended).</li><li>Both setup modes will work regardless of the "
"model of the console running the setup tool.</li></ul><hr></p>"),
&dialog);
label_description.setOpenExternalLinks(true);
layout.addWidget(&label_description);

View file

@ -238,6 +238,8 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
connect(ui->button_regenerate_mac, &QPushButton::clicked, this, &ConfigureSystem::RefreshMAC);
connect(ui->button_linked_console, &QPushButton::clicked, this,
&ConfigureSystem::UnlinkConsole);
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
ui->button_secure_info->setEnabled(false);
@ -561,6 +563,25 @@ void ConfigureSystem::RefreshMAC() {
ui->label_mac->setText(tr("MAC: %1").arg(QString::fromStdString(mac_address)));
}
void ConfigureSystem::UnlinkConsole() {
QMessageBox::StandardButton reply;
QString warning_text =
tr("This action will unlink your real console from Azahar, with the following "
"consequences:<br><ul><li>Your OTP, SecureInfo and LocalFriendCodeSeed will be removed "
"from Azahar.</li><li>Your friend list will reset and you will be logged out of your "
"NNID/PNID account.</li><li>System files and eshop titles obtained through Azahar will "
"become inaccessible until the same console is linked again (save data will not be "
"lost).</li></ul><br>Continue?");
reply =
QMessageBox::warning(this, tr("Warning"), warning_text, QMessageBox::No | QMessageBox::Yes);
if (reply == QMessageBox::No) {
return;
}
HW::UniqueData::UnlinkConsole();
RefreshSecureDataStatus();
}
void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
@ -601,6 +622,15 @@ void ConfigureSystem::RefreshSecureDataStatus() {
tr((std::string("Status: ") + status_to_str(HW::UniqueData::LoadOTP())).c_str()));
ui->label_movable_status->setText(
tr((std::string("Status: ") + status_to_str(HW::UniqueData::LoadMovable())).c_str()));
if (HW::UniqueData::IsFullConsoleLinked()) {
ui->linked_console->setVisible(true);
ui->button_otp->setEnabled(false);
ui->button_secure_info->setEnabled(false);
ui->button_friend_code_seed->setEnabled(false);
} else {
ui->linked_console->setVisible(false);
}
}
void ConfigureSystem::RetranslateUI() {
@ -625,6 +655,7 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_init_ticks_type->setVisible(false);
ui->label_init_ticks_value->setVisible(false);
ui->label_console_id->setVisible(false);
ui->label_mac->setVisible(false);
ui->label_sound->setVisible(false);
ui->label_language->setVisible(false);
ui->label_country->setVisible(false);
@ -646,6 +677,7 @@ void ConfigureSystem::SetupPerGameUI() {
ui->edit_init_ticks_value->setVisible(false);
ui->toggle_system_setup->setVisible(false);
ui->button_regenerate_console_id->setVisible(false);
ui->button_regenerate_mac->setVisible(false);
// Apps can change the state of the plugin loader, so plugins load
// to a chainloaded app with specific parameters. Don't allow
// the plugin loader state to be configured per-game as it may
@ -653,6 +685,7 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_plugin_loader->setVisible(false);
ui->plugin_loader->setVisible(false);
ui->allow_plugin_loader->setVisible(false);
ui->group_real_console_unique_data->setVisible(false);
ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
is_new_3ds);

View file

@ -52,6 +52,7 @@ private:
void UpdateInitTicks(int init_ticks_type);
void RefreshConsoleID();
void RefreshMAC();
void UnlinkConsole();
void InstallSecureData(const std::string& from_path, const std::string& to_path);
void RefreshSecureDataStatus();

View file

@ -518,122 +518,157 @@ online features (if installed)</string>
<string>Real Console Unique Data</string>
</property>
<layout class="QGridLayout" name="gridLayout1">
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="group_real_console_unique_data_core">
<layout class="QGridLayout" name="gridLayout2">
<item row="0" column="0" colspan="2">
<widget class="QWidget" name="linked_console">
<layout class="QHBoxLayout" name="horizontalLayout_linked_console">
<item>
<widget class="QLabel" name="label_linked_console">
<property name="text">
<string>Your real console is linked to Azahar.</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_linked_console">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Unlink</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_otp">
<property name="text">
<string>OTP</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="otp">
<layout class="QHBoxLayout" name="horizontalLayout_otp">
<item>
<widget class="QLabel" name="label_otp_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_otp">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_secure_info">
<property name="text">
<string>SecureInfo_A/B</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="secure_info">
<layout class="QHBoxLayout" name="horizontalLayout_secure_info">
<item>
<widget class="QLabel" name="label_secure_info_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_secure_info">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_friend_code_seed">
<property name="text">
<string>LocalFriendCodeSeed_A/B</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QWidget" name="friend_code_seed">
<layout class="QHBoxLayout" name="horizontalLayout_friend_code_seed">
<item>
<widget class="QLabel" name="label_friend_code_seed_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_friend_code_seed">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_secure_info">
<property name="text">
<string>SecureInfo_A/B</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="secure_info">
<layout class="QHBoxLayout" name="horizontalLayout_secure_info">
<item>
<widget class="QLabel" name="label_secure_info_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_secure_info">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_friend_code_seed">
<property name="text">
<string>LocalFriendCodeSeed_A/B</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="friend_code_seed">
<layout class="QHBoxLayout" name="horizontalLayout_friend_code_seed">
<item>
<widget class="QLabel" name="label_friend_code_seed_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_friend_code_seed">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_otp">
<property name="text">
<string>OTP</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QWidget" name="otp">
<layout class="QHBoxLayout" name="horizontalLayout_otp">
<item>
<widget class="QLabel" name="label_otp_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_otp">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Choose</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_movable">
<property name="text">
<string>movable.sed</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="1" column="1">
<widget class="QWidget" name="movable">
<layout class="QHBoxLayout" name="horizontalLayout_movable">
<item>

View file

@ -5,6 +5,7 @@
#include <cryptopp/sha.h>
#include "common/common_paths.h"
#include "common/logging/log.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/certificate.h"
#include "core/file_sys/otp.h"
#include "core/hw/aes/key.h"
@ -262,4 +263,30 @@ std::unique_ptr<FileUtil::IOFile> OpenUniqueCryptoFile(const std::string& filena
return std::make_unique<FileUtil::CryptoIOFile>(filename, openmode, key, ctr, flags);
}
bool IsFullConsoleLinked() {
return GetOTP().Valid() && GetSecureInfoA().IsValid() && GetLocalFriendCodeSeedB().IsValid();
}
void UnlinkConsole() {
// Remove all console unique data, as well as the act, nim and frd savefiles
const std::string system_save_data_path =
FileSys::GetSystemSaveDataContainerPath(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
constexpr std::array<std::array<u8, 8>, 3> save_data_ids{{
{0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x01, 0x00},
{0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x01, 0x00},
}};
for (auto& id : save_data_ids) {
const std::string final_path = FileSys::GetSystemSaveDataPath(system_save_data_path, id);
FileUtil::DeleteDirRecursively(final_path, 2);
}
FileUtil::Delete(GetOTPPath());
FileUtil::Delete(GetSecureInfoAPath());
FileUtil::Delete(GetLocalFriendCodeSeedBPath());
InvalidateSecureData();
}
} // namespace HW::UniqueData

View file

@ -154,4 +154,7 @@ void InvalidateSecureData();
std::unique_ptr<FileUtil::IOFile> OpenUniqueCryptoFile(const std::string& filename,
const char openmode[], UniqueCryptoFileID id,
int flags = 0);
bool IsFullConsoleLinked();
void UnlinkConsole();
} // namespace HW::UniqueData

View file

@ -342,7 +342,8 @@ void Apploader_Artic::EnsureClientConnected() {
if (is_initial_setup) {
// Ensure we are running the initial setup app in the correct version
auto req = client->NewRequest("System_IsAzaharInitialSetup");
auto req = client->NewRequest("System_ArticSetupVersion");
req.AddParameterU32(SETUP_TOOL_VERSION);
auto resp = client->Send(req);
if (!resp.has_value()) {
client_connected = false;
@ -355,7 +356,15 @@ void Apploader_Artic::EnsureClientConnected() {
return;
}
client_connected = *reinterpret_cast<u32*>(ret_buf->first) == INITIAL_SETUP_APP_VERSION;
if (*reinterpret_cast<u32*>(ret_buf->first) != SETUP_TOOL_VERSION) {
system.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected,
"\nIncompatible Artic Setup Tool version.\nCheck for Artic Setup Tool "
"or Azahar updates.");
client_connected = false;
client->Stop();
} else {
client_connected = true;
}
}
}
@ -385,6 +394,20 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
if (is_initial_setup) {
// If there is already a console linked, check it's the same device.
// Otherwise it could cause weird issues with account save data.
if (HW::UniqueData::IsFullConsoleLinked()) {
auto req = client->NewRequest("System_ReportDeviceID");
req.AddParameterU32(HW::UniqueData::GetOTP().GetDeviceID());
auto resp = client->Send(req);
if (!resp.has_value() || !resp->Succeeded())
return ResultStatus::ErrorArtic;
if (resp->GetMethodResult() != 0)
return ResultStatus::ErrorArtic;
}
// Request console unique data
for (int i = 0; i < 6; i++) {
std::string path;

View file

@ -93,7 +93,7 @@ public:
}
private:
static constexpr u32 INITIAL_SETUP_APP_VERSION = 0;
static constexpr u32 SETUP_TOOL_VERSION = 1;
/**
* Loads .code section into memory for booting
* @param process The newly created process