Merge pull request #4 from PabloMK7/amimprovements

Major revamps to match game loading decisions.
This commit is contained in:
PabloMK7 2025-02-27 14:39:12 +01:00 committed by GitHub
commit 5a0ad29bab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 4860 additions and 1115 deletions

View File

@ -507,8 +507,8 @@ void GMainWindow::InitializeWidgets() {
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
"indicate emulation is running faster or slower than a 3DS.")); "indicate emulation is running faster or slower than a 3DS."));
game_fps_label = new QLabel(); game_fps_label = new QLabel();
game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
"This will vary from game to game and scene to scene.")); "This will vary from app to app and scene to scene."));
emu_frametime_label = new QLabel(); emu_frametime_label = new QLabel();
emu_frametime_label->setToolTip( emu_frametime_label->setToolTip(
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
@ -756,7 +756,7 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot")); link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot")); link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
link_action_shortcut(ui->action_View_Lobby, link_action_shortcut(ui->action_View_Lobby,
QStringLiteral("Multiplayer Browse Public Game Lobby")); QStringLiteral("Multiplayer Browse Public Application Lobby"));
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room")); link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
link_action_shortcut(ui->action_Connect_To_Room, link_action_shortcut(ui->action_Connect_To_Room,
QStringLiteral("Multiplayer Direct Connect to Room")); QStringLiteral("Multiplayer Direct Connect to Room"));
@ -779,7 +779,7 @@ void GMainWindow::InitializeHotkeys() {
ToggleFullscreen(); ToggleFullscreen();
} }
}); });
connect_shortcut(QStringLiteral("Toggle Per-Game Speed"), [&] { connect_shortcut(QStringLiteral("Toggle Per-Application Speed"), [&] {
Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal()); Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal());
UpdateStatusBar(); UpdateStatusBar();
}); });
@ -1164,7 +1164,7 @@ void GMainWindow::OnUpdateFound(bool found, bool error) {
} }
if (emulation_running && !explicit_update_check) { if (emulation_running && !explicit_update_check) {
LOG_INFO(Frontend, "Update found, deferring as game is running"); LOG_INFO(Frontend, "Update found, deferring as application is running");
defer_update_prompt = true; defer_update_prompt = true;
return; return;
} }
@ -1223,7 +1223,7 @@ static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
//: TRANSLATORS: This string is shown to the user to explain why Citra needs to prevent the //: TRANSLATORS: This string is shown to the user to explain why Citra needs to prevent the
//: computer from sleeping //: computer from sleeping
options.insert(QString::fromLatin1("reason"), options.insert(QString::fromLatin1("reason"),
QCoreApplication::translate("GMainWindow", "Azahar is running a game")); QCoreApplication::translate("GMainWindow", "Azahar is running an application"));
// 0x4: Suspend lock; 0x8: Idle lock // 0x4: Suspend lock; 0x8: Idle lock
QDBusReply<QDBusObjectPath> reply = QDBusReply<QDBusObjectPath> reply =
xdp.call(QString::fromLatin1("Inhibit"), xdp.call(QString::fromLatin1("Inhibit"),
@ -1295,8 +1295,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorGetLoader: case Core::System::ResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString()); LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
QMessageBox::critical( QMessageBox::critical(
this, tr("Invalid ROM Format"), this, tr("Invalid App Format"),
tr("Your ROM format is not supported.<br/>Please follow the guides to redump your " tr("Your app format is not supported.<br/>Please follow the guides to redump your "
"<a " "<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/" "href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game " "dumping-game-cartridges/'>game "
@ -1308,10 +1308,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
break; break;
case Core::System::ResultStatus::ErrorSystemMode: case Core::System::ResultStatus::ErrorSystemMode:
LOG_CRITICAL(Frontend, "Failed to load ROM!"); LOG_CRITICAL(Frontend, "Failed to load App!");
QMessageBox::critical( QMessageBox::critical(
this, tr("ROM Corrupted"), this, tr("App Corrupted"),
tr("Your ROM is corrupted. <br/>Please follow the guides to redump your " tr("Your app is corrupted. <br/>Please follow the guides to redump your "
"<a " "<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/" "href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game " "dumping-game-cartridges/'>game "
@ -1323,23 +1323,17 @@ bool GMainWindow::LoadROM(const QString& filename) {
break; break;
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: {
QMessageBox::critical( QMessageBox::critical(this, tr("App Encrypted"),
this, tr("ROM Encrypted"), tr("Your app is encrypted. <br/>"
tr("Your ROM is encrypted. <br/>Please follow the guides to redump your " "<a "
"<a " "href='https://azahar-emu.org/blog/game-loading-changes/'>"
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/" "Please check our blog for more info.</a>"));
"dumping-game-cartridges/'>game "
"cartridges</a> or "
"<a "
"href='https://web.archive.org/web/20240304210011/https://citra-emu.org/wiki/"
"dumping-installed-titles/'>installed "
"titles</a>."));
break; break;
} }
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
QMessageBox::critical( QMessageBox::critical(
this, tr("Invalid ROM Format"), this, tr("Invalid App Format"),
tr("Your ROM format is not supported.<br/>Please follow the guides to redump your " tr("Your app format is not supported.<br/>Please follow the guides to redump your "
"<a " "<a "
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/" "href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
"dumping-game-cartridges/'>game " "dumping-game-cartridges/'>game "
@ -1351,8 +1345,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
break; break;
case Core::System::ResultStatus::ErrorLoader_ErrorGbaTitle: case Core::System::ResultStatus::ErrorLoader_ErrorGbaTitle:
QMessageBox::critical(this, tr("Unsupported ROM"), QMessageBox::critical(this, tr("Unsupported App"),
tr("GBA Virtual Console ROMs are not supported by Azahar.")); tr("GBA Virtual Console is not supported by Azahar."));
break; break;
case Core::System::ResultStatus::ErrorArticDisconnected: case Core::System::ResultStatus::ErrorArticDisconnected:
@ -1365,7 +1359,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
break; break;
default: default:
QMessageBox::critical( QMessageBox::critical(
this, tr("Error while loading ROM!"), this, tr("Error while loading App!"),
tr("An unknown error occurred. Please see the log for more details.")); tr("An unknown error occurred. Please see the log for more details."));
break; break;
} }
@ -1430,7 +1424,7 @@ void GMainWindow::BootGame(const QString& filename) {
const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())}; const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())};
const std::string config_file_name = const std::string config_file_name =
title_id == 0 ? name : fmt::format("{:016X}", title_id); title_id == 0 ? name : fmt::format("{:016X}", title_id);
LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name); LOG_INFO(Frontend, "Loading per application config file for title {}", config_file_name);
QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig); QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig);
} }
@ -1932,9 +1926,9 @@ bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int message,
switch (message) { switch (message) {
case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT: case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT:
buttons = QMessageBox::Yes | QMessageBox::No; buttons = QMessageBox::Yes | QMessageBox::No;
result = result = QMessageBox::information(
QMessageBox::information(parent, tr("Create Shortcut"), parent, tr("Create Shortcut"),
tr("Do you want to launch the game in fullscreen?"), buttons); tr("Do you want to launch the application in fullscreen?"), buttons);
return result == QMessageBox::Yes; return result == QMessageBox::Yes;
case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS: case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS:
QMessageBox::information(parent, tr("Create Shortcut"), QMessageBox::information(parent, tr("Create Shortcut"),
@ -2147,7 +2141,7 @@ void GMainWindow::OnGameListAddDirectory() {
UISettings::values.game_dirs.append(game_dir); UISettings::values.game_dirs.append(game_dir);
game_list->PopulateAsync(UISettings::values.game_dirs); game_list->PopulateAsync(UISettings::values.game_dirs);
} else { } else {
LOG_WARNING(Frontend, "Selected directory is already in the game list"); LOG_WARNING(Frontend, "Selected directory is already in the application list");
} }
} }
@ -2164,7 +2158,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const QString& file) {
u64 title_id{}; u64 title_id{};
if (!loader || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { if (!loader || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"), QMessageBox::information(this, tr("Properties"),
tr("The game properties could not be loaded.")); tr("The application properties could not be loaded."));
return; return;
} }
@ -2260,10 +2254,11 @@ void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString
QMessageBox::critical(this, tr("Invalid File"), tr("%1 is not a valid CIA").arg(filename)); QMessageBox::critical(this, tr("Invalid File"), tr("%1 is not a valid CIA").arg(filename));
break; break;
case Service::AM::InstallStatus::ErrorEncrypted: case Service::AM::InstallStatus::ErrorEncrypted:
QMessageBox::critical(this, tr("Encrypted File"), QMessageBox::critical(this, tr("CIA Encrypted"),
tr("%1 must be decrypted " tr("Your CIA file is encrypted.<br/>"
"before being used with Azahar. A real 3DS is required.") "<a "
.arg(filename)); "href='https://azahar-emu.org/blog/game-loading-changes/'>"
"Please check our blog for more info.</a>"));
break; break;
case Service::AM::InstallStatus::ErrorFileNotFound: case Service::AM::InstallStatus::ErrorFileNotFound:
QMessageBox::critical(this, tr("Unable to find File"), QMessageBox::critical(this, tr("Unable to find File"),
@ -2625,9 +2620,10 @@ void GMainWindow::OnLoadState() {
ASSERT(action); ASSERT(action);
if (UISettings::values.save_state_warning) { if (UISettings::values.save_state_warning) {
QMessageBox::warning(this, tr("Savestates"), QMessageBox::warning(
tr("Warning: Savestates are NOT a replacement for in-game saves, " this, tr("Savestates"),
"and are not meant to be reliable.\n\nUse at your own risk!")); tr("Warning: Savestates are NOT a replacement for in-application saves, "
"and are not meant to be reliable.\n\nUse at your own risk!"));
UISettings::values.save_state_warning = false; UISettings::values.save_state_warning = false;
config->Save(); config->Save();
} }
@ -2709,7 +2705,7 @@ void GMainWindow::OnLoadAmiibo() {
if (!nfc->IsSearchingForAmiibos()) { if (!nfc->IsSearchingForAmiibos()) {
QMessageBox::warning(this, tr("Error opening amiibo data file"), QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("Game is not looking for amiibos.")); tr("Application is not looking for amiibos."));
return; return;
} }
@ -2859,11 +2855,11 @@ void GMainWindow::OnCaptureScreenshot() {
const bool was_running = emu_thread->IsRunning(); const bool was_running = emu_thread->IsRunning();
if (was_running || if (was_running || (QMessageBox::question(this, tr("Application will unpause"),
(QMessageBox::question( tr("The application will be unpaused, and the next "
this, tr("Game will unpause"), "frame will be captured. Is this okay?"),
tr("The game will be unpaused, and the next frame will be captured. Is this okay?"), QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)) { QMessageBox::No) == QMessageBox::Yes)) {
if (was_running) { if (was_running) {
OnPauseGame(); OnPauseGame();
} }
@ -3136,7 +3132,7 @@ void GMainWindow::UpdateStatusBar() {
.arg(results.emulation_speed * 100.0, 0, 'f', 0) .arg(results.emulation_speed * 100.0, 0, 'f', 0)
.arg(Settings::values.frame_limit.GetValue())); .arg(Settings::values.frame_limit.GetValue()));
} }
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); game_fps_label->setText(tr("App: %1 FPS").arg(results.game_fps, 0, 'f', 0));
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
if (show_artic_label) { if (show_artic_label) {
@ -3340,7 +3336,8 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
if (can_continue) { if (can_continue) {
message_box.addButton(tr("Continue"), QMessageBox::RejectRole); message_box.addButton(tr("Continue"), QMessageBox::RejectRole);
} }
QPushButton* abort_button = message_box.addButton(tr("Quit Game"), QMessageBox::AcceptRole); QPushButton* abort_button =
message_box.addButton(tr("Quit Application"), QMessageBox::AcceptRole);
if (result != Core::System::ResultStatus::ShutdownRequested) if (result != Core::System::ResultStatus::ShutdownRequested)
message_box.exec(); message_box.exec();
@ -3470,7 +3467,7 @@ bool GMainWindow::ConfirmChangeGame() {
} }
auto answer = QMessageBox::question( auto answer = QMessageBox::question(
this, tr("Azahar"), tr("The game is still running. Would you like to stop emulation?"), this, tr("Azahar"), tr("The application is still running. Would you like to stop emulation?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No; return answer != QMessageBox::No;
} }
@ -3675,8 +3672,8 @@ void GMainWindow::RetranslateStatusBar() {
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
"indicate emulation is running faster or slower than a 3DS.")); "indicate emulation is running faster or slower than a 3DS."));
game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. " game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
"This will vary from game to game and scene to scene.")); "This will vary from app to app and scene to scene."));
emu_frametime_label->setToolTip( emu_frametime_label->setToolTip(
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For " tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
"full-speed emulation this should be at most 16.67 ms.")); "full-speed emulation this should be at most 16.67 ms."));

View File

@ -17,6 +17,7 @@
#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/ptm/ptm.h" #include "core/hle/service/ptm/ptm.h"
#include "core/hw/aes/key.h" #include "core/hw/aes/key.h"
#include "core/hw/unique_data.h"
#include "core/system_titles.h" #include "core/system_titles.h"
#include "ui_configure_system.h" #include "ui_configure_system.h"
@ -245,7 +246,7 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
this, tr("Select SecureInfo_A/B"), QString(), this, tr("Select SecureInfo_A/B"), QString(),
tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)")); tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)"));
ui->button_secure_info->setEnabled(true); ui->button_secure_info->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath()); InstallSecureData(file_path_qtstr.toStdString(), HW::UniqueData::GetSecureInfoAPath());
}); });
connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] { connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] {
ui->button_friend_code_seed->setEnabled(false); ui->button_friend_code_seed->setEnabled(false);
@ -254,14 +255,23 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A " tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A "
"LocalFriendCodeSeed_B);;All Files (*.*)")); "LocalFriendCodeSeed_B);;All Files (*.*)"));
ui->button_friend_code_seed->setEnabled(true); ui->button_friend_code_seed->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath()); InstallSecureData(file_path_qtstr.toStdString(),
HW::UniqueData::GetLocalFriendCodeSeedBPath());
}); });
connect(ui->button_ct_cert, &QPushButton::clicked, this, [this] { connect(ui->button_otp, &QPushButton::clicked, this, [this] {
ui->button_ct_cert->setEnabled(false); ui->button_otp->setEnabled(false);
const QString file_path_qtstr =
QFileDialog::getOpenFileName(this, tr("Select encrypted OTP file"), QString(),
tr("Binary file (*.bin);;All Files (*.*)"));
ui->button_otp->setEnabled(true);
InstallSecureData(file_path_qtstr.toStdString(), HW::UniqueData::GetOTPPath());
});
connect(ui->button_movable, &QPushButton::clicked, this, [this] {
ui->button_movable->setEnabled(false);
const QString file_path_qtstr = QFileDialog::getOpenFileName( const QString file_path_qtstr = QFileDialog::getOpenFileName(
this, tr("Select CTCert"), QString(), tr("CTCert.bin (*.bin);;All Files (*.*)")); this, tr("Select movable.sed"), QString(), tr("Sed file (*.sed);;All Files (*.*)"));
ui->button_ct_cert->setEnabled(true); ui->button_movable->setEnabled(true);
InstallCTCert(file_path_qtstr.toStdString()); InstallSecureData(file_path_qtstr.toStdString(), HW::UniqueData::GetMovablePath());
}); });
for (u8 i = 0; i < country_names.size(); i++) { for (u8 i = 0; i < country_names.size(); i++) {
@ -562,50 +572,39 @@ void ConfigureSystem::InstallSecureData(const std::string& from_path, const std:
if (from.empty() || from == to) { if (from.empty() || from == to) {
return; return;
} }
FileUtil::CreateFullPath(to_path); FileUtil::CreateFullPath(to);
FileUtil::Copy(from, to);
cfg->InvalidateSecureData();
RefreshSecureDataStatus();
}
void ConfigureSystem::InstallCTCert(const std::string& from_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
std::string to = FileUtil::SanitizePath(Service::AM::Module::GetCTCertPath(),
FileUtil::DirectorySeparator::PlatformDefault);
if (from.empty() || from == to) {
return;
}
FileUtil::Copy(from, to); FileUtil::Copy(from, to);
HW::UniqueData::InvalidateSecureData();
RefreshSecureDataStatus(); RefreshSecureDataStatus();
} }
void ConfigureSystem::RefreshSecureDataStatus() { void ConfigureSystem::RefreshSecureDataStatus() {
auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) { auto status_to_str = [](HW::UniqueData::SecureDataLoadStatus status) {
switch (status) { switch (status) {
case Service::CFG::SecureDataLoadStatus::Loaded: case HW::UniqueData::SecureDataLoadStatus::Loaded:
return "Loaded"; return "Loaded";
case Service::CFG::SecureDataLoadStatus::NotFound: case HW::UniqueData::SecureDataLoadStatus::InvalidSignature:
return "Loaded (Invalid Signature)";
case HW::UniqueData::SecureDataLoadStatus::NotFound:
return "Not Found"; return "Not Found";
case Service::CFG::SecureDataLoadStatus::Invalid: case HW::UniqueData::SecureDataLoadStatus::Invalid:
return "Invalid"; return "Invalid";
case Service::CFG::SecureDataLoadStatus::IOError: case HW::UniqueData::SecureDataLoadStatus::IOError:
return "IO Error"; return "IO Error";
default: default:
return ""; return "";
} }
}; };
Service::AM::CTCert ct_cert;
ui->label_secure_info_status->setText( ui->label_secure_info_status->setText(
tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str())); tr((std::string("Status: ") + status_to_str(HW::UniqueData::LoadSecureInfoA())).c_str()));
ui->label_friend_code_seed_status->setText( ui->label_friend_code_seed_status->setText(
tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str())); tr((std::string("Status: ") + status_to_str(HW::UniqueData::LoadLocalFriendCodeSeedB()))
ui->label_ct_cert_status->setText(
tr((std::string("Status: ") + status_to_str(static_cast<Service::CFG::SecureDataLoadStatus>(
Service::AM::Module::LoadCTCertFile(ct_cert))))
.c_str())); .c_str()));
ui->label_otp_status->setText(
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()));
} }
void ConfigureSystem::RetranslateUI() { void ConfigureSystem::RetranslateUI() {
@ -669,40 +668,7 @@ void ConfigureSystem::SetupPerGameUI() {
void ConfigureSystem::DownloadFromNUS() { void ConfigureSystem::DownloadFromNUS() {
ui->button_start_download->setEnabled(false); ui->button_start_download->setEnabled(false);
const auto mode = QMessageBox::critical(this, tr("Azahar"), tr("Downloading from NUS has been deprecated."));
static_cast<Core::SystemTitleSet>(1 << ui->combo_download_set->currentIndex());
const auto region = static_cast<u32>(ui->combo_download_region->currentIndex());
const std::vector<u64> titles = Core::GetSystemTitleIds(mode, region);
QProgressDialog progress(tr("Downloading files..."), tr("Cancel"), 0,
static_cast<int>(titles.size()), this);
progress.setWindowModality(Qt::WindowModal);
QFutureWatcher<void> future_watcher;
QObject::connect(&future_watcher, &QFutureWatcher<void>::finished, &progress,
&QProgressDialog::reset);
QObject::connect(&progress, &QProgressDialog::canceled, &future_watcher,
&QFutureWatcher<void>::cancel);
QObject::connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &progress,
&QProgressDialog::setValue);
auto failed = false;
const auto download_title = [&future_watcher, &failed](const u64& title_id) {
if (Service::AM::InstallFromNus(title_id) != Service::AM::InstallStatus::Success) {
failed = true;
future_watcher.cancel();
}
};
future_watcher.setFuture(QtConcurrent::map(titles, download_title));
progress.exec();
future_watcher.waitForFinished();
if (failed) {
QMessageBox::critical(this, tr("Azahar"), tr("Downloading system files failed."));
} else if (!future_watcher.isCanceled()) {
QMessageBox::information(this, tr("Azahar"), tr("Successfully downloaded system files."));
}
ui->button_start_download->setEnabled(true); ui->button_start_download->setEnabled(true);
} }

View File

@ -53,7 +53,6 @@ private:
void RefreshConsoleID(); void RefreshConsoleID();
void InstallSecureData(const std::string& from_path, const std::string& to_path); void InstallSecureData(const std::string& from_path, const std::string& to_path);
void InstallCTCert(const std::string& from_path);
void RefreshSecureDataStatus(); void RefreshSecureDataStatus();
void SetupPerGameUI(); void SetupPerGameUI();

View File

@ -629,24 +629,60 @@
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_ct_cert"> <widget class="QLabel" name="label_otp">
<property name="text"> <property name="text">
<string>CTCert</string> <string>OTP</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QWidget" name="ct_cert"> <widget class="QWidget" name="otp">
<layout class="QHBoxLayout" name="horizontalLayout_ct_cert"> <layout class="QHBoxLayout" name="horizontalLayout_otp">
<item> <item>
<widget class="QLabel" name="label_ct_cert_status"> <widget class="QLabel" name="label_otp_status">
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="button_ct_cert"> <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">
<widget class="QWidget" name="otp">
<layout class="QHBoxLayout" name="horizontalLayout_movable">
<item>
<widget class="QLabel" name="label_movable_status">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_movable">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>

View File

@ -596,7 +596,7 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall")); QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall"));
QAction* uninstall_all = uninstall_menu->addAction(tr("Everything")); QAction* uninstall_all = uninstall_menu->addAction(tr("Everything"));
uninstall_menu->addSeparator(); uninstall_menu->addSeparator();
QAction* uninstall_game = uninstall_menu->addAction(tr("Game")); QAction* uninstall_game = uninstall_menu->addAction(tr("Application"));
QAction* uninstall_update = uninstall_menu->addAction(tr("Update")); QAction* uninstall_update = uninstall_menu->addAction(tr("Update"));
QAction* uninstall_dlc = uninstall_menu->addAction(tr("DLC")); QAction* uninstall_dlc = uninstall_menu->addAction(tr("DLC"));
@ -736,7 +736,7 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
QMessageBox::StandardButton answer = QMessageBox::question( QMessageBox::StandardButton answer = QMessageBox::question(
this, tr("Azahar"), this, tr("Azahar"),
tr("Are you sure you want to completely uninstall '%1'?\n\nThis will " tr("Are you sure you want to completely uninstall '%1'?\n\nThis will "
"delete the game if installed, as well as any installed updates or DLC.") "delete the application if installed, as well as any installed updates or DLC.")
.arg(name), .arg(name),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (answer == QMessageBox::Yes) { if (answer == QMessageBox::Yes) {
@ -805,7 +805,7 @@ void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()]; UISettings::values.game_dirs[selected.data(GameListDir::GameDirRole).toInt()];
QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders"));
QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); QAction* delete_dir = context_menu.addAction(tr("Remove Application Directory"));
deep_scan->setCheckable(true); deep_scan->setCheckable(true);
deep_scan->setChecked(game_dir.deep_scan); deep_scan->setChecked(game_dir.deep_scan);
@ -885,18 +885,18 @@ void GameList::LoadCompatibilityList() {
QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")}; QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
if (!compat_list.open(QFile::ReadOnly | QFile::Text)) { if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {
LOG_ERROR(Frontend, "Unable to open game compatibility list"); LOG_ERROR(Frontend, "Unable to open application compatibility list");
return; return;
} }
if (compat_list.size() == 0) { if (compat_list.size() == 0) {
LOG_WARNING(Frontend, "Game compatibility list is empty"); LOG_WARNING(Frontend, "Application compatibility list is empty");
return; return;
} }
const QByteArray content = compat_list.readAll(); const QByteArray content = compat_list.readAll();
if (content.isEmpty()) { if (content.isEmpty()) {
LOG_ERROR(Frontend, "Unable to completely read game compatibility list"); LOG_ERROR(Frontend, "Unable to completely read application compatibility list");
return; return;
} }
@ -1004,12 +1004,12 @@ void GameList::LoadInterfaceLayout() {
} }
const QStringList GameList::supported_file_extensions = { const QStringList GameList::supported_file_extensions = {
QStringLiteral("3ds"), QStringLiteral("3dsx"), QStringLiteral("elf"), QStringLiteral("axf"), QStringLiteral("3dsx"), QStringLiteral("elf"), QStringLiteral("axf"),
QStringLiteral("cci"), QStringLiteral("cxi"), QStringLiteral("app")}; QStringLiteral("cci"), QStringLiteral("cxi"), QStringLiteral("app")};
void GameList::RefreshGameDirectory() { void GameList::RefreshGameDirectory() {
if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs); PopulateAsync(UISettings::values.game_dirs);
} }
} }
@ -1094,7 +1094,7 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent}
layout->setAlignment(Qt::AlignCenter); layout->setAlignment(Qt::AlignCenter);
image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
text->setText(tr("Double-click to add a new folder to the game list")); text->setText(tr("Double-click to add a new folder to the application list"));
QFont font = text->font(); QFont font = text->font();
font.setPointSize(20); font.setPointSize(20);
text->setFont(font); text->setFont(font);

View File

@ -163,7 +163,7 @@ public:
GameListItemPath() = default; GameListItemPath() = default;
GameListItemPath(const QString& game_path, std::span<const u8> smdh_data, u64 program_id, GameListItemPath(const QString& game_path, std::span<const u8> smdh_data, u64 program_id,
u64 extdata_id, Service::FS::MediaType media_type) { u64 extdata_id, Service::FS::MediaType media_type, bool is_encrypted) {
setData(type(), TypeRole); setData(type(), TypeRole);
setData(game_path, FullPathRole); setData(game_path, FullPathRole);
setData(qulonglong(program_id), ProgramIdRole); setData(qulonglong(program_id), ProgramIdRole);
@ -184,6 +184,9 @@ public:
if (UISettings::values.game_list_icon_size.GetValue() != if (UISettings::values.game_list_icon_size.GetValue() !=
UISettings::GameListIconSize::NoIcon) UISettings::GameListIconSize::NoIcon)
setData(GetDefaultIcon(large), Qt::DecorationRole); setData(GetDefaultIcon(large), Qt::DecorationRole);
if (is_encrypted) {
setData(QObject::tr("Unsupported encrypted application"), TitleRole);
}
return; return;
} }
@ -262,13 +265,13 @@ public:
}; };
// clang-format off // clang-format off
static const std::map<QString, CompatStatus> status_data = { static const std::map<QString, CompatStatus> status_data = {
{QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}}, {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("App functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
{QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}}, {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Great"), QT_TR_NOOP("App functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
{QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}}, {QStringLiteral("2"), {QStringLiteral("#94b242"), QT_TR_NOOP("Okay"), QT_TR_NOOP("App functions with major graphical or audio glitches, but app is playable from start to finish with\nworkarounds.")}},
{QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}}, {QStringLiteral("3"), {QStringLiteral("#f2d624"), QT_TR_NOOP("Bad"), QT_TR_NOOP("App functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
{QStringLiteral("4"), {QStringLiteral("#ff0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}}, {QStringLiteral("4"), {QStringLiteral("#ff0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("App is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
{QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}}, {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The app crashes when attempting to startup.")}},
{QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The app has not yet been tested.")}}};
// clang-format on // clang-format on
auto iterator = status_data.find(compatibility); auto iterator = status_data.find(compatibility);
@ -445,7 +448,7 @@ public:
int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue()); int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue());
setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size), Qt::DecorationRole); setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size), Qt::DecorationRole);
setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); setData(QObject::tr("Add New Application Directory"), Qt::DisplayRole);
} }
int type() const override { int type() const override {

View File

@ -108,7 +108,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
emit EntryReady( emit EntryReady(
{ {
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id, new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id,
extdata_id, media_type), extdata_id, media_type,
res == Loader::ResultStatus::ErrorEncrypted),
new GameListItemCompat(compatibility), new GameListItemCompat(compatibility),
new GameListItemRegion(smdh), new GameListItemRegion(smdh),
new GameListItem( new GameListItem(

View File

@ -446,7 +446,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Browse Public Game Lobby</string> <string>Browse Public Application Lobby</string>
</property> </property>
</action> </action>
<action name="action_Start_Room"> <action name="action_Start_Room">
@ -688,7 +688,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Configure Current Game...</string> <string>Configure Current Application...</string>
</property> </property>
<property name="menuRole"> <property name="menuRole">
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>

View File

@ -65,6 +65,6 @@
// Sys files // Sys files
#define SHARED_FONT "shared_font.bin" #define SHARED_FONT "shared_font.bin"
#define AES_KEYS "aes_keys.txt" #define KEYS_FILE "keys.txt"
#define BOOTROM9 "boot9.bin" #define BOOTROM9 "boot9.bin"
#define SECRET_SECTOR "sector0x96.bin" #define SECRET_SECTOR "sector0x96.bin"

View File

@ -58,5 +58,17 @@ HackManager hack_manager = {
}, },
}}, }},
{HackType::DECRYPTION_AUTHORIZED,
HackEntry{
.mode = HackAllowMode::ALLOW,
.affected_title_ids =
{
// NIM
0x0004013000002C02, // Normal
0x0004013000002C03, // Safe mode
0x0004013020002C03, // New 3DS safe mode
},
}},
}}; }};
} }

View File

@ -11,6 +11,7 @@ namespace Common::Hacks {
enum class HackType : int { enum class HackType : int {
RIGHT_EYE_DISABLE, RIGHT_EYE_DISABLE,
ACCURATE_MULTIPLICATION, ACCURATE_MULTIPLICATION,
DECRYPTION_AUTHORIZED,
}; };
class UserHackData {}; class UserHackData {};

View File

@ -121,6 +121,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(HW, LCD) \ SUB(HW, LCD) \
SUB(HW, GPU) \ SUB(HW, GPU) \
SUB(HW, AES) \ SUB(HW, AES) \
SUB(HW, RSA) \
SUB(HW, ECC) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Software) \ SUB(Render, Software) \

View File

@ -88,6 +88,8 @@ enum class Class : u8 {
HW_LCD, ///< LCD register emulation HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation HW_GPU, ///< GPU control emulation
HW_AES, ///< AES engine emulation HW_AES, ///< AES engine emulation
HW_RSA, ///< RSA engine emulation
HW_ECC, ///< ECC engine emulation
Frontend, ///< Emulator UI Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend Render_Software, ///< Software renderer backend

View File

@ -64,7 +64,8 @@ add_library(citra_core STATIC
file_sys/archive_systemsavedata.h file_sys/archive_systemsavedata.h
file_sys/artic_cache.cpp file_sys/artic_cache.cpp
file_sys/artic_cache.h file_sys/artic_cache.h
file_sys/cia_common.h file_sys/certificate.cpp
file_sys/certificate.h
file_sys/cia_container.cpp file_sys/cia_container.cpp
file_sys/cia_container.h file_sys/cia_container.h
file_sys/directory_backend.h file_sys/directory_backend.h
@ -80,6 +81,8 @@ add_library(citra_core STATIC
file_sys/layered_fs.h file_sys/layered_fs.h
file_sys/ncch_container.cpp file_sys/ncch_container.cpp
file_sys/ncch_container.h file_sys/ncch_container.h
file_sys/otp.cpp
file_sys/otp.h
file_sys/patch.cpp file_sys/patch.cpp
file_sys/patch.h file_sys/patch.h
file_sys/path_parser.cpp file_sys/path_parser.cpp
@ -97,6 +100,7 @@ add_library(citra_core STATIC
file_sys/secure_value_backend.h file_sys/secure_value_backend.h
file_sys/seed_db.cpp file_sys/seed_db.cpp
file_sys/seed_db.h file_sys/seed_db.h
file_sys/signature.h
file_sys/ticket.cpp file_sys/ticket.cpp
file_sys/ticket.h file_sys/ticket.h
file_sys/title_metadata.cpp file_sys/title_metadata.cpp
@ -449,8 +453,13 @@ add_library(citra_core STATIC
hw/aes/ccm.h hw/aes/ccm.h
hw/aes/key.cpp hw/aes/key.cpp
hw/aes/key.h hw/aes/key.h
hw/ecc.cpp
hw/ecc.h
hw/default_keys.h
hw/rsa/rsa.cpp hw/rsa/rsa.cpp
hw/rsa/rsa.h hw/rsa/rsa.h
hw/unique_data.cpp
hw/unique_data.h
hw/y2r.cpp hw/y2r.cpp
hw/y2r.h hw/y2r.h
loader/3dsx.cpp loader/3dsx.cpp
@ -482,7 +491,7 @@ add_library(citra_core STATIC
tracer/citrace.h tracer/citrace.h
tracer/recorder.cpp tracer/recorder.cpp
tracer/recorder.h tracer/recorder.h
) )
create_target_directory_groups(citra_core) create_target_directory_groups(citra_core)

View File

@ -0,0 +1,164 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cryptopp/oids.h>
#include "common/alignment.h"
#include "common/logging/log.h"
#include "core/file_sys/certificate.h"
#include "core/file_sys/signature.h"
#include "cryptopp/eccrypto.h"
#include "cryptopp/osrng.h"
namespace FileSys {
void Certificate::BuildECC(Certificate& parent, const std::array<u8, 0x40> issuer,
const std::array<u8, 0x40> name, u32 expiration) {
certificate_signature_type = SignatureType::EcdsaSha256;
certificate_body.key_type = PublicKeyType::ECC;
certificate_body.issuer = issuer;
certificate_body.name = name;
certificate_body.expiration = expiration;
auto [private_key, public_key] = HW::ECC::GenerateKeyPair();
SetPrivateKeyECC(private_key);
SetPublicKeyECC(public_key);
SetSignatureECC(parent.Sign(SerializeBody()));
if (!VerifyMyself(parent.GetPublicKeyECC())) {
LOG_ERROR(HW, "Failed to verify newly generated certificate");
}
}
void Certificate::BuildECC(const std::array<u8, 0x40> issuer, const std::array<u8, 0x40> name,
u32 expiration, HW::ECC::PrivateKey private_key,
HW::ECC::Signature signature) {
certificate_signature_type = SignatureType::EcdsaSha256;
certificate_body.key_type = PublicKeyType::ECC;
certificate_body.issuer = issuer;
certificate_body.name = name;
certificate_body.expiration = expiration;
SetPrivateKeyECC(private_key);
SetPublicKeyECC(HW::ECC::MakePublicKey(private_key));
SetSignatureECC(signature);
}
bool Certificate::VerifyMyself(const HW::ECC::PublicKey& parent_public) {
if (certificate_signature_type == SignatureType::EcdsaSha256) {
return HW::ECC::Verify(SerializeBody(), HW::ECC::CreateECCSignature(certificate_signature),
parent_public);
} else {
UNIMPLEMENTED();
return false;
}
}
bool Certificate::Verify(std::span<const u8> data, HW::ECC::Signature signature) {
if (certificate_body.key_type == PublicKeyType::ECC) {
return HW::ECC::Verify(data, signature, GetPublicKeyECC());
} else {
UNIMPLEMENTED();
}
return false;
}
HW::ECC::Signature Certificate::Sign(std::span<const u8> data) {
if (certificate_body.key_type == PublicKeyType::ECC) {
return HW::ECC::Sign(data, certificate_private_key_ecc);
} else {
UNIMPLEMENTED();
}
return HW::ECC::Signature();
}
std::vector<u8> Certificate::ECDHAgree(const HW::ECC::PublicKey& others_public_key) {
if (certificate_body.key_type != PublicKeyType::ECC) {
LOG_ERROR(HW, "Tried to agree with a non ECC certificate");
return {};
}
return HW::ECC::Agree(certificate_private_key_ecc, others_public_key);
}
std::vector<u8> Certificate::SerializeSignature() const {
std::vector<u8> ret;
ret.resize(Common::AlignUp(certificate_signature.size() + sizeof(u32) + 1, 0x40));
memcpy(ret.data(), &certificate_signature_type, sizeof(u32_be));
memcpy(ret.data() + sizeof(u32_be), certificate_signature.data(), certificate_signature.size());
return ret;
}
std::vector<u8> Certificate::SerializeBody() const {
std::vector<u8> ret;
ret.resize(Common::AlignUp(sizeof(Body) + certificate_public_key.size() + 1, 0x40));
memcpy(ret.data(), &certificate_body, sizeof(certificate_body));
memcpy(ret.data() + sizeof(certificate_body), certificate_public_key.data(),
certificate_public_key.size());
return ret;
}
std::vector<u8> Certificate::Serialize() const {
if (!IsValid()) {
return {};
}
auto signature = SerializeSignature();
auto body = SerializeBody();
signature.insert(signature.end(), body.begin(), body.end());
return signature;
}
void Certificate::SetPrivateKeyECC(const HW::ECC::PrivateKey& private_key) {
if (certificate_body.key_type != PublicKeyType::ECC) {
LOG_ERROR(HW, "Certificate is not ECC");
return;
}
certificate_private_key_ecc = private_key;
}
const HW::ECC::PrivateKey& Certificate::GetPrivateKeyECC() {
if (certificate_body.key_type != PublicKeyType::ECC) {
LOG_ERROR(HW, "Certificate is not ECC");
}
return certificate_private_key_ecc;
}
void Certificate::SetPublicKeyECC(const HW::ECC::PublicKey& public_key) {
if (certificate_body.key_type != PublicKeyType::ECC) {
LOG_ERROR(HW, "Certificate is not ECC");
return;
}
certificate_public_key.resize(public_key.xy.size());
memcpy(certificate_public_key.data(), public_key.xy.data(), public_key.xy.size());
}
HW::ECC::PublicKey Certificate::GetPublicKeyECC() {
if (certificate_body.key_type != PublicKeyType::ECC) {
LOG_ERROR(HW, "Certificate is not ECC");
return HW::ECC::PublicKey();
}
return HW::ECC::CreateECCPublicKey(certificate_public_key);
}
void Certificate::SetSignatureECC(const HW::ECC::Signature& signature) {
if (certificate_signature_type != SignatureType::EcdsaSha256) {
LOG_ERROR(HW, "Signature is not ECC");
return;
}
certificate_signature.resize(signature.rs.size());
memcpy(certificate_signature.data(), signature.rs.data(), signature.rs.size());
}
HW::ECC::Signature Certificate::GetSignatureECC() {
if (certificate_signature_type != SignatureType::EcdsaSha256) {
LOG_ERROR(HW, "Signature is not ECC");
return HW::ECC::Signature();
}
return HW::ECC::CreateECCSignature(certificate_signature);
}
} // namespace FileSys

View File

@ -0,0 +1,92 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hw/ecc.h"
namespace Loader {
enum class ResultStatus;
}
namespace FileSys {
class Certificate {
public:
#pragma pack(push, 1)
struct Body {
std::array<u8, 0x40> issuer;
u32_be key_type;
std::array<u8, 0x40> name;
u32_be expiration;
};
struct PublicKeyType {
enum : u32 {
RSA_4096 = 0,
RSA_2048 = 1,
ECC = 2,
};
};
#pragma pack(pop)
void BuildECC(Certificate& parent, const std::array<u8, 0x40> issuer,
const std::array<u8, 0x40> name, u32 expiration);
void BuildECC(const std::array<u8, 0x40> issuer, const std::array<u8, 0x40> name,
u32 expiration, HW::ECC::PrivateKey private_key, HW::ECC::Signature signature);
bool VerifyMyself(const HW::ECC::PublicKey& parent_public);
bool Verify(std::span<const u8> data, HW::ECC::Signature signature);
HW::ECC::Signature Sign(std::span<const u8> data);
std::vector<u8> ECDHAgree(const HW::ECC::PublicKey& others_public_key);
std::vector<u8> SerializeSignature() const;
std::vector<u8> SerializeBody() const;
std::vector<u8> Serialize() const;
const std::array<u8, 0x40> GetIssuer() const {
return certificate_body.issuer;
}
const std::array<u8, 0x40> GetName() const {
return certificate_body.name;
}
void SetPrivateKeyECC(const HW::ECC::PrivateKey& private_key);
const HW::ECC::PrivateKey& GetPrivateKeyECC();
void SetPublicKeyECC(const HW::ECC::PublicKey& public_key);
HW::ECC::PublicKey GetPublicKeyECC();
void SetSignatureECC(const HW::ECC::Signature& signature);
HW::ECC::Signature GetSignatureECC();
const bool IsValid() const {
return certificate_signature_type != 0u;
}
void Invalidate() {
certificate_signature_type = 0u;
}
private:
Body certificate_body;
u32_be certificate_signature_type;
std::vector<u8> certificate_signature;
std::vector<u8> certificate_public_key;
HW::ECC::PrivateKey certificate_private_key_ecc;
};
} // namespace FileSys

View File

@ -147,6 +147,11 @@ Loader::ResultStatus CIAContainer::LoadTicket(std::span<const u8> ticket_data, s
return cia_ticket.Load(ticket_data, offset); return cia_ticket.Load(ticket_data, offset);
} }
Loader::ResultStatus CIAContainer::LoadTicket(const Ticket& ticket) {
cia_ticket = ticket;
return Loader::ResultStatus::Success;
}
Loader::ResultStatus CIAContainer::LoadTitleMetadata(std::span<const u8> tmd_data, Loader::ResultStatus CIAContainer::LoadTitleMetadata(std::span<const u8> tmd_data,
std::size_t offset) { std::size_t offset) {
return cia_tmd.Load(tmd_data, offset); return cia_tmd.Load(tmd_data, offset);

View File

@ -44,6 +44,7 @@ public:
// Load parts of CIAs (for CIAs streamed in) // Load parts of CIAs (for CIAs streamed in)
Loader::ResultStatus LoadHeader(std::span<const u8> header_data, std::size_t offset = 0); Loader::ResultStatus LoadHeader(std::span<const u8> header_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(std::span<const u8> ticket_data, std::size_t offset = 0); Loader::ResultStatus LoadTicket(std::span<const u8> ticket_data, std::size_t offset = 0);
Loader::ResultStatus LoadTicket(const Ticket& ticket);
Loader::ResultStatus LoadTitleMetadata(std::span<const u8> tmd_data, std::size_t offset = 0); Loader::ResultStatus LoadTitleMetadata(std::span<const u8> tmd_data, std::size_t offset = 0);
Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0); Loader::ResultStatus LoadMetadata(std::span<const u8> meta_data, std::size_t offset = 0);

View File

@ -172,7 +172,11 @@ Loader::ResultStatus NCCHContainer::Load() {
if (is_loaded) if (is_loaded)
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
int block_size = kBlockSize;
if (file.IsOpen()) { if (file.IsOpen()) {
size_t file_size = file.GetSize();
// Reset read pointer in case this file has been read before. // Reset read pointer in case this file has been read before.
file.Seek(ncch_offset, SEEK_SET); file.Seek(ncch_offset, SEEK_SET);
@ -196,136 +200,21 @@ Loader::ResultStatus NCCHContainer::Load() {
return Loader::ResultStatus::ErrorInvalidFormat; return Loader::ResultStatus::ErrorInvalidFormat;
has_header = true; has_header = true;
bool failed_to_decrypt = false;
if (ncch_header.content_size == file_size) {
// The NCCH is a proto version, which does not use media size units
is_proto = true;
block_size = 1;
}
if (!ncch_header.no_crypto) { if (!ncch_header.no_crypto) {
is_encrypted = true; // Encrypted NCCH are not supported
return Loader::ResultStatus::ErrorEncrypted;
// Find primary and secondary keys
if (ncch_header.fixed_key) {
LOG_DEBUG(Service_FS, "Fixed-key crypto");
primary_key.fill(0);
secondary_key.fill(0);
} else {
using namespace HW::AES;
InitKeys();
std::array<u8, 16> key_y_primary, key_y_secondary;
std::copy(ncch_header.signature, ncch_header.signature + key_y_primary.size(),
key_y_primary.begin());
if (!ncch_header.seed_crypto) {
key_y_secondary = key_y_primary;
} else {
auto opt{FileSys::GetSeed(ncch_header.program_id)};
if (!opt.has_value()) {
LOG_ERROR(Service_FS, "Seed for program {:016X} not found",
ncch_header.program_id);
failed_to_decrypt = true;
} else {
auto seed{*opt};
std::array<u8, 32> input;
std::memcpy(input.data(), key_y_primary.data(), key_y_primary.size());
std::memcpy(input.data() + key_y_primary.size(), seed.data(), seed.size());
CryptoPP::SHA256 sha;
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
sha.CalculateDigest(hash.data(), input.data(), input.size());
std::memcpy(key_y_secondary.data(), hash.data(), key_y_secondary.size());
}
}
SetKeyY(KeySlotID::NCCHSecure1, key_y_primary);
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure1)) {
LOG_ERROR(Service_FS, "Secure1 KeyX missing");
failed_to_decrypt = true;
}
primary_key = GetNormalKey(KeySlotID::NCCHSecure1);
switch (ncch_header.secondary_key_slot) {
case 0:
LOG_DEBUG(Service_FS, "Secure1 crypto");
SetKeyY(KeySlotID::NCCHSecure1, key_y_secondary);
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure1)) {
LOG_ERROR(Service_FS, "Secure1 KeyX missing");
failed_to_decrypt = true;
}
secondary_key = GetNormalKey(KeySlotID::NCCHSecure1);
break;
case 1:
LOG_DEBUG(Service_FS, "Secure2 crypto");
SetKeyY(KeySlotID::NCCHSecure2, key_y_secondary);
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure2)) {
LOG_ERROR(Service_FS, "Secure2 KeyX missing");
failed_to_decrypt = true;
}
secondary_key = GetNormalKey(KeySlotID::NCCHSecure2);
break;
case 10:
LOG_DEBUG(Service_FS, "Secure3 crypto");
SetKeyY(KeySlotID::NCCHSecure3, key_y_secondary);
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure3)) {
LOG_ERROR(Service_FS, "Secure3 KeyX missing");
failed_to_decrypt = true;
}
secondary_key = GetNormalKey(KeySlotID::NCCHSecure3);
break;
case 11:
LOG_DEBUG(Service_FS, "Secure4 crypto");
SetKeyY(KeySlotID::NCCHSecure4, key_y_secondary);
if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure4)) {
LOG_ERROR(Service_FS, "Secure4 KeyX missing");
failed_to_decrypt = true;
}
secondary_key = GetNormalKey(KeySlotID::NCCHSecure4);
break;
}
}
// Find CTR for each section
// Written with reference to
// https://github.com/d0k3/GodMode9/blob/99af6a73be48fa7872649aaa7456136da0df7938/arm9/source/game/ncch.c#L34-L52
if (ncch_header.version == 0 || ncch_header.version == 2) {
LOG_DEBUG(Loader, "NCCH version 0/2");
// In this version, CTR for each section is a magic number prefixed by partition ID
// (reverse order)
std::reverse_copy(ncch_header.partition_id, ncch_header.partition_id + 8,
exheader_ctr.begin());
exefs_ctr = romfs_ctr = exheader_ctr;
exheader_ctr[8] = 1;
exefs_ctr[8] = 2;
romfs_ctr[8] = 3;
} else if (ncch_header.version == 1) {
LOG_DEBUG(Loader, "NCCH version 1");
// In this version, CTR for each section is the section offset prefixed by partition
// ID, as if the entire NCCH image is encrypted using a single CTR stream.
std::copy(ncch_header.partition_id, ncch_header.partition_id + 8,
exheader_ctr.begin());
exefs_ctr = romfs_ctr = exheader_ctr;
auto u32ToBEArray = [](u32 value) -> std::array<u8, 4> {
return std::array<u8, 4>{
static_cast<u8>(value >> 24),
static_cast<u8>((value >> 16) & 0xFF),
static_cast<u8>((value >> 8) & 0xFF),
static_cast<u8>(value & 0xFF),
};
};
auto offset_exheader = u32ToBEArray(0x200); // exheader offset
auto offset_exefs = u32ToBEArray(ncch_header.exefs_offset * kBlockSize);
auto offset_romfs = u32ToBEArray(ncch_header.romfs_offset * kBlockSize);
std::copy(offset_exheader.begin(), offset_exheader.end(),
exheader_ctr.begin() + 12);
std::copy(offset_exefs.begin(), offset_exefs.end(), exefs_ctr.begin() + 12);
std::copy(offset_romfs.begin(), offset_romfs.end(), romfs_ctr.begin() + 12);
} else {
LOG_ERROR(Service_FS, "Unknown NCCH version {}", ncch_header.version);
failed_to_decrypt = true;
}
} else {
LOG_DEBUG(Service_FS, "No crypto");
is_encrypted = false;
} }
// System archives and DLC don't have an extended header but have RomFS // System archives and DLC don't have an extended header but have RomFS
if (ncch_header.extended_header_size) { // Proto apps don't have an ext header size
if (ncch_header.extended_header_size || is_proto) {
auto read_exheader = [this](FileUtil::IOFile& file) { auto read_exheader = [this](FileUtil::IOFile& file) {
const std::size_t size = sizeof(exheader_header); const std::size_t size = sizeof(exheader_header);
return file && file.ReadBytes(&exheader_header, size) == size; return file && file.ReadBytes(&exheader_header, size) == size;
@ -335,26 +224,6 @@ Loader::ResultStatus NCCHContainer::Load() {
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
} }
if (is_encrypted) {
// This ID check is masked to low 32-bit as a toleration to ill-formed ROM created
// by merging games and its updates.
if ((exheader_header.system_info.jump_id & 0xFFFFFFFF) ==
(ncch_header.program_id & 0xFFFFFFFF)) {
LOG_WARNING(Service_FS, "NCCH is marked as encrypted but with decrypted "
"exheader. Force no crypto scheme.");
is_encrypted = false;
} else {
if (failed_to_decrypt) {
LOG_ERROR(Service_FS, "Failed to decrypt");
return Loader::ResultStatus::ErrorEncrypted;
}
CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exheader_header);
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption(
primary_key.data(), primary_key.size(), exheader_ctr.data())
.ProcessData(data, data, sizeof(exheader_header));
}
}
const auto mods_path = const auto mods_path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id)); GetModId(ncch_header.program_id));
@ -380,6 +249,11 @@ Loader::ResultStatus NCCHContainer::Load() {
is_tainted = true; is_tainted = true;
} }
if (is_proto) {
exheader_header.arm11_system_local_caps.priority = 0x30;
exheader_header.arm11_system_local_caps.resource_limit_category = 0;
}
is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1;
u32 entry_point = exheader_header.codeset_info.text.address; u32 entry_point = exheader_header.codeset_info.text.address;
u32 code_size = exheader_header.codeset_info.text.code_size; u32 code_size = exheader_header.codeset_info.text.code_size;
@ -409,8 +283,8 @@ Loader::ResultStatus NCCHContainer::Load() {
// DLC can have an ExeFS and a RomFS but no extended header // DLC can have an ExeFS and a RomFS but no extended header
if (ncch_header.exefs_size) { if (ncch_header.exefs_size) {
exefs_offset = ncch_header.exefs_offset * kBlockSize; exefs_offset = ncch_header.exefs_offset * block_size;
u32 exefs_size = ncch_header.exefs_size * kBlockSize; u32 exefs_size = ncch_header.exefs_size * block_size;
LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset); LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset);
LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size); LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size);
@ -419,13 +293,6 @@ Loader::ResultStatus NCCHContainer::Load() {
if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
if (is_encrypted) {
CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exefs_header);
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption(primary_key.data(),
primary_key.size(), exefs_ctr.data())
.ProcessData(data, data, sizeof(exefs_header));
}
exefs_file = FileUtil::IOFile(filepath, "rb"); exefs_file = FileUtil::IOFile(filepath, "rb");
has_exefs = true; has_exefs = true;
} }
@ -482,6 +349,29 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
if (result != Loader::ResultStatus::Success) if (result != Loader::ResultStatus::Success)
return result; return result;
int block_size = is_proto ? 1 : kBlockSize;
// Proto has a different exefs format
if (std::strcmp(name, ".code") == 0 && is_proto) {
std::vector<u8> ro;
std::vector<u8> rw;
auto res = LoadSectionExeFS(".text", buffer);
if (res != Loader::ResultStatus::Success) {
return res;
}
res = LoadSectionExeFS(".ro", ro);
if (res != Loader::ResultStatus::Success) {
return res;
}
res = LoadSectionExeFS(".rw", rw);
if (res != Loader::ResultStatus::Success) {
return res;
}
buffer.insert(buffer.end(), ro.begin(), ro.end());
buffer.insert(buffer.end(), rw.begin(), rw.end());
return res;
}
// Check if we have files that can drop-in and replace // Check if we have files that can drop-in and replace
result = LoadOverrideExeFSSection(name, buffer); result = LoadOverrideExeFSSection(name, buffer);
if (result == Loader::ResultStatus::Success || !has_exefs) if (result == Loader::ResultStatus::Success || !has_exefs)
@ -491,8 +381,8 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
// instead of the ExeFS. // instead of the ExeFS.
if (std::strcmp(name, "logo") == 0) { if (std::strcmp(name, "logo") == 0) {
if (ncch_header.logo_region_offset && ncch_header.logo_region_size) { if (ncch_header.logo_region_offset && ncch_header.logo_region_size) {
std::size_t logo_offset = ncch_header.logo_region_offset * kBlockSize; std::size_t logo_offset = ncch_header.logo_region_offset * block_size;
std::size_t logo_size = ncch_header.logo_region_size * kBlockSize; std::size_t logo_size = ncch_header.logo_region_size * block_size;
buffer.resize(logo_size); buffer.resize(logo_size);
file.Seek(ncch_offset + logo_offset, SEEK_SET); file.Seek(ncch_offset + logo_offset, SEEK_SET);
@ -522,31 +412,19 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
section.offset, section.size, section.name); section.offset, section.size, section.name);
s64 section_offset = s64 section_offset =
(section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); is_proto ? section.offset
: (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset);
exefs_file.Seek(section_offset, SEEK_SET); exefs_file.Seek(section_offset, SEEK_SET);
std::array<u8, 16> key; size_t section_size = is_proto ? Common::AlignUp(section.size, 0x10) : section.size;
if (strcmp(section.name, "icon") == 0 || strcmp(section.name, "banner") == 0) {
key = primary_key;
} else {
key = secondary_key;
}
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption dec(key.data(), key.size(),
exefs_ctr.data());
dec.Seek(section.offset + sizeof(ExeFs_Header));
if (strcmp(section.name, ".code") == 0 && is_compressed) { if (strcmp(section.name, ".code") == 0 && is_compressed) {
// Section is compressed, read compressed .code section... // Section is compressed, read compressed .code section...
std::vector<u8> temp_buffer(section.size); std::vector<u8> temp_buffer(section_size);
if (exefs_file.ReadBytes(temp_buffer.data(), temp_buffer.size()) != if (exefs_file.ReadBytes(temp_buffer.data(), temp_buffer.size()) !=
temp_buffer.size()) temp_buffer.size())
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
if (is_encrypted) {
dec.ProcessData(&temp_buffer[0], &temp_buffer[0], section.size);
}
// Decompress .code section... // Decompress .code section...
buffer.resize(LZSS_GetDecompressedSize(temp_buffer)); buffer.resize(LZSS_GetDecompressedSize(temp_buffer));
if (!LZSS_Decompress(temp_buffer, buffer)) { if (!LZSS_Decompress(temp_buffer, buffer)) {
@ -554,12 +432,9 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
} }
} else { } else {
// Section is uncompressed... // Section is uncompressed...
buffer.resize(section.size); buffer.resize(section_size);
if (exefs_file.ReadBytes(buffer.data(), section.size) != section.size) if (exefs_file.ReadBytes(buffer.data(), section_size) != section_size)
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
if (is_encrypted) {
dec.ProcessData(buffer.data(), buffer.data(), section.size);
}
} }
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
@ -667,6 +542,8 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
if (result != Loader::ResultStatus::Success) if (result != Loader::ResultStatus::Success)
return result; return result;
int block_size = is_proto ? 1 : kBlockSize;
if (ReadOverrideRomFS(romfs_file) == Loader::ResultStatus::Success) if (ReadOverrideRomFS(romfs_file) == Loader::ResultStatus::Success)
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
@ -678,8 +555,8 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
if (!file.IsOpen()) if (!file.IsOpen())
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * block_size) + 0x1000;
u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; u32 romfs_size = (ncch_header.romfs_size * block_size) - 0x1000;
LOG_DEBUG(Service_FS, "RomFS offset: 0x{:08X}", romfs_offset); LOG_DEBUG(Service_FS, "RomFS offset: 0x{:08X}", romfs_offset);
LOG_DEBUG(Service_FS, "RomFS size: 0x{:08X}", romfs_size); LOG_DEBUG(Service_FS, "RomFS size: 0x{:08X}", romfs_size);
@ -693,19 +570,14 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
std::shared_ptr<RomFSReader> direct_romfs; std::shared_ptr<RomFSReader> direct_romfs;
if (is_encrypted) {
direct_romfs = direct_romfs =
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset, std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size);
romfs_size, secondary_key, romfs_ctr, 0x1000);
} else {
direct_romfs = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner),
romfs_offset, romfs_size);
}
const auto path = const auto path =
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
GetModId(ncch_header.program_id)); GetModId(ncch_header.program_id));
if (use_layered_fs && if (!is_proto && use_layered_fs &&
(FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) { (FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) {
romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/", romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/",

View File

@ -343,18 +343,11 @@ private:
bool has_exefs = false; bool has_exefs = false;
bool has_romfs = false; bool has_romfs = false;
bool is_proto = false;
bool is_tainted = false; // Are there parts of this container being overridden? bool is_tainted = false; // Are there parts of this container being overridden?
bool is_loaded = false; bool is_loaded = false;
bool is_compressed = false; bool is_compressed = false;
bool is_encrypted = false;
// for decrypting exheader, exefs header and icon/banner section
std::array<u8, 16> primary_key{};
std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section
std::array<u8, 16> exheader_ctr{};
std::array<u8, 16> exefs_ctr{};
std::array<u8, 16> romfs_ctr{};
u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs
u32 exefs_offset = 0; u32 exefs_offset = 0;
u32 partition = 0; u32 partition = 0;

56
src/core/file_sys/otp.cpp Normal file
View File

@ -0,0 +1,56 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/otp.h"
#include "core/loader/loader.h"
namespace FileSys {
Loader::ResultStatus OTP::Load(const std::string& file_path, std::span<const u8> key,
std::span<const u8> iv) {
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen())
return Loader::ResultStatus::ErrorNotFound;
if (file.GetSize() != sizeof(OTPBin)) {
LOG_ERROR(HW_AES, "Invalid OTP size");
return Loader::ResultStatus::Error;
}
OTPBin temp_otp;
if (file.ReadBytes(&temp_otp, sizeof(OTPBin)) != sizeof(OTPBin)) {
return Loader::ResultStatus::Error;
}
// OTP is probably encrypted, decrypt it.
if (temp_otp.body.magic != otp_magic) {
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption d;
d.SetKeyWithIV(key.data(), key.size(), iv.data());
d.ProcessData(reinterpret_cast<u8*>(&temp_otp), reinterpret_cast<u8*>(&temp_otp),
sizeof(temp_otp));
if (temp_otp.body.magic != otp_magic) {
LOG_ERROR(HW_AES, "OTP failed to decrypt (or uses dev keys)");
return Loader::ResultStatus::Error;
}
}
// Verify OTP hash
CryptoPP::SHA256 hash;
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> digest;
hash.CalculateDigest(digest.data(), reinterpret_cast<u8*>(&temp_otp.body),
sizeof(temp_otp.body));
if (temp_otp.hash != digest) {
LOG_ERROR(HW_AES, "OTP is corrupted");
return Loader::ResultStatus::Error;
}
otp = temp_otp;
return Loader::ResultStatus::Success;
}
} // namespace FileSys

89
src/core/file_sys/otp.h Normal file
View File

@ -0,0 +1,89 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace Loader {
enum class ResultStatus;
}
namespace FileSys {
class OTP {
public:
static constexpr u32 otp_magic = 0xDEADB00F; // Very dead, such boof
#pragma pack(push, 1)
struct Body {
u32 magic;
u32 device_id;
std::array<u8, 0x10> fallback_movable_keyY;
u8 otp_version;
u8 system_type;
std::array<u8, 0x6> manufacture_date;
struct {
u32 expiry_date;
std::array<u8, 0x20> priv_key;
std::array<u8, 0x3C> signature;
} ctcert;
INSERT_PADDING_BYTES(0x10);
std::array<u8, 0x50> random_key_seed_bytes;
};
struct OTPBin {
Body body;
std::array<u8, 0x20> hash;
};
#pragma pack(pop)
static_assert(sizeof(OTPBin) == 0x100, "Invalid OTP size");
Loader::ResultStatus Load(const std::string& file_path, std::span<const u8> key,
std::span<const u8> iv);
u8 GetSystemType() const {
return otp.body.system_type;
}
bool IsDev() const {
return GetSystemType() != 0;
}
u32 GetDeviceID() const {
return otp.body.device_id;
}
u32 GetCTCertExpiration() const {
if (otp.body.otp_version < 5) {
return *reinterpret_cast<const u32_be*>(&otp.body.ctcert.expiry_date);
} else {
return otp.body.ctcert.expiry_date;
}
}
const std::array<u8, 0x20> GetCTCertPrivateKey() const {
return otp.body.ctcert.priv_key;
}
const std::array<u8, 0x3C> GetCTCertSignature() const {
return otp.body.ctcert.signature;
}
bool Valid() const {
return otp.body.magic == otp_magic;
}
void Invalidate() {
otp.body.magic = 0;
}
private:
OTPBin otp = {0};
};
} // namespace FileSys

View File

@ -25,11 +25,6 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
// Skip cache if the read is too big // Skip cache if the read is too big
if (segments.size() == 1 && segments[0].second > cache_line_size) { if (segments.size() == 1 && segments[0].second > cache_line_size) {
length = file.ReadAtBytes(buffer, length, file_offset + offset); length = file.ReadAtBytes(buffer, length, file_offset + offset);
if (is_encrypted) {
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
d.Seek(crypto_offset + offset);
d.ProcessData(buffer, buffer, length);
}
LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length); LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length);
return length; return length;
} }
@ -44,11 +39,6 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
if (!cache_entry.first) { if (!cache_entry.first) {
// If not found, read from disk and cache the data // If not found, read from disk and cache the data
read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page); read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
if (is_encrypted && read_size) {
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
d.Seek(crypto_offset + page);
d.ProcessData(cache_entry.second.data(), cache_entry.second.data(), read_size);
}
LOG_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second, LOG_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second,
(seg.first - page)); (seg.first - page));
} else { } else {

View File

@ -42,14 +42,7 @@ private:
class DirectRomFSReader : public RomFSReader { class DirectRomFSReader : public RomFSReader {
public: public:
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size) DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size)
: is_encrypted(false), file(std::move(file)), file_offset(file_offset), : file(std::move(file)), file_offset(file_offset), data_size(data_size) {}
data_size(data_size) {}
DirectRomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size,
const std::array<u8, 16>& key, const std::array<u8, 16>& ctr,
std::size_t crypto_offset)
: is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset),
crypto_offset(crypto_offset), data_size(data_size) {}
~DirectRomFSReader() override = default; ~DirectRomFSReader() override = default;
@ -64,12 +57,8 @@ public:
bool CacheReady(std::size_t file_offset, std::size_t length) override; bool CacheReady(std::size_t file_offset, std::size_t length) override;
private: private:
bool is_encrypted;
FileUtil::IOFile file; FileUtil::IOFile file;
std::array<u8, 16> key;
std::array<u8, 16> ctr;
u64 file_offset; u64 file_offset;
u64 crypto_offset;
u64 data_size; u64 data_size;
// Total cache size: 128KB // Total cache size: 128KB
@ -92,12 +81,8 @@ private:
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int) { void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<RomFSReader>(*this); ar& boost::serialization::base_object<RomFSReader>(*this);
ar & is_encrypted;
ar & file; ar & file;
ar & key;
ar & ctr;
ar & file_offset; ar & file_offset;
ar & crypto_offset;
ar & data_size; ar & data_size;
} }
friend class boost::serialization::access; friend class boost::serialization::access;

View File

@ -102,6 +102,16 @@ void SeedDB::Add(const Seed& seed) {
seeds.push_back(seed); seeds.push_back(seed);
} }
bool SeedDB::Delete(u64 title_id) {
auto it = std::find_if(seeds.begin(), seeds.end(),
[title_id](const auto& seed) { return seed.title_id == title_id; });
if (it == seeds.end()) {
return false;
}
seeds.erase(it);
return true;
}
std::size_t SeedDB::GetCount() const { std::size_t SeedDB::GetCount() const {
return seeds.size(); return seeds.size();
} }
@ -138,6 +148,21 @@ std::optional<Seed::Data> GetSeed(u64 title_id) {
return std::nullopt; return std::nullopt;
} }
bool DeleteSeed(u64 title_id) {
SeedDB db;
if (!db.Load()) {
LOG_ERROR(Service_FS, "Failed to load seed database");
return false;
}
bool found = db.Delete(title_id);
if (found) {
if (!db.Save()) {
LOG_ERROR(Service_FS, "Failed to save seed database");
}
}
return found;
}
u32 GetSeedCount() { u32 GetSeedCount() {
SeedDB db; SeedDB db;
if (!db.Load()) { if (!db.Load()) {

View File

@ -28,6 +28,7 @@ struct SeedDB {
bool Load(); bool Load();
bool Save(); bool Save();
void Add(const Seed& seed); void Add(const Seed& seed);
bool Delete(u64 title_id);
std::size_t GetCount() const; std::size_t GetCount() const;
auto FindSeedByTitleID(u64 title_id) const; auto FindSeedByTitleID(u64 title_id) const;
@ -35,6 +36,7 @@ struct SeedDB {
bool AddSeed(const Seed& seed); bool AddSeed(const Seed& seed);
std::optional<Seed::Data> GetSeed(u64 title_id); std::optional<Seed::Data> GetSeed(u64 title_id);
bool DeleteSeed(u64 title_id);
u32 GetSeedCount(); u32 GetSeedCount();
} // namespace FileSys } // namespace FileSys

View File

@ -1,15 +1,15 @@
// Copyright 2018 Citra Emulator Project // Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/logging/log.h"
namespace FileSys { namespace FileSys {
enum TMDSignatureType : u32 { enum SignatureType : u32 {
Rsa4096Sha1 = 0x10000, Rsa4096Sha1 = 0x10000,
Rsa2048Sha1 = 0x10001, Rsa2048Sha1 = 0x10001,
EllipticSha1 = 0x10002, EllipticSha1 = 0x10002,
@ -33,8 +33,7 @@ inline u32 GetSignatureSize(u32 signature_type) {
return 0x3C; return 0x3C;
} }
LOG_ERROR(Common_Filesystem, "Tried to read ticket with bad signature {}", signature_type); LOG_ERROR(Common_Filesystem, "Bad signature {}", signature_type);
return 0; return 0;
} }
} // namespace FileSys
} // namespace FileSys

View File

@ -5,16 +5,66 @@
#include <algorithm> #include <algorithm>
#include <cryptopp/aes.h> #include <cryptopp/aes.h>
#include <cryptopp/modes.h> #include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include "common/alignment.h" #include "common/alignment.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/certificate.h"
#include "core/file_sys/otp.h"
#include "core/file_sys/signature.h"
#include "core/file_sys/ticket.h" #include "core/file_sys/ticket.h"
#include "core/hle/service/am/am.h"
#include "core/hw/aes/key.h" #include "core/hw/aes/key.h"
#include "core/hw/ecc.h"
#include "core/hw/unique_data.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
namespace FileSys { namespace FileSys {
Loader::ResultStatus Ticket::DoTitlekeyFixup() {
if (ticket_body.console_id == 0u) {
// Common ticket, no need to fix up
return Loader::ResultStatus::Success;
}
auto& otp = HW::UniqueData::GetOTP();
auto& ct_cert = HW::UniqueData::GetCTCert();
if (!otp.Valid() || !ct_cert.IsValid()) {
LOG_ERROR(HW_AES, "Tried to fixup a ticket without a valid OTP/CTCert");
return Loader::ResultStatus::Error;
}
if (ticket_body.console_id != otp.GetDeviceID()) {
// Ticket does not correspond to this console, cannot fixup
LOG_ERROR(HW_AES, "Tried to fixup a ticket that does not correspond to this console");
return Loader::ResultStatus::Error;
}
HW::ECC::PublicKey ticket_ecc_public = HW::ECC::CreateECCPublicKey(ticket_body.ecc_public_key);
auto agreement = ct_cert.ECDHAgree(ticket_ecc_public);
if (agreement.empty()) {
LOG_ERROR(HW_AES, "Failed to perform ECDH agreement");
return Loader::ResultStatus::Error;
}
CryptoPP::SHA1 hash;
u8 digest[CryptoPP::SHA1::DIGESTSIZE];
hash.CalculateDigest(digest, agreement.data(), agreement.size());
std::vector<u8> key(0x10);
memcpy(key.data(), digest, key.size());
std::vector<u8> iv(0x10);
*reinterpret_cast<u64_be*>(iv.data()) = ticket_body.ticket_id;
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption{key.data(), key.size(), iv.data()}.ProcessData(
ticket_body.title_key.data(), ticket_body.title_key.data(), ticket_body.title_key.size());
return Loader::ResultStatus::Success;
}
Loader::ResultStatus Ticket::Load(std::span<const u8> file_data, std::size_t offset) { Loader::ResultStatus Ticket::Load(std::span<const u8> file_data, std::size_t offset) {
std::size_t total_size = static_cast<std::size_t>(file_data.size() - offset); std::size_t total_size = static_cast<std::size_t>(file_data.size() - offset);
serialized_size = total_size;
if (total_size < sizeof(u32)) if (total_size < sizeof(u32))
return Loader::ResultStatus::Error; return Loader::ResultStatus::Error;
@ -38,6 +88,65 @@ Loader::ResultStatus Ticket::Load(std::span<const u8> file_data, std::size_t off
std::memcpy(ticket_signature.data(), &file_data[offset + sizeof(u32)], signature_size); std::memcpy(ticket_signature.data(), &file_data[offset + sizeof(u32)], signature_size);
std::memcpy(&ticket_body, &file_data[offset + body_start], sizeof(Body)); std::memcpy(&ticket_body, &file_data[offset + body_start], sizeof(Body));
std::size_t content_index_start = body_end;
if (total_size < content_index_start + (2 * sizeof(u32)))
return Loader::ResultStatus::Error;
// Read content index size from the second u32 into it. Actual format is undocumented.
const size_t content_index_size = *reinterpret_cast<const u32_be*>(
&file_data[offset + content_index_start + 1 * sizeof(u32)]);
const size_t content_index_end = content_index_start + content_index_size;
if (total_size < content_index_end)
return Loader::ResultStatus::Error;
content_index.resize(content_index_size);
std::memcpy(content_index.data(), &file_data[offset + content_index_start], content_index_size);
return Loader::ResultStatus::Success;
}
Loader::ResultStatus Ticket::Load(u64 title_id, u64 ticket_id) {
FileUtil::IOFile f(Service::AM::GetTicketPath(title_id, ticket_id), "rb");
if (!f.IsOpen()) {
return Loader::ResultStatus::Error;
}
std::vector<u8> ticket_data(f.GetSize());
f.ReadBytes(ticket_data.data(), ticket_data.size());
return Load(ticket_data, 0);
}
std::vector<u8> Ticket::Serialize() const {
std::vector<u8> ret(sizeof(u32_be));
*reinterpret_cast<u32_be*>(ret.data()) = signature_type;
ret.insert(ret.end(), ticket_signature.begin(), ticket_signature.end());
u32 padding = 0x40 - (ret.size() % 0x40);
ret.insert(ret.end(), padding, 0);
std::span<const u8> body_span{reinterpret_cast<const u8*>(&ticket_body),
reinterpret_cast<const u8*>(&ticket_body) + sizeof(ticket_body)};
ret.insert(ret.end(), body_span.begin(), body_span.end());
ret.insert(ret.end(), content_index.begin(), content_index.end());
return ret;
}
Loader::ResultStatus Ticket::Save(const std::string& file_path) const {
FileUtil::IOFile file(file_path, "wb");
if (!file.IsOpen())
return Loader::ResultStatus::Error;
auto serialized = Serialize();
if (file.WriteBytes(serialized.data(), serialized.size()) != serialized.size()) {
return Loader::ResultStatus::Error;
}
return Loader::ResultStatus::Success; return Loader::ResultStatus::Success;
} }

View File

@ -43,20 +43,36 @@ public:
u8 audit; u8 audit;
INSERT_PADDING_BYTES(0x42); INSERT_PADDING_BYTES(0x42);
std::array<u8, 0x40> limits; std::array<u8, 0x40> limits;
std::array<u8, 0xAC> content_index;
}; };
static_assert(sizeof(Body) == 0x210, "Ticket body structure size is wrong"); static_assert(sizeof(Body) == 0x164, "Ticket body structure size is wrong");
#pragma pack(pop) #pragma pack(pop)
Loader::ResultStatus DoTitlekeyFixup();
Loader::ResultStatus Load(std::span<const u8> file_data, std::size_t offset = 0); Loader::ResultStatus Load(std::span<const u8> file_data, std::size_t offset = 0);
Loader::ResultStatus Load(u64 title_id, u64 ticket_id);
std::vector<u8> Serialize() const;
Loader::ResultStatus Save(const std::string& file_path) const;
std::optional<std::array<u8, 16>> GetTitleKey() const; std::optional<std::array<u8, 16>> GetTitleKey() const;
u64 GetTitleID() const { u64 GetTitleID() const {
return ticket_body.title_id; return ticket_body.title_id;
} }
u64 GetTicketID() const {
return ticket_body.ticket_id;
}
u16 GetVersion() const {
return ticket_body.ticket_title_version;
}
size_t GetSerializedSize() {
return serialized_size;
}
private: private:
Body ticket_body; Body ticket_body;
u32_be signature_type; u32_be signature_type;
std::vector<u8> ticket_signature; std::vector<u8> ticket_signature;
std::vector<u8> content_index;
size_t serialized_size = 0;
}; };
} // namespace FileSys } // namespace FileSys

View File

@ -6,7 +6,7 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/file_sys/cia_common.h" #include "core/file_sys/signature.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"

View File

@ -44,11 +44,12 @@ Result ErrEula::ReceiveParameterImpl(const Service::APT::MessageParameter& param
} }
Result ErrEula::Start(const Service::APT::MessageParameter& parameter) { Result ErrEula::Start(const Service::APT::MessageParameter& parameter) {
startup_param = parameter.buffer; memcpy(&param, parameter.buffer.data(), std::min(parameter.buffer.size(), sizeof(param)));
// TODO(Subv): Set the expected fields in the response buffer before resending it to the // Do something here, like showing error codes, or prompting for EULA agreement.
// application. if (param.type == DisplayType::Agree) {
// TODO(Subv): Reverse the parameter format for the ErrEula applet param.result = 1;
}
// Let the application know that we're closing. // Let the application know that we're closing.
Finalize(); Finalize();
@ -56,8 +57,8 @@ Result ErrEula::Start(const Service::APT::MessageParameter& parameter) {
} }
Result ErrEula::Finalize() { Result ErrEula::Finalize() {
std::vector<u8> buffer(startup_param.size()); std::vector<u8> buffer(sizeof(param));
std::fill(buffer.begin(), buffer.end(), 0); memcpy(buffer.data(), &param, buffer.size());
CloseApplet(nullptr, buffer); CloseApplet(nullptr, buffer);
return ResultSuccess; return ResultSuccess;
} }

View File

@ -6,11 +6,38 @@
#include "core/hle/applets/applet.h" #include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
namespace HLE::Applets { namespace HLE::Applets {
class ErrEula final : public Applet { class ErrEula final : public Applet {
public: public:
enum class DisplayType : u8 { ErrorCode = 0, Error = 1, Agree = 5 };
struct ErrEulaParam {
static constexpr u32 MESSAGE_SIZE = 1900;
// Input data
DisplayType type;
INSERT_PADDING_BYTES(0x3); // Unknown
s32 error_code;
INSERT_PADDING_BYTES(0x2); // Unknown
u16 language;
char16_t message[MESSAGE_SIZE];
bool allow_home_button;
INSERT_PADDING_BYTES(0x1); // Unknown
bool launch_system_settings;
INSERT_PADDING_BYTES(0x89); // Unknown
// Output data
u32 result;
u8 agreed_eula_minor;
u8 agreed_eula_major;
INSERT_PADDING_BYTES(0xA); // Unknown
};
static_assert(sizeof(ErrEulaParam) == 0xF80, "Invalid ErrEulaParam size");
explicit ErrEula(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent, explicit ErrEula(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager) bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: Applet(system, id, parent, preload, std::move(manager)) {} : Applet(system, id, parent, preload, std::move(manager)) {}
@ -27,7 +54,7 @@ private:
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory; std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// Parameter received by the applet on start. /// Parameter received by the applet on start.
std::vector<u8> startup_param; ErrEulaParam param{};
}; };
} // namespace HLE::Applets } // namespace HLE::Applets

View File

@ -205,12 +205,12 @@ Result TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem
buffer->GetPtr() + Memory::CITRA_PAGE_SIZE + page_offset, size); buffer->GetPtr() + Memory::CITRA_PAGE_SIZE + page_offset, size);
// Map the guard pages and mapped pages at once. // Map the guard pages and mapped pages at once.
target_address = auto target_address_result = dst_process->vm_manager.MapBackingMemoryToBase(
dst_process->vm_manager Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, buffer,
.MapBackingMemoryToBase(Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, static_cast<u32>(buffer->GetSize()), Kernel::MemoryState::Shared);
buffer, static_cast<u32>(buffer->GetSize()),
Kernel::MemoryState::Shared) ASSERT_MSG(target_address_result.Succeeded(), "Failed to map target address");
.Unwrap(); target_address = target_address_result.Unwrap();
// Change the permissions and state of the guard pages. // Change the permissions and state of the guard pages.
const VAddr low_guard_address = target_address; const VAddr low_guard_address = target_address;

View File

@ -2362,7 +2362,7 @@ void SVC::CallSVC(u32 immediate) {
if (info->func) { if (info->func) {
(this->*(info->func))(); (this->*(info->func))();
} else { } else {
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {}(..)", info->name); LOG_ERROR(Kernel_SVC, "unimplemented SVC function {:02X} {}(..)", info->id, info->name);
} }
} }
} }

View File

@ -13,6 +13,7 @@
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/ac/ac.h" #include "core/hle/service/ac/ac.h"
#include "core/hle/service/ac/ac_i.h" #include "core/hle/service/ac/ac_i.h"
@ -95,21 +96,20 @@ void Module::Interface::GetCloseResult(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_AC, "(STUBBED) called"); LOG_WARNING(Service_AC, "(STUBBED) called");
} }
void Module::Interface::GetWifiStatus(Kernel::HLERequestContext& ctx) { void Module::Interface::GetStatus(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
bool can_reach_internet = false;
std::shared_ptr<SOC::SOC_U> socu_module = SOC::GetService(ac->system);
if (socu_module) {
can_reach_internet = socu_module->GetDefaultInterfaceInfo().has_value();
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(can_reach_internet ? (Settings::values.is_new_3ds rb.Push<u32>(static_cast<u32>(Status::STATUS_INTERNET));
? WifiStatus::STATUS_CONNECTED_N3DS }
: WifiStatus::STATUS_CONNECTED_O3DS)
: WifiStatus::STATUS_DISCONNECTED)); void Module::Interface::GetWifiStatus(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(WifiStatus::STATUS_CONNECTED_SLOT1));
} }
void Module::Interface::GetInfraPriority(Kernel::HLERequestContext& ctx) { void Module::Interface::GetInfraPriority(Kernel::HLERequestContext& ctx) {
@ -176,8 +176,8 @@ void Module::Interface::IsConnected(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(ac->ac_connected); rb.Push(ac->ac_connected);
LOG_WARNING(Service_AC, "(STUBBED) called unk=0x{:08X} descriptor=0x{:08X} param=0x{:08X}", unk, LOG_DEBUG(Service_AC, "(STUBBED) called unk=0x{:08X} descriptor=0x{:08X} param=0x{:08X}", unk,
unk_descriptor, unk_param); unk_descriptor, unk_param);
} }
void Module::Interface::SetClientVersion(Kernel::HLERequestContext& ctx) { void Module::Interface::SetClientVersion(Kernel::HLERequestContext& ctx) {

View File

@ -82,7 +82,15 @@ public:
* AC::GetWifiStatus service function * AC::GetWifiStatus service function
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet. * 2 : Status
*/
void GetStatus(Kernel::HLERequestContext& ctx);
/**
* AC::GetWifiStatus service function
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : WifiStatus
*/ */
void GetWifiStatus(Kernel::HLERequestContext& ctx); void GetWifiStatus(Kernel::HLERequestContext& ctx);
@ -153,10 +161,17 @@ public:
}; };
protected: protected:
enum class Status {
STATUS_DISCONNECTED = 0,
STATUS_ENABLED = 1,
STATUS_CONNECTED = 2,
STATUS_INTERNET = 3,
};
enum class WifiStatus { enum class WifiStatus {
STATUS_DISCONNECTED = 0, STATUS_DISCONNECTED = 0,
STATUS_CONNECTED_O3DS = 1, STATUS_CONNECTED_SLOT1 = (1 << 0),
STATUS_CONNECTED_N3DS = 2, STATUS_CONNECTED_SLOT2 = (1 << 1),
STATUS_CONNECTED_SLOT3 = (1 << 2),
}; };
struct ACConfig { struct ACConfig {

View File

@ -17,7 +17,7 @@ AC_I::AC_I(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:i"
{0x0008, &AC_I::CloseAsync, "CloseAsync"}, {0x0008, &AC_I::CloseAsync, "CloseAsync"},
{0x0009, &AC_I::GetCloseResult, "GetCloseResult"}, {0x0009, &AC_I::GetCloseResult, "GetCloseResult"},
{0x000A, nullptr, "GetLastErrorCode"}, {0x000A, nullptr, "GetLastErrorCode"},
{0x000C, nullptr, "GetStatus"}, {0x000C, &AC_I::GetStatus, "GetStatus"},
{0x000D, &AC_I::GetWifiStatus, "GetWifiStatus"}, {0x000D, &AC_I::GetWifiStatus, "GetWifiStatus"},
{0x000E, nullptr, "GetCurrentAPInfo"}, {0x000E, nullptr, "GetCurrentAPInfo"},
{0x0010, nullptr, "GetCurrentNZoneInfo"}, {0x0010, nullptr, "GetCurrentNZoneInfo"},

View File

@ -17,7 +17,7 @@ AC_U::AC_U(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:u"
{0x0008, &AC_U::CloseAsync, "CloseAsync"}, {0x0008, &AC_U::CloseAsync, "CloseAsync"},
{0x0009, &AC_U::GetCloseResult, "GetCloseResult"}, {0x0009, &AC_U::GetCloseResult, "GetCloseResult"},
{0x000A, nullptr, "GetLastErrorCode"}, {0x000A, nullptr, "GetLastErrorCode"},
{0x000C, nullptr, "GetStatus"}, {0x000C, &AC_U::GetStatus, "GetStatus"},
{0x000D, &AC_U::GetWifiStatus, "GetWifiStatus"}, {0x000D, &AC_U::GetWifiStatus, "GetWifiStatus"},
{0x000E, nullptr, "GetCurrentAPInfo"}, {0x000E, nullptr, "GetCurrentAPInfo"},
{0x0010, nullptr, "GetCurrentNZoneInfo"}, {0x0010, nullptr, "GetCurrentNZoneInfo"},

View File

@ -25,8 +25,8 @@ u32 GetACTErrorCode(Result result) {
case ErrDescriptions::AlreadyInitialized: case ErrDescriptions::AlreadyInitialized:
error_code = ErrCodes::AlreadyInitialized; error_code = ErrCodes::AlreadyInitialized;
break; break;
case ErrDescriptions::ErrDesc103: case ErrDescriptions::AcStatusDisconnected:
error_code = ErrCodes::ErrCode225103; error_code = ErrCodes::AcStatusDisconnected;
break; break;
case ErrDescriptions::ErrDesc104: case ErrDescriptions::ErrDesc104:
error_code = ErrCodes::ErrCode225104; error_code = ErrCodes::ErrCode225104;

View File

@ -17,7 +17,7 @@ enum {
LibraryError = 100, LibraryError = 100,
NotInitialized = 101, NotInitialized = 101,
AlreadyInitialized = 102, AlreadyInitialized = 102,
ErrDesc103 = 103, AcStatusDisconnected = 103,
ErrDesc104 = 104, ErrDesc104 = 104,
Busy = 111, Busy = 111,
ErrDesc112 = 112, ErrDesc112 = 112,
@ -313,16 +313,16 @@ enum {
MailAddressNotConfirmed = 220001, // 022-0001 MailAddressNotConfirmed = 220001, // 022-0001
// Library errors // Library errors
LibraryError = 220500, // 022-0500 LibraryError = 220500, // 022-0500
NotInitialized = 220501, // 022-0501 NotInitialized = 220501, // 022-0501
AlreadyInitialized = 220502, // 022-0502 AlreadyInitialized = 220502, // 022-0502
ErrCode225103 = 225103, // 022-5103 AcStatusDisconnected = 225103, // 022-5103
ErrCode225104 = 225104, // 022-5104 ErrCode225104 = 225104, // 022-5104
Busy = 220511, // 022-0511 Busy = 220511, // 022-0511
ErrCode225112 = 225112, // 022-5112 ErrCode225112 = 225112, // 022-5112
NotImplemented = 220591, // 022-0591 NotImplemented = 220591, // 022-0591
Deprecated = 220592, // 022-0592 Deprecated = 220592, // 022-0592
DevelopmentOnly = 220593, // 022-0593 DevelopmentOnly = 220593, // 022-0593
InvalidArgument = 220600, // 022-0600 InvalidArgument = 220600, // 022-0600
InvalidPointer = 220601, // 022-0601 InvalidPointer = 220601, // 022-0601

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
#include "common/swap.h" #include "common/swap.h"
#include "core/file_sys/cia_container.h" #include "core/file_sys/cia_container.h"
#include "core/file_sys/file_backend.h" #include "core/file_sys/file_backend.h"
#include "core/file_sys/ncch_container.h"
#include "core/global.h" #include "core/global.h"
#include "core/hle/kernel/mutex.h" #include "core/hle/kernel/mutex.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@ -38,11 +39,15 @@ namespace Kernel {
class Mutex; class Mutex;
} }
namespace IPC {
class RequestParser;
}
namespace Service::AM { namespace Service::AM {
namespace ErrCodes { namespace ErrCodes {
enum { enum {
CIACurrentlyInstalling = 4, InvalidImportState = 4,
InvalidTID = 31, InvalidTID = 31,
EmptyCIA = 32, EmptyCIA = 32,
TryingToUninstallSystemApp = 44, TryingToUninstallSystemApp = 44,
@ -69,30 +74,33 @@ enum class InstallStatus : u32 {
ErrorEncrypted, ErrorEncrypted,
}; };
enum class CTCertLoadStatus { enum class ImportTitleContextState : u8 {
Loaded, NONE = 0,
NotFound, WAITING_FOR_IMPORT = 1,
Invalid, RESUMABLE = 2,
IOError, WAITING_FOR_COMMIT = 3,
ALREADY_EXISTS = 4,
DELETING = 5,
NEEDS_CLEANUP = 6,
}; };
struct CTCert { struct ImportTitleContext {
u32_be signature_type{}; u64 title_id;
std::array<u8, 0x1E> signature_r{}; u16 version;
std::array<u8, 0x1E> signature_s{}; ImportTitleContextState state;
INSERT_PADDING_BYTES(0x40) {}; u32 type;
std::array<char, 0x40> issuer{}; u64 size;
u32_be key_type{};
std::array<char, 0x40> key_id{};
u32_be expiration_time{};
std::array<u8, 0x1E> public_key_x{};
std::array<u8, 0x1E> public_key_y{};
INSERT_PADDING_BYTES(0x3C) {};
bool IsValid() const;
u32 GetDeviceID() const;
}; };
static_assert(sizeof(CTCert) == 0x180, "Invalid CTCert size."); static_assert(sizeof(ImportTitleContext) == 0x18, "Invalid ImportTitleContext size");
struct ImportContentContext {
u32 content_id;
u16 index;
ImportTitleContextState state;
u64 size;
u64 current_size;
};
static_assert(sizeof(ImportContentContext) == 0x18, "Invalid ImportContentContext size");
// Title ID valid length // Title ID valid length
constexpr std::size_t TITLE_ID_VALID_LENGTH = 16; constexpr std::size_t TITLE_ID_VALID_LENGTH = 16;
@ -102,26 +110,101 @@ constexpr u64 TWL_TITLE_ID_FLAG = 0x0000800000000000ULL;
// Progress callback for InstallCIA, receives bytes written and total bytes // Progress callback for InstallCIA, receives bytes written and total bytes
using ProgressCallback = void(std::size_t, std::size_t); using ProgressCallback = void(std::size_t, std::size_t);
class NCCHCryptoFile final {
public:
NCCHCryptoFile(const std::string& out_file);
void Write(const u8* buffer, std::size_t length);
bool IsError() {
return is_error;
}
private:
friend class CIAFile;
FileUtil::IOFile file;
bool is_error = false;
bool decryption_authorized = false;
std::size_t written = 0;
NCCH_Header ncch_header;
std::size_t header_size = 0;
bool header_parsed = false;
bool is_encrypted = false;
std::array<u8, 16>
primary_key{}; // for decrypting exheader, exefs header and icon/banner section
std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section
std::array<u8, 16> exheader_ctr{};
std::array<u8, 16> exefs_ctr{};
std::array<u8, 16> romfs_ctr{};
struct CryptoRegion {
enum Type {
EXHEADER = 0,
EXEFS_HDR = 1,
EXEFS_PRI = 2,
EXEFS_SEC = 3,
ROMFS = 4,
};
Type type;
size_t offset;
size_t size;
size_t seek_from;
};
std::vector<CryptoRegion> regions;
ExeFs_Header exefs_header;
std::size_t exefs_header_written = 0;
bool exefs_header_processed = false;
};
class CIAFile;
void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx);
// A file handled returned for CIAs to be written into and subsequently installed. // A file handled returned for CIAs to be written into and subsequently installed.
class CIAFile final : public FileSys::FileBackend { class CIAFile final : public FileSys::FileBackend {
public: public:
explicit CIAFile(Core::System& system_, Service::FS::MediaType media_type); explicit CIAFile(Core::System& system_, Service::FS::MediaType media_type,
bool from_cdn = false);
~CIAFile(); ~CIAFile();
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override; ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
Result WriteTicket(); Result WriteTicket();
Result WriteTitleMetadata(); Result WriteTitleMetadata(std::span<const u8> tmd_data, std::size_t offset);
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer); ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp, ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
const u8* buffer) override; const u8* buffer) override;
Result ProvideTicket(const FileSys::Ticket& ticket);
const FileSys::TitleMetadata& GetTMD();
CIAInstallState GetCiaInstallState() {
return install_state;
}
ResultVal<std::size_t> WriteContentDataIndexed(u16 content_index, u64 offset,
std::size_t length, const u8* buffer);
u64 GetSize() const override; u64 GetSize() const override;
bool SetSize(u64 size) const override; bool SetSize(u64 size) const override;
bool Close() override; bool Close() override;
void Flush() const override; void Flush() const override;
void SetDone() {
is_done = true;
}
private: private:
friend void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx);
Core::System& system; Core::System& system;
// Sections (tik, tmd, contents) are being imported individually
bool from_cdn;
bool decryption_authorized;
bool is_done = false;
bool is_closed = false;
// Whether it's installing an update, and what step of installation it is at // Whether it's installing an update, and what step of installation it is at
bool is_update = false; bool is_update = false;
CIAInstallState install_state = CIAInstallState::InstallStarted; CIAInstallState install_state = CIAInstallState::InstallStarted;
@ -133,13 +216,26 @@ private:
FileSys::CIAContainer container; FileSys::CIAContainer container;
std::vector<u8> data; std::vector<u8> data;
std::vector<u64> content_written; std::vector<u64> content_written;
std::vector<FileUtil::IOFile> content_files; std::vector<std::string> content_file_paths;
u16 current_content_index = -1;
std::unique_ptr<NCCHCryptoFile> current_content_file;
Service::FS::MediaType media_type; Service::FS::MediaType media_type;
class DecryptionState; class DecryptionState;
std::unique_ptr<DecryptionState> decryption_state; std::unique_ptr<DecryptionState> decryption_state;
}; };
class CurrentImportingTitle {
public:
explicit CurrentImportingTitle(Core::System& system_, u64 title_id_,
Service::FS::MediaType media_type_)
: cia_file(system_, media_type_, true), title_id(title_id_), media_type(media_type_) {}
CIAFile cia_file;
u64 title_id;
Service::FS::MediaType media_type;
};
// A file handled returned for Tickets to be written into and subsequently installed. // A file handled returned for Tickets to be written into and subsequently installed.
class TicketFile final : public FileSys::FileBackend { class TicketFile final : public FileSys::FileBackend {
public: public:
@ -154,9 +250,78 @@ public:
bool Close() override; bool Close() override;
void Flush() const override; void Flush() const override;
Result Commit();
u64 GetTitleID() {
return title_id;
}
u64 GetTicketID() {
return ticket_id;
}
private:
u64 written = 0;
u64 title_id, ticket_id;
std::vector<u8> data;
};
// A file handled returned for TMDs to be written into and subsequently installed.
class TMDFile final : public FileSys::FileBackend {
public:
explicit TMDFile(const std::shared_ptr<CurrentImportingTitle>& import_context)
: importing_title(import_context) {}
~TMDFile();
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
const u8* buffer) override;
u64 GetSize() const override;
bool SetSize(u64 size) const override;
bool Close() override;
void Flush() const override;
Result Commit();
private: private:
u64 written = 0; u64 written = 0;
std::vector<u8> data; std::vector<u8> data;
std::shared_ptr<CurrentImportingTitle> importing_title;
};
// A file handled returned for contents to be written into and subsequently installed.
class ContentFile final : public FileSys::FileBackend {
public:
explicit ContentFile(const std::shared_ptr<CurrentImportingTitle>& import_context, u16 index_,
ImportContentContext& import_context_)
: import_context(import_context_), importing_title(import_context), index(index_) {}
~ContentFile();
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
const u8* buffer) override;
u64 GetSize() const override;
bool SetSize(u64 size) const override;
bool Close() override;
void Flush() const override;
void Cancel(FS::MediaType media_type, u64 title_id);
ImportContentContext& GetImportContext() {
return import_context;
}
void SetWritten(u64 written_) {
written = written_;
}
private:
ImportContentContext& import_context;
u64 written = 0;
std::shared_ptr<CurrentImportingTitle> importing_title;
u16 index;
NCCH_Header ncch_header;
size_t ncch_copied = 0;
}; };
/** /**
@ -168,13 +333,6 @@ private:
InstallStatus InstallCIA(const std::string& path, InstallStatus InstallCIA(const std::string& path,
std::function<ProgressCallback>&& update_callback = nullptr); std::function<ProgressCallback>&& update_callback = nullptr);
/**
* Downloads and installs title form the Nintendo Update Service.
* @param title_id the title_id to download
* @returns whether the install was successful or error code
*/
InstallStatus InstallFromNus(u64 title_id, int version = -1);
/** /**
* Get the update title ID for a title * Get the update title ID for a title
* @param titleId the title ID * @param titleId the title ID
@ -189,6 +347,11 @@ u64 GetTitleUpdateId(u64 title_id);
*/ */
Service::FS::MediaType GetTitleMediaType(u64 titleId); Service::FS::MediaType GetTitleMediaType(u64 titleId);
/**
* Get the .tik path for a title_id and ticket_id.
*/
std::string GetTicketPath(u64 title_id, u64 ticket_id);
/** /**
* Get the .tmd path for a title * Get the .tmd path for a title
* @param media_type the media the title exists on * @param media_type the media the title exists on
@ -461,6 +624,36 @@ public:
*/ */
void GetDeviceID(Kernel::HLERequestContext& ctx); void GetDeviceID(Kernel::HLERequestContext& ctx);
void GetNumImportTitleContextsImpl(IPC::RequestParser& rp, FS::MediaType media_type,
bool include_installing, bool include_finalizing);
void GetImportTitleContextListImpl(IPC::RequestParser& rp, FS::MediaType media_type,
u32 list_count, bool include_installing,
bool include_finalizing);
void GetNumImportTitleContexts(Kernel::HLERequestContext& ctx);
void GetImportTitleContextList(Kernel::HLERequestContext& ctx);
void GetImportTitleContexts(Kernel::HLERequestContext& ctx);
void DeleteImportTitleContext(Kernel::HLERequestContext& ctx);
void GetNumImportContentContextsImpl(IPC::RequestParser& rp, u64 title_id,
FS::MediaType media_type);
void GetImportContentContextListImpl(IPC::RequestParser& rp, u32 list_count, u64 title_id,
FS::MediaType media_type);
void GetImportContentContextsImpl(IPC::RequestParser& rp, u32 list_count, u64 title_id,
FS::MediaType media_type);
void GetNumImportContentContexts(Kernel::HLERequestContext& ctx);
void GetImportContentContextList(Kernel::HLERequestContext& ctx);
void GetImportContentContexts(Kernel::HLERequestContext& ctx);
/** /**
* AM::NeedsCleanup service function * AM::NeedsCleanup service function
* Inputs: * Inputs:
@ -748,6 +941,40 @@ public:
*/ */
void EndImportTicket(Kernel::HLERequestContext& ctx); void EndImportTicket(Kernel::HLERequestContext& ctx);
void BeginImportTitle(Kernel::HLERequestContext& ctx);
void StopImportTitle(Kernel::HLERequestContext& ctx);
void ResumeImportTitle(Kernel::HLERequestContext& ctx);
void CancelImportTitle(Kernel::HLERequestContext& ctx);
void EndImportTitle(Kernel::HLERequestContext& ctx);
void BeginImportTmd(Kernel::HLERequestContext& ctx);
void EndImportTmd(Kernel::HLERequestContext& ctx);
void CreateImportContentContexts(Kernel::HLERequestContext& ctx);
void BeginImportContent(Kernel::HLERequestContext& ctx);
void ResumeImportContent(Kernel::HLERequestContext& ctx);
void StopImportContent(Kernel::HLERequestContext& ctx);
void CancelImportContent(Kernel::HLERequestContext& ctx);
void EndImportContent(Kernel::HLERequestContext& ctx);
void GetNumCurrentImportContentContexts(Kernel::HLERequestContext& ctx);
void GetCurrentImportContentContextList(Kernel::HLERequestContext& ctx);
void GetCurrentImportContentContexts(Kernel::HLERequestContext& ctx);
void Sign(Kernel::HLERequestContext& ctx);
/** /**
* AM::GetDeviceCert service function * AM::GetDeviceCert service function
* Inputs: * Inputs:
@ -758,6 +985,18 @@ public:
*/ */
void GetDeviceCert(Kernel::HLERequestContext& ctx); void GetDeviceCert(Kernel::HLERequestContext& ctx);
void DeleteTicketId(Kernel::HLERequestContext& ctx);
void GetNumTicketIds(Kernel::HLERequestContext& ctx);
void GetTicketIdList(Kernel::HLERequestContext& ctx);
void GetNumTicketsOfProgram(Kernel::HLERequestContext& ctx);
void ListTicketInfos(Kernel::HLERequestContext& ctx);
void ExportTicketWrapped(Kernel::HLERequestContext& ctx);
protected: protected:
std::shared_ptr<Module> am; std::shared_ptr<Module> am;
@ -765,19 +1004,9 @@ public:
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
}; };
/**
* Gets the CTCert.bin path in the host filesystem
* @returns std::string CTCert.bin path in the host filesystem
*/
static std::string GetCTCertPath();
/**
* Loads the CTCert.bin file from the filesystem.
* @returns CTCertLoadStatus indicating the file load status.
*/
static CTCertLoadStatus LoadCTCertFile(CTCert& output);
private: private:
void ScanForTickets();
/** /**
* Scans the for titles in a storage medium for listing. * Scans the for titles in a storage medium for listing.
* @param media_type the storage medium to scan * @param media_type the storage medium to scan
@ -791,9 +1020,14 @@ private:
Core::System& system; Core::System& system;
bool cia_installing = false; bool cia_installing = false;
bool force_old_device_id = false;
bool force_new_device_id = false;
std::array<std::vector<u64_le>, 3> am_title_list; std::array<std::vector<u64_le>, 3> am_title_list;
std::multimap<u64, u64> am_ticket_list;
std::shared_ptr<Kernel::Mutex> system_updater_mutex; std::shared_ptr<Kernel::Mutex> system_updater_mutex;
CTCert ct_cert{}; std::shared_ptr<CurrentImportingTitle> importing_title;
std::map<u64, ImportTitleContext> import_title_contexts;
std::multimap<u64, ImportContentContext> import_content_contexts;
template <class Archive> template <class Archive>
void serialize(Archive& ar, const unsigned int); void serialize(Archive& ar, const unsigned int);

View File

@ -20,21 +20,21 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0008, &AM_NET::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_NET::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_NET::GetTicketList, "GetTicketList"}, {0x0009, &AM_NET::GetTicketList, "GetTicketList"},
{0x000A, &AM_NET::GetDeviceID, "GetDeviceID"}, {0x000A, &AM_NET::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, &AM_NET::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, &AM_NET::GetImportTitleContextList, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, &AM_NET::GetImportTitleContexts, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"}, {0x000E, &AM_NET::DeleteImportTitleContext, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"}, {0x000F, &AM_NET::GetNumImportContentContexts, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"}, {0x0010, &AM_NET::GetImportContentContextList, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"}, {0x0011, &AM_NET::GetImportContentContexts, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"}, {0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_NET::NeedsCleanup, "NeedsCleanup"}, {0x0013, &AM_NET::NeedsCleanup, "NeedsCleanup"},
{0x0014, nullptr, "DoCleanup"}, {0x0014, &AM_NET::DoCleanup, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"}, {0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"}, {0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"}, {0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"}, {0x0018, nullptr, "InitializeTitleDatabase"},
{0x0019, nullptr, "QueryAvailableTitleDatabase"}, {0x0019, &AM_NET::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"}, {0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"}, {0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"}, {0x001C, nullptr, "ImportTwlBackup"},
@ -83,35 +83,35 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0801, &AM_NET::BeginImportTicket, "BeginImportTicket"}, {0x0801, &AM_NET::BeginImportTicket, "BeginImportTicket"},
{0x0802, nullptr, "CancelImportTicket"}, {0x0802, nullptr, "CancelImportTicket"},
{0x0803, &AM_NET::EndImportTicket, "EndImportTicket"}, {0x0803, &AM_NET::EndImportTicket, "EndImportTicket"},
{0x0804, nullptr, "BeginImportTitle"}, {0x0804, &AM_NET::BeginImportTitle, "BeginImportTitle"},
{0x0805, nullptr, "StopImportTitle"}, {0x0805, &AM_NET::StopImportTitle, "StopImportTitle"},
{0x0806, nullptr, "ResumeImportTitle"}, {0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"},
{0x0807, nullptr, "CancelImportTitle"}, {0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"},
{0x0808, nullptr, "EndImportTitle"}, {0x0808, &AM_NET::EndImportTitle, "EndImportTitle"},
{0x0809, nullptr, "CommitImportTitles"}, {0x0809, nullptr, "CommitImportTitles"},
{0x080A, nullptr, "BeginImportTmd"}, {0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"},
{0x080B, nullptr, "CancelImportTmd"}, {0x080B, nullptr, "CancelImportTmd"},
{0x080C, nullptr, "EndImportTmd"}, {0x080C, &AM_NET::EndImportTmd, "EndImportTmd"},
{0x080D, nullptr, "CreateImportContentContexts"}, {0x080D, &AM_NET::CreateImportContentContexts, "CreateImportContentContexts"},
{0x080E, nullptr, "BeginImportContent"}, {0x080E, &AM_NET::BeginImportContent, "BeginImportContent"},
{0x080F, nullptr, "StopImportContent"}, {0x080F, &AM_NET::StopImportContent, "StopImportContent"},
{0x0810, nullptr, "ResumeImportContent"}, {0x0810, &AM_NET::ResumeImportContent, "ResumeImportContent"},
{0x0811, nullptr, "CancelImportContent"}, {0x0811, &AM_NET::CancelImportContent, "CancelImportContent"},
{0x0812, nullptr, "EndImportContent"}, {0x0812, &AM_NET::EndImportContent, "EndImportContent"},
{0x0813, nullptr, "GetNumCurrentImportContentContexts"}, {0x0813, &AM_NET::GetNumCurrentImportContentContexts, "GetNumCurrentImportContentContexts"},
{0x0814, nullptr, "GetCurrentImportContentContextList"}, {0x0814, &AM_NET::GetCurrentImportContentContextList, "GetCurrentImportContentContextList"},
{0x0815, nullptr, "GetCurrentImportContentContexts"}, {0x0815, &AM_NET::GetCurrentImportContentContexts, "GetCurrentImportContentContexts"},
{0x0816, nullptr, "Sign"}, {0x0816, &AM_NET::Sign, "Sign"},
{0x0817, nullptr, "Verify"}, {0x0817, nullptr, "Verify"},
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"}, {0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
{0x0819, nullptr, "ImportCertificates"}, {0x0819, nullptr, "ImportCertificates"},
{0x081A, nullptr, "ImportCertificate"}, {0x081A, nullptr, "ImportCertificate"},
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"}, {0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
{0x081C, nullptr, "DeleteTicketId"}, {0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"},
{0x081D, nullptr, "GetNumTicketIds"}, {0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"},
{0x081E, nullptr, "GetTicketIdList"}, {0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"},
{0x081F, nullptr, "GetNumTicketsOfProgram"}, {0x081F, &AM_NET::GetNumTicketsOfProgram, "GetNumTicketsOfProgram"},
{0x0820, nullptr, "ListTicketInfos"}, {0x0820, &AM_NET::ListTicketInfos, "ListTicketInfos"},
{0x0821, nullptr, "GetRightsOnlyTicketData"}, {0x0821, nullptr, "GetRightsOnlyTicketData"},
{0x0822, nullptr, "GetNumCurrentContentInfos"}, {0x0822, nullptr, "GetNumCurrentContentInfos"},
{0x0823, nullptr, "FindCurrentContentInfos"}, {0x0823, nullptr, "FindCurrentContentInfos"},
@ -120,6 +120,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0826, nullptr, "UpdateImportContentContexts"}, {0x0826, nullptr, "UpdateImportContentContexts"},
{0x0827, nullptr, "DeleteAllDemoLaunchInfos"}, {0x0827, nullptr, "DeleteAllDemoLaunchInfos"},
{0x0828, nullptr, "BeginImportTitleForOverWrite"}, {0x0828, nullptr, "BeginImportTitleForOverWrite"},
{0x0829, &AM_NET::ExportTicketWrapped, "ExportTicketWrapped"},
// clang-format on // clang-format on
}; };
RegisterHandlers(functions); RegisterHandlers(functions);

View File

@ -20,13 +20,13 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_SYS::GetTicketList, "GetTicketList"}, {0x0009, &AM_SYS::GetTicketList, "GetTicketList"},
{0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"}, {0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, &AM_SYS::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, &AM_SYS::GetImportTitleContextList, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, &AM_SYS::GetImportTitleContexts, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"}, {0x000E, &AM_SYS::DeleteImportTitleContext, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"}, {0x000F, &AM_SYS::GetNumImportContentContexts, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"}, {0x0010, &AM_SYS::GetImportContentContextList, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"}, {0x0011, &AM_SYS::GetImportContentContexts, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"}, {0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"}, {0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"},
{0x0014, &AM_SYS::DoCleanup, "DoCleanup"}, {0x0014, &AM_SYS::DoCleanup, "DoCleanup"},
@ -42,10 +42,10 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x001E, nullptr, "ReadTwlBackupInfo"}, {0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"}, {0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"}, {0x0020, nullptr, "GetTwlArchiveResourceInfo"},
{0x0021, nullptr, "GetPersonalizedTicketInfoList"}, {0x0021, &AM_SYS::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"}, {0x0022, nullptr, "DeleteAllImportContextsFiltered"},
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"}, {0x0023, &AM_SYS::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
{0x0024, nullptr, "GetImportTitleContextListFiltered"}, {0x0024, &AM_SYS::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
{0x0025, &AM_SYS::CheckContentRights, "CheckContentRights"}, {0x0025, &AM_SYS::CheckContentRights, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"}, {0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"}, {0x0027, nullptr, "GetDemoLaunchInfos"},
@ -53,7 +53,7 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0029, nullptr, "DeleteUserProgramsAtomically"}, {0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"}, {0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"}, {0x002B, nullptr, "ListExistingContentInfosSystem"},
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"}, {0x002C, &AM_SYS::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
{0x002D, &AM_SYS::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"}, {0x002D, &AM_SYS::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x1001, &AM_SYS::GetDLCContentInfoCount, "GetDLCContentInfoCount"}, {0x1001, &AM_SYS::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
{0x1002, &AM_SYS::FindDLCContentInfos, "FindDLCContentInfos"}, {0x1002, &AM_SYS::FindDLCContentInfos, "FindDLCContentInfos"},

View File

@ -29,6 +29,7 @@
#include "core/hle/service/cfg/cfg_s.h" #include "core/hle/service/cfg/cfg_s.h"
#include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/cfg/cfg_u.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/hw/unique_data.h"
SERVICE_CONSTRUCT_IMPL(Service::CFG::Module) SERVICE_CONSTRUCT_IMPL(Service::CFG::Module)
SERIALIZE_EXPORT_IMPL(Service::CFG::Module) SERIALIZE_EXPORT_IMPL(Service::CFG::Module)
@ -217,11 +218,19 @@ void Module::Interface::GetRegion(Kernel::HLERequestContext& ctx) {
void Module::Interface::SecureInfoGetByte101(Kernel::HLERequestContext& ctx) { void Module::Interface::SecureInfoGetByte101(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u8 ret = 0; const auto& secure_info_a = HW::UniqueData::GetSecureInfoA();
if (cfg->secure_info_a_loaded) { const auto& local_friend_code_seed_b = HW::UniqueData::GetLocalFriendCodeSeedB();
ret = cfg->secure_info_a.unknown;
// Never happens on real hardware, but may happen if user didn't supply a dump.
// Always make sure to have available both secure data kinds or error otherwise.
if (!secure_info_a.IsValid() || !local_friend_code_seed_b.IsValid()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
ErrorLevel::Permanent));
} }
u8 ret = secure_info_a.body.unknown;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u8>(ret); rb.Push<u8>(ret);
@ -232,20 +241,25 @@ void Module::Interface::SecureInfoGetSerialNo(Kernel::HLERequestContext& ctx) {
[[maybe_unused]] u32 out_size = rp.Pop<u32>(); [[maybe_unused]] u32 out_size = rp.Pop<u32>();
auto out_buffer = rp.PopMappedBuffer(); auto out_buffer = rp.PopMappedBuffer();
if (out_buffer.GetSize() < sizeof(SecureInfoA::serial_number)) { if (out_buffer.GetSize() < sizeof(HW::UniqueData::SecureInfoA::body.serial_number)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::InvalidSize, ErrorModule::Config, rb.Push(Result(ErrorDescription::InvalidSize, ErrorModule::Config,
ErrorSummary::WrongArgument, ErrorLevel::Permanent)); ErrorSummary::WrongArgument, ErrorLevel::Permanent));
} }
const auto& secure_info_a = HW::UniqueData::GetSecureInfoA();
const auto& local_friend_code_seed_b = HW::UniqueData::GetLocalFriendCodeSeedB();
// Never happens on real hardware, but may happen if user didn't supply a dump. // Never happens on real hardware, but may happen if user didn't supply a dump.
// Always make sure to have available both secure data kinds or error otherwise. // Always make sure to have available both secure data kinds or error otherwise.
if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { if (!secure_info_a.IsValid() || !local_friend_code_seed_b.IsValid()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState, rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
ErrorLevel::Permanent)); ErrorLevel::Permanent));
} }
out_buffer.Write(&cfg->secure_info_a.serial_number, 0, sizeof(SecureInfoA::serial_number)); out_buffer.Write(secure_info_a.body.serial_number.data(), 0,
sizeof(HW::UniqueData::SecureInfoA::body.serial_number));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -398,27 +412,34 @@ void Module::Interface::GetLocalFriendCodeSeedData(Kernel::HLERequestContext& ct
auto out_buffer = rp.PopMappedBuffer(); auto out_buffer = rp.PopMappedBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (out_buffer.GetSize() < sizeof(LocalFriendCodeSeedB)) { if (out_buffer.GetSize() < sizeof(HW::UniqueData::LocalFriendCodeSeedB)) {
rb.Push(Result(ErrorDescription::InvalidSize, ErrorModule::Config, rb.Push(Result(ErrorDescription::InvalidSize, ErrorModule::Config,
ErrorSummary::WrongArgument, ErrorLevel::Permanent)); ErrorSummary::WrongArgument, ErrorLevel::Permanent));
} }
const auto& secure_info_a = HW::UniqueData::GetSecureInfoA();
const auto& local_friend_code_seed_b = HW::UniqueData::GetLocalFriendCodeSeedB();
// Never happens on real hardware, but may happen if user didn't supply a dump. // Never happens on real hardware, but may happen if user didn't supply a dump.
// Always make sure to have available both secure data kinds or error otherwise. // Always make sure to have available both secure data kinds or error otherwise.
if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { if (!secure_info_a.IsValid() || !local_friend_code_seed_b.IsValid()) {
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState, rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
ErrorLevel::Permanent)); ErrorLevel::Permanent));
} }
out_buffer.Write(&cfg->local_friend_code_seed_b, 0, sizeof(LocalFriendCodeSeedB)); out_buffer.Write(&local_friend_code_seed_b, 0, sizeof(HW::UniqueData::LocalFriendCodeSeedB));
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx) { void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const auto& secure_info_a = HW::UniqueData::GetSecureInfoA();
const auto& local_friend_code_seed_b = HW::UniqueData::GetLocalFriendCodeSeedB();
// Never happens on real hardware, but may happen if user didn't supply a dump. // Never happens on real hardware, but may happen if user didn't supply a dump.
// Always make sure to have available both secure data kinds or error otherwise. // Always make sure to have available both secure data kinds or error otherwise.
if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { if (!secure_info_a.IsValid() || !local_friend_code_seed_b.IsValid()) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState, rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
ErrorLevel::Permanent)); ErrorLevel::Permanent));
@ -426,7 +447,7 @@ void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u64>(cfg->local_friend_code_seed_b.friend_code_seed); rb.Push<u64>(local_friend_code_seed_b.body.friend_code_seed);
} }
void Module::Interface::FormatConfig(Kernel::HLERequestContext& ctx) { void Module::Interface::FormatConfig(Kernel::HLERequestContext& ctx) {
@ -601,14 +622,6 @@ Result Module::UpdateConfigNANDSavegame() {
return ResultSuccess; return ResultSuccess;
} }
std::string Module::GetLocalFriendCodeSeedBPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/LocalFriendCodeSeed_B";
}
std::string Module::GetSecureInfoAPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/SecureInfo_A";
}
Result Module::FormatConfig() { Result Module::FormatConfig() {
Result res = DeleteConfigNANDSaveFile(); Result res = DeleteConfigNANDSaveFile();
// The delete command fails if the file doesn't exist, so we have to check that too // The delete command fails if the file doesn't exist, so we have to check that too
@ -682,55 +695,6 @@ Result Module::LoadConfigNANDSaveFile() {
return FormatConfig(); return FormatConfig();
} }
void Module::InvalidateSecureData() {
secure_info_a_loaded = local_friend_code_seed_b_loaded = false;
}
SecureDataLoadStatus Module::LoadSecureInfoAFile() {
if (secure_info_a_loaded) {
return SecureDataLoadStatus::Loaded;
}
std::string file_path = GetSecureInfoAPath();
if (!FileUtil::Exists(file_path)) {
return SecureDataLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return SecureDataLoadStatus::IOError;
}
if (file.GetSize() != sizeof(SecureInfoA)) {
return SecureDataLoadStatus::Invalid;
}
if (file.ReadBytes(&secure_info_a, sizeof(SecureInfoA)) != sizeof(SecureInfoA)) {
return SecureDataLoadStatus::IOError;
}
secure_info_a_loaded = true;
return SecureDataLoadStatus::Loaded;
}
SecureDataLoadStatus Module::LoadLocalFriendCodeSeedBFile() {
if (local_friend_code_seed_b_loaded) {
return SecureDataLoadStatus::Loaded;
}
std::string file_path = GetLocalFriendCodeSeedBPath();
if (!FileUtil::Exists(file_path)) {
return SecureDataLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return SecureDataLoadStatus::IOError;
}
if (file.GetSize() != sizeof(LocalFriendCodeSeedB)) {
return SecureDataLoadStatus::Invalid;
}
if (file.ReadBytes(&local_friend_code_seed_b, sizeof(LocalFriendCodeSeedB)) !=
sizeof(LocalFriendCodeSeedB)) {
return SecureDataLoadStatus::IOError;
}
local_friend_code_seed_b_loaded = true;
return SecureDataLoadStatus::Loaded;
}
void Module::LoadMCUConfig() { void Module::LoadMCUConfig() {
FileUtil::IOFile mcu_data_file( FileUtil::IOFile mcu_data_file(
fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb"); fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb");
@ -768,8 +732,6 @@ Module::Module(Core::System& system_) : system(system_) {
SetEULAVersion(default_version); SetEULAVersion(default_version);
UpdateConfigNANDSavegame(); UpdateConfigNANDSavegame();
} }
LoadSecureInfoAFile();
LoadLocalFriendCodeSeedBFile();
} }
Module::~Module() = default; Module::~Module() = default;

View File

@ -21,6 +21,10 @@ namespace Core {
class System; class System;
} }
namespace HW::UniqueData {
enum class SecureDataLoadStatus;
}
namespace Service::CFG { namespace Service::CFG {
enum ConfigBlockID { enum ConfigBlockID {
@ -177,28 +181,6 @@ enum class AccessFlag : u16 {
}; };
DECLARE_ENUM_FLAG_OPERATORS(AccessFlag); DECLARE_ENUM_FLAG_OPERATORS(AccessFlag);
struct SecureInfoA {
std::array<u8, 0x100> signature;
u8 region;
u8 unknown;
std::array<u8, 0xF> serial_number;
};
static_assert(sizeof(SecureInfoA) == 0x111);
struct LocalFriendCodeSeedB {
std::array<u8, 0x100> signature;
u64 unknown;
u64 friend_code_seed;
};
static_assert(sizeof(LocalFriendCodeSeedB) == 0x110);
enum class SecureDataLoadStatus {
Loaded,
NotFound,
Invalid,
IOError,
};
class Module final { class Module final {
public: public:
Module(Core::System& system_); Module(Core::System& system_);
@ -635,34 +617,6 @@ public:
*/ */
Result UpdateConfigNANDSavegame(); Result UpdateConfigNANDSavegame();
/**
* Invalidates the loaded secure data so that it is loaded again.
*/
void InvalidateSecureData();
/**
* Loads the LocalFriendCodeSeed_B file from NAND.
* @returns LocalFriendCodeSeedBLoadStatus indicating the file load status.
*/
SecureDataLoadStatus LoadSecureInfoAFile();
/**
* Loads the LocalFriendCodeSeed_B file from NAND.
* @returns LocalFriendCodeSeedBLoadStatus indicating the file load status.
*/
SecureDataLoadStatus LoadLocalFriendCodeSeedBFile();
/**
* Gets the SecureInfo_A path in the host filesystem
* @returns std::string SecureInfo_A path in the host filesystem
*/
std::string GetSecureInfoAPath();
/**
* Gets the LocalFriendCodeSeed_B path in the host filesystem
* @returns std::string LocalFriendCodeSeed_B path in the host filesystem
*/
std::string GetLocalFriendCodeSeedBPath();
/** /**
* Saves MCU specific data * Saves MCU specific data
*/ */
@ -678,10 +632,6 @@ private:
std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer; std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer;
std::unique_ptr<FileSys::ArchiveBackend> cfg_system_save_data_archive; std::unique_ptr<FileSys::ArchiveBackend> cfg_system_save_data_archive;
u32 preferred_region_code = 0; u32 preferred_region_code = 0;
bool secure_info_a_loaded = false;
SecureInfoA secure_info_a;
bool local_friend_code_seed_b_loaded = false;
LocalFriendCodeSeedB local_friend_code_seed_b;
bool preferred_region_chosen = false; bool preferred_region_chosen = false;
MCUData mcu_data{}; MCUData mcu_data{};

View File

@ -2,6 +2,10 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cryptopp/aes.h>
#include <cryptopp/cmac.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include "common/archives.h" #include "common/archives.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
@ -25,6 +29,8 @@
#include "core/hle/service/am/am.h" #include "core/hle/service/am/am.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h" #include "core/hle/service/fs/fs_user.h"
#include "core/hw/aes/key.h"
#include "core/hw/unique_data.h"
SERVICE_CONSTRUCT_IMPL(Service::FS::FS_USER) SERVICE_CONSTRUCT_IMPL(Service::FS::FS_USER)
SERIALIZE_EXPORT_IMPL(Service::FS::FS_USER) SERIALIZE_EXPORT_IMPL(Service::FS::FS_USER)
@ -1111,6 +1117,47 @@ void FS_USER::GetArchiveResource(Kernel::HLERequestContext& ctx) {
rb.PushRaw(*resource); rb.PushRaw(*resource);
} }
void FS_USER::ExportIntegrityVerificationSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto buf = rp.PopMappedBuffer();
constexpr size_t hashed_movable_size = 0x110;
constexpr size_t cipher_movable_size = 0x120;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
auto& movable_struct = HW::UniqueData::GetMovableSed();
if (!movable_struct.IsValid()) {
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::FS, ErrorSummary::NotFound,
ErrorLevel::Permanent));
return;
}
std::vector<u8> movable(cipher_movable_size);
memcpy(movable.data(), &movable_struct, cipher_movable_size);
auto movable_cmac = HW::AES::GetMovableKey(true);
auto movable_key = HW::AES::GetMovableKey(false);
CryptoPP::SHA256 hash;
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> movable_digest;
hash.CalculateDigest(movable_digest.data(), movable.data(), hashed_movable_size);
CryptoPP::CMAC<CryptoPP::AES> cmac(movable_cmac.data(), movable_cmac.size());
std::array<u8, CryptoPP::AES::BLOCKSIZE> cmac_hash;
cmac.Update(movable_digest.data(), movable_digest.size());
cmac.Final(cmac_hash.data());
CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption{movable_key.data(), movable_key.size(),
cmac_hash.data()}
.ProcessData(movable.data(), movable.data(), cipher_movable_size);
movable.insert(movable.begin(), cmac_hash.begin(), cmac_hash.end());
buf.Write(movable.data(), 0, std::min(buf.GetSize(), movable.size()));
rb.Push(ResultSuccess);
}
void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) { void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const auto archive_id = rp.PopEnum<FS::ArchiveIdCode>(); const auto archive_id = rp.PopEnum<FS::ArchiveIdCode>();
@ -1262,6 +1309,7 @@ void FS_USER::GetSpecialContentIndex(Kernel::HLERequestContext& ctx) {
void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) { void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
LOG_DEBUG(Service_FS, "");
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u32>(FileSys::GetSeedCount()); rb.Push<u32>(FileSys::GetSeedCount());
} }
@ -1269,12 +1317,68 @@ void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) {
void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) { void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u64 title_id{rp.Pop<u64>()}; u64 title_id{rp.Pop<u64>()};
LOG_INFO(Service_FS, "Adding seed for title_id={:016X}", title_id);
FileSys::Seed::Data seed{rp.PopRaw<FileSys::Seed::Data>()}; FileSys::Seed::Data seed{rp.PopRaw<FileSys::Seed::Data>()};
FileSys::AddSeed({title_id, seed, {}}); FileSys::AddSeed({title_id, seed, {}});
IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)}; IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void FS_USER::DeleteSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u64 title_id{rp.Pop<u64>()};
bool found = FileSys::DeleteSeed(title_id);
IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
rb.Push(found ? ResultSuccess
: Result(FileSys::ErrCodes::RomFSNotFound, ErrorModule::FS,
ErrorSummary::NotFound, ErrorLevel::Status));
}
void FS_USER::GetSeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u64 title_id{rp.Pop<u64>()};
auto seed = FileSys::GetSeed(title_id);
if (!seed.has_value()) {
IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
rb.Push(Result(FileSys::ErrCodes::RomFSNotFound, ErrorModule::FS, ErrorSummary::NotFound,
ErrorLevel::Status));
return;
}
IPC::RequestBuilder rb{rp.MakeBuilder(5, 0)};
rb.Push(ResultSuccess);
rb.PushRaw<FileSys::Seed::Data>(seed.value());
}
void FS_USER::SetUnknown0x80Data(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u64 title_id{rp.Pop<u64>()};
std::array<u8, 0x80> unknown_data = rp.PopRaw<std::array<u8, 0x80>>();
IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
rb.Push(ResultSuccess);
LOG_WARNING(Service_FS, "(STUBBED) title_id={:016X}", title_id);
}
void FS_USER::GetUnknown0x80Data(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u64 title_id{rp.Pop<u64>()};
std::array<u8, 0x80> unknown_data = {0};
IPC::RequestBuilder rb{rp.MakeBuilder(0x21, 0)};
rb.Push(Result(FileSys::ErrCodes::RomFSNotFound, ErrorModule::FS, ErrorSummary::NotFound,
ErrorLevel::Status));
rb.PushRaw(unknown_data);
LOG_WARNING(Service_FS, "(STUBBED) title_id={:016X}", title_id);
}
void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
const u64 value = rp.Pop<u64>(); const u64 value = rp.Pop<u64>();
@ -1729,7 +1833,7 @@ FS_USER::FS_USER(Core::System& system)
{0x0847, nullptr, "FormatCtrCardUserSaveData"}, {0x0847, nullptr, "FormatCtrCardUserSaveData"},
{0x0848, nullptr, "GetSdmcCtrRootPath"}, {0x0848, nullptr, "GetSdmcCtrRootPath"},
{0x0849, &FS_USER::GetArchiveResource, "GetArchiveResource"}, {0x0849, &FS_USER::GetArchiveResource, "GetArchiveResource"},
{0x084A, nullptr, "ExportIntegrityVerificationSeed"}, {0x084A, &FS_USER::ExportIntegrityVerificationSeed, "ExportIntegrityVerificationSeed"},
{0x084B, nullptr, "ImportIntegrityVerificationSeed"}, {0x084B, nullptr, "ImportIntegrityVerificationSeed"},
{0x084C, &FS_USER::FormatSaveData, "FormatSaveData"}, {0x084C, &FS_USER::FormatSaveData, "FormatSaveData"},
{0x084D, nullptr, "GetLegacySubBannerData"}, {0x084D, nullptr, "GetLegacySubBannerData"},
@ -1767,7 +1871,11 @@ FS_USER::FS_USER(Core::System& system)
{0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" }, {0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" },
{0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" }, {0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" },
{0x087A, &FS_USER::AddSeed, "AddSeed"}, {0x087A, &FS_USER::AddSeed, "AddSeed"},
{0x087B, &FS_USER::GetSeed, "GetSeed"},
{0x087C, &FS_USER::DeleteSeed, "GetSeed"},
{0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"}, {0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"},
{0x0880, &FS_USER::SetUnknown0x80Data, "SetUnknown0x80Data"},
{0x0881, &FS_USER::GetUnknown0x80Data, "GetUnknown0x80Data"},
{0x0886, nullptr, "CheckUpdatedDat"}, {0x0886, nullptr, "CheckUpdatedDat"},
// clang-format on // clang-format on
}; };

View File

@ -524,6 +524,8 @@ private:
*/ */
void GetArchiveResource(Kernel::HLERequestContext& ctx); void GetArchiveResource(Kernel::HLERequestContext& ctx);
void ExportIntegrityVerificationSeed(Kernel::HLERequestContext& ctx);
/** /**
* FS_User::GetFormatInfo service function. * FS_User::GetFormatInfo service function.
* Inputs: * Inputs:
@ -610,17 +612,6 @@ private:
*/ */
void GetSpecialContentIndex(Kernel::HLERequestContext& ctx); void GetSpecialContentIndex(Kernel::HLERequestContext& ctx);
/**
* FS_User::GetNumSeeds service function.
* Inputs:
* 0 : 0x087D0000
* Outputs:
* 0 : 0x087D0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of seeds in the SEEDDB
*/
void GetNumSeeds(Kernel::HLERequestContext& ctx);
/** /**
* FS_User::AddSeed service function. * FS_User::AddSeed service function.
* Inputs: * Inputs:
@ -633,6 +624,25 @@ private:
*/ */
void AddSeed(Kernel::HLERequestContext& ctx); void AddSeed(Kernel::HLERequestContext& ctx);
void GetSeed(Kernel::HLERequestContext& ctx);
void DeleteSeed(Kernel::HLERequestContext& ctx);
/**
* FS_User::GetNumSeeds service function.
* Inputs:
* 0 : 0x087D0000
* Outputs:
* 0 : 0x087D0080
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Number of seeds in the SEEDDB
*/
void GetNumSeeds(Kernel::HLERequestContext& ctx);
void SetUnknown0x80Data(Kernel::HLERequestContext& ctx);
void GetUnknown0x80Data(Kernel::HLERequestContext& ctx);
/** /**
* FS_User::SetSaveDataSecureValue service function. * FS_User::SetSaveDataSecureValue service function.
* Inputs: * Inputs:

View File

@ -0,0 +1,85 @@
/* Generated by bin2c, do not edit manually */
/* Contents of file ctr-common-1-cert.bin */
const long int ctr_common_1_cert_bin_size = 1264;
const unsigned char ctr_common_1_cert_bin[1264] = {
0x2B, 0x7B, 0xB5, 0xB5, 0x8F, 0xD2, 0x86, 0x75, 0xDB, 0x69, 0x02, 0xD9, 0x90, 0xCD, 0xC1, 0x1B,
0xFC, 0xA9, 0x3D, 0xD8, 0x51, 0x82, 0xEE, 0x75, 0xE1, 0x22, 0x0C, 0x98, 0xFB, 0xE6, 0xB6, 0xE1,
0xBF, 0xD0, 0x85, 0x15, 0x2F, 0x34, 0xF7, 0x27, 0x5C, 0x6D, 0xAB, 0x23, 0x0E, 0x04, 0x75, 0xCE,
0x95, 0x57, 0x99, 0x35, 0xFC, 0x30, 0xC2, 0x4E, 0xF7, 0xAE, 0x42, 0x37, 0x94, 0x31, 0xFA, 0x84,
0xB5, 0x51, 0x58, 0xE7, 0x8B, 0x37, 0xC3, 0x45, 0xB5, 0x7D, 0xAE, 0x27, 0x92, 0x9E, 0xB8, 0xF2,
0x07, 0x1C, 0xAF, 0x67, 0xA9, 0x1C, 0x27, 0x73, 0x7E, 0x60, 0xD2, 0xCF, 0x37, 0x71, 0x32, 0xD1,
0x35, 0x51, 0x0A, 0x23, 0xB0, 0x3B, 0x5B, 0x08, 0x91, 0xE1, 0xE1, 0xE9, 0x5D, 0x2B, 0x5B, 0xF0,
0xE3, 0xBB, 0x5F, 0xBD, 0xF0, 0x9B, 0xB5, 0xD7, 0x37, 0x96, 0x76, 0x54, 0x52, 0xBA, 0x79, 0xC2,
0x7E, 0xFE, 0x57, 0xD4, 0x4E, 0x5F, 0x05, 0x4A, 0x0D, 0xB6, 0x15, 0x49, 0x08, 0x06, 0xF6, 0x3D,
0xF9, 0xAB, 0x04, 0x2F, 0xEB, 0x26, 0x21, 0x56, 0x2B, 0x1C, 0xAC, 0x85, 0xB0, 0x5A, 0xC8, 0x4E,
0x67, 0x9B, 0x8C, 0xAA, 0xB8, 0x47, 0x6E, 0x7B, 0x72, 0x93, 0x7C, 0x32, 0x3C, 0x20, 0xFD, 0x0B,
0x61, 0x3C, 0x91, 0xC1, 0x26, 0x6F, 0xD8, 0xD7, 0x79, 0x85, 0x34, 0xD1, 0x4D, 0x1C, 0xCC, 0x66,
0x42, 0xDB, 0xBE, 0xD8, 0x8E, 0xB0, 0x49, 0xEA, 0x0A, 0x68, 0x56, 0x16, 0x17, 0x49, 0x57, 0x82,
0x7B, 0x5D, 0xBD, 0x1C, 0x0A, 0x97, 0xBB, 0xA5, 0x2E, 0x78, 0xE6, 0x86, 0x4C, 0xC6, 0xD1, 0x76,
0xCB, 0x30, 0x31, 0xB2, 0x02, 0xF8, 0xDA, 0x0A, 0x50, 0x86, 0xA6, 0x2E, 0x23, 0x2B, 0x44, 0xF9,
0x51, 0x81, 0xAB, 0x71, 0xE8, 0x6A, 0x61, 0xD9, 0x37, 0x32, 0x9C, 0xDD, 0x91, 0x68, 0x79, 0x97,
0x04, 0x09, 0xD7, 0x96, 0x3B, 0x52, 0xCF, 0x63, 0x66, 0x25, 0xDD, 0xE4, 0x8A, 0xB6, 0xFE, 0x0D,
0x15, 0x55, 0xD3, 0xD5, 0xB5, 0x1D, 0x73, 0x32, 0xD0, 0xAD, 0x46, 0xF0, 0x8E, 0x74, 0x1B, 0x70,
0x39, 0xBA, 0x5A, 0x14, 0xB4, 0x16, 0x38, 0x3C, 0xA3, 0x82, 0x82, 0x1C, 0xCA, 0x29, 0xD4, 0xA1,
0x5D, 0x26, 0x7D, 0xDA, 0x5B, 0x0B, 0x4C, 0xE8, 0xF8, 0xDF, 0x99, 0x9C, 0x67, 0xA5, 0x43, 0xA6,
0xCC, 0x38, 0x10, 0xFF, 0xE9, 0xC9, 0x66, 0x04, 0xF7, 0x52, 0x25, 0x7B, 0x28, 0xA3, 0xD7, 0x01,
0xFE, 0xC2, 0xFE, 0x50, 0xBE, 0x02, 0x99, 0x17, 0x87, 0x71, 0xD4, 0xBB, 0x03, 0x67, 0xFF, 0x8A,
0x4B, 0x03, 0xEB, 0x43, 0x16, 0xB2, 0x24, 0x04, 0x89, 0x50, 0x22, 0x1F, 0x51, 0xB0, 0xF8, 0xF0,
0x40, 0x83, 0x56, 0x46, 0xCF, 0x8A, 0xE7, 0x48, 0xB7, 0x50, 0x85, 0x3B, 0x27, 0xDD, 0xBA, 0xA2,
0x6F, 0xC3, 0x5E, 0x5B, 0xBE, 0xD4, 0xF0, 0x1C, 0x11, 0xC6, 0xD7, 0x91, 0x99, 0x57, 0x55, 0x17,
0xBC, 0xCC, 0x3D, 0xC7, 0x27, 0xB3, 0xE7, 0x52, 0x3A, 0x2E, 0x39, 0x8A, 0x8F, 0xE2, 0xCB, 0x11,
0x09, 0xAF, 0xCD, 0xED, 0x61, 0xFC, 0x32, 0x3D, 0xB9, 0xA5, 0x54, 0x09, 0xC6, 0x67, 0x9E, 0xD2,
0xB2, 0x77, 0xD8, 0x39, 0xC7, 0xFD, 0x3B, 0x7A, 0x4F, 0x27, 0xED, 0x6E, 0x16, 0x4B, 0xFA, 0x4B,
0x95, 0xD4, 0x47, 0x8D, 0x93, 0x3B, 0x4B, 0x5B, 0xD7, 0x2D, 0x7D, 0x59, 0x34, 0x66, 0x11, 0xF8,
0xF0, 0x24, 0x5B, 0xC8, 0xB1, 0xAA, 0xE9, 0x92, 0x50, 0xAA, 0xFA, 0xBE, 0xEF, 0x61, 0xBA, 0x17,
0xDA, 0xCB, 0xD5, 0x7C, 0x80, 0x53, 0x93, 0x90, 0x38, 0xFC, 0x4D, 0xF7, 0x0D, 0x99, 0x83, 0xF2,
0x3C, 0x54, 0xD9, 0xDF, 0x3A, 0x5D, 0xFF, 0xB5, 0x8F, 0x87, 0x5A, 0xD4, 0x07, 0x60, 0xF1, 0x5B,
0x84, 0x09, 0xE9, 0xD4, 0x1D, 0xD4, 0xA9, 0x1A, 0xA9, 0x4F, 0xF3, 0xA6, 0x2D, 0x38, 0xBA, 0x52,
0x1C, 0x92, 0xB5, 0x10, 0x1A, 0x95, 0x4C, 0xEB, 0xDB, 0xE9, 0xD7, 0xB1, 0xF8, 0x01, 0x74, 0x07,
0xE7, 0x52, 0x91, 0xA7, 0xDD, 0x12, 0x2D, 0x13, 0x25, 0xDE, 0xEE, 0x8B, 0x94, 0x74, 0xD0, 0x4C,
0x3E, 0x76, 0x98, 0xF5, 0x45, 0xC9, 0xA9, 0x8F, 0xEB, 0x60, 0x54, 0xC2, 0xEA, 0xCB, 0xA1, 0xB7,
0x49, 0xE8, 0x12, 0x10, 0xF1, 0x2C, 0xE6, 0x05, 0xF7, 0xBC, 0x2A, 0x00, 0xC6, 0x75, 0x0A, 0x08,
0x14, 0x86, 0x14, 0x5E, 0x42, 0x7C, 0x5B, 0xA2, 0x83, 0x5E, 0x80, 0xCD, 0x0B, 0x42, 0x4E, 0x33,
0xDE, 0xBB, 0x94, 0x3F, 0x04, 0x5D, 0xAA, 0x82, 0x35, 0x59, 0x51, 0xCB, 0x98, 0x02, 0x13, 0x30,
0x63, 0x10, 0x8A, 0x93, 0x36, 0x2B, 0xA4, 0x5E, 0x9B, 0x9C, 0xB6, 0x0A, 0x88, 0x17, 0x31, 0x33,
0x28, 0xFB, 0x44, 0x01, 0xD4, 0x13, 0x57, 0x5E, 0x2C, 0xEB, 0x00, 0x15, 0x8A, 0x76, 0x31, 0x2A,
0xDA, 0xF6, 0xF1, 0x8A, 0x80, 0xE6, 0xA9, 0xAF, 0xDA, 0x61, 0xC3, 0x75, 0x76, 0x18, 0x55, 0xBF,
0x49, 0x2B, 0x51, 0xFF, 0x04, 0xF2, 0x6C, 0xD8, 0x78, 0xF1, 0xDE, 0xCB, 0x5E, 0x0E, 0x0D, 0xFB,
0xFB, 0x76, 0xA2, 0x5C, 0x02, 0x62, 0x6F, 0x04, 0x4F, 0x64, 0xE0, 0x44, 0xD9, 0x6C, 0xA2, 0x12,
0x1F, 0x46, 0x8D, 0xAC, 0xFF, 0x4D, 0x00, 0x8C, 0xD7, 0x6A, 0x87, 0xF3, 0x28, 0x56, 0xD5, 0x99,
0xD6, 0xAE, 0x58, 0xE9, 0x38, 0x6B, 0xDF, 0xF1, 0x4C, 0xD4, 0xD0, 0x44, 0x6D, 0x51, 0x74, 0xE8,
0xC4, 0x4F, 0xDF, 0x3F, 0xF3, 0x26, 0x70, 0xB6, 0x1D, 0xC0, 0x4C, 0x9E, 0x81, 0xC7, 0xC7, 0x78,
0x30, 0xF7, 0xFA, 0x4D, 0xF5, 0x20, 0xDC, 0x3E, 0xB9, 0xCB, 0xBC, 0x5C, 0x8C, 0x96, 0x04, 0xAC,
0x32, 0x8F, 0x64, 0xC2, 0x3C, 0x61, 0x16, 0xC3, 0x3A, 0x43, 0x6E, 0xA1, 0xB2, 0xA3, 0x71, 0xA7,
0xB8, 0x76, 0x21, 0xC2, 0x59, 0x53, 0xE8, 0xC4, 0x23, 0xD7, 0xC0, 0xA5, 0x47, 0xF1, 0x3E, 0xED,
0x89, 0x1B, 0x61, 0xC7, 0x5D, 0x10, 0x23, 0xBF, 0xE2, 0x98, 0x05, 0xB8, 0x47, 0x1E, 0x24, 0x25,
0xEB, 0x88, 0x74, 0xD0, 0x6A, 0xC7, 0xEA, 0x78, 0x51, 0x85, 0xB6, 0x61, 0x03, 0xB2, 0xF8, 0x2B,
0x60, 0x6B, 0x56, 0x27, 0x7F, 0x03, 0xC8, 0xFA, 0xB0, 0xC8, 0xE0, 0x7F, 0x9E, 0xFD, 0xC8, 0x18,
0xB9, 0x03, 0x13, 0xC2, 0x78, 0x5D, 0x79, 0xA9, 0x66, 0x1D, 0x15, 0x11, 0x3D, 0x22, 0xD3, 0xBF,
0xB6, 0xF8, 0x6F, 0xF4, 0x64, 0x6C, 0x93, 0xC0, 0xB6, 0xB8, 0xB4, 0x00, 0x54, 0x7E, 0xC2, 0x89,
0x93, 0xF7, 0x13, 0xFE, 0xF0, 0xFB, 0x9D, 0x81, 0x3D, 0x73, 0xED, 0xC8, 0xDE, 0x92, 0x80, 0x9E,
0xCB, 0xDB, 0xD3, 0x39, 0xB3, 0x01, 0x85, 0x5B, 0x73, 0x30, 0x35, 0x82, 0xCB, 0x34, 0xD8, 0x93,
0x90, 0x26, 0x93, 0xC4, 0xE1, 0xB1, 0x72, 0xFF, 0xA8, 0xC1, 0x9B, 0x59, 0x2D, 0xCA, 0x4C, 0xEB,
0x82, 0x98, 0x3C, 0x09, 0xD7, 0x1D, 0x2E, 0x89, 0xFB, 0xB5, 0x73, 0x3A, 0x8D, 0xA3, 0x40, 0x1A,
0xCE, 0xE7, 0xD0, 0x70, 0x52, 0x18, 0x8E, 0x6C, 0x9C, 0x92, 0x45, 0x6E, 0xAA, 0x00, 0xBB, 0xB4,
0xBB, 0x60, 0x3B, 0xF1, 0xD4, 0x1F, 0xCE, 0xA1, 0x0E, 0x90, 0x92, 0x29, 0xFA, 0x83, 0x3B, 0xEC,
0xB3, 0x1D, 0x4B, 0x68, 0x2C, 0x48, 0x2C, 0x10, 0x23, 0xEF, 0x89, 0x46, 0x57, 0xA8, 0x2A, 0x7F,
0xBA, 0x5D, 0x9C, 0xCA, 0xA7, 0x48, 0xE6, 0xA6, 0x0A, 0xDE, 0x00, 0x20, 0x01, 0x09, 0xD9, 0x75,
0xAA, 0x25, 0x3E, 0x45, 0x20, 0xA2, 0x0C, 0x2A, 0x91, 0x18, 0xEF, 0xE6, 0xAA, 0xA2, 0x90, 0x14,
0x9C, 0xAB, 0xC8, 0x8D, 0x49, 0xC6, 0x72, 0x58, 0xF0, 0x0A, 0x59, 0xBA, 0xFE, 0xAE, 0x73, 0xE5,
0x48, 0xE0, 0x71, 0xEF, 0xE8, 0x00, 0x2E, 0xB2, 0xC9, 0xF9, 0x1A, 0x59, 0xC5, 0x41, 0x83, 0x00,
0xDF, 0xE3, 0x26, 0x8F, 0x01, 0x12, 0xDA, 0xE3, 0x8F, 0xE1, 0xAA, 0xC6, 0xBC, 0x0E, 0x48, 0x49,
0x8E, 0x30, 0x40, 0x8C, 0xAD, 0x80, 0x5B, 0xAF, 0x89, 0x60, 0x33, 0xE6, 0x91, 0x61, 0x6B, 0xF2,
0x09, 0x7B, 0xCD, 0x54, 0x1D, 0x2B, 0x30, 0x40, 0x02, 0x06, 0xB0, 0x72, 0xE7, 0x5E, 0xE4, 0x53,
0xD7, 0x25, 0x18, 0x75, 0x40, 0xCB, 0xA9, 0x11, 0xCC, 0x3D, 0xFB, 0x14, 0xEA, 0x40, 0xAF, 0x10,
0x05, 0xFF, 0xE8, 0xE1, 0x87, 0x86, 0x8E, 0x01, 0xB5, 0x09, 0xB6, 0xE9, 0xD7, 0x26, 0xD7, 0x56,
0xA4, 0x22, 0x88, 0x44, 0x74, 0xB7, 0x78, 0xD2, 0x81, 0x50, 0xB4, 0x35, 0xC9, 0x14, 0x47, 0xB4,
0xAC, 0xCE, 0xED, 0xCB, 0xEE, 0xD5, 0x9C, 0x75, 0xDB, 0x18, 0x94, 0x71, 0xC7, 0xB1, 0xA8, 0x42,
0x70, 0x66, 0xDF, 0x14, 0x6D, 0x05, 0x6A, 0x6A, 0x9B, 0xC4, 0xA2, 0x8C, 0xDB, 0xFE, 0x92, 0x23,
0x4F, 0xEA, 0x66, 0x89, 0xFB, 0xD9, 0xC1, 0x8D, 0xE3, 0x70, 0xF9, 0x5F, 0x1E, 0x1A, 0x9E, 0x9A,
0xBC, 0x7E, 0xDD, 0xAB, 0x9D, 0xB9, 0x56, 0xCB, 0xDC, 0xFB, 0x3F, 0x32, 0xCF, 0xC3, 0x17, 0x1B,
0x25, 0x6E, 0xFF, 0xE8, 0xBD, 0x32, 0x57, 0xA3, 0x8B, 0x06, 0x9B, 0x1F, 0xA7, 0xFC, 0xE7, 0x3A,
0x89, 0x54, 0xFA, 0x8E, 0x1B, 0xAD, 0xBF, 0x3C, 0xDD, 0x00, 0x49, 0xA3, 0xC1, 0x8D, 0xC8, 0x6D,
0x8D, 0x43, 0xCC, 0x4E, 0xE5, 0xAE, 0xC9, 0x6C, 0x0C, 0x20, 0x09, 0x77, 0xDA, 0x8C, 0xE7, 0xAC
};

View File

@ -0,0 +1,83 @@
/* Generated by bin2c, do not edit manually */
/* Contents of file ctr-common-1-key.bin */
const long int ctr_common_1_key_bin_size = 1232;
const unsigned char ctr_common_1_key_bin[1232] = {
0x8E, 0xCB, 0xB5, 0xCF, 0x61, 0x5F, 0xBD, 0xE0, 0x41, 0x62, 0x2F, 0x18, 0x27, 0xC1, 0x3A, 0x72,
0x5F, 0x6A, 0xF3, 0x6C, 0xDD, 0x26, 0x28, 0x30, 0xE3, 0x55, 0xAD, 0x2F, 0xF8, 0xE3, 0xA3, 0x0C,
0x3B, 0x67, 0x26, 0xA1, 0xCD, 0x67, 0x28, 0x7C, 0xBB, 0xA5, 0xA5, 0xF7, 0x01, 0x55, 0x00, 0x87,
0x09, 0xB7, 0x51, 0xE6, 0xA5, 0x8D, 0x93, 0xD0, 0xE3, 0x8D, 0x45, 0x2A, 0xDB, 0xAF, 0xFC, 0x51,
0xD8, 0x94, 0xF6, 0xC5, 0xA9, 0x5E, 0xBA, 0x29, 0xD3, 0xB5, 0x5E, 0x98, 0x92, 0x84, 0xEA, 0x31,
0xDC, 0xDF, 0xFE, 0x0B, 0x63, 0x78, 0xE6, 0xA0, 0x07, 0x6F, 0x33, 0xB4, 0x13, 0x6B, 0xD1, 0x48,
0x7E, 0x3D, 0x21, 0xD1, 0x0E, 0xB0, 0x19, 0xC1, 0xB9, 0x16, 0xB5, 0xE0, 0x95, 0xFE, 0x99, 0xCD,
0x82, 0xBA, 0x89, 0x15, 0xCE, 0xF3, 0xB1, 0xB0, 0xA2, 0xE3, 0xC2, 0x7F, 0xE4, 0xF7, 0x65, 0x8F,
0x77, 0xB9, 0x15, 0x65, 0x4D, 0x3C, 0x42, 0xA6, 0x90, 0xDC, 0x0D, 0xFF, 0xA7, 0xE6, 0x59, 0x3A,
0xF5, 0x8D, 0xE9, 0xEC, 0x45, 0xB7, 0xA6, 0x1E, 0xBA, 0x0E, 0x5B, 0x99, 0x19, 0x13, 0xE2, 0x9F,
0x9A, 0x47, 0xDC, 0xDB, 0x9A, 0x71, 0x9E, 0x1C, 0xCF, 0x15, 0xEE, 0xCA, 0x4A, 0x57, 0x38, 0x7E,
0x65, 0xF5, 0xB3, 0x2C, 0x51, 0xB3, 0x14, 0xC3, 0xF8, 0x9D, 0x58, 0x46, 0xF1, 0xE8, 0xA9, 0x83,
0xE7, 0x3F, 0x1A, 0x0F, 0xEF, 0x3E, 0x37, 0xAC, 0x5C, 0xFE, 0x11, 0x2F, 0xA3, 0x78, 0x4B, 0xB7,
0xA4, 0x12, 0x89, 0x87, 0xC5, 0xEB, 0x4E, 0x31, 0x10, 0xB6, 0x8D, 0xE9, 0xE3, 0xD8, 0x88, 0x4D,
0x4E, 0x89, 0xDC, 0x3E, 0x17, 0x63, 0xC9, 0xA4, 0x75, 0x10, 0xD3, 0x89, 0x65, 0x2C, 0x10, 0x8C,
0xEE, 0x95, 0x8E, 0x8A, 0x36, 0x48, 0x69, 0x4E, 0xAF, 0x08, 0xB9, 0x7F, 0x12, 0x7C, 0x1D, 0x7A,
0x41, 0x58, 0xAE, 0xDD, 0x8E, 0x2A, 0xC0, 0x68, 0xED, 0x90, 0x6C, 0x61, 0x78, 0xA7, 0x7F, 0xE2,
0x7A, 0x0D, 0xDF, 0x75, 0x4B, 0x76, 0x72, 0x13, 0x90, 0xDC, 0x5A, 0x3C, 0xDB, 0x02, 0xA5, 0xCB,
0x5A, 0xA8, 0x2D, 0x11, 0x77, 0xD3, 0xC6, 0x9D, 0xE8, 0xC7, 0x5A, 0x9E, 0xB2, 0x3D, 0xF5, 0xCB,
0x98, 0xA2, 0x48, 0xF8, 0xBF, 0x5F, 0x31, 0xB4, 0x83, 0xFB, 0xAE, 0x40, 0x73, 0x02, 0x1A, 0x27,
0x30, 0x42, 0x87, 0xCE, 0x44, 0x1E, 0x8C, 0x05, 0x7B, 0xAE, 0x9C, 0xED, 0xE0, 0x3A, 0x21, 0x86,
0x7F, 0xC1, 0xC2, 0x32, 0xE3, 0xB0, 0x1B, 0x65, 0x70, 0xA0, 0xB0, 0x1F, 0xD7, 0x24, 0x3D, 0xA7,
0x6B, 0x68, 0xA2, 0x6B, 0xA0, 0x23, 0x21, 0x1A, 0x8E, 0xBE, 0xC0, 0xCB, 0xCD, 0xC4, 0x91, 0xFF,
0xF9, 0x05, 0xCE, 0x78, 0xB7, 0xE9, 0x9F, 0xAC, 0x11, 0xC4, 0x66, 0x31, 0x7C, 0xE2, 0x8A, 0x1C,
0x2E, 0xBC, 0xE1, 0x0B, 0xC4, 0x91, 0xE0, 0xED, 0xE7, 0x6F, 0x1A, 0x7E, 0x55, 0x7E, 0xAA, 0xD0,
0xC2, 0xDD, 0x1B, 0xF5, 0xB7, 0x0C, 0x7E, 0x26, 0x8C, 0x31, 0x25, 0xDC, 0x43, 0x21, 0x00, 0x2E,
0x30, 0x49, 0xC8, 0x1C, 0x65, 0xF9, 0x33, 0x35, 0x9F, 0xBA, 0x16, 0xFA, 0xF6, 0x86, 0xA5, 0x02,
0xB5, 0x8C, 0x97, 0x75, 0x78, 0x55, 0x96, 0xC6, 0xAC, 0x8D, 0xE8, 0x46, 0xE5, 0x46, 0xF0, 0x95,
0x53, 0xB6, 0x2C, 0x38, 0x5B, 0xA1, 0x1E, 0x9C, 0x51, 0x05, 0x73, 0xF4, 0xD0, 0x66, 0x71, 0x48,
0xC2, 0xA0, 0x00, 0xCF, 0xBC, 0x8C, 0xAD, 0xE5, 0xED, 0x77, 0x74, 0xE2, 0xB9, 0xC8, 0x9D, 0x76,
0x4B, 0x57, 0xF6, 0xFD, 0x52, 0xCA, 0xAD, 0xB7, 0xF0, 0x04, 0x84, 0xF4, 0x82, 0xE0, 0x5F, 0xBE,
0x91, 0x00, 0xE4, 0xE6, 0x9C, 0xE6, 0x86, 0xEE, 0x64, 0xC1, 0xBC, 0x4A, 0xE6, 0x14, 0x5F, 0xEF,
0xDB, 0x1E, 0xED, 0x03, 0x79, 0xDB, 0x79, 0x1B, 0x8E, 0x8E, 0x50, 0x55, 0xB5, 0x45, 0xAB, 0xBC,
0x09, 0x95, 0x6C, 0xC4, 0x13, 0x1A, 0x8A, 0x19, 0x9A, 0x8B, 0xA1, 0xE6, 0xD4, 0xA8, 0xA8, 0x57,
0x7A, 0xED, 0xCD, 0xFD, 0x8A, 0xF2, 0xFB, 0xE4, 0x2A, 0x96, 0xDF, 0xFF, 0xB0, 0xC9, 0x36, 0xD9,
0x21, 0x6E, 0xB3, 0x6B, 0x46, 0x38, 0xC6, 0xF0, 0xE3, 0x12, 0x4F, 0x77, 0x9E, 0x59, 0x09, 0xCE,
0x6E, 0x6F, 0x62, 0xDF, 0xCE, 0x8A, 0xFD, 0x44, 0x64, 0x3E, 0x62, 0x07, 0x2B, 0x13, 0x22, 0xC9,
0x0D, 0xC4, 0x92, 0x05, 0x3F, 0x80, 0x9D, 0xDC, 0xC4, 0x00, 0x79, 0x09, 0x0B, 0x8A, 0xF6, 0x1C,
0x20, 0x0F, 0x15, 0xB2, 0xE4, 0x32, 0x07, 0x4A, 0x94, 0xA6, 0x48, 0x52, 0x32, 0xF1, 0x68, 0x9E,
0x85, 0xAB, 0xC1, 0xA9, 0x43, 0xF5, 0x43, 0xC1, 0x57, 0x9E, 0x30, 0xC3, 0xBC, 0xA2, 0x44, 0x68,
0xF0, 0x2C, 0xE4, 0xC0, 0x4E, 0x73, 0xFF, 0x94, 0xAB, 0x62, 0x7F, 0x5B, 0x46, 0x0C, 0xB4, 0x32,
0x59, 0x49, 0x4E, 0xA9, 0xA0, 0x6B, 0xA6, 0xB6, 0xE1, 0x33, 0xA3, 0x30, 0x34, 0x9D, 0x69, 0x0A,
0xF5, 0x0B, 0x46, 0xAF, 0x12, 0xB2, 0x76, 0xCD, 0xF8, 0x81, 0x64, 0x4A, 0x40, 0xAE, 0x27, 0x0C,
0xFB, 0x36, 0x4E, 0xB5, 0x3F, 0xA1, 0x9C, 0x27, 0xF7, 0xB0, 0xCC, 0xBE, 0x46, 0x5A, 0x07, 0x01,
0xF8, 0x31, 0x6E, 0x8B, 0x36, 0x60, 0x5A, 0x35, 0x1B, 0xAE, 0x70, 0x18, 0x31, 0xDF, 0x62, 0x61,
0x2C, 0x06, 0x8F, 0x03, 0x46, 0x80, 0x3F, 0xC3, 0xAD, 0xE6, 0xE8, 0x5D, 0x01, 0xCC, 0xBB, 0x02,
0x75, 0x73, 0x26, 0x16, 0xA4, 0xC4, 0xF3, 0x3F, 0x28, 0xC0, 0x69, 0x41, 0xD3, 0x9B, 0x2F, 0xC1,
0x6D, 0xC0, 0xA4, 0x43, 0xB7, 0x83, 0x91, 0x02, 0x61, 0x68, 0x77, 0x2D, 0x46, 0xA3, 0xC2, 0x60,
0x21, 0x9D, 0x12, 0x00, 0x02, 0x5F, 0x49, 0x73, 0x96, 0x6E, 0x32, 0xC9, 0x61, 0xB2, 0x8F, 0xEB,
0x76, 0x5F, 0xBD, 0xD4, 0x49, 0x31, 0xDD, 0xA6, 0xC8, 0x99, 0x03, 0xC8, 0x0C, 0xE9, 0xF9, 0x99,
0x3A, 0xDE, 0xD7, 0xB7, 0x95, 0x99, 0x76, 0x84, 0xE3, 0x9C, 0x5D, 0x31, 0x9C, 0x63, 0x3F, 0x81,
0xCF, 0x91, 0x85, 0xC6, 0x49, 0x6C, 0x52, 0x11, 0x35, 0x7E, 0x5F, 0x86, 0x00, 0x5C, 0xA6, 0xA7,
0xCA, 0x3C, 0x5C, 0x1B, 0xED, 0x10, 0xA4, 0xB8, 0xDC, 0x30, 0x89, 0x3C, 0x19, 0x98, 0x4C, 0xB5,
0x17, 0x85, 0x58, 0x9D, 0x1F, 0x94, 0xC8, 0x07, 0xED, 0x9B, 0xC0, 0x8A, 0xC3, 0x60, 0xC7, 0xA9,
0xA0, 0xF8, 0x67, 0xD4, 0x3B, 0x17, 0xBD, 0x67, 0x86, 0x71, 0x1D, 0xB6, 0xA9, 0x46, 0xE3, 0xEE,
0xAA, 0x38, 0x0D, 0xEC, 0xD8, 0xBB, 0x5C, 0xF2, 0xC8, 0x35, 0x4D, 0x19, 0x26, 0x39, 0xE0, 0x58,
0xA9, 0x6B, 0x14, 0x84, 0x8D, 0x17, 0xFF, 0xA0, 0x47, 0x6D, 0xDD, 0xFA, 0x97, 0x74, 0xA8, 0x4F,
0x9C, 0xF1, 0x03, 0x11, 0xCE, 0x69, 0x8F, 0xD6, 0x90, 0x3B, 0x75, 0x61, 0xD0, 0xA1, 0x74, 0x55,
0x47, 0xC9, 0x32, 0xA7, 0x68, 0xB7, 0x9C, 0x22, 0xA5, 0x31, 0x04, 0x52, 0xA2, 0x74, 0x26, 0xCF,
0x1E, 0x0C, 0xEE, 0x93, 0xFB, 0x38, 0xFA, 0x08, 0x5B, 0x95, 0xC2, 0x25, 0x3A, 0x7C, 0x8F, 0x14,
0xD4, 0x7B, 0x24, 0xF5, 0xCF, 0x91, 0x2A, 0xBD, 0x91, 0x9B, 0x19, 0x79, 0xBC, 0x91, 0x44, 0x2D,
0x33, 0x9D, 0x3F, 0xDD, 0x78, 0x7D, 0x77, 0x6F, 0xF1, 0xD8, 0xAF, 0x1F, 0xC3, 0xC8, 0xA7, 0xBD,
0xB5, 0xF1, 0x2C, 0xCD, 0x66, 0x3B, 0xE1, 0xB0, 0xC8, 0x9A, 0x87, 0x6A, 0x00, 0xFC, 0x5B, 0x19,
0xB5, 0x43, 0x99, 0x0B, 0xCF, 0xB8, 0x15, 0x3E, 0x1A, 0x8A, 0x9C, 0x4A, 0xBF, 0x75, 0x9C, 0x04,
0x71, 0x53, 0xBC, 0x57, 0xBE, 0x99, 0x82, 0xAA, 0xFF, 0x3B, 0x9B, 0x53, 0x62, 0xC3, 0x81, 0x1C,
0x4B, 0x6A, 0xBF, 0x21, 0x19, 0xF6, 0x5E, 0x9C, 0x77, 0x70, 0x41, 0xCE, 0x02, 0x84, 0x4C, 0x5D,
0xBA, 0x57, 0x32, 0xE3, 0x3C, 0x48, 0x98, 0xD6, 0x42, 0x1A, 0x5F, 0x8C, 0x67, 0xD8, 0x5A, 0x29,
0xDA, 0x4F, 0x24, 0xC6, 0xA8, 0xFE, 0xC6, 0x68, 0x86, 0xAE, 0xD5, 0xE0, 0x25, 0x9C, 0x74, 0xCC,
0xB9, 0x1E, 0x20, 0xE3, 0x70, 0xD7, 0x82, 0x4B, 0x6D, 0x50, 0xB0, 0x91, 0x9D, 0x2B, 0x0A, 0xD5,
0xEB, 0x42, 0x60, 0x96, 0xAC, 0x5F, 0x1F, 0xD4, 0x2C, 0xF8, 0x20, 0x2B, 0xAA, 0x48, 0xBE, 0x73,
0x6C, 0x38, 0x3A, 0xAA, 0x60, 0xAF, 0x6D, 0x13, 0xAB, 0xF7, 0x7F, 0x36, 0x18, 0x26, 0xEF, 0x1F,
0x87, 0xC1, 0x18, 0x6F, 0xEA, 0xF2, 0x1C, 0x84, 0xF3, 0xE0, 0xA2, 0xA9, 0x2A, 0x36, 0xCC, 0xDE,
0x38, 0x24, 0xCB, 0x23, 0xB3, 0xCC, 0x6F, 0xAA, 0xF3, 0x44, 0x47, 0x01, 0x0B, 0x00, 0xC0, 0x4F,
0x65, 0xD8, 0xF2, 0xB1, 0x79, 0x12, 0x10, 0x09, 0x5D, 0xFD, 0x63, 0x3A, 0xD7, 0xBE, 0xF3, 0xB2,
0x9C, 0x0B, 0xCA, 0x0C, 0xE7, 0x4E, 0xE0, 0xBC, 0xFE, 0xB1, 0x0D, 0xB5, 0x87, 0x46, 0xD8, 0x3B,
0x0B, 0xF1, 0x35, 0xD5, 0x0E, 0x92, 0xB2, 0xD3, 0x04, 0x72, 0x28, 0xA4, 0xC2, 0xB0, 0xA8, 0xD6,
0xB9, 0xC4, 0x85, 0x97, 0x96, 0xA8, 0xED, 0xA2, 0xE6, 0x5D, 0x22, 0x5C, 0x30, 0x67, 0xA8, 0x8C
};

View File

@ -28,6 +28,9 @@ SERIALIZE_EXPORT_IMPL(Service::HTTP::SessionData)
namespace Service::HTTP { namespace Service::HTTP {
#include "ctr-common-1-cert.h"
#include "ctr-common-1-key.h"
namespace ErrCodes { namespace ErrCodes {
enum { enum {
InvalidRequestState = 22, InvalidRequestState = 22,
@ -576,10 +579,15 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) {
// For now make every request async in it's own thread. // For now make every request async in it's own thread.
// This always returns success, but the request is only performed when it hasn't started // This always returns success, but the request is only performed when it hasn't started
if (http_context.state == RequestState::NotStarted) { if (http_context.state == RequestState::NotStarted) {
http_context.request_future = if (http_context.method == RequestMethod::Post && !http_context.post_data_added) {
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context)); http_context.post_pending_request = true;
http_context.current_copied_data = 0; } else {
http_context.current_copied_data = 0;
http_context.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context));
}
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -615,9 +623,13 @@ void HTTP_C::BeginRequestAsync(Kernel::HLERequestContext& ctx) {
// This always returns success, but the request is only performed when it hasn't started // This always returns success, but the request is only performed when it hasn't started
if (http_context.state == RequestState::NotStarted) { if (http_context.state == RequestState::NotStarted) {
http_context.request_future = if (http_context.method == RequestMethod::Post && !http_context.post_data_added) {
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context)); http_context.post_pending_request = true;
http_context.current_copied_data = 0; } else {
http_context.current_copied_data = 0;
http_context.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context));
}
} }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@ -946,6 +958,7 @@ void HTTP_C::AddPostDataAscii(Kernel::HLERequestContext& ctx) {
Context::Param param_value(name, value); Context::Param param_value(name, value);
http_context.post_data.emplace(name, param_value); http_context.post_data.emplace(name, param_value);
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1004,6 +1017,7 @@ void HTTP_C::AddPostDataBinary(Kernel::HLERequestContext& ctx) {
Context::Param param_value(name, value); Context::Param param_value(name, value);
http_context.post_data.emplace(name, param_value); http_context.post_data.emplace(name, param_value);
http_context.force_multipart = true; http_context.force_multipart = true;
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1053,6 +1067,7 @@ void HTTP_C::AddPostDataRaw(Kernel::HLERequestContext& ctx) {
http_context.post_data_raw.resize(buffer.GetSize()); http_context.post_data_raw.resize(buffer.GetSize());
buffer.Read(http_context.post_data_raw.data(), 0, buffer.GetSize()); buffer.Read(http_context.post_data_raw.data(), 0, buffer.GetSize());
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1144,8 +1159,8 @@ void HTTP_C::SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout)
Context& http_context = GetContext(context_handle); Context& http_context = GetContext(context_handle);
if (http_context.state == RequestState::NotStarted) { if (http_context.state != RequestState::NotStarted) {
LOG_ERROR(Service_HTTP, "Tried to send Post data on a context that has not been started"); LOG_ERROR(Service_HTTP, "Tried to send Post data on a context that has been started");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ErrorInvalidRequestState); rb.Push(ErrorInvalidRequestState);
rb.PushMappedBuffer(value_buffer); rb.PushMappedBuffer(value_buffer);
@ -1163,6 +1178,7 @@ void HTTP_C::SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout)
Context::Param param_value(name, value); Context::Param param_value(name, value);
http_context.post_data.emplace(name, param_value); http_context.post_data.emplace(name, param_value);
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1227,6 +1243,7 @@ void HTTP_C::SendPostDataBinaryImpl(Kernel::HLERequestContext& ctx, bool timeout
Context::Param param_value(name, value); Context::Param param_value(name, value);
http_context.post_data.emplace(name, param_value); http_context.post_data.emplace(name, param_value);
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1295,6 +1312,7 @@ void HTTP_C::SendPostDataRawImpl(Kernel::HLERequestContext& ctx, bool timeout) {
Context::Param raw_param(value); Context::Param raw_param(value);
std::string value_string(value.begin(), value.end()); std::string value_string(value.begin(), value.end());
http_context.post_data.emplace(value_string, raw_param); http_context.post_data.emplace(value_string, raw_param);
http_context.post_data_added = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1360,20 +1378,129 @@ void HTTP_C::NotifyFinishSendPostData(Kernel::HLERequestContext& ctx) {
Context& http_context = GetContext(context_handle); Context& http_context = GetContext(context_handle);
if (http_context.state == RequestState::NotStarted) { if (http_context.state != RequestState::NotStarted) {
LOG_ERROR(Service_HTTP, LOG_ERROR(Service_HTTP, "Tried to notfy finish Post on a context that has been started");
"Tried to notfy finish Post on a context that has not been started"); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ErrorInvalidRequestState);
return;
}
if (!http_context.post_pending_request) {
LOG_ERROR(Service_HTTP, "Tried to notfy finish Post on a context that has not begun");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ErrorInvalidRequestState);
return;
}
if (!http_context.post_data_added) {
LOG_ERROR(Service_HTTP, "Tried to notfy finish Post on a context that has no post data");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ErrorInvalidRequestState); rb.Push(ErrorInvalidRequestState);
return; return;
} }
http_context.finish_post_data.Set(); http_context.finish_post_data.Set();
http_context.post_pending_request = false;
http_context.current_copied_data = 0;
http_context.request_future =
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void HTTP_C::GetResponseData(Kernel::HLERequestContext& ctx) {
GetResponseDataImpl(ctx, false);
}
void HTTP_C::GetResponseDataTimeout(Kernel::HLERequestContext& ctx) {
GetResponseDataImpl(ctx, true);
}
void HTTP_C::GetResponseDataImpl(Kernel::HLERequestContext& ctx, bool timeout) {
IPC::RequestParser rp(ctx);
struct AsyncData {
// Input
u32 context_handle;
bool timeout;
u64 timeout_nanos;
u32 data_max_len;
Kernel::MappedBuffer* data_buffer;
// Output
Result async_res = ResultSuccess;
};
std::shared_ptr<AsyncData> async_data = std::make_shared<AsyncData>();
async_data->timeout = timeout;
async_data->context_handle = rp.Pop<u32>();
async_data->data_max_len = rp.Pop<u32>();
if (timeout) {
async_data->timeout_nanos = rp.Pop<u64>();
}
async_data->data_buffer = &rp.PopMappedBuffer();
if (!PerformStateChecks(ctx, rp, async_data->context_handle)) {
return;
}
ctx.RunAsync(
[this, async_data](Kernel::HLERequestContext& ctx) {
Context& http_context = GetContext(async_data->context_handle);
if (async_data->timeout) {
const auto wait_res = http_context.request_future.wait_for(
std::chrono::nanoseconds(async_data->timeout_nanos));
if (wait_res == std::future_status::timeout) {
async_data->async_res = ErrorTimeout;
}
} else {
http_context.request_future.wait();
}
return 0;
},
[this, async_data](Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb(ctx, 2, 0);
if (async_data->async_res != ResultSuccess) {
rb.Push(async_data->async_res);
return;
}
Context& http_context = GetContext(async_data->context_handle);
auto& headers = http_context.response.headers;
std::vector<u8> out;
if (async_data->timeout) {
LOG_DEBUG(Service_HTTP, "timeout={}", async_data->timeout_nanos);
} else {
LOG_DEBUG(Service_HTTP, "");
}
// httplib does not keep the raw HTTP header data, so we need to reconstruct it.
// Sadly, the order of headers is lost, but for now it's good enough.
std::string hdr =
fmt::format("{} {} {}\r\n", http_context.response.version,
http_context.response.status, http_context.response.reason);
out.insert(out.end(), hdr.begin(), hdr.end());
for (auto& h : headers) {
hdr = fmt::format("{}: {}\r\n", h.first, h.second);
out.insert(out.end(), hdr.begin(), hdr.end());
}
hdr = "\r\n";
out.insert(out.end(), hdr.begin(), hdr.end());
size_t write_size = std::min(out.size(), async_data->data_buffer->GetSize());
async_data->data_buffer->Write(out.data(), 0, write_size);
rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(write_size));
});
}
void HTTP_C::GetResponseHeader(Kernel::HLERequestContext& ctx) { void HTTP_C::GetResponseHeader(Kernel::HLERequestContext& ctx) {
GetResponseHeaderImpl(ctx, false); GetResponseHeaderImpl(ctx, false);
} }
@ -1746,9 +1873,6 @@ void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) {
if (!ClCertA.init) { if (!ClCertA.init) {
LOG_ERROR(Service_HTTP, "called but ClCertA is missing"); LOG_ERROR(Service_HTTP, "called but ClCertA is missing");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(static_cast<Result>(-1));
return;
} }
const auto& it = std::find_if(client_certs.begin(), client_certs.end(), const auto& it = std::find_if(client_certs.begin(), client_certs.end(),
@ -1961,7 +2085,15 @@ bool HTTP_C::PerformStateChecks(Kernel::HLERequestContext& ctx, IPC::RequestPars
} }
void HTTP_C::DecryptClCertA() { void HTTP_C::DecryptClCertA() {
if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::SSLKey)) {
LOG_ERROR(Service_HTTP, "NormalKey in KeySlot 0x0D missing");
return;
}
HW::AES::AESKey key = HW::AES::GetNormalKey(HW::AES::KeySlotID::SSLKey);
static constexpr u32 iv_length = 16; static constexpr u32 iv_length = 16;
std::vector<u8> cert_file_data;
std::vector<u8> key_file_data;
FileSys::NCCHArchive archive(0x0004001b00010002, Service::FS::MediaType::NAND); FileSys::NCCHArchive archive(0x0004001b00010002, Service::FS::MediaType::NAND);
@ -1972,63 +2104,68 @@ void HTTP_C::DecryptClCertA() {
open_mode.read_flag.Assign(1); open_mode.read_flag.Assign(1);
auto file_result = archive.OpenFile(file_path, open_mode, 0); auto file_result = archive.OpenFile(file_path, open_mode, 0);
if (file_result.Failed()) { if (file_result.Failed()) {
LOG_ERROR(Service_HTTP, "ClCertA file missing"); LOG_ERROR(Service_HTTP, "ClCertA file missing, using default");
return;
cert_file_data.resize(ctr_common_1_cert_bin_size);
memcpy(cert_file_data.data(), ctr_common_1_cert_bin, cert_file_data.size());
key_file_data.resize(ctr_common_1_key_bin_size);
memcpy(key_file_data.data(), ctr_common_1_key_bin, key_file_data.size());
} else {
auto romfs = std::move(file_result).Unwrap();
std::vector<u8> romfs_buffer(romfs->GetSize());
romfs->Read(0, romfs_buffer.size(), romfs_buffer.data());
romfs->Close();
const RomFS::RomFSFile cert_file =
RomFS::GetFile(romfs_buffer.data(), {u"ctr-common-1-cert.bin"});
if (cert_file.Length() == 0) {
LOG_ERROR(Service_HTTP, "ctr-common-1-cert.bin missing");
return;
}
if (cert_file.Length() <= iv_length) {
LOG_ERROR(Service_HTTP, "ctr-common-1-cert.bin size is too small. Size: {}",
cert_file.Length());
return;
}
cert_file_data.resize(cert_file.Length());
memcpy(cert_file_data.data(), cert_file.Data(), cert_file.Length());
const RomFS::RomFSFile key_file =
RomFS::GetFile(romfs_buffer.data(), {u"ctr-common-1-key.bin"});
if (key_file.Length() == 0) {
LOG_ERROR(Service_HTTP, "ctr-common-1-key.bin missing");
return;
}
if (key_file.Length() <= iv_length) {
LOG_ERROR(Service_HTTP, "ctr-common-1-key.bin size is too small. Size: {}",
key_file.Length());
return;
}
key_file_data.resize(key_file.Length());
memcpy(key_file_data.data(), key_file.Data(), key_file.Length());
} }
auto romfs = std::move(file_result).Unwrap(); std::vector<u8> cert_data(cert_file_data.size() - iv_length);
std::vector<u8> romfs_buffer(romfs->GetSize());
romfs->Read(0, romfs_buffer.size(), romfs_buffer.data());
romfs->Close();
if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::SSLKey)) {
LOG_ERROR(Service_HTTP, "NormalKey in KeySlot 0x0D missing");
return;
}
HW::AES::AESKey key = HW::AES::GetNormalKey(HW::AES::KeySlotID::SSLKey);
const RomFS::RomFSFile cert_file =
RomFS::GetFile(romfs_buffer.data(), {u"ctr-common-1-cert.bin"});
if (cert_file.Length() == 0) {
LOG_ERROR(Service_HTTP, "ctr-common-1-cert.bin missing");
return;
}
if (cert_file.Length() <= iv_length) {
LOG_ERROR(Service_HTTP, "ctr-common-1-cert.bin size is too small. Size: {}",
cert_file.Length());
return;
}
std::vector<u8> cert_data(cert_file.Length() - iv_length);
using CryptoPP::AES; using CryptoPP::AES;
CryptoPP::CBC_Mode<AES>::Decryption aes_cert; CryptoPP::CBC_Mode<AES>::Decryption aes_cert;
std::array<u8, iv_length> cert_iv; std::array<u8, iv_length> cert_iv;
std::memcpy(cert_iv.data(), cert_file.Data(), iv_length); std::memcpy(cert_iv.data(), cert_file_data.data(), iv_length);
aes_cert.SetKeyWithIV(key.data(), AES::BLOCKSIZE, cert_iv.data()); aes_cert.SetKeyWithIV(key.data(), AES::BLOCKSIZE, cert_iv.data());
aes_cert.ProcessData(cert_data.data(), cert_file.Data() + iv_length, aes_cert.ProcessData(cert_data.data(), cert_file_data.data() + iv_length,
cert_file.Length() - iv_length); cert_file_data.size() - iv_length);
const RomFS::RomFSFile key_file = std::vector<u8> key_data(key_file_data.size() - iv_length);
RomFS::GetFile(romfs_buffer.data(), {u"ctr-common-1-key.bin"});
if (key_file.Length() == 0) {
LOG_ERROR(Service_HTTP, "ctr-common-1-key.bin missing");
return;
}
if (key_file.Length() <= iv_length) {
LOG_ERROR(Service_HTTP, "ctr-common-1-key.bin size is too small. Size: {}",
key_file.Length());
return;
}
std::vector<u8> key_data(key_file.Length() - iv_length);
CryptoPP::CBC_Mode<AES>::Decryption aes_key; CryptoPP::CBC_Mode<AES>::Decryption aes_key;
std::array<u8, iv_length> key_iv; std::array<u8, iv_length> key_iv;
std::memcpy(key_iv.data(), key_file.Data(), iv_length); std::memcpy(key_iv.data(), key_file_data.data(), iv_length);
aes_key.SetKeyWithIV(key.data(), AES::BLOCKSIZE, key_iv.data()); aes_key.SetKeyWithIV(key.data(), AES::BLOCKSIZE, key_iv.data());
aes_key.ProcessData(key_data.data(), key_file.Data() + iv_length, aes_key.ProcessData(key_data.data(), key_file_data.data() + iv_length,
key_file.Length() - iv_length); key_file_data.size() - iv_length);
ClCertA.certificate = std::move(cert_data); ClCertA.certificate = std::move(cert_data);
ClCertA.private_key = std::move(key_data); ClCertA.private_key = std::move(key_data);
@ -2069,8 +2206,8 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
{0x001D, &HTTP_C::NotifyFinishSendPostData, "NotifyFinishSendPostData"}, {0x001D, &HTTP_C::NotifyFinishSendPostData, "NotifyFinishSendPostData"},
{0x001E, &HTTP_C::GetResponseHeader, "GetResponseHeader"}, {0x001E, &HTTP_C::GetResponseHeader, "GetResponseHeader"},
{0x001F, &HTTP_C::GetResponseHeaderTimeout, "GetResponseHeaderTimeout"}, {0x001F, &HTTP_C::GetResponseHeaderTimeout, "GetResponseHeaderTimeout"},
{0x0020, nullptr, "GetResponseData"}, {0x0020, &HTTP_C::GetResponseData, "GetResponseData"},
{0x0021, nullptr, "GetResponseDataTimeout"}, {0x0021, &HTTP_C::GetResponseDataTimeout, "GetResponseDataTimeout"},
{0x0022, &HTTP_C::GetResponseStatusCode, "GetResponseStatusCode"}, {0x0022, &HTTP_C::GetResponseStatusCode, "GetResponseStatusCode"},
{0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"}, {0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"},
{0x0024, &HTTP_C::AddTrustedRootCA, "AddTrustedRootCA"}, {0x0024, &HTTP_C::AddTrustedRootCA, "AddTrustedRootCA"},

View File

@ -274,6 +274,8 @@ public:
u32 socket_buffer_size; u32 socket_buffer_size;
std::vector<RequestHeader> headers; std::vector<RequestHeader> headers;
const ClCertAData* clcert_data; const ClCertAData* clcert_data;
bool post_data_added = false;
bool post_pending_request = false;
Params post_data; Params post_data;
std::string post_data_raw; std::string post_data_raw;
PostDataEncoding post_data_encoding = PostDataEncoding::Auto; PostDataEncoding post_data_encoding = PostDataEncoding::Auto;
@ -699,6 +701,12 @@ private:
*/ */
void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx); void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx);
void GetResponseData(Kernel::HLERequestContext& ctx);
void GetResponseDataTimeout(Kernel::HLERequestContext& ctx);
void GetResponseDataImpl(Kernel::HLERequestContext& ctx, bool timeout);
/** /**
* GetResponseHeaderImpl: * GetResponseHeaderImpl:
* Implements GetResponseHeader and GetResponseHeaderTimeout service functions * Implements GetResponseHeader and GetResponseHeaderTimeout service functions

View File

@ -7,14 +7,20 @@
#include <sstream> #include <sstream>
#include <boost/iostreams/device/file_descriptor.hpp> #include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp> #include <boost/iostreams/stream.hpp>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include "common/common_paths.h" #include "common/common_paths.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/file_sys/certificate.h"
#include "core/file_sys/otp.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hw/aes/arithmetic128.h" #include "core/hw/aes/arithmetic128.h"
#include "core/hw/aes/key.h" #include "core/hw/aes/key.h"
#include "core/hw/default_keys.h"
#include "core/hw/rsa/rsa.h" #include "core/hw/rsa/rsa.h"
#include "core/loader/loader.h"
namespace HW::AES { namespace HW::AES {
@ -26,8 +32,7 @@ namespace {
// On a real 3DS the generation for the normal key is hardware based, and thus the constant can't // On a real 3DS the generation for the normal key is hardware based, and thus the constant can't
// get dumped. Generated normal keys are also not accessible on a 3DS. The used formula for // get dumped. Generated normal keys are also not accessible on a 3DS. The used formula for
// calculating the constant is a software implementation of what the hardware generator does. // calculating the constant is a software implementation of what the hardware generator does.
constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, AESKey generator_constant;
0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}};
AESKey HexToKey(const std::string& hex) { AESKey HexToKey(const std::string& hex) {
if (hex.size() < 32) { if (hex.size() < 32) {
@ -125,6 +130,12 @@ std::array<std::optional<AESKey>, NumDlpNfcKeyYs> dlp_nfc_key_y_slots;
std::array<NfcSecret, NumNfcSecrets> nfc_secrets; std::array<NfcSecret, NumNfcSecrets> nfc_secrets;
AESIV nfc_iv; AESIV nfc_iv;
AESKey otp_key;
AESIV otp_iv;
KeySlot movable_key;
KeySlot movable_cmac;
struct KeyDesc { struct KeyDesc {
char key_type; char key_type;
std::size_t slot_id; std::size_t slot_id;
@ -132,96 +143,29 @@ struct KeyDesc {
bool same_as_before; bool same_as_before;
}; };
void LoadBootromKeys() {
constexpr std::array<KeyDesc, 80> keys = {
{{'X', 0x2C, false}, {'X', 0x2D, true}, {'X', 0x2E, true}, {'X', 0x2F, true},
{'X', 0x30, false}, {'X', 0x31, true}, {'X', 0x32, true}, {'X', 0x33, true},
{'X', 0x34, false}, {'X', 0x35, true}, {'X', 0x36, true}, {'X', 0x37, true},
{'X', 0x38, false}, {'X', 0x39, true}, {'X', 0x3A, true}, {'X', 0x3B, true},
{'X', 0x3C, false}, {'X', 0x3D, false}, {'X', 0x3E, false}, {'X', 0x3F, false},
{'Y', 0x4, false}, {'Y', 0x5, false}, {'Y', 0x6, false}, {'Y', 0x7, false},
{'Y', 0x8, false}, {'Y', 0x9, false}, {'Y', 0xA, false}, {'Y', 0xB, false},
{'N', 0xC, false}, {'N', 0xD, true}, {'N', 0xE, true}, {'N', 0xF, true},
{'N', 0x10, false}, {'N', 0x11, true}, {'N', 0x12, true}, {'N', 0x13, true},
{'N', 0x14, false}, {'N', 0x15, false}, {'N', 0x16, false}, {'N', 0x17, false},
{'N', 0x18, false}, {'N', 0x19, true}, {'N', 0x1A, true}, {'N', 0x1B, true},
{'N', 0x1C, false}, {'N', 0x1D, true}, {'N', 0x1E, true}, {'N', 0x1F, true},
{'N', 0x20, false}, {'N', 0x21, true}, {'N', 0x22, true}, {'N', 0x23, true},
{'N', 0x24, false}, {'N', 0x25, true}, {'N', 0x26, true}, {'N', 0x27, true},
{'N', 0x28, true}, {'N', 0x29, false}, {'N', 0x2A, false}, {'N', 0x2B, false},
{'N', 0x2C, false}, {'N', 0x2D, true}, {'N', 0x2E, true}, {'N', 0x2F, true},
{'N', 0x30, false}, {'N', 0x31, true}, {'N', 0x32, true}, {'N', 0x33, true},
{'N', 0x34, false}, {'N', 0x35, true}, {'N', 0x36, true}, {'N', 0x37, true},
{'N', 0x38, false}, {'N', 0x39, true}, {'N', 0x3A, true}, {'N', 0x3B, true},
{'N', 0x3C, true}, {'N', 0x3D, false}, {'N', 0x3E, false}, {'N', 0x3F, false}}};
// Bootrom sets all these keys when executed, but later some of the normal keys get overwritten
// by other applications e.g. process9. These normal keys thus aren't used by any application
// and have no value for emulation
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
auto file = FileUtil::IOFile(filepath, "rb");
if (!file) {
return;
}
const std::size_t length = file.GetSize();
if (length != 65536) {
LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length);
return;
}
constexpr std::size_t KEY_SECTION_START = 55760;
file.Seek(KEY_SECTION_START, SEEK_SET); // Jump to the key section
AESKey new_key;
for (const auto& key : keys) {
if (!key.same_as_before) {
file.ReadArray(new_key.data(), new_key.size());
if (!file) {
LOG_ERROR(HW_AES, "Reading from Bootrom9 failed");
return;
}
}
LOG_DEBUG(HW_AES, "Loaded Slot{:#02x} Key{} from Bootrom9.", key.slot_id, key.key_type);
switch (key.key_type) {
case 'X':
key_slots.at(key.slot_id).SetKeyX(new_key);
break;
case 'Y':
key_slots.at(key.slot_id).SetKeyY(new_key);
break;
case 'N':
key_slots.at(key.slot_id).SetNormalKey(new_key);
break;
default:
LOG_ERROR(HW_AES, "Invalid key type {}", key.key_type);
break;
}
}
}
void LoadPresetKeys() { void LoadPresetKeys() {
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS; auto s = GetKeysStream();
FileUtil::CreateFullPath(filepath); // Create path if not already created
boost::iostreams::stream<boost::iostreams::file_descriptor_source> file; std::string mode = "";
FileUtil::OpenFStream<std::ios_base::in>(file, filepath);
if (!file.is_open()) {
return;
}
while (!file.eof()) { while (!s.eof()) {
std::string line; std::string line;
std::getline(file, line); std::getline(s, line);
// Ignore empty or commented lines. // Ignore empty or commented lines.
if (line.empty() || line.starts_with("#")) { if (line.empty() || line.starts_with("#")) {
continue; continue;
} }
if (line.starts_with(":")) {
mode = line.substr(1);
continue;
}
if (mode != "AES") {
continue;
}
const auto parts = Common::SplitString(line, '='); const auto parts = Common::SplitString(line, '=');
if (parts.size() != 2) { if (parts.size() != 2) {
LOG_ERROR(HW_AES, "Failed to parse {}", line); LOG_ERROR(HW_AES, "Failed to parse {}", line);
@ -265,6 +209,31 @@ void LoadPresetKeys() {
continue; continue;
} }
if (name == "generatorConstant") {
generator_constant = key;
continue;
}
if (name == "otpKey") {
otp_key = key;
continue;
}
if (name == "otpIV") {
otp_iv = key;
continue;
}
if (name == "movableKeyY") {
movable_key.SetKeyY(key);
continue;
}
if (name == "movableCmacY") {
movable_cmac.SetKeyY(key);
continue;
}
if (name == "dlpKeyY") { if (name == "dlpKeyY") {
dlp_nfc_key_y_slots[DlpNfcKeyY::Dlp] = key; dlp_nfc_key_y_slots[DlpNfcKeyY::Dlp] = key;
continue; continue;
@ -310,15 +279,38 @@ void LoadPresetKeys() {
} // namespace } // namespace
std::istringstream GetKeysStream() {
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + KEYS_FILE;
FileUtil::CreateFullPath(filepath); // Create path if not already created
boost::iostreams::stream<boost::iostreams::file_descriptor_source> file;
FileUtil::OpenFStream<std::ios_base::in>(file, filepath);
std::istringstream ret;
if (file.is_open()) {
return std::istringstream(std::string(std::istreambuf_iterator<char>(file), {}));
} else {
// The key data is encrypted in the source to prevent easy access to it for unintended
// purposes.
std::vector<u8> kiv(16);
std::string s(default_keys_enc_size, ' ');
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption(kiv.data(), kiv.size(), kiv.data())
.ProcessData(reinterpret_cast<u8*>(s.data()), default_keys_enc, s.size());
return std::istringstream(s);
}
}
void InitKeys(bool force) { void InitKeys(bool force) {
static bool initialized = false; static bool initialized = false;
if (initialized && !force) { if (initialized && !force) {
return; return;
} }
initialized = true; initialized = true;
HW::RSA::InitSlots();
LoadBootromKeys();
LoadPresetKeys(); LoadPresetKeys();
movable_key.SetKeyX(key_slots[0x35].x);
movable_cmac.SetKeyX(key_slots[0x35].x);
HW::RSA::InitSlots();
HW::ECC::InitSlots();
} }
void SetKeyX(std::size_t slot_id, const AESKey& key) { void SetKeyX(std::size_t slot_id, const AESKey& key) {
@ -371,4 +363,12 @@ const AESIV& GetNfcIv() {
return nfc_iv; return nfc_iv;
} }
std::pair<AESKey, AESIV> GetOTPKeyIV() {
return {otp_key, otp_iv};
}
const AESKey& GetMovableKey(bool cmac_key) {
return cmac_key ? movable_cmac.normal.value() : movable_key.normal.value();
}
} // namespace HW::AES } // namespace HW::AES

View File

@ -6,9 +6,15 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <istream>
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
namespace FileSys {
class Certificate;
class OTP;
} // namespace FileSys
namespace HW::AES { namespace HW::AES {
enum KeySlotID : std::size_t { enum KeySlotID : std::size_t {
@ -74,6 +80,8 @@ constexpr std::size_t AES_BLOCK_SIZE = 16;
using AESKey = std::array<u8, AES_BLOCK_SIZE>; using AESKey = std::array<u8, AES_BLOCK_SIZE>;
using AESIV = std::array<u8, AES_BLOCK_SIZE>; using AESIV = std::array<u8, AES_BLOCK_SIZE>;
std::istringstream GetKeysStream();
void InitKeys(bool force = false); void InitKeys(bool force = false);
void SetKeyX(std::size_t slot_id, const AESKey& key); void SetKeyX(std::size_t slot_id, const AESKey& key);
@ -90,5 +98,8 @@ void SelectDlpNfcKeyYIndex(u8 index);
bool NfcSecretsAvailable(); bool NfcSecretsAvailable();
const NfcSecret& GetNfcSecret(NfcSecretId secret_id); const NfcSecret& GetNfcSecret(NfcSecretId secret_id);
const AESIV& GetNfcIv(); const AESIV& GetNfcIv();
std::pair<AESKey, AESIV> GetOTPKeyIV();
const AESKey& GetMovableKey(bool cmac_key);
} // namespace HW::AES } // namespace HW::AES

466
src/core/hw/default_keys.h Normal file
View File

@ -0,0 +1,466 @@
/* Generated by bin2c, do not edit manually */
/* Contents of file keys.enc */
const long int default_keys_enc_size = 7360;
const unsigned char default_keys_enc[7360] = {
0x4E, 0x81, 0xE9, 0x54, 0xCC, 0xDE, 0xFD, 0x56, 0x7D, 0xD2, 0x72, 0xE6, 0xD9, 0xCD, 0x8E, 0x11,
0xE1, 0x7F, 0x74, 0xF4, 0xFC, 0x54, 0xA6, 0xA4, 0x27, 0xC2, 0xD7, 0x50, 0xEA, 0xE7, 0xBE, 0xC9,
0xA7, 0x5E, 0xE0, 0x2E, 0x4A, 0xBE, 0xF5, 0xD5, 0x0D, 0x22, 0x76, 0x2E, 0xB6, 0x80, 0xD8, 0x54,
0x88, 0x60, 0x77, 0x80, 0xDB, 0xC3, 0x33, 0x8F, 0xF6, 0x83, 0x79, 0xF9, 0x0D, 0x0E, 0xEC, 0xD2,
0x8D, 0x1C, 0xAB, 0x2C, 0x6B, 0xE7, 0xFE, 0xF1, 0x25, 0x63, 0x37, 0x3A, 0x9D, 0xF1, 0xD9, 0x7D,
0xB4, 0x9F, 0xA9, 0xA0, 0x25, 0x45, 0x92, 0xBA, 0xCE, 0xEA, 0x68, 0x8F, 0x5E, 0x05, 0x27, 0x0A,
0x3F, 0xFC, 0x77, 0x67, 0x7C, 0x3B, 0x4C, 0x42, 0x40, 0xFC, 0xE8, 0xFA, 0xC8, 0x87, 0xDA, 0xA2,
0xE4, 0x67, 0xAC, 0x61, 0x09, 0xB8, 0x4B, 0xF6, 0x87, 0x73, 0xE8, 0x0E, 0xBE, 0x05, 0x68, 0xE8,
0x1A, 0x31, 0x30, 0x19, 0xB6, 0xE9, 0x41, 0x9F, 0xC0, 0x9D, 0x69, 0xEE, 0x23, 0x36, 0xF3, 0x43,
0x2C, 0x22, 0x33, 0x1D, 0x94, 0x25, 0xC1, 0xD0, 0xA1, 0x13, 0x55, 0x7D, 0xDE, 0x2B, 0xB3, 0x88,
0xF6, 0x99, 0x7F, 0x33, 0x37, 0x49, 0xF1, 0xB7, 0x2F, 0x6D, 0x3A, 0x47, 0x27, 0xB0, 0x19, 0xA3,
0x5C, 0xC0, 0xE2, 0xF0, 0x50, 0xC0, 0xC4, 0xC1, 0x08, 0xDA, 0x29, 0xED, 0x42, 0x22, 0x79, 0xD7,
0xBD, 0xDA, 0x12, 0x44, 0x78, 0xF3, 0xC5, 0x6E, 0x9E, 0xFC, 0x40, 0x18, 0xA0, 0x0B, 0x96, 0x94,
0x5B, 0x4F, 0x5E, 0x3A, 0x97, 0xA7, 0x13, 0x5E, 0x47, 0x48, 0xD2, 0x62, 0xFB, 0x6B, 0x56, 0x56,
0x86, 0xF6, 0x6C, 0x6D, 0x8A, 0xB8, 0x25, 0xDA, 0xB6, 0x0E, 0x0C, 0x29, 0x92, 0x84, 0xCF, 0x0C,
0x4A, 0x19, 0x4B, 0xCA, 0x0D, 0x86, 0x82, 0x5A, 0x7D, 0x57, 0x49, 0x95, 0x82, 0x2F, 0x48, 0x1D,
0xC7, 0x51, 0xAD, 0xCB, 0x86, 0x4D, 0x4F, 0x13, 0x6D, 0x7E, 0xE5, 0xF6, 0x2D, 0xD1, 0x36, 0xF9,
0x6F, 0xEC, 0xCB, 0xC0, 0x2F, 0x5E, 0x85, 0x7C, 0xC9, 0x53, 0x13, 0xAC, 0xB8, 0xBE, 0x51, 0xC7,
0xDC, 0xB2, 0x0A, 0x37, 0x8B, 0x41, 0x45, 0x36, 0x5E, 0x4C, 0x4A, 0x8D, 0x47, 0x84, 0xF0, 0xF5,
0x0F, 0x1C, 0x4B, 0x86, 0x6C, 0x32, 0x16, 0x1C, 0x0D, 0x72, 0x70, 0x85, 0x50, 0xC9, 0x69, 0x69,
0xD0, 0x8B, 0x34, 0x82, 0xA9, 0xE6, 0x84, 0x9C, 0xEE, 0xFE, 0x4E, 0xD6, 0x57, 0x53, 0x72, 0x58,
0x72, 0x0F, 0xD9, 0xA0, 0xFD, 0xE3, 0x33, 0xE1, 0x8D, 0xC8, 0xD1, 0x04, 0x3E, 0x48, 0x6B, 0x6B,
0x89, 0x9C, 0x70, 0xCB, 0x1D, 0x8F, 0x79, 0x18, 0x5A, 0xE8, 0x96, 0xEC, 0x19, 0xB6, 0x22, 0x81,
0xC0, 0x5D, 0x0B, 0xF6, 0x4D, 0x47, 0x2D, 0x04, 0x93, 0x11, 0x07, 0x12, 0x90, 0x46, 0x16, 0x00,
0x61, 0x8B, 0xC3, 0xAA, 0xBD, 0x49, 0x92, 0xE2, 0x71, 0x9A, 0x69, 0x0B, 0xA9, 0x81, 0x32, 0x48,
0x77, 0x24, 0xB3, 0x07, 0xEF, 0xAD, 0xCF, 0x7A, 0xB6, 0x9F, 0xF5, 0x9F, 0xBD, 0xA1, 0xF0, 0xEA,
0x1B, 0xD3, 0x6F, 0xBA, 0xDB, 0xC9, 0x67, 0x1A, 0x3B, 0x7C, 0x43, 0xA5, 0xA8, 0x3A, 0x67, 0x91,
0x3E, 0x12, 0x6A, 0x67, 0x4A, 0x40, 0x5C, 0x62, 0x67, 0xB2, 0xB4, 0xB2, 0xCB, 0xA8, 0xB9, 0x0C,
0xF4, 0x3D, 0xA3, 0x94, 0xA9, 0x4E, 0xEB, 0x1C, 0x34, 0x96, 0xA3, 0x62, 0x30, 0xCD, 0xB7, 0x5E,
0xD5, 0x79, 0xE5, 0x96, 0xF8, 0x1F, 0x89, 0x65, 0x36, 0xC3, 0x1C, 0x08, 0xC0, 0x2C, 0x42, 0x0F,
0x2A, 0xF1, 0xBB, 0x35, 0x34, 0x52, 0xFA, 0x29, 0xF9, 0x73, 0x00, 0x8E, 0x99, 0x6F, 0xB3, 0xAE,
0x18, 0xA1, 0x6B, 0xA3, 0x87, 0xF7, 0x38, 0x69, 0x51, 0xFA, 0x23, 0x73, 0x15, 0x23, 0x15, 0xD1,
0xED, 0x04, 0x44, 0x00, 0xB1, 0x50, 0x2A, 0xCF, 0xED, 0x7B, 0x47, 0xA4, 0xFA, 0x7F, 0x1E, 0x1B,
0xE0, 0xCE, 0x3F, 0x3D, 0xE6, 0x8D, 0x8E, 0xEA, 0x1D, 0xDC, 0xB2, 0xF7, 0x15, 0xB3, 0xF7, 0xC7,
0x75, 0x4E, 0xC8, 0x10, 0xBE, 0xCB, 0x1E, 0x02, 0x35, 0xC4, 0xD4, 0x04, 0x2B, 0x8A, 0x16, 0x02,
0x5B, 0x2F, 0x69, 0x75, 0x6E, 0x1B, 0x4B, 0x93, 0x87, 0xFB, 0x7D, 0x9E, 0xC0, 0xFB, 0x5C, 0xAA,
0xC8, 0x8B, 0xEC, 0x8F, 0x85, 0xC1, 0x99, 0xC7, 0xD2, 0xAE, 0x07, 0x51, 0x8A, 0xFF, 0x53, 0x92,
0xEB, 0x0C, 0x01, 0x74, 0x35, 0xEF, 0xCB, 0x78, 0x14, 0x3D, 0x72, 0xBC, 0xF7, 0x6D, 0xC1, 0x3D,
0x43, 0x00, 0x11, 0x89, 0x1B, 0x72, 0xCC, 0x5F, 0x5B, 0x0E, 0x86, 0x2E, 0x76, 0x99, 0x6A, 0x3D,
0x91, 0x95, 0x0A, 0x35, 0x51, 0x2A, 0x88, 0x94, 0xEC, 0xEA, 0x78, 0x20, 0x36, 0x8F, 0x62, 0x45,
0xE4, 0x32, 0x66, 0x7C, 0x9F, 0x25, 0x50, 0xE0, 0x33, 0xBA, 0xEC, 0xC5, 0x97, 0x18, 0xC5, 0x10,
0x5C, 0xCD, 0x4E, 0x39, 0xE7, 0x71, 0xB3, 0xC1, 0x7D, 0xBA, 0xBA, 0x9A, 0xCB, 0x58, 0xD8, 0xFA,
0xC8, 0xAB, 0x3A, 0x0E, 0x37, 0x7C, 0xE5, 0xD1, 0x18, 0x0D, 0x13, 0x61, 0x2A, 0x32, 0x14, 0xD7,
0x5E, 0x5E, 0x77, 0x84, 0x8A, 0xBC, 0xCA, 0xFC, 0x6C, 0x23, 0xF9, 0x30, 0x83, 0x98, 0x15, 0x6C,
0x66, 0x42, 0x10, 0x27, 0x5D, 0xBB, 0x18, 0x21, 0x42, 0x79, 0xB0, 0x6E, 0x86, 0x0A, 0xE6, 0xB1,
0x0D, 0xDE, 0x64, 0x90, 0xB2, 0x82, 0xB3, 0x71, 0x91, 0xC6, 0x42, 0xC3, 0x5D, 0x0F, 0xC6, 0x45,
0x18, 0xD6, 0xD7, 0xE1, 0x6B, 0xC3, 0xB8, 0x8D, 0xD2, 0x59, 0xF9, 0xFF, 0xE8, 0x1C, 0x65, 0xF9,
0x7B, 0xAF, 0x84, 0x8C, 0x5D, 0xF3, 0x84, 0x9E, 0x49, 0x66, 0x2F, 0xCE, 0x1B, 0x77, 0xA2, 0xC3,
0x73, 0xB4, 0xA1, 0x0B, 0x61, 0xFF, 0x7D, 0x4B, 0xB2, 0x45, 0x0E, 0xE8, 0x91, 0x61, 0x26, 0x56,
0xCA, 0x23, 0x71, 0x89, 0x70, 0xF1, 0xAE, 0x3D, 0x34, 0x9F, 0xE9, 0x83, 0x4A, 0xA1, 0xA5, 0xF2,
0x42, 0x8E, 0xA1, 0xA9, 0x4F, 0x50, 0xE5, 0x8E, 0x2D, 0x03, 0xEB, 0x5C, 0x92, 0xD2, 0xA7, 0xA0,
0xAB, 0x77, 0x97, 0x0B, 0x97, 0xDE, 0x28, 0x64, 0x6C, 0x0C, 0xA4, 0xB0, 0x3A, 0xF6, 0x81, 0xF9,
0x59, 0x5E, 0xAA, 0xC2, 0xA0, 0x0D, 0xA3, 0x91, 0x05, 0xDB, 0x38, 0xC3, 0xA7, 0x0C, 0x31, 0x14,
0xE9, 0xF9, 0x4F, 0xD9, 0x55, 0x6B, 0x31, 0x93, 0x23, 0x8F, 0xBC, 0x67, 0x8E, 0xD6, 0xD5, 0x65,
0xA0, 0xD9, 0x8A, 0xF9, 0x23, 0xF5, 0x27, 0xD2, 0x67, 0x97, 0x75, 0x97, 0x10, 0x9F, 0x81, 0xAF,
0xF5, 0x8C, 0xF0, 0xD2, 0x8F, 0xDE, 0x14, 0x59, 0xC0, 0x01, 0xD2, 0xCB, 0x08, 0x37, 0x52, 0xD1,
0x56, 0x1A, 0xF3, 0xE5, 0x1C, 0xFF, 0x9B, 0xAF, 0x5D, 0x51, 0xA6, 0x0E, 0x88, 0x10, 0xE2, 0x9B,
0xE0, 0x5E, 0x0D, 0xE3, 0x01, 0x50, 0x17, 0xBB, 0xA1, 0x49, 0x30, 0xA5, 0x7D, 0x73, 0xFC, 0xF8,
0x0E, 0xA3, 0x8E, 0x1E, 0x6E, 0x67, 0x48, 0x8F, 0xFA, 0xF4, 0x24, 0x33, 0xB7, 0x19, 0x10, 0xBB,
0x06, 0x74, 0x39, 0x4E, 0xAA, 0x61, 0xA6, 0x24, 0x74, 0xA4, 0x90, 0xD8, 0x41, 0x42, 0xEC, 0xA0,
0x83, 0x1E, 0x19, 0x60, 0x55, 0x01, 0x86, 0x59, 0x87, 0x69, 0x8A, 0xBE, 0x25, 0xDB, 0xA4, 0xD0,
0xC2, 0x37, 0x96, 0xE4, 0x67, 0xF6, 0x23, 0x76, 0xD5, 0x55, 0x42, 0x33, 0x0F, 0x93, 0x4C, 0x26,
0x5F, 0x55, 0x3F, 0xF0, 0xC5, 0x6D, 0x03, 0x39, 0xF1, 0xD3, 0x66, 0x3D, 0x53, 0xEF, 0xA8, 0x3D,
0x06, 0x03, 0xE6, 0x47, 0x5E, 0xBA, 0x08, 0x2E, 0xB6, 0x99, 0x25, 0xBD, 0x40, 0x7A, 0x53, 0xC6,
0x13, 0x49, 0xF4, 0xD3, 0xFD, 0xE5, 0x06, 0x0D, 0xA6, 0x75, 0xBB, 0x9F, 0x8D, 0x23, 0x3E, 0x6A,
0x59, 0x61, 0xFA, 0xB3, 0x10, 0xCF, 0x5A, 0x53, 0xA0, 0xF6, 0x5D, 0x93, 0x1E, 0x4A, 0x5C, 0x2B,
0x55, 0xAE, 0xD0, 0x6F, 0x15, 0xC1, 0x87, 0x4D, 0x65, 0x52, 0xCC, 0xA1, 0x30, 0x92, 0x5A, 0x58,
0x6E, 0x3A, 0x81, 0x3D, 0xD4, 0x99, 0x93, 0x9A, 0x22, 0x78, 0x03, 0xD2, 0x08, 0x45, 0x66, 0xD5,
0x55, 0x8F, 0x3B, 0x42, 0x14, 0xB6, 0x15, 0x04, 0x99, 0x5B, 0x71, 0x37, 0x29, 0x6A, 0x3D, 0x5C,
0x78, 0x63, 0x05, 0x0F, 0xC9, 0xA1, 0x90, 0xA1, 0xDF, 0x19, 0x62, 0x53, 0x58, 0x26, 0x70, 0x2E,
0xF6, 0x61, 0x48, 0xE1, 0x8F, 0x30, 0x98, 0x37, 0x9F, 0x82, 0xA0, 0xE2, 0x87, 0x81, 0x09, 0xD8,
0xC0, 0x4D, 0x70, 0x24, 0x9C, 0x69, 0x90, 0x1A, 0x3B, 0x62, 0x47, 0x7B, 0xD2, 0x5D, 0x83, 0xC6,
0xFA, 0xFE, 0xE6, 0x99, 0x1B, 0x3D, 0x18, 0x71, 0xA3, 0x15, 0xE8, 0x3A, 0xAD, 0x98, 0xAA, 0xD6,
0x6C, 0xAD, 0x41, 0x4E, 0x29, 0x50, 0x33, 0xD2, 0xF6, 0xA7, 0x0D, 0x1B, 0x3D, 0xDE, 0x77, 0x44,
0x77, 0xA4, 0x1B, 0x3F, 0x70, 0x92, 0xDE, 0xB2, 0x97, 0x96, 0x3D, 0x74, 0x7A, 0x24, 0x92, 0xD3,
0x83, 0xD3, 0x37, 0x13, 0xB1, 0xB2, 0x25, 0xD4, 0x69, 0x6A, 0x8D, 0x44, 0x61, 0xC0, 0x9F, 0x9E,
0x23, 0x78, 0x8A, 0xE2, 0xAB, 0xB6, 0xBE, 0xC3, 0xCB, 0x08, 0x5C, 0x2A, 0xF9, 0x77, 0x99, 0xA3,
0x85, 0x3A, 0x71, 0x28, 0xA3, 0xFD, 0x9E, 0x07, 0xC2, 0x93, 0x19, 0x0B, 0xEE, 0x84, 0x6B, 0xE5,
0xA0, 0x1E, 0xD1, 0x86, 0x5C, 0x91, 0x13, 0x21, 0x62, 0x18, 0x1E, 0xE8, 0x33, 0x48, 0x4C, 0x96,
0xD8, 0x1F, 0xB1, 0x0D, 0xF0, 0xA6, 0x8D, 0x9A, 0x22, 0x69, 0xE5, 0x2C, 0xBE, 0xFC, 0x7F, 0x6D,
0x45, 0xC7, 0xB3, 0x61, 0xF6, 0x8F, 0x13, 0xDD, 0xF0, 0x73, 0xD6, 0x08, 0xA1, 0x3E, 0x86, 0xC4,
0xD1, 0xC1, 0x2E, 0xF6, 0xD2, 0x3A, 0x0E, 0x64, 0xE9, 0x9A, 0x27, 0xA6, 0x48, 0xCF, 0x9D, 0xFC,
0x85, 0xC2, 0xF4, 0x14, 0x3C, 0x77, 0x6D, 0x1B, 0xE9, 0xD0, 0x8B, 0xA6, 0x80, 0x69, 0x28, 0x98,
0x3A, 0xC1, 0x0D, 0x90, 0xBD, 0xC8, 0x52, 0x1F, 0x3E, 0x61, 0xDA, 0xF2, 0x4A, 0xF3, 0x64, 0x66,
0x72, 0x03, 0xEF, 0x34, 0xAD, 0x29, 0xBF, 0xDD, 0xA7, 0x87, 0x75, 0x92, 0x55, 0x13, 0x6F, 0x94,
0x81, 0xC0, 0x01, 0xA5, 0x34, 0x48, 0x9A, 0xDA, 0x76, 0xC7, 0xE5, 0x87, 0xB7, 0xF4, 0x03, 0x33,
0xB3, 0x99, 0x17, 0x54, 0x00, 0x82, 0x6A, 0x02, 0xEF, 0xD8, 0x9C, 0x75, 0xF2, 0x68, 0x8C, 0x4D,
0xEB, 0x19, 0xB5, 0x80, 0x72, 0x52, 0xBA, 0x0D, 0x50, 0x00, 0x35, 0x4D, 0x50, 0xA9, 0x87, 0xA9,
0xD0, 0x23, 0x3D, 0x92, 0xD6, 0x7F, 0xDB, 0xD9, 0xED, 0x04, 0x1F, 0x0D, 0x15, 0xD9, 0x86, 0x0B,
0x67, 0xF8, 0xF4, 0x1B, 0x60, 0x44, 0x66, 0x3E, 0x6F, 0x5E, 0xF6, 0x8F, 0x7B, 0x98, 0x03, 0xC0,
0x3A, 0xB8, 0x49, 0xC2, 0x94, 0xB0, 0x4A, 0x03, 0xD6, 0xDA, 0xCD, 0x01, 0xEA, 0x7C, 0x9D, 0x0A,
0xED, 0x7E, 0x6A, 0x6C, 0xE9, 0x62, 0x9F, 0xD6, 0x0A, 0x6F, 0xEE, 0x42, 0x6C, 0xC5, 0x3C, 0x70,
0xAF, 0xDF, 0x69, 0x54, 0x57, 0x67, 0x7E, 0xF1, 0x49, 0x2A, 0xDC, 0x27, 0x37, 0x3C, 0xA5, 0x37,
0x99, 0x9A, 0xFF, 0x4F, 0x1E, 0x5E, 0xF6, 0xD4, 0x52, 0xE8, 0x3F, 0x90, 0xC6, 0x06, 0xD5, 0x8E,
0x26, 0xFD, 0x6E, 0xD8, 0xE3, 0x9D, 0x8C, 0x89, 0xE4, 0xE0, 0x7D, 0xEA, 0x3F, 0x02, 0xB4, 0xA1,
0x6E, 0x1E, 0x7C, 0x49, 0xA5, 0x43, 0x67, 0x24, 0x6B, 0xED, 0x96, 0x83, 0xAB, 0x5A, 0x15, 0xA9,
0x35, 0x72, 0x50, 0x68, 0x0B, 0x21, 0xF5, 0xF9, 0xD0, 0xC3, 0x73, 0x7F, 0xE9, 0xD4, 0xEF, 0x51,
0x5B, 0x45, 0xC4, 0x27, 0x2B, 0xAF, 0x47, 0x6F, 0x07, 0x66, 0x72, 0x74, 0xB0, 0xC6, 0x9B, 0x9E,
0x6A, 0x2D, 0xA2, 0x09, 0x93, 0x83, 0xF6, 0x36, 0xDB, 0x46, 0xC2, 0xEC, 0x56, 0x5B, 0x1F, 0x13,
0x23, 0x16, 0x6C, 0xAC, 0xD8, 0x31, 0xB2, 0x1A, 0x87, 0x25, 0x55, 0xFE, 0xC4, 0x08, 0x9D, 0x48,
0x41, 0x72, 0x0B, 0x6A, 0x1C, 0x3E, 0xEF, 0x91, 0x2E, 0xA6, 0x26, 0xD9, 0xF1, 0xF9, 0x0D, 0xD8,
0xFF, 0xE2, 0x2D, 0x6E, 0x96, 0xEF, 0x72, 0x88, 0xE0, 0x64, 0xFF, 0xE6, 0xE0, 0xA2, 0x93, 0xF8,
0x66, 0xA6, 0x24, 0xCD, 0x6F, 0x93, 0x1C, 0xA0, 0x72, 0xEA, 0x02, 0x31, 0x89, 0x20, 0x9D, 0x62,
0x24, 0x3E, 0x01, 0x69, 0x58, 0x06, 0xF5, 0xAA, 0x9D, 0xC3, 0x2F, 0x36, 0xD2, 0xDB, 0x73, 0x7D,
0x8C, 0x19, 0x95, 0xF2, 0x95, 0x6F, 0x93, 0xA7, 0x35, 0x3B, 0xEB, 0x65, 0xFC, 0x95, 0x4F, 0x74,
0x6F, 0xE6, 0xC7, 0x8B, 0xEA, 0x28, 0x9B, 0xF1, 0x74, 0x2A, 0xC6, 0x54, 0xFC, 0x84, 0x80, 0xA3,
0xB2, 0x1B, 0x33, 0x2F, 0xE6, 0x95, 0x42, 0x95, 0xEC, 0xC5, 0x44, 0x19, 0x4C, 0xB8, 0x38, 0x96,
0xE8, 0x13, 0xBB, 0x4E, 0x75, 0xDB, 0xEB, 0xC5, 0xFB, 0x10, 0x3B, 0x51, 0xEB, 0x4A, 0x23, 0xCA,
0x43, 0x13, 0x15, 0xD4, 0x19, 0x87, 0xB1, 0xB8, 0x19, 0xFF, 0x5B, 0x06, 0xA7, 0x0F, 0xD3, 0xFF,
0x5C, 0x8C, 0x89, 0xC3, 0x9C, 0xE2, 0x0C, 0x96, 0x5D, 0xD9, 0x8C, 0x2D, 0xCC, 0x70, 0x87, 0x0C,
0x1C, 0x11, 0x36, 0x77, 0xE5, 0xF2, 0x60, 0x40, 0xA0, 0xDF, 0x20, 0xD5, 0xB2, 0x71, 0x99, 0xA3,
0x58, 0x46, 0xB4, 0x5A, 0xAB, 0xE9, 0x33, 0x7C, 0xA4, 0xD5, 0x17, 0x6C, 0x15, 0xA7, 0x82, 0x23,
0x82, 0x6C, 0x4D, 0x76, 0xFA, 0x94, 0xD0, 0xDE, 0xA3, 0xEB, 0x2F, 0x60, 0xAA, 0x8F, 0x5C, 0x80,
0xD3, 0x9A, 0x97, 0xBB, 0x23, 0x22, 0xC7, 0xA1, 0x78, 0x2A, 0x1D, 0x3C, 0xF8, 0xF9, 0x98, 0xF4,
0x11, 0x8B, 0x07, 0xEF, 0x73, 0x58, 0xC6, 0x01, 0xA0, 0x7E, 0x63, 0x9C, 0x9D, 0x24, 0x4A, 0x0F,
0x4D, 0x8C, 0xD4, 0xA8, 0x34, 0x57, 0x5A, 0x48, 0x19, 0x0B, 0x7C, 0x96, 0x38, 0x7C, 0x24, 0x48,
0x16, 0xEB, 0x4D, 0xED, 0xF5, 0x62, 0x3D, 0xD6, 0xF8, 0xB2, 0xAD, 0xF3, 0x6A, 0xE1, 0xA0, 0x8D,
0x08, 0x91, 0xE3, 0x2A, 0x09, 0x04, 0x7C, 0x94, 0xF1, 0x59, 0x13, 0x66, 0x3D, 0xA8, 0x91, 0xFD,
0xBD, 0x85, 0x72, 0xF6, 0x91, 0xAA, 0x59, 0x0C, 0xC6, 0xE0, 0x84, 0x3A, 0xCB, 0x2B, 0x6C, 0x57,
0x53, 0x4F, 0xFB, 0x1B, 0x0D, 0x5B, 0xD5, 0xA3, 0x91, 0xC9, 0x6F, 0x33, 0xC9, 0xC6, 0x33, 0xF9,
0x8A, 0xF6, 0x52, 0x5C, 0x38, 0x17, 0x95, 0x1F, 0x5C, 0x4C, 0xB4, 0x35, 0x9D, 0xE2, 0xAA, 0x14,
0x77, 0xD3, 0x2E, 0x6C, 0xB3, 0x0A, 0xA3, 0x6E, 0xEA, 0x6E, 0x5D, 0x37, 0xB5, 0x38, 0x9D, 0x68,
0x7E, 0x2C, 0x6B, 0x6B, 0xB7, 0x97, 0x19, 0x1B, 0x66, 0xA3, 0x13, 0x34, 0xC2, 0x0F, 0x9B, 0xC6,
0x58, 0x2F, 0xCC, 0x69, 0x2A, 0xAC, 0x02, 0x7F, 0xE4, 0xF7, 0x62, 0x26, 0x18, 0x9F, 0xE0, 0x0F,
0x5F, 0xB3, 0xD5, 0x30, 0xEE, 0x5C, 0xCA, 0xD3, 0xAB, 0x84, 0x95, 0xF9, 0xE6, 0x42, 0xA7, 0x00,
0x82, 0x3F, 0x6A, 0x98, 0x07, 0xD0, 0x78, 0x19, 0x26, 0xDC, 0x9B, 0xC6, 0x9C, 0xC4, 0x1E, 0xDB,
0x6B, 0x16, 0xEF, 0x3E, 0xE1, 0xBE, 0xB0, 0xF7, 0x03, 0xDD, 0xD5, 0xC0, 0x80, 0x0E, 0xCA, 0xAC,
0xF0, 0x2D, 0xBA, 0x4E, 0xE8, 0x14, 0xCF, 0x57, 0xD4, 0xFE, 0x42, 0x0C, 0xB6, 0x33, 0xC0, 0x6F,
0xFD, 0xA1, 0xC4, 0xDE, 0x50, 0x05, 0xE1, 0xC0, 0x4D, 0xE1, 0x72, 0x03, 0xFF, 0xBB, 0x34, 0x10,
0xDB, 0x4E, 0xD0, 0x01, 0x81, 0x13, 0x63, 0x0B, 0xF3, 0xD6, 0xA0, 0xF2, 0x74, 0xBD, 0xA7, 0x15,
0xB5, 0xAB, 0x38, 0xBE, 0x8A, 0x75, 0x3C, 0xDA, 0xAA, 0x57, 0xF4, 0xF9, 0x7A, 0x29, 0xC8, 0x00,
0x63, 0xC9, 0xB2, 0xBA, 0x98, 0x13, 0x6F, 0x23, 0x00, 0xDF, 0x98, 0xCE, 0x51, 0x4E, 0xB0, 0xC3,
0x81, 0x0A, 0x41, 0xCD, 0x2C, 0x27, 0x31, 0xF9, 0xEC, 0x3D, 0x1C, 0x86, 0x20, 0x31, 0x84, 0x17,
0x59, 0xF1, 0x86, 0x82, 0x5B, 0x68, 0xAC, 0xED, 0x04, 0x81, 0x6D, 0xE8, 0x73, 0x04, 0x29, 0xFE,
0x94, 0x50, 0x18, 0x82, 0xA4, 0x93, 0x98, 0x00, 0x19, 0xC1, 0xDC, 0xD6, 0x55, 0xB2, 0x5A, 0x63,
0xF9, 0x3D, 0xA5, 0x2A, 0xC0, 0x6C, 0x93, 0xBD, 0xE8, 0x4C, 0x8C, 0x74, 0xCD, 0xC2, 0x1C, 0x70,
0xBB, 0xAC, 0xAE, 0xFB, 0x71, 0xDC, 0x43, 0x89, 0x72, 0x77, 0x90, 0x6F, 0x99, 0xC3, 0x42, 0x00,
0x33, 0xAD, 0x63, 0xE9, 0xFE, 0x04, 0xC3, 0xF7, 0xA1, 0x7D, 0xDD, 0x1E, 0x4B, 0x8C, 0x06, 0xC8,
0x2C, 0x4A, 0x9A, 0x4C, 0x1E, 0x15, 0xC3, 0xE3, 0x44, 0x05, 0xA7, 0x5D, 0xBE, 0x64, 0x46, 0x0B,
0x64, 0x40, 0x84, 0xF4, 0xA7, 0x3C, 0xA9, 0x37, 0x19, 0x9A, 0x6B, 0xF5, 0xB2, 0xCC, 0x69, 0xCB,
0xA0, 0x41, 0xD8, 0x7D, 0x36, 0x64, 0xA6, 0x5E, 0x57, 0xAD, 0xC3, 0x2C, 0xAA, 0x8C, 0x6C, 0x92,
0x9F, 0x66, 0xEC, 0xB9, 0xE9, 0xC0, 0x41, 0x46, 0x10, 0xAB, 0x20, 0xF3, 0x56, 0x5E, 0xA0, 0xCB,
0xCA, 0x05, 0x64, 0x40, 0xF2, 0xFC, 0xFA, 0x4F, 0x83, 0xEC, 0xC7, 0xBA, 0xEA, 0x9F, 0x2D, 0x4F,
0xA3, 0x17, 0xD4, 0x60, 0x13, 0x93, 0x0B, 0xB2, 0xEA, 0xB0, 0x16, 0x6A, 0x95, 0xA7, 0x2D, 0x4C,
0x94, 0x17, 0xE0, 0x62, 0xA7, 0xFA, 0x0B, 0x8A, 0x9A, 0x55, 0xB6, 0x36, 0x83, 0xB1, 0x2E, 0x11,
0x6E, 0x98, 0xE4, 0x8E, 0x14, 0x8B, 0xAD, 0x0B, 0x48, 0x37, 0x1B, 0x11, 0x44, 0xE4, 0xA3, 0x5C,
0x93, 0x17, 0x66, 0xB3, 0x16, 0x9C, 0x4E, 0xB0, 0x66, 0x47, 0x5E, 0x8B, 0xEC, 0x42, 0x18, 0xD7,
0x7A, 0x51, 0xF8, 0xBA, 0x3C, 0xB6, 0xC7, 0x5E, 0x24, 0x71, 0x1E, 0x65, 0x05, 0x2C, 0x92, 0x0C,
0xEA, 0x9E, 0x5A, 0x0F, 0x47, 0xF0, 0x05, 0x92, 0xD9, 0xDC, 0x85, 0x3C, 0x04, 0x29, 0x29, 0xC8,
0x34, 0xD3, 0xAB, 0x64, 0x94, 0xAB, 0xBC, 0x3E, 0xD9, 0xED, 0xB0, 0x4E, 0xEC, 0x76, 0x62, 0x0A,
0xEC, 0xD1, 0x83, 0x4B, 0x5F, 0xDD, 0x60, 0x69, 0x03, 0xC5, 0x2F, 0x30, 0x0A, 0xEE, 0x7D, 0x96,
0xBC, 0x86, 0xB0, 0x66, 0x1B, 0x72, 0x61, 0x12, 0x88, 0xE2, 0x2A, 0x84, 0x6B, 0xFF, 0x96, 0xB8,
0x1A, 0x6B, 0x73, 0x2E, 0xB0, 0xDF, 0xA1, 0xC3, 0x02, 0x6E, 0xCE, 0x55, 0xB6, 0xD1, 0xB6, 0x14,
0x85, 0xD6, 0x6F, 0x9E, 0xEC, 0x6E, 0xEE, 0x20, 0xF2, 0xF9, 0x68, 0x39, 0x40, 0x97, 0x1D, 0xEC,
0x4A, 0x1D, 0x7C, 0x5B, 0xAE, 0x87, 0x80, 0xAF, 0x0A, 0xCC, 0x3A, 0x02, 0x5B, 0xC7, 0x64, 0x42,
0x61, 0x8B, 0xBD, 0xEC, 0x15, 0xFE, 0xF5, 0xC7, 0x17, 0x99, 0x6F, 0x0A, 0x54, 0xCC, 0x2B, 0x91,
0xA6, 0x5A, 0x44, 0x48, 0xE3, 0x8B, 0x52, 0x69, 0xC2, 0x8E, 0xD6, 0x70, 0xA9, 0x6A, 0x8D, 0x2C,
0x45, 0xA3, 0xAF, 0xB3, 0x55, 0x57, 0x4B, 0xB6, 0x5B, 0xF4, 0x45, 0xB9, 0x8B, 0xB2, 0xD4, 0x5C,
0xB2, 0xEA, 0xD3, 0x89, 0x2C, 0xDE, 0x3F, 0x5B, 0xB7, 0x7A, 0x31, 0xA0, 0xEB, 0xA5, 0x11, 0x0A,
0xE5, 0xB0, 0x98, 0x98, 0xAD, 0xD0, 0x17, 0xA6, 0xF7, 0xCF, 0xB8, 0x88, 0x34, 0xBC, 0xC7, 0x4A,
0xE8, 0x3E, 0x44, 0x04, 0x94, 0xB8, 0x65, 0x74, 0xCE, 0x69, 0x81, 0x3E, 0x7C, 0x65, 0x7B, 0xEA,
0x50, 0xE5, 0xF3, 0xB7, 0xB4, 0xC0, 0xE7, 0xFC, 0x64, 0xEA, 0x06, 0xA0, 0xBC, 0x76, 0x8F, 0xD8,
0xBD, 0x86, 0xF7, 0x5C, 0xD4, 0xC5, 0x50, 0xC6, 0xFD, 0xE3, 0xF5, 0x31, 0xDA, 0xC5, 0x13, 0x81,
0xCE, 0xE4, 0xB5, 0xA0, 0x88, 0xAA, 0x2D, 0x6D, 0xD3, 0x8E, 0x7B, 0x48, 0x85, 0x40, 0xD7, 0x53,
0x16, 0x36, 0x8D, 0x4E, 0xAD, 0x0B, 0x2B, 0x8B, 0x5E, 0x3C, 0x9A, 0x69, 0xDA, 0x02, 0xCC, 0x67,
0xD1, 0x03, 0x19, 0x36, 0xEB, 0xE2, 0xEC, 0xAF, 0xB6, 0xF8, 0x26, 0xA4, 0x20, 0x11, 0x28, 0xFA,
0xA7, 0xE3, 0x16, 0x87, 0x60, 0x33, 0x56, 0x16, 0xBB, 0xE4, 0xEC, 0xE4, 0xD1, 0x7F, 0x02, 0x01,
0x5D, 0x3E, 0xE6, 0x18, 0x05, 0xED, 0x1B, 0xA8, 0x29, 0x7F, 0xED, 0x13, 0x04, 0xC5, 0x24, 0x6C,
0xA4, 0x5F, 0x9A, 0xE4, 0xBC, 0x84, 0x03, 0x2A, 0x6B, 0xB6, 0x38, 0x0B, 0x63, 0x93, 0x2B, 0xA5,
0xB2, 0x9E, 0x3C, 0x7C, 0x5C, 0x14, 0xA4, 0xC4, 0xFE, 0x57, 0xAE, 0xB0, 0x56, 0xE6, 0xA1, 0x3D,
0x56, 0x94, 0xA7, 0xC3, 0x7D, 0x63, 0x79, 0x1C, 0xA2, 0x62, 0xCF, 0x9E, 0x7C, 0x0F, 0x87, 0x20,
0x34, 0x82, 0xD3, 0xDD, 0x00, 0x4D, 0xB5, 0xC4, 0xF6, 0x54, 0x86, 0x2F, 0xB3, 0xDC, 0x70, 0x1C,
0x00, 0x0A, 0x0C, 0x6C, 0x32, 0x40, 0x5B, 0x4A, 0x51, 0x6E, 0x12, 0xC3, 0x38, 0x70, 0x73, 0xA5,
0xEB, 0x5D, 0x01, 0xE3, 0x5E, 0xF4, 0x4F, 0x6D, 0xBC, 0xFD, 0x2E, 0xAF, 0xA8, 0x9D, 0xFE, 0x6D,
0x2B, 0x31, 0x1A, 0x4E, 0xC2, 0x6D, 0xC9, 0xBD, 0xAD, 0x41, 0x56, 0x15, 0x74, 0xD9, 0xAE, 0x4E,
0x36, 0x79, 0xF1, 0x23, 0x8F, 0xAA, 0x85, 0x05, 0xB3, 0x1A, 0x5B, 0x5D, 0x2C, 0xB4, 0xA4, 0x09,
0xA1, 0xF4, 0x8D, 0x2B, 0x82, 0x97, 0x9E, 0xDB, 0xEF, 0xA5, 0xB9, 0x9B, 0x43, 0x7D, 0x51, 0xE3,
0x0B, 0x3A, 0x7C, 0xFD, 0xA4, 0x86, 0xBD, 0x1E, 0x57, 0x57, 0x46, 0x1C, 0xF2, 0x45, 0xD3, 0x3F,
0xEB, 0x8E, 0x32, 0x12, 0xEC, 0x16, 0x0D, 0x14, 0xF6, 0x47, 0xCA, 0xF6, 0x30, 0xB0, 0xFA, 0x36,
0xE7, 0xC9, 0xE3, 0x1A, 0x23, 0xB1, 0xF3, 0x88, 0xD0, 0xB8, 0x4B, 0x33, 0xF4, 0xB9, 0xE8, 0x6E,
0xBD, 0x60, 0xED, 0x78, 0xC3, 0xF6, 0x4D, 0x95, 0x7B, 0x9C, 0x25, 0x41, 0x18, 0x1E, 0x24, 0xA2,
0xFB, 0x9A, 0x78, 0x03, 0x0A, 0xAB, 0x5D, 0x65, 0x80, 0xE0, 0xC7, 0x82, 0x96, 0xED, 0x73, 0xA1,
0x9E, 0x16, 0x15, 0xEE, 0xE1, 0x8E, 0x96, 0xBD, 0xCA, 0x0C, 0x74, 0x69, 0xF1, 0x80, 0x63, 0x41,
0x65, 0x70, 0xA0, 0x44, 0x22, 0x80, 0x34, 0x1E, 0xDE, 0x6A, 0x8B, 0x35, 0x80, 0xD2, 0x79, 0x46,
0x90, 0x02, 0xD1, 0x39, 0x7A, 0x52, 0x4B, 0x78, 0xD5, 0x75, 0x6D, 0x3D, 0xF2, 0x23, 0x9E, 0x3E,
0xC9, 0xB8, 0x8F, 0x39, 0xFA, 0x8C, 0x9A, 0x8C, 0xBD, 0x56, 0xDE, 0x99, 0x76, 0xBB, 0xF1, 0xBC,
0xBA, 0xB8, 0x65, 0x23, 0x42, 0x82, 0xB2, 0x88, 0xD1, 0x2D, 0x65, 0xFB, 0x37, 0x51, 0x8F, 0xB7,
0xDC, 0x83, 0xAB, 0x03, 0x00, 0x23, 0x22, 0x4B, 0x65, 0x3C, 0x1F, 0xBE, 0x57, 0x93, 0x37, 0xD5,
0x10, 0xA6, 0xBD, 0xFB, 0xD4, 0x5A, 0xFE, 0xA5, 0x94, 0x51, 0xD6, 0xE5, 0x2D, 0x31, 0xDB, 0x42,
0xC1, 0x07, 0xBE, 0xAF, 0x5F, 0xE6, 0x36, 0x0C, 0x27, 0x34, 0xDE, 0x9E, 0xA6, 0xCD, 0x5B, 0x66,
0xF2, 0x86, 0x29, 0xAF, 0x87, 0xA9, 0xC6, 0xF2, 0xBD, 0xE7, 0xFB, 0x08, 0x55, 0xDC, 0xFD, 0x35,
0x00, 0x4E, 0x55, 0xA7, 0x4E, 0xF5, 0xB7, 0x2D, 0x81, 0xE0, 0xF8, 0x5E, 0x62, 0x4B, 0x32, 0x1F,
0x50, 0x01, 0x4D, 0xAD, 0x6D, 0x1A, 0x5F, 0xE0, 0xBA, 0x2E, 0xD9, 0x03, 0x57, 0xD5, 0xEC, 0xFF,
0x66, 0x12, 0xA7, 0xFF, 0xA0, 0xCD, 0x2C, 0xA4, 0xBB, 0x84, 0x20, 0x03, 0xD3, 0x96, 0x7D, 0x5D,
0x71, 0xCB, 0x9F, 0xE8, 0x23, 0x04, 0x66, 0xC1, 0xD1, 0x28, 0xC8, 0xE3, 0x14, 0xC2, 0xFC, 0xAA,
0x3D, 0x2E, 0x77, 0x34, 0xBD, 0x70, 0xC5, 0xA5, 0xA0, 0x0F, 0x63, 0xF5, 0x2B, 0x48, 0x8E, 0xC3,
0x9C, 0x5D, 0xA2, 0x29, 0xFC, 0x80, 0x87, 0x5B, 0x37, 0x17, 0x65, 0x33, 0xF4, 0x15, 0x59, 0x28,
0x5D, 0xDA, 0x69, 0xFD, 0x0E, 0x1E, 0x22, 0xE4, 0xA7, 0x04, 0xA5, 0xDA, 0x05, 0x72, 0x4F, 0xA7,
0x08, 0x11, 0x58, 0xAE, 0xA9, 0xD4, 0x61, 0xA5, 0x30, 0x32, 0xBF, 0xB7, 0xA7, 0xD0, 0x87, 0x81,
0xF6, 0x79, 0x16, 0x43, 0x1F, 0xFB, 0x92, 0xE5, 0x53, 0xA5, 0xC8, 0x4D, 0xA6, 0x71, 0x6E, 0x47,
0x5E, 0x69, 0x59, 0xE8, 0x50, 0x57, 0x9D, 0x2B, 0x0C, 0x4E, 0xCE, 0x33, 0x43, 0xE1, 0xD2, 0xA0,
0x8D, 0x7B, 0x83, 0x0A, 0xEB, 0xC0, 0x12, 0x12, 0x08, 0x7A, 0xFA, 0x54, 0x46, 0x58, 0x2F, 0x9C,
0xDB, 0xE9, 0x33, 0x3F, 0xB6, 0x8B, 0xF5, 0x26, 0xBF, 0xD7, 0xA4, 0x6E, 0x29, 0xB3, 0x2E, 0x7D,
0x43, 0x5E, 0xCE, 0x69, 0x3C, 0xA7, 0xBA, 0xB9, 0xCF, 0xA4, 0x38, 0xF7, 0x9D, 0x19, 0x0D, 0x7B,
0xD7, 0x48, 0x42, 0x70, 0x18, 0xF3, 0xB2, 0x06, 0x2A, 0xDF, 0xAF, 0xCB, 0xCE, 0xAC, 0x00, 0x32,
0x90, 0x1B, 0x05, 0x95, 0x79, 0x52, 0x31, 0x6F, 0x84, 0xFA, 0x0E, 0x72, 0xC1, 0xA1, 0x62, 0x8B,
0xDE, 0x15, 0x8F, 0x23, 0x86, 0x22, 0x53, 0x65, 0xAA, 0xFC, 0xD6, 0xC1, 0xDC, 0xFE, 0x4F, 0xF4,
0x85, 0x5F, 0xEB, 0xDC, 0x29, 0x06, 0x60, 0x41, 0x6C, 0x1B, 0xAB, 0x63, 0x87, 0x93, 0x15, 0x35,
0x20, 0xB2, 0x13, 0x36, 0x7C, 0x19, 0x62, 0x11, 0x3D, 0x33, 0x68, 0xCB, 0xDB, 0x82, 0xBA, 0x41,
0x01, 0x1B, 0xD7, 0xE8, 0xCC, 0xFC, 0xA9, 0x2E, 0x93, 0xC6, 0xC9, 0xBA, 0x6B, 0x11, 0x43, 0x14,
0x50, 0xD0, 0x26, 0x59, 0x45, 0xDA, 0x42, 0x23, 0x13, 0x46, 0x5D, 0x1E, 0x49, 0xB0, 0xDB, 0x48,
0xCF, 0xE6, 0xB8, 0x7C, 0xD8, 0x69, 0x4F, 0xB5, 0xC3, 0x0F, 0x57, 0xB2, 0x1D, 0x10, 0x17, 0x40,
0x02, 0xA7, 0x7F, 0xE2, 0xFB, 0xCD, 0xBA, 0x3C, 0x6D, 0x14, 0x32, 0x27, 0x75, 0x5C, 0xB5, 0xE8,
0x1D, 0x85, 0xAD, 0xD7, 0xD6, 0x49, 0x06, 0x96, 0x0C, 0xBA, 0xA7, 0x1C, 0x15, 0x0E, 0x59, 0x1A,
0x3C, 0xA0, 0x69, 0x6E, 0x96, 0xB6, 0x71, 0x4E, 0x54, 0xD8, 0xA9, 0x58, 0xB0, 0x61, 0xBE, 0x0E,
0xC8, 0x77, 0xC7, 0x44, 0xD3, 0xBA, 0x9B, 0x64, 0xA6, 0xBF, 0x10, 0xC3, 0x09, 0x6D, 0xB2, 0x70,
0xC0, 0xCC, 0xCB, 0xD0, 0x87, 0x05, 0xAF, 0x54, 0xA5, 0x7D, 0xA0, 0xDA, 0x8B, 0xB2, 0xE4, 0x1E,
0x1C, 0x0E, 0x35, 0x3D, 0x00, 0xCC, 0xCB, 0x96, 0x1D, 0xD7, 0xFD, 0xD1, 0xCE, 0xE8, 0xE7, 0x6A,
0xA2, 0xE8, 0xC4, 0x5F, 0xD0, 0xB9, 0x84, 0x3D, 0xE3, 0x5E, 0x67, 0xA8, 0x60, 0x52, 0x0B, 0x80,
0x6A, 0x34, 0x9B, 0x12, 0xA2, 0x2E, 0x0E, 0x0E, 0x1B, 0x7B, 0x34, 0xD1, 0xEE, 0xA2, 0xF4, 0xFF,
0x51, 0xB3, 0x51, 0x62, 0x12, 0x96, 0xB6, 0x4B, 0x00, 0xBB, 0xDC, 0x67, 0xAB, 0xE8, 0x74, 0x55,
0xCD, 0x0E, 0x70, 0xB8, 0xE4, 0x10, 0x81, 0x7E, 0x1D, 0xF8, 0x2D, 0x0C, 0x7A, 0x38, 0x39, 0xA2,
0xEB, 0xC8, 0x34, 0x73, 0xF2, 0x59, 0x5D, 0x29, 0xB5, 0xE3, 0x60, 0xD3, 0x55, 0xFA, 0xB1, 0xEB,
0xBA, 0x6E, 0x21, 0x52, 0x24, 0xDE, 0x74, 0x29, 0xC4, 0x95, 0xA3, 0x7E, 0xEA, 0xC8, 0x75, 0x54,
0x65, 0xF7, 0xA9, 0xF6, 0x24, 0xAB, 0x90, 0xF4, 0xF0, 0x3F, 0x26, 0xDD, 0x3F, 0xBE, 0xE3, 0xD8,
0x5A, 0x4C, 0xA8, 0x12, 0x10, 0x61, 0x28, 0xE5, 0xD8, 0xF4, 0x1C, 0xB3, 0x49, 0x29, 0xE0, 0x3C,
0x4B, 0x3C, 0x3D, 0x8B, 0x38, 0x9A, 0xDE, 0xDF, 0x44, 0x55, 0x6C, 0x5B, 0x8B, 0x7C, 0x96, 0x9D,
0xB3, 0x9A, 0x60, 0x6A, 0x07, 0xFA, 0xE2, 0x01, 0xEF, 0x8F, 0xF7, 0xBA, 0xDB, 0xEE, 0xFE, 0xF0,
0x8B, 0x38, 0x06, 0xE0, 0x53, 0x20, 0x3E, 0x0E, 0x0A, 0x9A, 0x00, 0x8D, 0x0A, 0x31, 0xFA, 0xD5,
0xBE, 0x2E, 0x9B, 0x40, 0x2F, 0x2C, 0x2D, 0xF2, 0x9B, 0x87, 0xD8, 0xC1, 0x6C, 0x15, 0x31, 0xC2,
0x04, 0xA1, 0x1F, 0xBA, 0xD1, 0xBE, 0x89, 0x99, 0xF8, 0xC7, 0x09, 0x82, 0x6F, 0xAD, 0xDF, 0xC5,
0x92, 0xCA, 0x99, 0x01, 0x0E, 0xC0, 0xD3, 0x94, 0xF7, 0xB9, 0x16, 0x29, 0xDC, 0x3F, 0xB5, 0xCA,
0x72, 0x79, 0xEC, 0x04, 0x5B, 0x23, 0xC7, 0x5D, 0x49, 0x7C, 0x6E, 0xB4, 0x74, 0xEB, 0x22, 0x0D,
0xC4, 0x63, 0x23, 0x9E, 0xD0, 0x1C, 0xF1, 0x28, 0xF7, 0x77, 0x1C, 0x3E, 0x11, 0xE7, 0x64, 0x4E,
0x81, 0x07, 0xEE, 0x06, 0x69, 0x1A, 0x06, 0x94, 0xC9, 0x48, 0xA8, 0x2C, 0x7B, 0xF1, 0xD5, 0x6E,
0x41, 0xB7, 0xD0, 0xEF, 0x47, 0x74, 0xA7, 0x4F, 0xCB, 0xB1, 0x18, 0x6F, 0x78, 0x43, 0x3A, 0x88,
0xC7, 0xEC, 0x2D, 0xF9, 0xD0, 0x3A, 0xE3, 0x5C, 0xFC, 0xA2, 0xDF, 0x88, 0xED, 0x79, 0x04, 0x37,
0x17, 0xCC, 0xE8, 0x78, 0x17, 0x01, 0x47, 0x69, 0xF6, 0x15, 0xF3, 0x35, 0x6B, 0xCA, 0xBD, 0xBA,
0x9A, 0xF6, 0xFD, 0x8E, 0x03, 0xA9, 0x2F, 0xD4, 0x4B, 0x83, 0xC2, 0x7E, 0xDF, 0xE4, 0xCC, 0xE3,
0x43, 0x00, 0x77, 0xA4, 0x6A, 0xD0, 0xA7, 0x77, 0x65, 0xC1, 0xC0, 0x5B, 0x89, 0x9E, 0x4E, 0xA8,
0x9F, 0x15, 0x9C, 0x05, 0x73, 0xC0, 0x7D, 0x3E, 0xC2, 0x19, 0x3E, 0xFF, 0x52, 0x72, 0x7E, 0x51,
0xA7, 0xB6, 0xF2, 0xD3, 0x01, 0x24, 0xBA, 0x2D, 0x83, 0x8A, 0xED, 0x3A, 0xE5, 0x21, 0xAE, 0xB7,
0x6E, 0xD8, 0xCE, 0x9B, 0xE4, 0xE4, 0x38, 0x3F, 0xA5, 0xBA, 0xFA, 0x33, 0x88, 0xCC, 0x77, 0x23,
0x22, 0x37, 0x1C, 0xE2, 0xA0, 0xF2, 0xBE, 0x53, 0x39, 0xEB, 0xF3, 0x52, 0xED, 0x79, 0xBB, 0xE9,
0xDE, 0xB0, 0xB5, 0x02, 0xEB, 0xC0, 0xF9, 0xA9, 0x83, 0x09, 0x0E, 0x38, 0x25, 0x46, 0x33, 0x21,
0xC5, 0x46, 0xEA, 0x58, 0x9C, 0x20, 0xD2, 0x64, 0x89, 0xF4, 0x0C, 0x91, 0x48, 0x0F, 0x36, 0x85,
0xCB, 0x30, 0x1E, 0x7B, 0x3C, 0x16, 0x9A, 0xB5, 0xA5, 0xB1, 0x82, 0x8A, 0x69, 0xC2, 0xCB, 0x14,
0xFB, 0x5E, 0xDD, 0x5C, 0xC1, 0x24, 0xF5, 0x64, 0x01, 0xF3, 0x87, 0x82, 0x9A, 0xA2, 0x91, 0x3A,
0xB5, 0xCC, 0xE9, 0x50, 0x01, 0xCF, 0x73, 0xF1, 0xE1, 0x58, 0xFB, 0x84, 0xF7, 0x67, 0xA0, 0x13,
0xEC, 0xD1, 0x1B, 0x7A, 0x72, 0xA1, 0x5B, 0x04, 0xEA, 0x68, 0x3D, 0xCA, 0x9B, 0x08, 0x0D, 0x0D,
0x87, 0x1F, 0x08, 0x5B, 0xE2, 0x9B, 0x72, 0x63, 0xE8, 0x73, 0xD2, 0x79, 0x4D, 0xCF, 0x89, 0x78,
0x32, 0x16, 0xE3, 0x12, 0xCF, 0x3F, 0x5C, 0x82, 0x51, 0x1D, 0x14, 0x73, 0xC3, 0x52, 0x0A, 0xE4,
0xF7, 0x79, 0x44, 0x3C, 0x6A, 0x72, 0xA6, 0xA5, 0xAD, 0xC0, 0xBF, 0xDA, 0x75, 0xC4, 0x35, 0x20,
0xA0, 0x64, 0x7E, 0x22, 0xCE, 0xE5, 0x1C, 0x3F, 0x78, 0xB5, 0x29, 0x3A, 0x45, 0x67, 0xED, 0x25,
0x80, 0xDE, 0x1B, 0x89, 0x38, 0x6F, 0x16, 0x52, 0x2D, 0x1F, 0x9F, 0xDF, 0xD3, 0xA0, 0xC9, 0x18,
0x03, 0x61, 0x54, 0xA1, 0x8F, 0x05, 0x8D, 0x4F, 0x00, 0xF8, 0xA7, 0xE1, 0x51, 0x9C, 0x99, 0xA1,
0xE4, 0xA1, 0x31, 0x16, 0xAC, 0xBC, 0x91, 0xB2, 0x73, 0x5B, 0x36, 0xE1, 0x7A, 0xD9, 0x82, 0xBD,
0xD2, 0x4C, 0x31, 0x45, 0x1A, 0x55, 0xAF, 0x41, 0xE8, 0x34, 0xD9, 0x86, 0xF1, 0x61, 0xA2, 0x11,
0x19, 0x78, 0xA1, 0x47, 0x66, 0x42, 0xAE, 0xA8, 0x84, 0x56, 0x0F, 0x34, 0x7A, 0xC5, 0x80, 0x7B,
0xE6, 0xE3, 0xAF, 0x64, 0x86, 0xE8, 0x2D, 0x54, 0xA2, 0x01, 0x8B, 0xD1, 0x2D, 0x71, 0x76, 0x0A,
0x62, 0xEA, 0x17, 0x38, 0xF6, 0x19, 0x5F, 0x59, 0xAB, 0xB9, 0x84, 0xCF, 0x1F, 0x7F, 0x8E, 0x31,
0x92, 0xDF, 0x4E, 0x14, 0x72, 0xAF, 0x56, 0x27, 0x4B, 0x54, 0xDE, 0x57, 0x2B, 0xCE, 0x06, 0x47,
0x5F, 0x01, 0x5C, 0xE0, 0xA2, 0xFF, 0x0F, 0x1F, 0xBC, 0x3B, 0x53, 0x72, 0x18, 0xEA, 0x94, 0x07,
0x71, 0xAF, 0x4B, 0x43, 0x02, 0x6A, 0x7E, 0x21, 0xAB, 0x4A, 0x84, 0x1B, 0xA7, 0x62, 0xF4, 0x1F,
0xB1, 0xD6, 0xDD, 0x08, 0xC4, 0x6C, 0xAB, 0xC0, 0x44, 0xAE, 0x5F, 0x98, 0xB8, 0x45, 0xEC, 0x0D,
0x8A, 0x1B, 0xE2, 0xE2, 0x54, 0x42, 0x51, 0xF2, 0xF9, 0xDB, 0x11, 0xB5, 0x20, 0xF4, 0xEE, 0xF8,
0x92, 0x2E, 0x9A, 0x1A, 0x68, 0xE9, 0xCF, 0xC8, 0x8D, 0xCE, 0x33, 0x6E, 0xF1, 0xDD, 0xFF, 0xEB,
0xB8, 0x49, 0xB8, 0x56, 0x59, 0x32, 0x71, 0xB2, 0x28, 0xF9, 0xA4, 0x98, 0xC3, 0xB8, 0xDF, 0x0F,
0xCD, 0x06, 0x76, 0x6C, 0xCD, 0x46, 0x8E, 0x2F, 0x91, 0xDE, 0x3C, 0x7D, 0x18, 0xD4, 0x86, 0x15,
0x43, 0xC7, 0xE5, 0xB3, 0xB0, 0xFF, 0x81, 0x32, 0x3C, 0x3B, 0xE0, 0x98, 0xB7, 0x4C, 0xFD, 0xE9,
0x13, 0x54, 0x7E, 0xAF, 0x04, 0x56, 0x0F, 0x8E, 0x0A, 0xE1, 0x55, 0xFF, 0x19, 0x84, 0x8D, 0xB9,
0x63, 0x91, 0xD8, 0x10, 0x27, 0x7B, 0xD5, 0x05, 0x28, 0x5F, 0xF9, 0xE5, 0xFC, 0xA2, 0x25, 0xFB,
0xE3, 0x36, 0x91, 0x55, 0xBB, 0xA6, 0xE0, 0x64, 0x20, 0xF6, 0x27, 0x23, 0x9E, 0x82, 0xFB, 0x7C,
0x2A, 0xE5, 0x39, 0x2D, 0xA3, 0x4B, 0xD1, 0x82, 0xF5, 0x68, 0x1F, 0x42, 0xE4, 0x0B, 0xB0, 0x2E,
0x37, 0x3C, 0x2A, 0x12, 0x61, 0xEC, 0x54, 0x1D, 0xA2, 0xA3, 0x89, 0x54, 0x25, 0xAD, 0x17, 0xE0,
0x8A, 0xFB, 0xA8, 0xF4, 0x6D, 0xAF, 0xF0, 0x84, 0x12, 0xE1, 0x92, 0x72, 0x9B, 0x41, 0x99, 0xA6,
0x3E, 0x12, 0x74, 0x28, 0x6F, 0x9C, 0xA3, 0x63, 0xC6, 0x88, 0x76, 0xC6, 0x22, 0x76, 0xEC, 0x48,
0x2B, 0xB5, 0x41, 0x81, 0x45, 0xBB, 0xCB, 0x4D, 0x9D, 0x77, 0x95, 0x49, 0x5F, 0x43, 0x27, 0x40,
0xD1, 0x4E, 0xEB, 0x2B, 0xD9, 0x0D, 0x7B, 0xD3, 0x36, 0xAE, 0x18, 0x9E, 0x45, 0x36, 0xA1, 0xA0,
0xB2, 0x30, 0xF1, 0x82, 0xE5, 0x73, 0x5F, 0xC4, 0x75, 0x43, 0xE9, 0xD7, 0x16, 0xE1, 0x98, 0xB6,
0x60, 0xEB, 0x43, 0x4D, 0x5C, 0xBE, 0x0C, 0xC1, 0x92, 0x8D, 0x9E, 0x25, 0x3A, 0x55, 0xF6, 0x66,
0x7C, 0x7F, 0xB4, 0x3F, 0x08, 0xA3, 0x1F, 0xC4, 0xCE, 0x05, 0x0F, 0xBF, 0x99, 0x4D, 0x40, 0x5A,
0x56, 0xC1, 0x50, 0x87, 0x07, 0xDF, 0xED, 0xA2, 0x43, 0x2A, 0xB1, 0x69, 0x36, 0x44, 0xD3, 0x42,
0x48, 0x53, 0xF6, 0xD8, 0xA9, 0xD7, 0x61, 0xCB, 0x12, 0x8B, 0xCC, 0x5A, 0xE2, 0x47, 0xCD, 0x8C,
0xCB, 0xBC, 0x2A, 0x56, 0xE0, 0x00, 0x0F, 0x99, 0x61, 0xE4, 0x4C, 0xAE, 0x83, 0x7D, 0xFB, 0xEF,
0x61, 0x49, 0x40, 0xF9, 0x37, 0x27, 0x12, 0x34, 0xF4, 0x85, 0xAB, 0x27, 0xB6, 0x96, 0xBF, 0xFE,
0x00, 0xD1, 0x5A, 0xF6, 0x55, 0x90, 0x64, 0x8C, 0x95, 0xCF, 0x15, 0x02, 0x31, 0x45, 0x4D, 0x70,
0xC4, 0xF9, 0xFC, 0x57, 0x06, 0x93, 0x46, 0x03, 0xE3, 0x94, 0x3C, 0x94, 0x52, 0x54, 0xED, 0x02,
0x44, 0x9D, 0x61, 0xB9, 0x74, 0x84, 0xA4, 0x06, 0x9F, 0x1D, 0x38, 0x26, 0xE2, 0x8E, 0x09, 0x11,
0xA9, 0xAF, 0xA7, 0xFE, 0xE8, 0xFF, 0xFB, 0xF2, 0x07, 0xC7, 0xFF, 0x00, 0xC0, 0x9D, 0xDD, 0x91,
0x38, 0x4B, 0x65, 0x0F, 0xE5, 0xB2, 0xD1, 0xF2, 0x20, 0x91, 0x19, 0x8C, 0x44, 0xB3, 0x71, 0x6A,
0x68, 0x3D, 0xE4, 0x4F, 0x56, 0x16, 0xFD, 0x25, 0x61, 0x95, 0x5B, 0xA4, 0xE7, 0xED, 0x8B, 0x07,
0x25, 0x69, 0xDA, 0xFB, 0xED, 0x6C, 0x60, 0x62, 0x5E, 0x9A, 0x3F, 0x8C, 0xC8, 0xE7, 0xF6, 0xC0,
0x3F, 0xF1, 0x0E, 0x6A, 0xFE, 0x65, 0xC3, 0x04, 0xCC, 0xBD, 0x6E, 0x01, 0x5A, 0xE5, 0xF1, 0x00,
0xA6, 0xDC, 0xE7, 0x19, 0x23, 0x6D, 0xB3, 0xB2, 0x72, 0x93, 0x8F, 0x83, 0xFC, 0x1F, 0x3E, 0x8F,
0x1D, 0x20, 0x68, 0x05, 0x2F, 0x53, 0x7F, 0xE1, 0x71, 0x7F, 0xFC, 0x80, 0x71, 0x8D, 0x51, 0xFF,
0xE1, 0x57, 0x77, 0x13, 0xDA, 0x99, 0x77, 0x27, 0xF7, 0xDB, 0xAD, 0x43, 0xB7, 0xC1, 0x13, 0x65,
0x0E, 0xE6, 0x54, 0xCB, 0xC4, 0x3E, 0x3B, 0x49, 0x8E, 0x3F, 0xFE, 0xDC, 0x8D, 0x17, 0x10, 0x72,
0x12, 0xC2, 0x65, 0xB3, 0x16, 0x05, 0xB2, 0xAC, 0xB5, 0x7D, 0xC8, 0x40, 0x72, 0xC7, 0xCF, 0xC5,
0x7B, 0xA2, 0x54, 0xA3, 0xC6, 0x7C, 0x2C, 0x24, 0x13, 0x33, 0xEF, 0x8C, 0xC3, 0x5A, 0x07, 0xD1,
0x18, 0xF8, 0x18, 0xEF, 0x06, 0xC6, 0x5A, 0x78, 0xC6, 0x63, 0x11, 0x09, 0xC2, 0xFC, 0x73, 0x75,
0xF0, 0x92, 0x7A, 0x90, 0xD9, 0xE4, 0xDB, 0xCF, 0x4F, 0x0A, 0x8D, 0x04, 0x5E, 0xF2, 0x8A, 0x7D,
0xBD, 0x9E, 0xFD, 0x59, 0x62, 0x0D, 0xE5, 0x53, 0x6B, 0xE0, 0xD5, 0x24, 0xB4, 0x53, 0xD4, 0xAD,
0xBF, 0xC0, 0x26, 0x86, 0x95, 0x82, 0x80, 0x21, 0x86, 0x18, 0xED, 0xE4, 0xDE, 0x94, 0x9B, 0x5F,
0x6C, 0x41, 0x90, 0x9E, 0x7B, 0x9C, 0x5F, 0x37, 0x11, 0xD1, 0xFF, 0x17, 0xA3, 0x90, 0xFE, 0x87,
0xE8, 0x0C, 0x2D, 0x44, 0xD3, 0x7C, 0x2D, 0x63, 0xA4, 0xD0, 0x7D, 0xC3, 0x69, 0x6A, 0x44, 0x65,
0xD3, 0xB3, 0xEB, 0x77, 0xAA, 0x4E, 0x88, 0xCE, 0xDB, 0x3F, 0x71, 0x9C, 0x67, 0xA6, 0x72, 0xA9,
0xFD, 0x3E, 0x70, 0x94, 0xB0, 0xA8, 0x82, 0x41, 0xB6, 0x19, 0x87, 0xD8, 0x47, 0x3A, 0x02, 0xCA,
0x25, 0x17, 0x11, 0x2F, 0xEB, 0xDA, 0x6C, 0x2A, 0x76, 0xA9, 0x19, 0x33, 0xB6, 0x80, 0xBA, 0x88,
0xDD, 0xAE, 0xCF, 0x31, 0x7F, 0x8C, 0x9B, 0x28, 0x5D, 0x5A, 0xE8, 0xE1, 0x09, 0x3F, 0xF0, 0x25,
0x88, 0xAF, 0xBC, 0xBB, 0x07, 0x1F, 0x16, 0xCA, 0x74, 0xB3, 0xF0, 0xEE, 0x24, 0x51, 0x80, 0xDD,
0x21, 0xE0, 0x8A, 0xC7, 0xA4, 0x26, 0x42, 0xFA, 0x0B, 0x4A, 0x7F, 0x8D, 0x41, 0xED, 0x05, 0x1D,
0x0F, 0xE6, 0xF2, 0x33, 0xF3, 0xA8, 0x27, 0x0E, 0x11, 0x15, 0xED, 0x59, 0x1A, 0x02, 0x8E, 0xCA,
0x87, 0xCA, 0x09, 0x50, 0x59, 0xC8, 0x1F, 0xA6, 0xC9, 0x60, 0xB3, 0x4D, 0x60, 0x82, 0x12, 0x3F,
0x83, 0x1B, 0x69, 0x6D, 0xCB, 0x43, 0x39, 0x20, 0x93, 0xDF, 0x53, 0xCD, 0xCA, 0x8F, 0x9F, 0x15,
0x01, 0xE7, 0xDA, 0x60, 0xAD, 0x2F, 0xCC, 0xBE, 0x09, 0x4E, 0x0F, 0x35, 0x20, 0x6D, 0xCD, 0x32,
0xB0, 0x51, 0x78, 0x17, 0xEE, 0x06, 0x72, 0x9D, 0x66, 0xB0, 0x0D, 0x09, 0x78, 0xA4, 0x9D, 0x9C,
0x16, 0x4A, 0x3B, 0xBE, 0x89, 0x6B, 0x1B, 0xD8, 0xBD, 0xAF, 0x00, 0xAE, 0x13, 0x86, 0xE3, 0x38,
0xB9, 0x15, 0x30, 0xFC, 0x5E, 0x74, 0x5F, 0xFE, 0xB1, 0xEC, 0xCF, 0xB6, 0xE2, 0xBF, 0x63, 0x28,
0xA7, 0x3E, 0x64, 0xC6, 0xC1, 0x72, 0x86, 0x72, 0xFE, 0x03, 0xB1, 0x60, 0x03, 0x16, 0x6F, 0xDA,
0xE7, 0x48, 0x33, 0x58, 0x66, 0x07, 0x6C, 0x96, 0x5A, 0x29, 0x7D, 0xBF, 0x1C, 0xA1, 0x1A, 0xC6,
0x1C, 0x90, 0x6D, 0x5C, 0x94, 0xB4, 0x4A, 0x94, 0x5C, 0x4D, 0xAC, 0x13, 0x07, 0x5A, 0xF8, 0x68,
0x37, 0x3C, 0x9C, 0x33, 0xF8, 0x7E, 0x85, 0x38, 0x57, 0x28, 0xA9, 0xE3, 0xEE, 0xD8, 0x15, 0x4F,
0xB7, 0x02, 0x0D, 0x6B, 0xB7, 0xF0, 0x27, 0x1E, 0x23, 0x95, 0x46, 0x50, 0x75, 0x4A, 0x87, 0x0D,
0x2E, 0xCA, 0xC0, 0x37, 0x6E, 0x20, 0x19, 0x1D, 0xD6, 0x1C, 0xFE, 0x66, 0x67, 0xBB, 0x61, 0x91,
0xC8, 0x0D, 0x77, 0xB9, 0xE1, 0x03, 0x82, 0x73, 0x40, 0xB5, 0x61, 0x94, 0xC9, 0x71, 0x38, 0xFE,
0x88, 0x25, 0x3B, 0xD6, 0x67, 0x78, 0x79, 0xBA, 0xE1, 0xBD, 0x12, 0x93, 0x57, 0x91, 0xB8, 0x2C,
0xEE, 0x4E, 0x48, 0xEC, 0x43, 0x18, 0xFC, 0x33, 0xF3, 0x80, 0x7E, 0xB0, 0x1B, 0xAC, 0xA4, 0x6A,
0x08, 0xF9, 0x7A, 0x72, 0x7F, 0x0D, 0xAE, 0x5D, 0x80, 0x29, 0xA0, 0x9C, 0x73, 0xF1, 0xD5, 0xB5,
0x56, 0x5F, 0xF7, 0xDD, 0xDE, 0x51, 0xB2, 0x40, 0xDA, 0x13, 0x8B, 0x57, 0x34, 0xDA, 0x52, 0x6E,
0xCF, 0x2D, 0x12, 0x60, 0x80, 0x48, 0x84, 0x23, 0xB0, 0x8A, 0x5D, 0x34, 0xA1, 0x29, 0x70, 0x7A,
0xED, 0x01, 0x44, 0x3B, 0xFC, 0x73, 0x22, 0x37, 0x08, 0x95, 0xD8, 0x7D, 0x07, 0xEF, 0x6B, 0xCD,
0x46, 0x73, 0x6A, 0xD3, 0xAE, 0x13, 0x7D, 0xAE, 0xAF, 0x30, 0xA6, 0xD6, 0x68, 0x10, 0x1E, 0x1C,
0x2D, 0x64, 0xF3, 0xA9, 0xE6, 0xE1, 0x10, 0x46, 0xC3, 0x12, 0xFB, 0x29, 0x6A, 0x15, 0xA6, 0x06,
0x5C, 0x9E, 0xD3, 0x32, 0xE4, 0xCD, 0x96, 0x29, 0xDD, 0xB3, 0x05, 0xEA, 0x27, 0xA5, 0x95, 0x66,
0xB0, 0x79, 0x64, 0x70, 0x26, 0x7C, 0x46, 0x39, 0x18, 0x79, 0x8D, 0xDC, 0x83, 0x6F, 0x11, 0xF0,
0x05, 0xC5, 0xDC, 0x54, 0xEB, 0xB7, 0xA1, 0x13, 0x4B, 0x13, 0x28, 0x49, 0x91, 0xCC, 0x2A, 0x26,
0xB5, 0xF0, 0x25, 0x8E, 0xF4, 0xC0, 0xFF, 0x2F, 0x37, 0xD5, 0x57, 0x7E, 0x25, 0xBF, 0xBE, 0x91,
0x5C, 0x83, 0x6D, 0xC5, 0x4A, 0xDE, 0x7C, 0xC8, 0x37, 0xEA, 0x0F, 0x51, 0xEE, 0x18, 0xDE, 0xC8,
0xCC, 0xA6, 0xCE, 0x07, 0xB9, 0x12, 0x1A, 0xB9, 0x7F, 0x08, 0x44, 0x34, 0x15, 0x58, 0x32, 0x69,
0x8C, 0xEA, 0x44, 0x8B, 0x05, 0x15, 0x7A, 0xD7, 0xBB, 0xD2, 0x0B, 0x57, 0x25, 0x11, 0x75, 0x3A,
0x47, 0xD4, 0xA7, 0x37, 0x40, 0x0C, 0x0A, 0x25, 0x21, 0x85, 0x2E, 0x95, 0x5F, 0x15, 0x15, 0x8D,
0x8E, 0xBB, 0x0B, 0x6D, 0xE8, 0x58, 0x3F, 0x0C, 0x1D, 0xAD, 0xE0, 0x61, 0x7F, 0xE5, 0x58, 0xD5,
0x11, 0x95, 0x90, 0x2B, 0x1D, 0x28, 0x58, 0xE2, 0xB9, 0x9D, 0x52, 0x65, 0x98, 0xE4, 0xF7, 0x4C,
0x3B, 0x63, 0x3F, 0x9A, 0xBC, 0x17, 0x38, 0xA5, 0xAC, 0x28, 0x37, 0x66, 0x38, 0xC3, 0x79, 0xC1,
0x43, 0x2E, 0xAB, 0x26, 0x22, 0xA6, 0xCF, 0xB3, 0x9F, 0xBE, 0xAC, 0x7A, 0xF4, 0x31, 0xCA, 0x8D,
0x03, 0xDD, 0xE6, 0xCC, 0xB3, 0x08, 0x57, 0x9C, 0x1E, 0xC2, 0xC6, 0x73, 0xB5, 0x54, 0x95, 0xD8,
0xC6, 0xC2, 0xF1, 0x69, 0x4F, 0x1E, 0xE6, 0xB1, 0x77, 0x3E, 0x02, 0x86, 0x26, 0x06, 0x2B, 0x34,
0xA1, 0x51, 0xC0, 0x3D, 0x8D, 0xC6, 0xD6, 0x4A, 0xD8, 0x29, 0x18, 0x4C, 0x0B, 0xEB, 0x74, 0x1C,
0xDC, 0x13, 0x62, 0xB4, 0xEE, 0x96, 0xF4, 0x48, 0x09, 0x5D, 0x60, 0xD3, 0xAC, 0x85, 0xDF, 0x9A,
0x10, 0xAB, 0x1A, 0x94, 0x31, 0x74, 0xF3, 0x1D, 0x76, 0x87, 0xB2, 0x3A, 0xD1, 0xF7, 0x93, 0x79,
0x67, 0x37, 0x6A, 0x97, 0x1F, 0xB9, 0x9E, 0xE4, 0xA1, 0xD3, 0xEF, 0xB4, 0xE6, 0xB2, 0x76, 0x9B,
0xAA, 0x68, 0x2E, 0xA3, 0x18, 0x23, 0xE4, 0xA9, 0xAF, 0xFC, 0x8D, 0xBE, 0x19, 0x18, 0xB6, 0xF9,
0xCE, 0xD2, 0xE2, 0xB5, 0xEC, 0x06, 0x43, 0xDD, 0x5C, 0x19, 0xE9, 0xC0, 0x63, 0x08, 0x87, 0xAE,
0xDE, 0x34, 0xAB, 0xDA, 0xE8, 0xA3, 0x47, 0x85, 0xD5, 0xB1, 0x7A, 0xC2, 0x0F, 0x6A, 0x82, 0x5A,
0xA1, 0xA1, 0x1D, 0xCF, 0xC3, 0xB9, 0xA4, 0x87, 0x79, 0x01, 0x4A, 0x59, 0xB0, 0xBB, 0xB0, 0x01,
0x1E, 0x3C, 0x7A, 0xB7, 0xAF, 0xAF, 0xF7, 0x5A, 0x53, 0x17, 0x4B, 0xEB, 0x83, 0x15, 0xB2, 0x9F,
0x02, 0x3B, 0x19, 0xDA, 0x8C, 0x91, 0x00, 0xF3, 0x78, 0x52, 0xF6, 0xB5, 0x7A, 0x90, 0x1D, 0x77,
0x33, 0x35, 0x85, 0x06, 0x7D, 0x5F, 0x0D, 0xF1, 0xC6, 0x2B, 0x9D, 0x6E, 0xEA, 0x9F, 0xC3, 0xC6,
0x87, 0xCD, 0x65, 0x74, 0xEB, 0x42, 0x5B, 0x21, 0x5F, 0xE3, 0xAA, 0x66, 0x44, 0xE2, 0xBE, 0x91,
0xC8, 0xF9, 0xDA, 0xEC, 0x04, 0x8C, 0x5B, 0x5B, 0x1F, 0x52, 0x09, 0x57, 0x26, 0x2E, 0x95, 0xFD,
0xCC, 0xF1, 0xDD, 0xB1, 0xBB, 0x98, 0x33, 0xE6, 0x1A, 0xBE, 0x44, 0x1C, 0xC2, 0xD1, 0x33, 0xB7,
0xE2, 0x26, 0xDE, 0xC5, 0x0E, 0x28, 0x6A, 0x30, 0xD3, 0x81, 0x42, 0xDA, 0x48, 0x0E, 0x6F, 0xB2,
0x33, 0xF3, 0xB3, 0x0C, 0xE1, 0x29, 0xB0, 0x77, 0xA0, 0x6C, 0xCB, 0x17, 0xE1, 0x81, 0x66, 0x8F,
0x77, 0xDD, 0x3E, 0x5D, 0x46, 0x84, 0xF2, 0x9B, 0x7F, 0xAE, 0x2A, 0x8A, 0xCF, 0xB6, 0xB5, 0x92,
0xF0, 0x8B, 0x1C, 0xF4, 0x38, 0x7F, 0x14, 0x26, 0x00, 0x4B, 0x90, 0x55, 0x11, 0x35, 0xE5, 0x84,
0x50, 0x5A, 0x79, 0x53, 0x5B, 0xB3, 0xB2, 0xD7, 0xAB, 0x6D, 0x87, 0xC5, 0x94, 0xF9, 0x6F, 0xEA,
0x74, 0xA0, 0x6F, 0x72, 0x76, 0x66, 0x76, 0xBB, 0x74, 0xF4, 0x4A, 0x69, 0x37, 0x7C, 0xC9, 0x0D,
0x3F, 0xDE, 0x47, 0xE9, 0x56, 0xC0, 0x97, 0x9A, 0x3A, 0xB0, 0x05, 0xDC, 0x99, 0xA4, 0x9C, 0x1C,
0x49, 0x57, 0xBE, 0xB0, 0xE1, 0xB0, 0xDC, 0xAD, 0xC1, 0xCC, 0x31, 0xB1, 0x4B, 0xE2, 0x63, 0x7F,
0x7A, 0xB4, 0x84, 0x55, 0x5E, 0xEF, 0x85, 0x4B, 0xD7, 0x5D, 0x60, 0x9B, 0x82, 0x47, 0x3C, 0x45,
0x68, 0x5F, 0xCB, 0x59, 0x0E, 0xA3, 0x62, 0x61, 0xE6, 0x1B, 0x7D, 0x29, 0x36, 0x04, 0x57, 0x7E,
0x73, 0xC6, 0x92, 0x52, 0x1C, 0x07, 0x47, 0xA1, 0x46, 0x9C, 0x55, 0x68, 0xFB, 0xC1, 0x11, 0x5A,
0x85, 0x08, 0x09, 0xE7, 0xF9, 0x81, 0xC8, 0x0A, 0xEC, 0x5A, 0x46, 0x49, 0x5A, 0x84, 0xB0, 0xEF,
0x90, 0x79, 0xD8, 0xC5, 0x26, 0xBE, 0x1E, 0xF5, 0x5E, 0xBE, 0x6F, 0x39, 0xD0, 0x96, 0xD1, 0x3B,
0xAD, 0xD7, 0x2A, 0x91, 0xB5, 0x48, 0x58, 0x10, 0x79, 0x9B, 0x05, 0x98, 0x66, 0xAD, 0xF8, 0x38,
0xC4, 0xF6, 0x56, 0xCC, 0xF3, 0x7D, 0x4B, 0xB6, 0x97, 0xD6, 0xE8, 0x8B, 0xC5, 0xFB, 0x83, 0x69,
0x46, 0xE1, 0x4E, 0xF5, 0x67, 0xD5, 0x7B, 0x06, 0x8C, 0x5A, 0x82, 0x5C, 0x60, 0x33, 0xB1, 0xD3,
0x50, 0x30, 0x52, 0x4E, 0xDA, 0x85, 0xBA, 0x98, 0x30, 0xB4, 0xAB, 0x22, 0x49, 0xC9, 0xD8, 0xB2,
0xE0, 0x63, 0x1F, 0x12, 0x32, 0x1E, 0xD6, 0x05, 0x21, 0x86, 0x58, 0x53, 0x4F, 0xEA, 0x2A, 0x59,
0x75, 0x35, 0x2D, 0x1A, 0x82, 0xEF, 0x8C, 0x71, 0x3B, 0xCD, 0x78, 0x32, 0xE8, 0xD2, 0x30, 0x12,
0x79, 0x22, 0x4F, 0x4D, 0xAE, 0xFF, 0xA5, 0x48, 0x3C, 0xCA, 0x5F, 0x6A, 0x14, 0xB4, 0x96, 0xB7,
0x6C, 0xD9, 0xC1, 0xD7, 0x24, 0xF7, 0xDE, 0x14, 0x70, 0x70, 0x14, 0xEE, 0x68, 0x4F, 0x39, 0x36,
0xA9, 0xE0, 0x27, 0xEC, 0xFE, 0x03, 0x5D, 0x2D, 0xBD, 0x66, 0x8E, 0xA7, 0xB4, 0x1F, 0xA0, 0x94,
0xBE, 0x51, 0xDE, 0x44, 0xEC, 0xE4, 0x6D, 0xFD, 0xAA, 0xAF, 0x9C, 0x3A, 0x41, 0x87, 0xF8, 0x4E,
0x8A, 0xF4, 0x08, 0xC5, 0xB3, 0xD0, 0xB9, 0x1B, 0x23, 0x77, 0x7F, 0x39, 0x9D, 0xAE, 0x0C, 0xAA,
0x63, 0xB3, 0x99, 0x84, 0xFB, 0xCE, 0x79, 0x7A, 0x34, 0x78, 0x36, 0xA2, 0x38, 0xF8, 0xF7, 0x2E,
0x0C, 0x7D, 0xBD, 0xBB, 0xF8, 0x5B, 0x5F, 0x33, 0x92, 0x9E, 0x01, 0xEA, 0x85, 0x77, 0xA7, 0xB0,
0xA5, 0x06, 0xEB, 0xF3, 0x75, 0x5A, 0x2E, 0xDD, 0xD6, 0x7A, 0x07, 0xE4, 0x24, 0x24, 0xC7, 0x76,
0x52, 0xAD, 0x7B, 0x3C, 0x45, 0x29, 0xB8, 0x01, 0x32, 0xE5, 0x85, 0x0F, 0x2F, 0x50, 0x19, 0x54,
0x20, 0x67, 0x58, 0x8B, 0x65, 0xFA, 0x4D, 0x4E, 0xA9, 0x70, 0xC4, 0x9A, 0x3C, 0xF3, 0xB0, 0x35,
0x02, 0x13, 0x5B, 0xD6, 0xB2, 0x63, 0xBF, 0x2E, 0xB4, 0xB6, 0xC1, 0x7C, 0x8B, 0x75, 0xF6, 0x99,
0x67, 0xBC, 0xC6, 0xA1, 0xB1, 0x58, 0xF1, 0x72, 0x3A, 0x92, 0x31, 0x5C, 0x2C, 0x2E, 0x1D, 0xF3,
0x09, 0x9E, 0xD3, 0x18, 0xDB, 0x39, 0x11, 0x3D, 0xE1, 0x9D, 0x7B, 0xE3, 0x4D, 0x16, 0xCF, 0x4F,
0x2B, 0xF9, 0x97, 0xEF, 0x6A, 0x3A, 0x30, 0xAE, 0x6B, 0x90, 0x85, 0x51, 0x14, 0x78, 0xD9, 0xDF,
0xDC, 0x65, 0x3C, 0x83, 0xF5, 0xE9, 0xE6, 0xB5, 0x8B, 0x42, 0x8C, 0xBE, 0x05, 0x78, 0xE4, 0x9A,
0xBA, 0x21, 0xD4, 0x30, 0x48, 0x89, 0x92, 0xE4, 0x7E, 0xF9, 0x43, 0x4D, 0x2C, 0xDF, 0xDE, 0x8E,
0x63, 0xFB, 0x40, 0xDD, 0x0E, 0x2C, 0x34, 0x4F, 0x44, 0xAE, 0x29, 0xA2, 0x48, 0x58, 0x60, 0xC8,
0xC7, 0x64, 0x1F, 0x69, 0x99, 0xD1, 0x01, 0x91, 0x81, 0x42, 0x54, 0x10, 0xBE, 0x82, 0x18, 0x39,
0x78, 0xE4, 0x4E, 0xFA, 0xB6, 0xE6, 0x48, 0xB8, 0x36, 0x65, 0xDF, 0x00, 0xF0, 0x12, 0x60, 0xB3,
0x74, 0x28, 0x1E, 0x68, 0xF1, 0x40, 0x9A, 0x29, 0xA1, 0xBB, 0x21, 0x9D, 0x96, 0x61, 0x8F, 0x85,
0x6C, 0x88, 0x58, 0x91, 0x79, 0xF6, 0x88, 0x3A, 0x9A, 0x54, 0xC0, 0xE5, 0x13, 0x88, 0x30, 0x4A,
0x65, 0xE1, 0x8D, 0x0D, 0x10, 0x61, 0xD8, 0xA5, 0x90, 0x02, 0xED, 0xA6, 0xE9, 0x49, 0xD9, 0xC7,
0x86, 0x2C, 0xFF, 0xAC, 0xD6, 0x4E, 0xED, 0x5C, 0x4F, 0xA2, 0x8E, 0xF9, 0x18, 0x3B, 0xDE, 0x16,
0x04, 0xD2, 0x75, 0xEC, 0x15, 0x9F, 0xF0, 0x01, 0xB5, 0xE7, 0x0C, 0x96, 0xBE, 0xC4, 0xBE, 0xEA,
0xDB, 0xB7, 0x2B, 0xFC, 0x73, 0x6A, 0x1D, 0x0B, 0x74, 0xD8, 0x64, 0x57, 0xD0, 0xB9, 0x4F, 0x9A,
0x72, 0x74, 0x07, 0xC5, 0x8D, 0xDB, 0x81, 0x4C, 0x13, 0x77, 0xCD, 0xDA, 0x01, 0x8E, 0xDF, 0xFF,
0xCA, 0x11, 0x62, 0x37, 0xC9, 0xAC, 0xFD, 0x94, 0xF4, 0xCC, 0x42, 0xC7, 0x9B, 0xD1, 0xF9, 0x4D,
0x85, 0x3B, 0xDC, 0xBF, 0xFC, 0x20, 0x4D, 0xE1, 0x52, 0xCD, 0x29, 0xF1, 0x7D, 0x2A, 0x54, 0xA1,
0x2E, 0x18, 0x7B, 0xDD, 0x05, 0xE0, 0x36, 0x7D, 0x7C, 0x40, 0x11, 0x9A, 0xC8, 0xE1, 0x63, 0x39,
0x7D, 0x72, 0x54, 0xB2, 0x1C, 0xE1, 0x40, 0x13, 0x6A, 0x1F, 0x76, 0xB1, 0xAD, 0x75, 0xE3, 0x24,
0x7B, 0x3F, 0xA9, 0xCA, 0xFD, 0x28, 0x76, 0x6F, 0x65, 0x63, 0xA7, 0xCC, 0x71, 0x84, 0xE3, 0x04,
0xC5, 0x05, 0x17, 0x5A, 0x1F, 0xD0, 0xEA, 0x69, 0xBB, 0x7A, 0xE1, 0xA1, 0xB0, 0xFB, 0xE0, 0xD2,
0x70, 0x1F, 0x6B, 0x5C, 0x86, 0xE4, 0xDE, 0x8C, 0x5C, 0xC8, 0x36, 0xA9, 0xDD, 0x5D, 0x13, 0x82,
0xDB, 0x6E, 0x93, 0x00, 0x77, 0x8C, 0xE1, 0xD3, 0x9A, 0x0C, 0x4D, 0xF4, 0x5A, 0x10, 0xDB, 0xBF,
0x3D, 0xD0, 0x6C, 0x4E, 0xEC, 0x64, 0xA2, 0xF4, 0x5D, 0x29, 0x80, 0x4B, 0xE7, 0xA1, 0x14, 0xAE,
0xB4, 0x78, 0x8B, 0x6E, 0xCB, 0xA1, 0xB2, 0x02, 0x35, 0xC7, 0x4E, 0x58, 0x7C, 0x98, 0x46, 0x05,
0xCE, 0x56, 0x83, 0xB4, 0x5E, 0x82, 0x65, 0xF1, 0xC9, 0x9B, 0x29, 0xAC, 0x42, 0xEB, 0xE5, 0xF1,
0x1D, 0x1A, 0x11, 0x0C, 0x63, 0xAD, 0xFD, 0xCF, 0x40, 0x68, 0xF1, 0xB3, 0xF4, 0x62, 0xB9, 0x9B,
0x6C, 0x6C, 0x13, 0x94, 0xAF, 0x82, 0x80, 0xE0, 0xBA, 0x6C, 0xCB, 0x84, 0x0D, 0xA0, 0xE4, 0xAA,
0x15, 0x8F, 0xAD, 0x29, 0xCE, 0x79, 0x7B, 0xF6, 0x70, 0x3F, 0xCC, 0x8B, 0x92, 0xA9, 0xC5, 0x17,
0xBA, 0xE0, 0xF0, 0x9B, 0x96, 0x8E, 0x7F, 0xA0, 0x2F, 0x2C, 0x5F, 0x54, 0xF6, 0x5A, 0x9E, 0xBB,
0xAC, 0x6C, 0xE3, 0xBF, 0xC4, 0x1C, 0xAB, 0x14, 0xEB, 0x12, 0xCE, 0xD8, 0xA5, 0x44, 0xCC, 0x4F,
0x4B, 0x08, 0x4F, 0x2C, 0x00, 0x81, 0xD5, 0x17, 0x22, 0x07, 0x42, 0xCA, 0xFD, 0x49, 0xAD, 0x06,
0x95, 0xC8, 0xD1, 0xC7, 0x3E, 0x39, 0x34, 0x1C, 0x41, 0x99, 0xC2, 0xAB, 0x8A, 0xED, 0x50, 0x12,
0xE8, 0xC7, 0x75, 0x52, 0x19, 0x2B, 0xD4, 0xCC, 0xD3, 0xFA, 0x84, 0xE7, 0x0C, 0xCE, 0xE3, 0x93,
0xCA, 0x60, 0xE5, 0xB7, 0x06, 0xDB, 0x84, 0xEE, 0x79, 0xA7, 0x54, 0x76, 0xE9, 0x46, 0x85, 0xEA,
0x4F, 0xF3, 0xA1, 0xEF, 0x10, 0xC1, 0x4C, 0x12, 0xB0, 0xEE, 0x23, 0xDD, 0x81, 0x3A, 0x6E, 0x9F,
0x01, 0x03, 0x04, 0x7C, 0x6D, 0x47, 0x84, 0xB7, 0xE7, 0x19, 0xE3, 0x4E, 0xCF, 0x23, 0x3A, 0xB2,
0x23, 0x33, 0x00, 0xCB, 0x07, 0x78, 0x51, 0x8B, 0x0C, 0x30, 0x7A, 0x1F, 0x41, 0x14, 0x75, 0xFF,
0x9E, 0x43, 0x81, 0xF4, 0x15, 0x89, 0x8B, 0xB7, 0x2B, 0xBC, 0x62, 0x48, 0x64, 0xD9, 0x26, 0xBE,
0xEA, 0x22, 0xB6, 0x22, 0xB8, 0x6F, 0x2B, 0xB6, 0x9B, 0x8F, 0xC7, 0x63, 0x03, 0x83, 0xA7, 0x22,
0xF8, 0x5C, 0x08, 0x87, 0x70, 0xA2, 0xCB, 0x6B, 0xD6, 0xC9, 0xF6, 0x59, 0x60, 0x8C, 0x10, 0xFA,
0x3C, 0xAD, 0x15, 0x1F, 0x8F, 0x18, 0x01, 0x2F, 0xB9, 0x2C, 0x01, 0x59, 0x76, 0x18, 0xE6, 0x55,
0x98, 0x23, 0x33, 0xA9, 0x05, 0xFC, 0x4C, 0xF3, 0x9A, 0xCB, 0xBA, 0x42, 0x60, 0x0C, 0x50, 0xEB,
0x69, 0xF1, 0x22, 0x73, 0x03, 0x4B, 0x38, 0x74, 0xBF, 0xBB, 0x7B, 0x4C, 0x7F, 0x30, 0xF0, 0x21,
0x8C, 0x73, 0x24, 0x69, 0x1F, 0x7F, 0xF4, 0x18, 0x0E, 0x4F, 0xB7, 0x99, 0x8D, 0x5A, 0xE9, 0xF7,
0x79, 0x8B, 0x25, 0xCB, 0xC7, 0xEA, 0x8C, 0xA7, 0x36, 0x33, 0x72, 0x78, 0x2B, 0x9B, 0x4F, 0xA6,
0x53, 0x10, 0xFA, 0xF7, 0x1C, 0x66, 0xBB, 0x7C, 0x72, 0x18, 0xBE, 0x91, 0x5C, 0x8C, 0xED, 0x75,
0x3C, 0x35, 0xF4, 0x49, 0xAD, 0xB0, 0x49, 0x67, 0x05, 0x6B, 0x96, 0x46, 0xF9, 0x2D, 0xAF, 0x8E,
0x90, 0x3A, 0x3C, 0x35, 0xF5, 0x66, 0x7E, 0xE8, 0x08, 0x04, 0x0D, 0xBF, 0x6E, 0x4E, 0x3F, 0xAD,
0x0A, 0xB9, 0x06, 0xDF, 0x4B, 0xD1, 0x9E, 0x5D, 0x69, 0x13, 0x4B, 0xCD, 0xB6, 0xE7, 0x4A, 0x12,
0xF2, 0x94, 0xE7, 0xBF, 0xE3, 0x48, 0x65, 0xF2, 0xD4, 0x2A, 0xCF, 0x17, 0xC2, 0xF8, 0x50, 0xC5,
0xF9, 0x3B, 0x11, 0x04, 0x5D, 0x84, 0xE6, 0x9D, 0x97, 0x31, 0xDB, 0x72, 0xD0, 0x0F, 0x3E, 0x9D,
0xE2, 0x5B, 0x81, 0xBF, 0xA3, 0xD1, 0xB3, 0x5D, 0xCF, 0x1D, 0x70, 0xDA, 0x96, 0xA3, 0x67, 0xA5,
0xF0, 0x5E, 0xA8, 0xD0, 0x15, 0x47, 0x3D, 0xAE, 0xA4, 0x33, 0x80, 0xF4, 0x90, 0x1B, 0x27, 0xD1,
0x4F, 0x5F, 0x2B, 0x1B, 0xA4, 0xEE, 0x5F, 0x3F, 0x0F, 0xC2, 0x70, 0x13, 0x42, 0x42, 0x98, 0xA1,
0xC4, 0x0C, 0x2B, 0xB1, 0x4E, 0x69, 0xF2, 0xBC, 0xB5, 0x21, 0xD2, 0x11, 0xA4, 0xC0, 0x3D, 0xB5,
0xBE, 0x4D, 0x0E, 0x84, 0x9B, 0xF0, 0xB0, 0x0F, 0xE2, 0x23, 0xCB, 0x66, 0x1D, 0x71, 0x4F, 0x20,
0x2F, 0x3C, 0x9D, 0xC8, 0x3C, 0x11, 0xA7, 0x47, 0xA4, 0x3A, 0x46, 0xB0, 0x83, 0xA1, 0xD5, 0x34,
0x71, 0xA9, 0x8B, 0xDB, 0xBA, 0x6F, 0x49, 0xDC, 0x69, 0xC9, 0xBF, 0xFE, 0x6C, 0x12, 0x58, 0x3B,
0xDB, 0x42, 0x39, 0xF7, 0x63, 0xB2, 0xA4, 0xFD, 0x30, 0x48, 0x45, 0x4E, 0x02, 0x15, 0xBD, 0x4B,
0xC3, 0x59, 0xF9, 0x0B, 0x87, 0x1F, 0xEB, 0x90, 0x0E, 0x69, 0x0C, 0xDE, 0x15, 0xD8, 0x7C, 0xA6,
0xD2, 0xFC, 0xFA, 0x5F, 0xAE, 0x9B, 0x7D, 0x76, 0x0B, 0xE8, 0x53, 0xE4, 0x10, 0xC6, 0xF0, 0x3D,
0x33, 0x7A, 0x9E, 0x4E, 0x57, 0x0F, 0x58, 0xB6, 0x13, 0x95, 0x89, 0x6D, 0x84, 0xC6, 0xB2, 0x22,
0x37, 0xF9, 0x99, 0x27, 0xCC, 0x10, 0xA2, 0x14, 0x84, 0x0F, 0x9A, 0xE7, 0xB9, 0x52, 0xFC, 0xB3,
0x3F, 0x97, 0x07, 0x67, 0xB5, 0xE5, 0x00, 0x32, 0x4D, 0x90, 0x4D, 0x6A, 0xFE, 0x17, 0xE1, 0xAC,
0x58, 0x00, 0x69, 0xC8, 0x7A, 0x87, 0x60, 0x10, 0xB5, 0x9C, 0x64, 0xFC, 0xAE, 0xD8, 0x86, 0x88,
0x5A, 0x76, 0xED, 0x72, 0x97, 0x19, 0x90, 0xD1, 0xE3, 0xAF, 0x6E, 0x07, 0x2F, 0x7A, 0xBB, 0xC6,
0x0D, 0x63, 0x53, 0x23, 0x00, 0xA3, 0x8A, 0x88, 0x2B, 0x21, 0x9C, 0x3A, 0x9D, 0x86, 0x16, 0xE9,
};

212
src/core/hw/ecc.cpp Normal file
View File

@ -0,0 +1,212 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/hw/aes/key.h"
#include "core/hw/ecc.h"
#include "cryptopp/osrng.h"
namespace HW::ECC {
PublicKey root_public;
CryptoPPInteger PrivateKey::AsCryptoPPInteger() const {
return CryptoPP::Integer(x.data(), x.size(), CryptoPP::Integer::UNSIGNED,
CryptoPP::BIG_ENDIAN_ORDER);
}
CryptoPPECCPrivateKey PrivateKey::AsCryptoPPPrivateKey() const {
CryptoPPECCPrivateKey private_key_cpp;
CryptoPP::AutoSeededRandomPool prng;
private_key_cpp.Initialize(CryptoPP::ASN1::sect233r1(), AsCryptoPPInteger());
if (!private_key_cpp.Validate(prng, 3)) {
LOG_ERROR(HW, "Failed to verify ECC private key");
}
return private_key_cpp;
}
CryptoPPPoint PublicKey::AsCryptoPPPoint() const {
return CryptoPP::EC2N::Point(CryptoPP::PolynomialMod2(x.data(), x.size()),
CryptoPP::PolynomialMod2(y.data(), y.size()));
}
CryptoPPECCPublicKey PublicKey::AsCryptoPPPublicKey() const {
CryptoPPECCPublicKey public_key_cpp;
public_key_cpp.Initialize(CryptoPP::ASN1::sect233r1(), AsCryptoPPPoint());
return public_key_cpp;
}
std::vector<u8> HexToVector(const std::string& hex) {
std::vector<u8> vector(hex.size() / 2);
for (std::size_t i = 0; i < vector.size(); ++i) {
vector[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), nullptr, 16));
}
return vector;
}
void InitSlots() {
static bool initialized = false;
if (initialized)
return;
initialized = true;
auto s = HW::AES::GetKeysStream();
std::string mode = "";
while (!s.eof()) {
std::string line;
std::getline(s, line);
// Ignore empty or commented lines.
if (line.empty() || line.starts_with("#")) {
continue;
}
if (line.starts_with(":")) {
mode = line.substr(1);
continue;
}
if (mode != "ECC") {
continue;
}
const auto parts = Common::SplitString(line, '=');
if (parts.size() != 2) {
LOG_ERROR(HW_RSA, "Failed to parse {}", line);
continue;
}
const std::string& name = parts[0];
std::vector<u8> key;
try {
key = HexToVector(parts[1]);
} catch (const std::logic_error& e) {
LOG_ERROR(HW_RSA, "Invalid key {}: {}", parts[1], e.what());
continue;
}
if (name == "rootPublicXY") {
memcpy(root_public.xy.data(), key.data(), std::min(root_public.xy.size(), key.size()));
continue;
}
}
}
PrivateKey CreateECCPrivateKey(std::span<const u8> private_key_x, bool fix_up) {
CryptoPPECCPrivateKey private_key;
CryptoPPInteger privk_x(private_key_x.data(), private_key_x.size(), CryptoPP::Integer::UNSIGNED,
CryptoPP::BIG_ENDIAN_ORDER);
// The ECC library Nintendo used to generate private keys does not limit the private key
// size to be inside the subgroup order. To fix this, we do a modulo operation with the
// subgroup order, otherwise CryptoPP will fail to use the key.
if (fix_up) {
CryptoPP::DL_GroupParameters_EC<CryptoPP::EC2N> params(CryptoPP::ASN1::sect233r1());
privk_x = privk_x % params.GetSubgroupOrder();
}
private_key.Initialize(CryptoPP::ASN1::sect233r1(), privk_x);
PrivateKey ret;
private_key.GetPrivateExponent().Encode(ret.x.data(), ret.x.size());
return ret;
}
PublicKey CreateECCPublicKey(std::span<const u8> public_key_xy) {
ASSERT_MSG(public_key_xy.size() <= sizeof(PublicKey::xy), "Invalid public key length");
PublicKey ret;
memcpy(ret.xy.data(), public_key_xy.data(), ret.xy.size());
return ret;
}
Signature CreateECCSignature(std::span<const u8> signature_rs) {
ASSERT_MSG(signature_rs.size() <= sizeof(Signature::rs), "Invalid signature length");
Signature ret;
memcpy(ret.rs.data(), signature_rs.data(), ret.rs.size());
return ret;
}
PublicKey MakePublicKey(const CryptoPPECCPrivateKey& private_key_cpp) {
CryptoPPECCPublicKey public_key_cpp;
PublicKey public_key;
private_key_cpp.MakePublicKey(public_key_cpp);
public_key_cpp.GetPublicElement().x.Encode(public_key.x.data(), public_key.x.size());
public_key_cpp.GetPublicElement().y.Encode(public_key.y.data(), public_key.y.size());
return public_key;
}
PublicKey MakePublicKey(const PrivateKey& private_key) {
return MakePublicKey(private_key.AsCryptoPPPrivateKey());
}
std::pair<PrivateKey, PublicKey> GenerateKeyPair() {
CryptoPPECCPrivateKey private_key_cpp;
PrivateKey private_key;
CryptoPP::AutoSeededRandomPool prng;
private_key_cpp.Initialize(prng, CryptoPP::ASN1::sect233r1());
private_key_cpp.GetPrivateExponent().Encode(private_key.x.data(), private_key.x.size());
return std::make_pair(private_key, MakePublicKey(private_key_cpp));
}
Signature Sign(std::span<const u8> data, PrivateKey private_key) {
CryptoPP::ECDSA<CryptoPP::EC2N, CryptoPP::SHA256>::Signer signer(
private_key.AsCryptoPPPrivateKey());
CryptoPP::AutoSeededRandomPool prng;
Signature ret;
signer.SignMessage(prng, data.data(), data.size(), ret.rs.data());
return ret;
}
bool Verify(std::span<const u8> data, Signature signature, PublicKey public_key) {
CryptoPP::ECDSA<CryptoPP::EC2N, CryptoPP::SHA256>::Verifier verifier(
public_key.AsCryptoPPPublicKey());
return verifier.VerifyMessage(data.data(), data.size(), signature.rs.data(),
signature.rs.size());
}
std::vector<u8> Agree(PrivateKey private_key, PublicKey others_public_key) {
CryptoPP::ECDH<CryptoPP::EC2N, CryptoPP::NoCofactorMultiplication>::Domain domain(
CryptoPP::ASN1::sect233r1());
CryptoPP::DL_GroupParameters_EC<CryptoPP::EC2N> params(CryptoPP::ASN1::sect233r1());
std::vector<u8> agreement(domain.AgreedValueLength());
std::vector<u8> private_encoded(domain.PrivateKeyLength());
private_key.AsCryptoPPInteger().Encode(private_encoded.data(), private_encoded.size());
std::vector<u8> others_public_encoded(params.GetEncodedElementSize(true));
params.EncodeElement(true, others_public_key.AsCryptoPPPoint(), others_public_encoded.data());
if (!domain.Agree(agreement.data(), private_encoded.data(), others_public_encoded.data())) {
LOG_ERROR(HW, "ECDH agreement failed");
}
return agreement;
}
const PublicKey& GetRootPublicKey() {
return root_public;
}
} // namespace HW::ECC

70
src/core/hw/ecc.h Normal file
View File

@ -0,0 +1,70 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <span>
#include <vector>
#include <cryptopp/oids.h>
#include "common/common_types.h"
#include "cryptopp/eccrypto.h"
namespace HW::ECC {
// All the supported 3DS ECC operations use the sect233r1 curve,
// so we default to sizes for this curve only.
static constexpr size_t INT_SIZE = 0x1E;
using CryptoPPInteger = CryptoPP::Integer;
using CryptoPPPoint = CryptoPP::EC2N::Point;
using CryptoPPECCPrivateKey = CryptoPP::ECDSA<CryptoPP::EC2N, CryptoPP::SHA256>::PrivateKey;
using CryptoPPECCPublicKey = CryptoPP::ECDSA<CryptoPP::EC2N, CryptoPP::SHA256>::PublicKey;
struct PrivateKey {
std::array<u8, INT_SIZE> x;
CryptoPPInteger AsCryptoPPInteger() const;
CryptoPPECCPrivateKey AsCryptoPPPrivateKey() const;
};
union PublicKey {
struct {
std::array<u8, INT_SIZE> x;
std::array<u8, INT_SIZE> y;
};
std::array<u8, INT_SIZE * 2> xy;
CryptoPPPoint AsCryptoPPPoint() const;
CryptoPPECCPublicKey AsCryptoPPPublicKey() const;
};
union Signature {
struct {
std::array<u8, INT_SIZE> r;
std::array<u8, INT_SIZE> s;
};
std::array<u8, INT_SIZE * 2> rs;
};
void InitSlots();
PrivateKey CreateECCPrivateKey(std::span<const u8> private_key_x, bool fix_up = false);
PublicKey CreateECCPublicKey(std::span<const u8> public_key_xy);
Signature CreateECCSignature(std::span<const u8> signature_rs);
PublicKey MakePublicKey(const CryptoPPECCPrivateKey& private_key_cpp);
PublicKey MakePublicKey(const PrivateKey& private_key);
std::pair<PrivateKey, PublicKey> GenerateKeyPair();
Signature Sign(std::span<const u8> data, PrivateKey private_key);
bool Verify(std::span<const u8> data, Signature signature, PublicKey public_key);
std::vector<u8> Agree(PrivateKey private_key, PublicKey others_public_key);
const PublicKey& GetRootPublicKey();
} // namespace HW::ECC

View File

@ -3,6 +3,8 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <sstream> #include <sstream>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <cryptopp/hex.h> #include <cryptopp/hex.h>
#include <cryptopp/integer.h> #include <cryptopp/integer.h>
#include <cryptopp/nbtheory.h> #include <cryptopp/nbtheory.h>
@ -11,39 +13,82 @@
#include "common/common_paths.h" #include "common/common_paths.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hw/aes/key.h"
#include "core/hw/rsa/rsa.h" #include "core/hw/rsa/rsa.h"
#include "cryptopp/osrng.h"
#include "cryptopp/rsa.h"
namespace HW::RSA { namespace HW::RSA {
namespace {
std::vector<u8> HexToBytes(const std::string& hex) {
std::vector<u8> bytes;
for (unsigned int i = 0; i < hex.length(); i += 2) {
std::string byteString = hex.substr(i, 2);
u8 byte = static_cast<u8>(std::strtol(byteString.c_str(), nullptr, 16));
bytes.push_back(byte);
}
return bytes;
};
} // namespace
constexpr std::size_t SlotSize = 4; constexpr std::size_t SlotSize = 4;
std::array<RsaSlot, SlotSize> rsa_slots; std::array<RsaSlot, SlotSize> rsa_slots;
std::vector<u8> RsaSlot::GetSignature(std::span<const u8> message) const { RsaSlot ticket_wrap_slot;
RsaSlot secure_info_slot;
RsaSlot local_friend_code_seed_slot;
std::vector<u8> RsaSlot::ModularExponentiation(std::span<const u8> message,
int out_size_bytes) const {
CryptoPP::Integer sig = CryptoPP::Integer sig =
CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()), CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
CryptoPP::Integer(exponent.data(), exponent.size()), CryptoPP::Integer(exponent.data(), exponent.size()),
CryptoPP::Integer(modulus.data(), modulus.size())); CryptoPP::Integer(modulus.data(), modulus.size()));
std::stringstream ss;
ss << std::hex << sig; std::vector<u8> result((out_size_bytes == -1) ? sig.MinEncodedSize() : out_size_bytes);
CryptoPP::HexDecoder decoder; sig.Encode(result.data(), result.size());
decoder.Put(reinterpret_cast<unsigned char*>(ss.str().data()), ss.str().size()); return result;
decoder.MessageEnd(); }
std::vector<u8> result(decoder.MaxRetrievable());
decoder.Get(result.data(), result.size()); std::vector<u8> RsaSlot::Sign(std::span<const u8> message) const {
return HexToBytes(ss.str()); if (private_d.empty()) {
LOG_ERROR(HW, "Cannot sign, RSA slot does not have a private key");
return {};
}
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::PrivateKey private_key;
private_key.Initialize(CryptoPP::Integer(modulus.data(), modulus.size()),
CryptoPP::Integer(exponent.data(), exponent.size()),
CryptoPP::Integer(private_d.data(), private_d.size()));
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Signer signer(private_key);
CryptoPP::AutoSeededRandomPool prng;
std::vector<u8> ret(signer.SignatureLength());
signer.SignMessage(prng, message.data(), message.size(), ret.data());
return ret;
}
bool RsaSlot::Verify(std::span<const u8> message, std::span<const u8> signature) const {
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::PublicKey public_key;
public_key.Initialize(CryptoPP::Integer(modulus.data(), modulus.size()),
CryptoPP::Integer(exponent.data(), exponent.size()));
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(public_key);
return verifier.VerifyMessage(message.data(), message.size(), signature.data(),
signature.size());
}
std::vector<u8> HexToVector(const std::string& hex) {
std::vector<u8> vector(hex.size() / 2);
for (std::size_t i = 0; i < vector.size(); ++i) {
vector[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), nullptr, 16));
}
return vector;
}
std::optional<std::pair<std::size_t, char>> ParseKeySlotName(const std::string& full_name) {
std::size_t slot;
char type;
int end;
if (std::sscanf(full_name.c_str(), "slot0x%zX%c%n", &slot, &type, &end) == 2 &&
end == static_cast<int>(full_name.size())) {
return std::make_pair(slot, type);
} else {
return std::nullopt;
}
} }
void InitSlots() { void InitSlots() {
@ -52,62 +97,119 @@ void InitSlots() {
return; return;
initialized = true; initialized = true;
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9; auto s = HW::AES::GetKeysStream();
FileUtil::IOFile file(filepath, "rb");
if (!file) { std::string mode = "";
return;
while (!s.eof()) {
std::string line;
std::getline(s, line);
// Ignore empty or commented lines.
if (line.empty() || line.starts_with("#")) {
continue;
}
if (line.starts_with(":")) {
mode = line.substr(1);
continue;
}
if (mode != "RSA") {
continue;
}
const auto parts = Common::SplitString(line, '=');
if (parts.size() != 2) {
LOG_ERROR(HW_RSA, "Failed to parse {}", line);
continue;
}
const std::string& name = parts[0];
std::vector<u8> key;
try {
key = HexToVector(parts[1]);
} catch (const std::logic_error& e) {
LOG_ERROR(HW_RSA, "Invalid key {}: {}", parts[1], e.what());
continue;
}
if (name == "ticketWrapExp") {
ticket_wrap_slot.SetExponent(key);
continue;
}
if (name == "ticketWrapMod") {
ticket_wrap_slot.SetModulus(key);
continue;
}
if (name == "secureInfoExp") {
secure_info_slot.SetExponent(key);
continue;
}
if (name == "secureInfoMod") {
secure_info_slot.SetModulus(key);
continue;
}
if (name == "lfcsExp") {
local_friend_code_seed_slot.SetExponent(key);
continue;
}
if (name == "lfcsMod") {
local_friend_code_seed_slot.SetModulus(key);
continue;
}
const auto key_slot = ParseKeySlotName(name);
if (!key_slot) {
LOG_ERROR(HW_RSA, "Invalid key name '{}'", name);
continue;
}
if (key_slot->first >= SlotSize) {
LOG_ERROR(HW_RSA, "Out of range key slot ID {:#X}", key_slot->first);
continue;
}
switch (key_slot->second) {
case 'X':
rsa_slots.at(key_slot->first).SetExponent(key);
break;
case 'M':
rsa_slots.at(key_slot->first).SetModulus(key);
break;
case 'P':
rsa_slots.at(key_slot->first).SetPrivateD(key);
break;
default:
LOG_ERROR(HW_RSA, "Invalid key type '{}'", key_slot->second);
break;
}
} }
const std::size_t length = file.GetSize();
if (length != 65536) {
LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length);
return;
}
constexpr std::size_t RSA_MODULUS_POS = 0xB3E0;
file.Seek(RSA_MODULUS_POS, SEEK_SET);
std::vector<u8> modulus(256);
file.ReadArray(modulus.data(), modulus.size());
constexpr std::size_t RSA_EXPONENT_POS = 0xB4E0;
file.Seek(RSA_EXPONENT_POS, SEEK_SET);
std::vector<u8> exponent(256);
file.ReadArray(exponent.data(), exponent.size());
rsa_slots[0] = RsaSlot(std::move(exponent), std::move(modulus));
// TODO(B3N30): Initalize the other slots. But since they aren't used at all, we can skip them
// for now
} }
RsaSlot GetSlot(std::size_t slot_id) { static RsaSlot empty_slot;
const RsaSlot& GetSlot(std::size_t slot_id) {
if (slot_id >= rsa_slots.size()) if (slot_id >= rsa_slots.size())
return RsaSlot{}; return empty_slot;
return rsa_slots[slot_id]; return rsa_slots[slot_id];
} }
std::vector<u8> CreateASN1Message(std::span<const u8> data) { const RsaSlot& GetTicketWrapSlot() {
static constexpr std::array<u8, 224> asn1_header = { return ticket_wrap_slot;
{0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x31, 0x30, 0x0D, 0x06,
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}};
std::vector<u8> message(asn1_header.begin(), asn1_header.end()); const RsaSlot& GetSecureInfoSlot() {
CryptoPP::SHA256 sha; return secure_info_slot;
message.resize(message.size() + CryptoPP::SHA256::DIGESTSIZE); }
sha.CalculateDigest(message.data() + asn1_header.size(), data.data(), data.size());
return message; const RsaSlot& GetLocalFriendCodeSeedSlot() {
return local_friend_code_seed_slot;
} }
} // namespace HW::RSA } // namespace HW::RSA

View File

@ -15,23 +15,57 @@ public:
RsaSlot() = default; RsaSlot() = default;
RsaSlot(std::vector<u8> exponent, std::vector<u8> modulus) RsaSlot(std::vector<u8> exponent, std::vector<u8> modulus)
: init(true), exponent(std::move(exponent)), modulus(std::move(modulus)) {} : init(true), exponent(std::move(exponent)), modulus(std::move(modulus)) {}
std::vector<u8> GetSignature(std::span<const u8> message) const;
std::vector<u8> ModularExponentiation(std::span<const u8> message,
int out_size_bytes = -1) const;
std::vector<u8> Sign(std::span<const u8> message) const;
bool Verify(std::span<const u8> message, std::span<const u8> signature) const;
explicit operator bool() const { explicit operator bool() const {
// TODO(B3N30): Maybe check if exponent and modulus are vailid // TODO(B3N30): Maybe check if exponent and modulus are vailid
return init; return init;
} }
void SetExponent(const std::vector<u8>& e) {
exponent = e;
}
const std::vector<u8>& GetExponent() const {
return exponent;
}
void SetModulus(const std::vector<u8>& m) {
modulus = m;
}
const std::vector<u8>& GetModulus() const {
return modulus;
}
void SetPrivateD(const std::vector<u8>& d) {
private_d = d;
}
const std::vector<u8>& GetPrivateD() const {
return private_d;
}
private: private:
bool init = false; bool init = false;
std::vector<u8> exponent; std::vector<u8> exponent;
std::vector<u8> modulus; std::vector<u8> modulus;
std::vector<u8> private_d;
}; };
void InitSlots(); void InitSlots();
RsaSlot GetSlot(std::size_t slot_id); const RsaSlot& GetSlot(std::size_t slot_id);
std::vector<u8> CreateASN1Message(std::span<const u8> data); const RsaSlot& GetTicketWrapSlot();
const RsaSlot& GetSecureInfoSlot();
const RsaSlot& GetLocalFriendCodeSeedSlot();
} // namespace HW::RSA } // namespace HW::RSA

228
src/core/hw/unique_data.cpp Normal file
View File

@ -0,0 +1,228 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_paths.h"
#include "core/file_sys/certificate.h"
#include "core/file_sys/otp.h"
#include "core/hw/aes/key.h"
#include "core/hw/ecc.h"
#include "core/hw/rsa/rsa.h"
#include "core/hw/unique_data.h"
#include "core/loader/loader.h"
namespace HW::UniqueData {
static SecureInfoA secure_info_a;
static bool secure_info_a_signature_valid = false;
static LocalFriendCodeSeedB local_friend_code_seed_b;
static bool local_friend_code_seed_b_signature_valid = false;
static FileSys::OTP otp;
static FileSys::Certificate ct_cert;
static MovableSedFull movable;
static bool movable_signature_valid = false;
bool SecureInfoA::VerifySignature() const {
return HW::RSA::GetSecureInfoSlot().Verify(
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature);
}
bool LocalFriendCodeSeedB::VerifySignature() const {
return HW::RSA::GetLocalFriendCodeSeedSlot().Verify(
std::span<const u8>(reinterpret_cast<const u8*>(&body), sizeof(body)), signature);
}
bool MovableSed::VerifySignature() const {
return lfcs.VerifySignature();
}
SecureDataLoadStatus LoadSecureInfoA() {
if (secure_info_a.IsValid()) {
return secure_info_a_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
std::string file_path = GetSecureInfoAPath();
if (!FileUtil::Exists(file_path)) {
return SecureDataLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return SecureDataLoadStatus::IOError;
}
if (file.GetSize() != sizeof(SecureInfoA)) {
return SecureDataLoadStatus::Invalid;
}
if (file.ReadBytes(&secure_info_a, sizeof(SecureInfoA)) != sizeof(SecureInfoA)) {
secure_info_a.Invalidate();
return SecureDataLoadStatus::IOError;
}
secure_info_a_signature_valid = secure_info_a.VerifySignature();
if (!secure_info_a_signature_valid) {
LOG_WARNING(HW, "SecureInfo_A signature check failed");
}
return secure_info_a_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
SecureDataLoadStatus LoadLocalFriendCodeSeedB() {
if (local_friend_code_seed_b.IsValid()) {
return local_friend_code_seed_b_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
std::string file_path = GetLocalFriendCodeSeedBPath();
if (!FileUtil::Exists(file_path)) {
return SecureDataLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return SecureDataLoadStatus::IOError;
}
if (file.GetSize() != sizeof(LocalFriendCodeSeedB)) {
return SecureDataLoadStatus::Invalid;
}
if (file.ReadBytes(&local_friend_code_seed_b, sizeof(LocalFriendCodeSeedB)) !=
sizeof(LocalFriendCodeSeedB)) {
local_friend_code_seed_b.Invalidate();
return SecureDataLoadStatus::IOError;
}
local_friend_code_seed_b_signature_valid = local_friend_code_seed_b.VerifySignature();
if (!local_friend_code_seed_b_signature_valid) {
LOG_WARNING(HW, "LocalFriendCodeSeed_B signature check failed");
}
return local_friend_code_seed_b_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
SecureDataLoadStatus LoadOTP() {
if (otp.Valid()) {
return SecureDataLoadStatus::Loaded;
}
const std::string filepath = GetOTPPath();
auto otp_keyiv = HW::AES::GetOTPKeyIV();
auto loader_status = otp.Load(filepath, otp_keyiv.first, otp_keyiv.second);
if (loader_status != Loader::ResultStatus::Success) {
otp.Invalidate();
ct_cert.Invalidate();
return loader_status == Loader::ResultStatus::ErrorNotFound ? SecureDataLoadStatus::NotFound
: SecureDataLoadStatus::Invalid;
}
constexpr const char* issuer_ret = "Nintendo CA - G3_NintendoCTR2prod";
constexpr const char* issuer_dev = "Nintendo CA - G3_NintendoCTR2dev";
std::array<u8, 0x40> issuer = {0};
if (otp.IsDev()) {
memcpy(issuer.data(), issuer_dev, strlen(issuer_dev));
} else {
memcpy(issuer.data(), issuer_ret, strlen(issuer_ret));
}
std::string name_str = fmt::format("CT{:08X}-{:02X}", otp.GetDeviceID(), otp.GetSystemType());
std::array<u8, 0x40> name = {0};
memcpy(name.data(), name_str.data(), name_str.size());
ct_cert.BuildECC(issuer, name, otp.GetCTCertExpiration(),
HW::ECC::CreateECCPrivateKey(otp.GetCTCertPrivateKey(), true),
HW::ECC::CreateECCSignature(otp.GetCTCertSignature()));
if (!ct_cert.VerifyMyself(HW::ECC::GetRootPublicKey())) {
LOG_ERROR(HW, "CTCert failed verification");
otp.Invalidate();
ct_cert.Invalidate();
return SecureDataLoadStatus::IOError;
}
return SecureDataLoadStatus::Loaded;
}
SecureDataLoadStatus LoadMovable() {
if (movable.IsValid()) {
return movable_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
std::string file_path = GetMovablePath();
if (!FileUtil::Exists(file_path)) {
return SecureDataLoadStatus::NotFound;
}
FileUtil::IOFile file(file_path, "rb");
if (!file.IsOpen()) {
return SecureDataLoadStatus::IOError;
}
if (file.GetSize() != sizeof(MovableSedFull)) {
if (file.GetSize() == sizeof(MovableSed)) {
LOG_WARNING(HW, "Uninitialized movable.sed files are not supported");
}
return SecureDataLoadStatus::Invalid;
}
if (file.ReadBytes(&movable, sizeof(MovableSedFull)) != sizeof(MovableSedFull)) {
movable.Invalidate();
return SecureDataLoadStatus::IOError;
}
movable_signature_valid = movable.VerifySignature();
if (!movable_signature_valid) {
LOG_WARNING(HW, "LocalFriendCodeSeed_B signature check failed");
}
return movable_signature_valid ? SecureDataLoadStatus::Loaded
: SecureDataLoadStatus::InvalidSignature;
}
std::string GetSecureInfoAPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/SecureInfo_A";
}
std::string GetLocalFriendCodeSeedBPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/LocalFriendCodeSeed_B";
}
std::string GetOTPPath() {
return FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "otp.bin";
}
std::string GetMovablePath() {
return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "private/movable.sed";
}
SecureInfoA& GetSecureInfoA() {
LoadSecureInfoA();
return secure_info_a;
}
LocalFriendCodeSeedB& GetLocalFriendCodeSeedB() {
LoadLocalFriendCodeSeedB();
return local_friend_code_seed_b;
}
FileSys::Certificate& HW::UniqueData::GetCTCert() {
LoadOTP();
return ct_cert;
}
FileSys::OTP& GetOTP() {
LoadOTP();
return otp;
}
MovableSedFull& GetMovableSed() {
LoadMovable();
return movable;
}
void InvalidateSecureData() {
secure_info_a.Invalidate();
local_friend_code_seed_b.Invalidate();
otp.Invalidate();
ct_cert.Invalidate();
movable.Invalidate();
}
} // namespace HW::UniqueData

133
src/core/hw/unique_data.h Normal file
View File

@ -0,0 +1,133 @@
// Copyright 2024 Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <vector>
#include "common/common_types.h"
namespace FileSys {
class Certificate;
class OTP;
} // namespace FileSys
namespace HW::UniqueData {
struct SecureInfoA {
std::array<u8, 0x100> signature;
struct {
u8 region;
u8 unknown;
std::array<u8, 0xF> serial_number;
} body;
bool IsValid() const {
for (auto c : body.serial_number) {
if (c != 0) {
return true;
}
}
return false;
}
void Invalidate() {
memset(body.serial_number.data(), 0, body.serial_number.size());
}
bool VerifySignature() const;
};
static_assert(sizeof(SecureInfoA) == 0x111);
struct LocalFriendCodeSeedB {
std::array<u8, 0x100> signature;
struct {
u64 unknown;
u64 friend_code_seed;
} body;
bool IsValid() const {
return body.friend_code_seed != 0;
}
void Invalidate() {
body.friend_code_seed = 0;
}
bool VerifySignature() const;
};
static_assert(sizeof(LocalFriendCodeSeedB) == 0x110);
struct MovableSed {
static constexpr std::array<u8, 0x4> seed_magic{0x53, 0x45, 0x45, 0x44};
std::array<u8, 0x4> magic;
u32 seed_info;
LocalFriendCodeSeedB lfcs;
std::array<u8, 0x8> key_y;
bool IsValid() const {
return magic == seed_magic;
}
void Invalidate() {
magic = {0x0, 0x0, 0x0, 0x0};
}
bool VerifySignature() const;
};
static_assert(sizeof(MovableSed) == 0x120);
struct MovableSedFull {
struct {
MovableSed sed;
std::array<u8, 0x10> unknown;
} body;
std::array<u8, 0x10> aes_mac;
bool IsValid() const {
return body.sed.magic == MovableSed::seed_magic;
}
void Invalidate() {
body.sed.magic = {0x0, 0x0, 0x0, 0x0};
}
bool VerifySignature() const {
// TODO(PabloMK7): Implement AES MAC verification
return body.sed.VerifySignature();
}
};
static_assert(sizeof(MovableSedFull) == 0x140);
enum class SecureDataLoadStatus {
Loaded = 0,
InvalidSignature = 1,
NotFound = -1,
Invalid = -2,
IOError = -3,
};
SecureDataLoadStatus LoadSecureInfoA();
SecureDataLoadStatus LoadLocalFriendCodeSeedB();
SecureDataLoadStatus LoadOTP();
SecureDataLoadStatus LoadMovable();
std::string GetSecureInfoAPath();
std::string GetLocalFriendCodeSeedBPath();
std::string GetOTPPath();
std::string GetMovablePath();
SecureInfoA& GetSecureInfoA();
LocalFriendCodeSeedB& GetLocalFriendCodeSeedB();
FileSys::Certificate& GetCTCert();
FileSys::OTP& GetOTP();
MovableSedFull& GetMovableSed();
void InvalidateSecureData();
} // namespace HW::UniqueData

View File

@ -75,6 +75,7 @@ enum class ResultStatus {
ErrorEncrypted, ErrorEncrypted,
ErrorGbaTitle, ErrorGbaTitle,
ErrorArtic, ErrorArtic,
ErrorNotFound,
}; };
constexpr u32 MakeMagic(char a, char b, char c, char d) { constexpr u32 MakeMagic(char a, char b, char c, char d) {