mirror of
https://github.com/azahar-emu/azahar.git
synced 2025-03-04 01:35:21 +01:00
Merge pull request #4 from PabloMK7/amimprovements
Major revamps to match game loading decisions.
This commit is contained in:
commit
5a0ad29bab
@ -507,8 +507,8 @@ void GMainWindow::InitializeWidgets() {
|
||||
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
|
||||
"indicate emulation is running faster or slower than a 3DS."));
|
||||
game_fps_label = new QLabel();
|
||||
game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. "
|
||||
"This will vary from game to game and scene to scene."));
|
||||
game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
|
||||
"This will vary from app to app and scene to scene."));
|
||||
emu_frametime_label = new QLabel();
|
||||
emu_frametime_label->setToolTip(
|
||||
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_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
|
||||
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_Connect_To_Room,
|
||||
QStringLiteral("Multiplayer Direct Connect to Room"));
|
||||
@ -779,7 +779,7 @@ void GMainWindow::InitializeHotkeys() {
|
||||
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());
|
||||
UpdateStatusBar();
|
||||
});
|
||||
@ -1164,7 +1164,7 @@ void GMainWindow::OnUpdateFound(bool found, bool error) {
|
||||
}
|
||||
|
||||
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;
|
||||
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
|
||||
//: computer from sleeping
|
||||
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
|
||||
QDBusReply<QDBusObjectPath> reply =
|
||||
xdp.call(QString::fromLatin1("Inhibit"),
|
||||
@ -1295,8 +1295,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
case Core::System::ResultStatus::ErrorGetLoader:
|
||||
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filename.toStdString());
|
||||
QMessageBox::critical(
|
||||
this, tr("Invalid ROM Format"),
|
||||
tr("Your ROM format is not supported.<br/>Please follow the guides to redump your "
|
||||
this, tr("Invalid App Format"),
|
||||
tr("Your app format is not supported.<br/>Please follow the guides to redump your "
|
||||
"<a "
|
||||
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
|
||||
"dumping-game-cartridges/'>game "
|
||||
@ -1308,10 +1308,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorSystemMode:
|
||||
LOG_CRITICAL(Frontend, "Failed to load ROM!");
|
||||
LOG_CRITICAL(Frontend, "Failed to load App!");
|
||||
QMessageBox::critical(
|
||||
this, tr("ROM Corrupted"),
|
||||
tr("Your ROM is corrupted. <br/>Please follow the guides to redump your "
|
||||
this, tr("App Corrupted"),
|
||||
tr("Your app is corrupted. <br/>Please follow the guides to redump your "
|
||||
"<a "
|
||||
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
|
||||
"dumping-game-cartridges/'>game "
|
||||
@ -1323,23 +1323,17 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: {
|
||||
QMessageBox::critical(
|
||||
this, tr("ROM Encrypted"),
|
||||
tr("Your ROM is encrypted. <br/>Please follow the guides to redump your "
|
||||
"<a "
|
||||
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
|
||||
"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>."));
|
||||
QMessageBox::critical(this, tr("App Encrypted"),
|
||||
tr("Your app is encrypted. <br/>"
|
||||
"<a "
|
||||
"href='https://azahar-emu.org/blog/game-loading-changes/'>"
|
||||
"Please check our blog for more info.</a>"));
|
||||
break;
|
||||
}
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
|
||||
QMessageBox::critical(
|
||||
this, tr("Invalid ROM Format"),
|
||||
tr("Your ROM format is not supported.<br/>Please follow the guides to redump your "
|
||||
this, tr("Invalid App Format"),
|
||||
tr("Your app format is not supported.<br/>Please follow the guides to redump your "
|
||||
"<a "
|
||||
"href='https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/"
|
||||
"dumping-game-cartridges/'>game "
|
||||
@ -1351,8 +1345,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorLoader_ErrorGbaTitle:
|
||||
QMessageBox::critical(this, tr("Unsupported ROM"),
|
||||
tr("GBA Virtual Console ROMs are not supported by Azahar."));
|
||||
QMessageBox::critical(this, tr("Unsupported App"),
|
||||
tr("GBA Virtual Console is not supported by Azahar."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorArticDisconnected:
|
||||
@ -1365,7 +1359,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
break;
|
||||
default:
|
||||
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."));
|
||||
break;
|
||||
}
|
||||
@ -1430,7 +1424,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())};
|
||||
const std::string config_file_name =
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1932,9 +1926,9 @@ bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int message,
|
||||
switch (message) {
|
||||
case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT:
|
||||
buttons = QMessageBox::Yes | QMessageBox::No;
|
||||
result =
|
||||
QMessageBox::information(parent, tr("Create Shortcut"),
|
||||
tr("Do you want to launch the game in fullscreen?"), buttons);
|
||||
result = QMessageBox::information(
|
||||
parent, tr("Create Shortcut"),
|
||||
tr("Do you want to launch the application in fullscreen?"), buttons);
|
||||
return result == QMessageBox::Yes;
|
||||
case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS:
|
||||
QMessageBox::information(parent, tr("Create Shortcut"),
|
||||
@ -2147,7 +2141,7 @@ void GMainWindow::OnGameListAddDirectory() {
|
||||
UISettings::values.game_dirs.append(game_dir);
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
} 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{};
|
||||
if (!loader || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
QMessageBox::information(this, tr("Properties"),
|
||||
tr("The game properties could not be loaded."));
|
||||
tr("The application properties could not be loaded."));
|
||||
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));
|
||||
break;
|
||||
case Service::AM::InstallStatus::ErrorEncrypted:
|
||||
QMessageBox::critical(this, tr("Encrypted File"),
|
||||
tr("%1 must be decrypted "
|
||||
"before being used with Azahar. A real 3DS is required.")
|
||||
.arg(filename));
|
||||
QMessageBox::critical(this, tr("CIA Encrypted"),
|
||||
tr("Your CIA file is encrypted.<br/>"
|
||||
"<a "
|
||||
"href='https://azahar-emu.org/blog/game-loading-changes/'>"
|
||||
"Please check our blog for more info.</a>"));
|
||||
break;
|
||||
case Service::AM::InstallStatus::ErrorFileNotFound:
|
||||
QMessageBox::critical(this, tr("Unable to find File"),
|
||||
@ -2625,9 +2620,10 @@ void GMainWindow::OnLoadState() {
|
||||
ASSERT(action);
|
||||
|
||||
if (UISettings::values.save_state_warning) {
|
||||
QMessageBox::warning(this, tr("Savestates"),
|
||||
tr("Warning: Savestates are NOT a replacement for in-game saves, "
|
||||
"and are not meant to be reliable.\n\nUse at your own risk!"));
|
||||
QMessageBox::warning(
|
||||
this, tr("Savestates"),
|
||||
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;
|
||||
config->Save();
|
||||
}
|
||||
@ -2709,7 +2705,7 @@ void GMainWindow::OnLoadAmiibo() {
|
||||
|
||||
if (!nfc->IsSearchingForAmiibos()) {
|
||||
QMessageBox::warning(this, tr("Error opening amiibo data file"),
|
||||
tr("Game is not looking for amiibos."));
|
||||
tr("Application is not looking for amiibos."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2859,11 +2855,11 @@ void GMainWindow::OnCaptureScreenshot() {
|
||||
|
||||
const bool was_running = emu_thread->IsRunning();
|
||||
|
||||
if (was_running ||
|
||||
(QMessageBox::question(
|
||||
this, tr("Game will unpause"),
|
||||
tr("The game will be unpaused, and the next frame will be captured. Is this okay?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes)) {
|
||||
if (was_running || (QMessageBox::question(this, tr("Application will unpause"),
|
||||
tr("The application will be unpaused, and the next "
|
||||
"frame will be captured. Is this okay?"),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No) == QMessageBox::Yes)) {
|
||||
if (was_running) {
|
||||
OnPauseGame();
|
||||
}
|
||||
@ -3136,7 +3132,7 @@ void GMainWindow::UpdateStatusBar() {
|
||||
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
|
||||
.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));
|
||||
|
||||
if (show_artic_label) {
|
||||
@ -3340,7 +3336,8 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||
if (can_continue) {
|
||||
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)
|
||||
message_box.exec();
|
||||
|
||||
@ -3470,7 +3467,7 @@ bool GMainWindow::ConfirmChangeGame() {
|
||||
}
|
||||
|
||||
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);
|
||||
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% "
|
||||
"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. "
|
||||
"This will vary from game to game and scene to scene."));
|
||||
game_fps_label->setToolTip(tr("How many frames per second the app is currently displaying. "
|
||||
"This will vary from app to app and scene to scene."));
|
||||
emu_frametime_label->setToolTip(
|
||||
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."));
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/system_titles.h"
|
||||
#include "ui_configure_system.h"
|
||||
|
||||
@ -245,7 +246,7 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
|
||||
this, tr("Select SecureInfo_A/B"), QString(),
|
||||
tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;All Files (*.*)"));
|
||||
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] {
|
||||
ui->button_friend_code_seed->setEnabled(false);
|
||||
@ -254,14 +255,23 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
|
||||
tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A "
|
||||
"LocalFriendCodeSeed_B);;All Files (*.*)"));
|
||||
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] {
|
||||
ui->button_ct_cert->setEnabled(false);
|
||||
connect(ui->button_otp, &QPushButton::clicked, this, [this] {
|
||||
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(
|
||||
this, tr("Select CTCert"), QString(), tr("CTCert.bin (*.bin);;All Files (*.*)"));
|
||||
ui->button_ct_cert->setEnabled(true);
|
||||
InstallCTCert(file_path_qtstr.toStdString());
|
||||
this, tr("Select movable.sed"), QString(), tr("Sed file (*.sed);;All Files (*.*)"));
|
||||
ui->button_movable->setEnabled(true);
|
||||
InstallSecureData(file_path_qtstr.toStdString(), HW::UniqueData::GetMovablePath());
|
||||
});
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
FileUtil::CreateFullPath(to_path);
|
||||
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::CreateFullPath(to);
|
||||
FileUtil::Copy(from, to);
|
||||
HW::UniqueData::InvalidateSecureData();
|
||||
RefreshSecureDataStatus();
|
||||
}
|
||||
|
||||
void ConfigureSystem::RefreshSecureDataStatus() {
|
||||
auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) {
|
||||
auto status_to_str = [](HW::UniqueData::SecureDataLoadStatus status) {
|
||||
switch (status) {
|
||||
case Service::CFG::SecureDataLoadStatus::Loaded:
|
||||
case HW::UniqueData::SecureDataLoadStatus::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";
|
||||
case Service::CFG::SecureDataLoadStatus::Invalid:
|
||||
case HW::UniqueData::SecureDataLoadStatus::Invalid:
|
||||
return "Invalid";
|
||||
case Service::CFG::SecureDataLoadStatus::IOError:
|
||||
case HW::UniqueData::SecureDataLoadStatus::IOError:
|
||||
return "IO Error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
Service::AM::CTCert ct_cert;
|
||||
|
||||
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(
|
||||
tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str()));
|
||||
ui->label_ct_cert_status->setText(
|
||||
tr((std::string("Status: ") + status_to_str(static_cast<Service::CFG::SecureDataLoadStatus>(
|
||||
Service::AM::Module::LoadCTCertFile(ct_cert))))
|
||||
tr((std::string("Status: ") + status_to_str(HW::UniqueData::LoadLocalFriendCodeSeedB()))
|
||||
.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() {
|
||||
@ -669,40 +668,7 @@ void ConfigureSystem::SetupPerGameUI() {
|
||||
void ConfigureSystem::DownloadFromNUS() {
|
||||
ui->button_start_download->setEnabled(false);
|
||||
|
||||
const auto mode =
|
||||
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."));
|
||||
}
|
||||
QMessageBox::critical(this, tr("Azahar"), tr("Downloading from NUS has been deprecated."));
|
||||
|
||||
ui->button_start_download->setEnabled(true);
|
||||
}
|
||||
|
@ -53,7 +53,6 @@ private:
|
||||
void RefreshConsoleID();
|
||||
|
||||
void InstallSecureData(const std::string& from_path, const std::string& to_path);
|
||||
void InstallCTCert(const std::string& from_path);
|
||||
void RefreshSecureDataStatus();
|
||||
|
||||
void SetupPerGameUI();
|
||||
|
@ -629,24 +629,60 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_ct_cert">
|
||||
<widget class="QLabel" name="label_otp">
|
||||
<property name="text">
|
||||
<string>CTCert</string>
|
||||
<string>OTP</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QWidget" name="ct_cert">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_ct_cert">
|
||||
<widget class="QWidget" name="otp">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_otp">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_ct_cert_status">
|
||||
<widget class="QLabel" name="label_otp_status">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -596,7 +596,7 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
|
||||
QMenu* uninstall_menu = context_menu.addMenu(tr("Uninstall"));
|
||||
QAction* uninstall_all = uninstall_menu->addAction(tr("Everything"));
|
||||
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_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(
|
||||
this, tr("Azahar"),
|
||||
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),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
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()];
|
||||
|
||||
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->setChecked(game_dir.deep_scan);
|
||||
@ -885,18 +885,18 @@ void GameList::LoadCompatibilityList() {
|
||||
QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (compat_list.size() == 0) {
|
||||
LOG_WARNING(Frontend, "Game compatibility list is empty");
|
||||
LOG_WARNING(Frontend, "Application compatibility list is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray content = compat_list.readAll();
|
||||
if (content.isEmpty()) {
|
||||
LOG_ERROR(Frontend, "Unable to completely read game compatibility list");
|
||||
LOG_ERROR(Frontend, "Unable to completely read application compatibility list");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1004,12 +1004,12 @@ void GameList::LoadInterfaceLayout() {
|
||||
}
|
||||
|
||||
const QStringList GameList::supported_file_extensions = {
|
||||
QStringLiteral("3ds"), QStringLiteral("3dsx"), QStringLiteral("elf"), QStringLiteral("axf"),
|
||||
QStringLiteral("cci"), QStringLiteral("cxi"), QStringLiteral("app")};
|
||||
QStringLiteral("3dsx"), QStringLiteral("elf"), QStringLiteral("axf"),
|
||||
QStringLiteral("cci"), QStringLiteral("cxi"), QStringLiteral("app")};
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1094,7 +1094,7 @@ GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent}
|
||||
layout->setAlignment(Qt::AlignCenter);
|
||||
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();
|
||||
font.setPointSize(20);
|
||||
text->setFont(font);
|
||||
|
@ -163,7 +163,7 @@ public:
|
||||
|
||||
GameListItemPath() = default;
|
||||
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(game_path, FullPathRole);
|
||||
setData(qulonglong(program_id), ProgramIdRole);
|
||||
@ -184,6 +184,9 @@ public:
|
||||
if (UISettings::values.game_list_icon_size.GetValue() !=
|
||||
UISettings::GameListIconSize::NoIcon)
|
||||
setData(GetDefaultIcon(large), Qt::DecorationRole);
|
||||
if (is_encrypted) {
|
||||
setData(QObject::tr("Unsupported encrypted application"), TitleRole);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -262,13 +265,13 @@ public:
|
||||
};
|
||||
// clang-format off
|
||||
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("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("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("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("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("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game 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("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("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("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("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("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 app crashes when attempting to startup.")}},
|
||||
{QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The app has not yet been tested.")}}};
|
||||
// clang-format on
|
||||
|
||||
auto iterator = status_data.find(compatibility);
|
||||
@ -445,7 +448,7 @@ public:
|
||||
|
||||
int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue());
|
||||
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 {
|
||||
|
@ -108,7 +108,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
emit EntryReady(
|
||||
{
|
||||
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 GameListItemRegion(smdh),
|
||||
new GameListItem(
|
||||
|
@ -446,7 +446,7 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse Public Game Lobby</string>
|
||||
<string>Browse Public Application Lobby</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Start_Room">
|
||||
@ -688,7 +688,7 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure Current Game...</string>
|
||||
<string>Configure Current Application...</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::NoRole</enum>
|
||||
|
@ -65,6 +65,6 @@
|
||||
|
||||
// Sys files
|
||||
#define SHARED_FONT "shared_font.bin"
|
||||
#define AES_KEYS "aes_keys.txt"
|
||||
#define KEYS_FILE "keys.txt"
|
||||
#define BOOTROM9 "boot9.bin"
|
||||
#define SECRET_SECTOR "sector0x96.bin"
|
||||
|
@ -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
|
||||
},
|
||||
}},
|
||||
|
||||
}};
|
||||
}
|
@ -11,6 +11,7 @@ namespace Common::Hacks {
|
||||
enum class HackType : int {
|
||||
RIGHT_EYE_DISABLE,
|
||||
ACCURATE_MULTIPLICATION,
|
||||
DECRYPTION_AUTHORIZED,
|
||||
};
|
||||
|
||||
class UserHackData {};
|
||||
|
@ -121,6 +121,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(HW, LCD) \
|
||||
SUB(HW, GPU) \
|
||||
SUB(HW, AES) \
|
||||
SUB(HW, RSA) \
|
||||
SUB(HW, ECC) \
|
||||
CLS(Frontend) \
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
|
@ -88,6 +88,8 @@ enum class Class : u8 {
|
||||
HW_LCD, ///< LCD register emulation
|
||||
HW_GPU, ///< GPU control emulation
|
||||
HW_AES, ///< AES engine emulation
|
||||
HW_RSA, ///< RSA engine emulation
|
||||
HW_ECC, ///< ECC engine emulation
|
||||
Frontend, ///< Emulator UI
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
|
@ -64,7 +64,8 @@ add_library(citra_core STATIC
|
||||
file_sys/archive_systemsavedata.h
|
||||
file_sys/artic_cache.cpp
|
||||
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.h
|
||||
file_sys/directory_backend.h
|
||||
@ -80,6 +81,8 @@ add_library(citra_core STATIC
|
||||
file_sys/layered_fs.h
|
||||
file_sys/ncch_container.cpp
|
||||
file_sys/ncch_container.h
|
||||
file_sys/otp.cpp
|
||||
file_sys/otp.h
|
||||
file_sys/patch.cpp
|
||||
file_sys/patch.h
|
||||
file_sys/path_parser.cpp
|
||||
@ -97,6 +100,7 @@ add_library(citra_core STATIC
|
||||
file_sys/secure_value_backend.h
|
||||
file_sys/seed_db.cpp
|
||||
file_sys/seed_db.h
|
||||
file_sys/signature.h
|
||||
file_sys/ticket.cpp
|
||||
file_sys/ticket.h
|
||||
file_sys/title_metadata.cpp
|
||||
@ -449,8 +453,13 @@ add_library(citra_core STATIC
|
||||
hw/aes/ccm.h
|
||||
hw/aes/key.cpp
|
||||
hw/aes/key.h
|
||||
hw/ecc.cpp
|
||||
hw/ecc.h
|
||||
hw/default_keys.h
|
||||
hw/rsa/rsa.cpp
|
||||
hw/rsa/rsa.h
|
||||
hw/unique_data.cpp
|
||||
hw/unique_data.h
|
||||
hw/y2r.cpp
|
||||
hw/y2r.h
|
||||
loader/3dsx.cpp
|
||||
@ -482,7 +491,7 @@ add_library(citra_core STATIC
|
||||
tracer/citrace.h
|
||||
tracer/recorder.cpp
|
||||
tracer/recorder.h
|
||||
)
|
||||
)
|
||||
|
||||
create_target_directory_groups(citra_core)
|
||||
|
||||
|
164
src/core/file_sys/certificate.cpp
Normal file
164
src/core/file_sys/certificate.cpp
Normal 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
|
92
src/core/file_sys/certificate.h
Normal file
92
src/core/file_sys/certificate.h
Normal 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
|
@ -147,6 +147,11 @@ Loader::ResultStatus CIAContainer::LoadTicket(std::span<const u8> ticket_data, s
|
||||
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,
|
||||
std::size_t offset) {
|
||||
return cia_tmd.Load(tmd_data, offset);
|
||||
|
@ -44,6 +44,7 @@ public:
|
||||
// Load parts of CIAs (for CIAs streamed in)
|
||||
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(const Ticket& ticket);
|
||||
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);
|
||||
|
||||
|
@ -172,7 +172,11 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
if (is_loaded)
|
||||
return Loader::ResultStatus::Success;
|
||||
|
||||
int block_size = kBlockSize;
|
||||
|
||||
if (file.IsOpen()) {
|
||||
size_t file_size = file.GetSize();
|
||||
|
||||
// Reset read pointer in case this file has been read before.
|
||||
file.Seek(ncch_offset, SEEK_SET);
|
||||
|
||||
@ -196,136 +200,21 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
return Loader::ResultStatus::ErrorInvalidFormat;
|
||||
|
||||
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) {
|
||||
is_encrypted = true;
|
||||
|
||||
// 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;
|
||||
// Encrypted NCCH are not supported
|
||||
return Loader::ResultStatus::ErrorEncrypted;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
const std::size_t size = sizeof(exheader_header);
|
||||
return file && file.ReadBytes(&exheader_header, size) == size;
|
||||
@ -335,26 +224,6 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
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 =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
@ -380,6 +249,11 @@ Loader::ResultStatus NCCHContainer::Load() {
|
||||
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;
|
||||
u32 entry_point = exheader_header.codeset_info.text.address;
|
||||
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
|
||||
if (ncch_header.exefs_size) {
|
||||
exefs_offset = ncch_header.exefs_offset * kBlockSize;
|
||||
u32 exefs_size = ncch_header.exefs_size * kBlockSize;
|
||||
exefs_offset = ncch_header.exefs_offset * block_size;
|
||||
u32 exefs_size = ncch_header.exefs_size * block_size;
|
||||
|
||||
LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset);
|
||||
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))
|
||||
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");
|
||||
has_exefs = true;
|
||||
}
|
||||
@ -482,6 +349,29 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
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
|
||||
result = LoadOverrideExeFSSection(name, buffer);
|
||||
if (result == Loader::ResultStatus::Success || !has_exefs)
|
||||
@ -491,8 +381,8 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
// instead of the ExeFS.
|
||||
if (std::strcmp(name, "logo") == 0) {
|
||||
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_size = ncch_header.logo_region_size * kBlockSize;
|
||||
std::size_t logo_offset = ncch_header.logo_region_offset * block_size;
|
||||
std::size_t logo_size = ncch_header.logo_region_size * block_size;
|
||||
|
||||
buffer.resize(logo_size);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
std::array<u8, 16> key;
|
||||
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));
|
||||
size_t section_size = is_proto ? Common::AlignUp(section.size, 0x10) : section.size;
|
||||
|
||||
if (strcmp(section.name, ".code") == 0 && is_compressed) {
|
||||
// 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()) !=
|
||||
temp_buffer.size())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
if (is_encrypted) {
|
||||
dec.ProcessData(&temp_buffer[0], &temp_buffer[0], section.size);
|
||||
}
|
||||
|
||||
// Decompress .code section...
|
||||
buffer.resize(LZSS_GetDecompressedSize(temp_buffer));
|
||||
if (!LZSS_Decompress(temp_buffer, buffer)) {
|
||||
@ -554,12 +432,9 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect
|
||||
}
|
||||
} else {
|
||||
// Section is uncompressed...
|
||||
buffer.resize(section.size);
|
||||
if (exefs_file.ReadBytes(buffer.data(), section.size) != section.size)
|
||||
buffer.resize(section_size);
|
||||
if (exefs_file.ReadBytes(buffer.data(), section_size) != section_size)
|
||||
return Loader::ResultStatus::Error;
|
||||
if (is_encrypted) {
|
||||
dec.ProcessData(buffer.data(), buffer.data(), section.size);
|
||||
}
|
||||
}
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
@ -667,6 +542,8 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
if (result != Loader::ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
int block_size = is_proto ? 1 : kBlockSize;
|
||||
|
||||
if (ReadOverrideRomFS(romfs_file) == Loader::ResultStatus::Success)
|
||||
return Loader::ResultStatus::Success;
|
||||
|
||||
@ -678,8 +555,8 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
if (!file.IsOpen())
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000;
|
||||
u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000;
|
||||
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * block_size) + 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 size: 0x{:08X}", romfs_size);
|
||||
@ -693,19 +570,14 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romf
|
||||
return Loader::ResultStatus::Error;
|
||||
|
||||
std::shared_ptr<RomFSReader> direct_romfs;
|
||||
if (is_encrypted) {
|
||||
direct_romfs =
|
||||
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset,
|
||||
romfs_size, secondary_key, romfs_ctr, 0x1000);
|
||||
} else {
|
||||
direct_romfs = std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner),
|
||||
romfs_offset, romfs_size);
|
||||
}
|
||||
|
||||
direct_romfs =
|
||||
std::make_shared<DirectRomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size);
|
||||
|
||||
const auto path =
|
||||
fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
GetModId(ncch_header.program_id));
|
||||
if (use_layered_fs &&
|
||||
if (!is_proto && use_layered_fs &&
|
||||
(FileUtil::Exists(path + "romfs/") || FileUtil::Exists(path + "romfs_ext/"))) {
|
||||
|
||||
romfs_file = std::make_shared<LayeredFS>(std::move(direct_romfs), path + "romfs/",
|
||||
|
@ -343,18 +343,11 @@ private:
|
||||
bool has_exefs = false;
|
||||
bool has_romfs = false;
|
||||
|
||||
bool is_proto = false;
|
||||
bool is_tainted = false; // Are there parts of this container being overridden?
|
||||
bool is_loaded = 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 exefs_offset = 0;
|
||||
u32 partition = 0;
|
||||
|
56
src/core/file_sys/otp.cpp
Normal file
56
src/core/file_sys/otp.cpp
Normal 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
89
src/core/file_sys/otp.h
Normal 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
|
@ -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
|
||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||
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);
|
||||
return length;
|
||||
}
|
||||
@ -44,11 +39,6 @@ std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length,
|
||||
if (!cache_entry.first) {
|
||||
// If not found, read from disk and cache the data
|
||||
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,
|
||||
(seg.first - page));
|
||||
} else {
|
||||
|
@ -42,14 +42,7 @@ private:
|
||||
class DirectRomFSReader : public RomFSReader {
|
||||
public:
|
||||
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),
|
||||
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) {}
|
||||
: file(std::move(file)), file_offset(file_offset), data_size(data_size) {}
|
||||
|
||||
~DirectRomFSReader() override = default;
|
||||
|
||||
@ -64,12 +57,8 @@ public:
|
||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||
|
||||
private:
|
||||
bool is_encrypted;
|
||||
FileUtil::IOFile file;
|
||||
std::array<u8, 16> key;
|
||||
std::array<u8, 16> ctr;
|
||||
u64 file_offset;
|
||||
u64 crypto_offset;
|
||||
u64 data_size;
|
||||
|
||||
// Total cache size: 128KB
|
||||
@ -92,12 +81,8 @@ private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
ar & is_encrypted;
|
||||
ar & file;
|
||||
ar & key;
|
||||
ar & ctr;
|
||||
ar & file_offset;
|
||||
ar & crypto_offset;
|
||||
ar & data_size;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
|
@ -102,6 +102,16 @@ void SeedDB::Add(const Seed& 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 {
|
||||
return seeds.size();
|
||||
}
|
||||
@ -138,6 +148,21 @@ std::optional<Seed::Data> GetSeed(u64 title_id) {
|
||||
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() {
|
||||
SeedDB db;
|
||||
if (!db.Load()) {
|
||||
|
@ -28,6 +28,7 @@ struct SeedDB {
|
||||
bool Load();
|
||||
bool Save();
|
||||
void Add(const Seed& seed);
|
||||
bool Delete(u64 title_id);
|
||||
|
||||
std::size_t GetCount() const;
|
||||
auto FindSeedByTitleID(u64 title_id) const;
|
||||
@ -35,6 +36,7 @@ struct SeedDB {
|
||||
|
||||
bool AddSeed(const Seed& seed);
|
||||
std::optional<Seed::Data> GetSeed(u64 title_id);
|
||||
bool DeleteSeed(u64 title_id);
|
||||
u32 GetSeedCount();
|
||||
|
||||
} // namespace FileSys
|
||||
|
@ -1,15 +1,15 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Copyright 2024 Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum TMDSignatureType : u32 {
|
||||
enum SignatureType : u32 {
|
||||
Rsa4096Sha1 = 0x10000,
|
||||
Rsa2048Sha1 = 0x10001,
|
||||
EllipticSha1 = 0x10002,
|
||||
@ -33,8 +33,7 @@ inline u32 GetSignatureSize(u32 signature_type) {
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
} // namespace FileSys
|
@ -5,16 +5,66 @@
|
||||
#include <algorithm>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cryptopp/sha.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/hle/service/am/am.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/hw/ecc.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
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) {
|
||||
std::size_t total_size = static_cast<std::size_t>(file_data.size() - offset);
|
||||
serialized_size = total_size;
|
||||
if (total_size < sizeof(u32))
|
||||
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_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;
|
||||
}
|
||||
|
||||
|
@ -43,20 +43,36 @@ public:
|
||||
u8 audit;
|
||||
INSERT_PADDING_BYTES(0x42);
|
||||
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)
|
||||
|
||||
Loader::ResultStatus DoTitlekeyFixup();
|
||||
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;
|
||||
u64 GetTitleID() const {
|
||||
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:
|
||||
Body ticket_body;
|
||||
u32_be signature_type;
|
||||
std::vector<u8> ticket_signature;
|
||||
std::vector<u8> content_index;
|
||||
|
||||
size_t serialized_size = 0;
|
||||
};
|
||||
} // namespace FileSys
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/file_util.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/loader/loader.h"
|
||||
|
||||
|
@ -44,11 +44,12 @@ Result ErrEula::ReceiveParameterImpl(const Service::APT::MessageParameter& param
|
||||
}
|
||||
|
||||
Result ErrEula::Start(const Service::APT::MessageParameter& parameter) {
|
||||
startup_param = parameter.buffer;
|
||||
memcpy(¶m, 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
|
||||
// application.
|
||||
// TODO(Subv): Reverse the parameter format for the ErrEula applet
|
||||
// Do something here, like showing error codes, or prompting for EULA agreement.
|
||||
if (param.type == DisplayType::Agree) {
|
||||
param.result = 1;
|
||||
}
|
||||
|
||||
// Let the application know that we're closing.
|
||||
Finalize();
|
||||
@ -56,8 +57,8 @@ Result ErrEula::Start(const Service::APT::MessageParameter& parameter) {
|
||||
}
|
||||
|
||||
Result ErrEula::Finalize() {
|
||||
std::vector<u8> buffer(startup_param.size());
|
||||
std::fill(buffer.begin(), buffer.end(), 0);
|
||||
std::vector<u8> buffer(sizeof(param));
|
||||
memcpy(buffer.data(), ¶m, buffer.size());
|
||||
CloseApplet(nullptr, buffer);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
@ -6,11 +6,38 @@
|
||||
|
||||
#include "core/hle/applets/applet.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace HLE::Applets {
|
||||
|
||||
class ErrEula final : public Applet {
|
||||
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,
|
||||
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
|
||||
: Applet(system, id, parent, preload, std::move(manager)) {}
|
||||
@ -27,7 +54,7 @@ private:
|
||||
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
|
||||
|
||||
/// Parameter received by the applet on start.
|
||||
std::vector<u8> startup_param;
|
||||
ErrEulaParam param{};
|
||||
};
|
||||
|
||||
} // namespace HLE::Applets
|
||||
|
@ -205,12 +205,12 @@ Result TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem
|
||||
buffer->GetPtr() + Memory::CITRA_PAGE_SIZE + page_offset, size);
|
||||
|
||||
// Map the guard pages and mapped pages at once.
|
||||
target_address =
|
||||
dst_process->vm_manager
|
||||
.MapBackingMemoryToBase(Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE,
|
||||
buffer, static_cast<u32>(buffer->GetSize()),
|
||||
Kernel::MemoryState::Shared)
|
||||
.Unwrap();
|
||||
auto target_address_result = dst_process->vm_manager.MapBackingMemoryToBase(
|
||||
Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, buffer,
|
||||
static_cast<u32>(buffer->GetSize()), Kernel::MemoryState::Shared);
|
||||
|
||||
ASSERT_MSG(target_address_result.Succeeded(), "Failed to map target address");
|
||||
target_address = target_address_result.Unwrap();
|
||||
|
||||
// Change the permissions and state of the guard pages.
|
||||
const VAddr low_guard_address = target_address;
|
||||
|
@ -2362,7 +2362,7 @@ void SVC::CallSVC(u32 immediate) {
|
||||
if (info->func) {
|
||||
(this->*(info->func))();
|
||||
} else {
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {}(..)", info->name);
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {:02X} {}(..)", info->id, info->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/ac/ac.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");
|
||||
}
|
||||
|
||||
void Module::Interface::GetWifiStatus(Kernel::HLERequestContext& ctx) {
|
||||
void Module::Interface::GetStatus(Kernel::HLERequestContext& 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);
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(static_cast<u32>(can_reach_internet ? (Settings::values.is_new_3ds
|
||||
? WifiStatus::STATUS_CONNECTED_N3DS
|
||||
: WifiStatus::STATUS_CONNECTED_O3DS)
|
||||
: WifiStatus::STATUS_DISCONNECTED));
|
||||
rb.Push<u32>(static_cast<u32>(Status::STATUS_INTERNET));
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -176,8 +176,8 @@ void Module::Interface::IsConnected(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(ac->ac_connected);
|
||||
|
||||
LOG_WARNING(Service_AC, "(STUBBED) called unk=0x{:08X} descriptor=0x{:08X} param=0x{:08X}", unk,
|
||||
unk_descriptor, unk_param);
|
||||
LOG_DEBUG(Service_AC, "(STUBBED) called unk=0x{:08X} descriptor=0x{:08X} param=0x{:08X}", unk,
|
||||
unk_descriptor, unk_param);
|
||||
}
|
||||
|
||||
void Module::Interface::SetClientVersion(Kernel::HLERequestContext& ctx) {
|
||||
|
@ -82,7 +82,15 @@ public:
|
||||
* AC::GetWifiStatus service function
|
||||
* Outputs:
|
||||
* 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);
|
||||
|
||||
@ -153,10 +161,17 @@ public:
|
||||
};
|
||||
|
||||
protected:
|
||||
enum class Status {
|
||||
STATUS_DISCONNECTED = 0,
|
||||
STATUS_ENABLED = 1,
|
||||
STATUS_CONNECTED = 2,
|
||||
STATUS_INTERNET = 3,
|
||||
};
|
||||
enum class WifiStatus {
|
||||
STATUS_DISCONNECTED = 0,
|
||||
STATUS_CONNECTED_O3DS = 1,
|
||||
STATUS_CONNECTED_N3DS = 2,
|
||||
STATUS_CONNECTED_SLOT1 = (1 << 0),
|
||||
STATUS_CONNECTED_SLOT2 = (1 << 1),
|
||||
STATUS_CONNECTED_SLOT3 = (1 << 2),
|
||||
};
|
||||
|
||||
struct ACConfig {
|
||||
|
@ -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"},
|
||||
{0x0009, &AC_I::GetCloseResult, "GetCloseResult"},
|
||||
{0x000A, nullptr, "GetLastErrorCode"},
|
||||
{0x000C, nullptr, "GetStatus"},
|
||||
{0x000C, &AC_I::GetStatus, "GetStatus"},
|
||||
{0x000D, &AC_I::GetWifiStatus, "GetWifiStatus"},
|
||||
{0x000E, nullptr, "GetCurrentAPInfo"},
|
||||
{0x0010, nullptr, "GetCurrentNZoneInfo"},
|
||||
|
@ -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"},
|
||||
{0x0009, &AC_U::GetCloseResult, "GetCloseResult"},
|
||||
{0x000A, nullptr, "GetLastErrorCode"},
|
||||
{0x000C, nullptr, "GetStatus"},
|
||||
{0x000C, &AC_U::GetStatus, "GetStatus"},
|
||||
{0x000D, &AC_U::GetWifiStatus, "GetWifiStatus"},
|
||||
{0x000E, nullptr, "GetCurrentAPInfo"},
|
||||
{0x0010, nullptr, "GetCurrentNZoneInfo"},
|
||||
|
@ -25,8 +25,8 @@ u32 GetACTErrorCode(Result result) {
|
||||
case ErrDescriptions::AlreadyInitialized:
|
||||
error_code = ErrCodes::AlreadyInitialized;
|
||||
break;
|
||||
case ErrDescriptions::ErrDesc103:
|
||||
error_code = ErrCodes::ErrCode225103;
|
||||
case ErrDescriptions::AcStatusDisconnected:
|
||||
error_code = ErrCodes::AcStatusDisconnected;
|
||||
break;
|
||||
case ErrDescriptions::ErrDesc104:
|
||||
error_code = ErrCodes::ErrCode225104;
|
||||
|
@ -17,7 +17,7 @@ enum {
|
||||
LibraryError = 100,
|
||||
NotInitialized = 101,
|
||||
AlreadyInitialized = 102,
|
||||
ErrDesc103 = 103,
|
||||
AcStatusDisconnected = 103,
|
||||
ErrDesc104 = 104,
|
||||
Busy = 111,
|
||||
ErrDesc112 = 112,
|
||||
@ -313,16 +313,16 @@ enum {
|
||||
MailAddressNotConfirmed = 220001, // 022-0001
|
||||
|
||||
// Library errors
|
||||
LibraryError = 220500, // 022-0500
|
||||
NotInitialized = 220501, // 022-0501
|
||||
AlreadyInitialized = 220502, // 022-0502
|
||||
ErrCode225103 = 225103, // 022-5103
|
||||
ErrCode225104 = 225104, // 022-5104
|
||||
Busy = 220511, // 022-0511
|
||||
ErrCode225112 = 225112, // 022-5112
|
||||
NotImplemented = 220591, // 022-0591
|
||||
Deprecated = 220592, // 022-0592
|
||||
DevelopmentOnly = 220593, // 022-0593
|
||||
LibraryError = 220500, // 022-0500
|
||||
NotInitialized = 220501, // 022-0501
|
||||
AlreadyInitialized = 220502, // 022-0502
|
||||
AcStatusDisconnected = 225103, // 022-5103
|
||||
ErrCode225104 = 225104, // 022-5104
|
||||
Busy = 220511, // 022-0511
|
||||
ErrCode225112 = 225112, // 022-5112
|
||||
NotImplemented = 220591, // 022-0591
|
||||
Deprecated = 220592, // 022-0592
|
||||
DevelopmentOnly = 220593, // 022-0593
|
||||
|
||||
InvalidArgument = 220600, // 022-0600
|
||||
InvalidPointer = 220601, // 022-0601
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/cia_container.h"
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/ncch_container.h"
|
||||
#include "core/global.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/result.h"
|
||||
@ -38,11 +39,15 @@ namespace Kernel {
|
||||
class Mutex;
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
class RequestParser;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
CIACurrentlyInstalling = 4,
|
||||
InvalidImportState = 4,
|
||||
InvalidTID = 31,
|
||||
EmptyCIA = 32,
|
||||
TryingToUninstallSystemApp = 44,
|
||||
@ -69,30 +74,33 @@ enum class InstallStatus : u32 {
|
||||
ErrorEncrypted,
|
||||
};
|
||||
|
||||
enum class CTCertLoadStatus {
|
||||
Loaded,
|
||||
NotFound,
|
||||
Invalid,
|
||||
IOError,
|
||||
enum class ImportTitleContextState : u8 {
|
||||
NONE = 0,
|
||||
WAITING_FOR_IMPORT = 1,
|
||||
RESUMABLE = 2,
|
||||
WAITING_FOR_COMMIT = 3,
|
||||
ALREADY_EXISTS = 4,
|
||||
DELETING = 5,
|
||||
NEEDS_CLEANUP = 6,
|
||||
};
|
||||
|
||||
struct CTCert {
|
||||
u32_be signature_type{};
|
||||
std::array<u8, 0x1E> signature_r{};
|
||||
std::array<u8, 0x1E> signature_s{};
|
||||
INSERT_PADDING_BYTES(0x40) {};
|
||||
std::array<char, 0x40> issuer{};
|
||||
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;
|
||||
struct ImportTitleContext {
|
||||
u64 title_id;
|
||||
u16 version;
|
||||
ImportTitleContextState state;
|
||||
u32 type;
|
||||
u64 size;
|
||||
};
|
||||
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
|
||||
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
|
||||
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.
|
||||
class CIAFile final : public FileSys::FileBackend {
|
||||
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();
|
||||
|
||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||
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> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||
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;
|
||||
bool SetSize(u64 size) const override;
|
||||
bool Close() override;
|
||||
void Flush() const override;
|
||||
|
||||
void SetDone() {
|
||||
is_done = true;
|
||||
}
|
||||
|
||||
private:
|
||||
friend void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx);
|
||||
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
|
||||
bool is_update = false;
|
||||
CIAInstallState install_state = CIAInstallState::InstallStarted;
|
||||
@ -133,13 +216,26 @@ private:
|
||||
FileSys::CIAContainer container;
|
||||
std::vector<u8> data;
|
||||
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;
|
||||
|
||||
class DecryptionState;
|
||||
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.
|
||||
class TicketFile final : public FileSys::FileBackend {
|
||||
public:
|
||||
@ -154,9 +250,78 @@ public:
|
||||
bool Close() 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:
|
||||
u64 written = 0;
|
||||
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,
|
||||
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
|
||||
* @param titleId the title ID
|
||||
@ -189,6 +347,11 @@ u64 GetTitleUpdateId(u64 title_id);
|
||||
*/
|
||||
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
|
||||
* @param media_type the media the title exists on
|
||||
@ -461,6 +624,36 @@ public:
|
||||
*/
|
||||
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
|
||||
* Inputs:
|
||||
@ -748,6 +941,40 @@ public:
|
||||
*/
|
||||
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
|
||||
* Inputs:
|
||||
@ -758,6 +985,18 @@ public:
|
||||
*/
|
||||
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:
|
||||
std::shared_ptr<Module> am;
|
||||
|
||||
@ -765,19 +1004,9 @@ public:
|
||||
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:
|
||||
void ScanForTickets();
|
||||
|
||||
/**
|
||||
* Scans the for titles in a storage medium for listing.
|
||||
* @param media_type the storage medium to scan
|
||||
@ -791,9 +1020,14 @@ private:
|
||||
|
||||
Core::System& system;
|
||||
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::multimap<u64, u64> am_ticket_list;
|
||||
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>
|
||||
void serialize(Archive& ar, const unsigned int);
|
||||
|
@ -20,21 +20,21 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
||||
{0x0008, &AM_NET::GetNumTickets, "GetNumTickets"},
|
||||
{0x0009, &AM_NET::GetTicketList, "GetTicketList"},
|
||||
{0x000A, &AM_NET::GetDeviceID, "GetDeviceID"},
|
||||
{0x000B, nullptr, "GetNumImportTitleContexts"},
|
||||
{0x000C, nullptr, "GetImportTitleContextList"},
|
||||
{0x000D, nullptr, "GetImportTitleContexts"},
|
||||
{0x000E, nullptr, "DeleteImportTitleContext"},
|
||||
{0x000F, nullptr, "GetNumImportContentContexts"},
|
||||
{0x0010, nullptr, "GetImportContentContextList"},
|
||||
{0x0011, nullptr, "GetImportContentContexts"},
|
||||
{0x000B, &AM_NET::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
|
||||
{0x000C, &AM_NET::GetImportTitleContextList, "GetImportTitleContextList"},
|
||||
{0x000D, &AM_NET::GetImportTitleContexts, "GetImportTitleContexts"},
|
||||
{0x000E, &AM_NET::DeleteImportTitleContext, "DeleteImportTitleContext"},
|
||||
{0x000F, &AM_NET::GetNumImportContentContexts, "GetNumImportContentContexts"},
|
||||
{0x0010, &AM_NET::GetImportContentContextList, "GetImportContentContextList"},
|
||||
{0x0011, &AM_NET::GetImportContentContexts, "GetImportContentContexts"},
|
||||
{0x0012, nullptr, "DeleteImportContentContexts"},
|
||||
{0x0013, &AM_NET::NeedsCleanup, "NeedsCleanup"},
|
||||
{0x0014, nullptr, "DoCleanup"},
|
||||
{0x0014, &AM_NET::DoCleanup, "DoCleanup"},
|
||||
{0x0015, nullptr, "DeleteAllImportContexts"},
|
||||
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
|
||||
{0x0017, nullptr, "ImportTwlBackupLegacy"},
|
||||
{0x0018, nullptr, "InitializeTitleDatabase"},
|
||||
{0x0019, nullptr, "QueryAvailableTitleDatabase"},
|
||||
{0x0019, &AM_NET::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
|
||||
{0x001A, nullptr, "CalcTwlBackupSize"},
|
||||
{0x001B, nullptr, "ExportTwlBackup"},
|
||||
{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"},
|
||||
{0x0802, nullptr, "CancelImportTicket"},
|
||||
{0x0803, &AM_NET::EndImportTicket, "EndImportTicket"},
|
||||
{0x0804, nullptr, "BeginImportTitle"},
|
||||
{0x0805, nullptr, "StopImportTitle"},
|
||||
{0x0806, nullptr, "ResumeImportTitle"},
|
||||
{0x0807, nullptr, "CancelImportTitle"},
|
||||
{0x0808, nullptr, "EndImportTitle"},
|
||||
{0x0804, &AM_NET::BeginImportTitle, "BeginImportTitle"},
|
||||
{0x0805, &AM_NET::StopImportTitle, "StopImportTitle"},
|
||||
{0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"},
|
||||
{0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"},
|
||||
{0x0808, &AM_NET::EndImportTitle, "EndImportTitle"},
|
||||
{0x0809, nullptr, "CommitImportTitles"},
|
||||
{0x080A, nullptr, "BeginImportTmd"},
|
||||
{0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"},
|
||||
{0x080B, nullptr, "CancelImportTmd"},
|
||||
{0x080C, nullptr, "EndImportTmd"},
|
||||
{0x080D, nullptr, "CreateImportContentContexts"},
|
||||
{0x080E, nullptr, "BeginImportContent"},
|
||||
{0x080F, nullptr, "StopImportContent"},
|
||||
{0x0810, nullptr, "ResumeImportContent"},
|
||||
{0x0811, nullptr, "CancelImportContent"},
|
||||
{0x0812, nullptr, "EndImportContent"},
|
||||
{0x0813, nullptr, "GetNumCurrentImportContentContexts"},
|
||||
{0x0814, nullptr, "GetCurrentImportContentContextList"},
|
||||
{0x0815, nullptr, "GetCurrentImportContentContexts"},
|
||||
{0x0816, nullptr, "Sign"},
|
||||
{0x080C, &AM_NET::EndImportTmd, "EndImportTmd"},
|
||||
{0x080D, &AM_NET::CreateImportContentContexts, "CreateImportContentContexts"},
|
||||
{0x080E, &AM_NET::BeginImportContent, "BeginImportContent"},
|
||||
{0x080F, &AM_NET::StopImportContent, "StopImportContent"},
|
||||
{0x0810, &AM_NET::ResumeImportContent, "ResumeImportContent"},
|
||||
{0x0811, &AM_NET::CancelImportContent, "CancelImportContent"},
|
||||
{0x0812, &AM_NET::EndImportContent, "EndImportContent"},
|
||||
{0x0813, &AM_NET::GetNumCurrentImportContentContexts, "GetNumCurrentImportContentContexts"},
|
||||
{0x0814, &AM_NET::GetCurrentImportContentContextList, "GetCurrentImportContentContextList"},
|
||||
{0x0815, &AM_NET::GetCurrentImportContentContexts, "GetCurrentImportContentContexts"},
|
||||
{0x0816, &AM_NET::Sign, "Sign"},
|
||||
{0x0817, nullptr, "Verify"},
|
||||
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
|
||||
{0x0819, nullptr, "ImportCertificates"},
|
||||
{0x081A, nullptr, "ImportCertificate"},
|
||||
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
|
||||
{0x081C, nullptr, "DeleteTicketId"},
|
||||
{0x081D, nullptr, "GetNumTicketIds"},
|
||||
{0x081E, nullptr, "GetTicketIdList"},
|
||||
{0x081F, nullptr, "GetNumTicketsOfProgram"},
|
||||
{0x0820, nullptr, "ListTicketInfos"},
|
||||
{0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"},
|
||||
{0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"},
|
||||
{0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"},
|
||||
{0x081F, &AM_NET::GetNumTicketsOfProgram, "GetNumTicketsOfProgram"},
|
||||
{0x0820, &AM_NET::ListTicketInfos, "ListTicketInfos"},
|
||||
{0x0821, nullptr, "GetRightsOnlyTicketData"},
|
||||
{0x0822, nullptr, "GetNumCurrentContentInfos"},
|
||||
{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"},
|
||||
{0x0827, nullptr, "DeleteAllDemoLaunchInfos"},
|
||||
{0x0828, nullptr, "BeginImportTitleForOverWrite"},
|
||||
{0x0829, &AM_NET::ExportTicketWrapped, "ExportTicketWrapped"},
|
||||
// clang-format on
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
@ -20,13 +20,13 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
||||
{0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"},
|
||||
{0x0009, &AM_SYS::GetTicketList, "GetTicketList"},
|
||||
{0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"},
|
||||
{0x000B, nullptr, "GetNumImportTitleContexts"},
|
||||
{0x000C, nullptr, "GetImportTitleContextList"},
|
||||
{0x000D, nullptr, "GetImportTitleContexts"},
|
||||
{0x000E, nullptr, "DeleteImportTitleContext"},
|
||||
{0x000F, nullptr, "GetNumImportContentContexts"},
|
||||
{0x0010, nullptr, "GetImportContentContextList"},
|
||||
{0x0011, nullptr, "GetImportContentContexts"},
|
||||
{0x000B, &AM_SYS::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
|
||||
{0x000C, &AM_SYS::GetImportTitleContextList, "GetImportTitleContextList"},
|
||||
{0x000D, &AM_SYS::GetImportTitleContexts, "GetImportTitleContexts"},
|
||||
{0x000E, &AM_SYS::DeleteImportTitleContext, "DeleteImportTitleContext"},
|
||||
{0x000F, &AM_SYS::GetNumImportContentContexts, "GetNumImportContentContexts"},
|
||||
{0x0010, &AM_SYS::GetImportContentContextList, "GetImportContentContextList"},
|
||||
{0x0011, &AM_SYS::GetImportContentContexts, "GetImportContentContexts"},
|
||||
{0x0012, nullptr, "DeleteImportContentContexts"},
|
||||
{0x0013, &AM_SYS::NeedsCleanup, "NeedsCleanup"},
|
||||
{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"},
|
||||
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
|
||||
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
|
||||
{0x0021, nullptr, "GetPersonalizedTicketInfoList"},
|
||||
{0x0021, &AM_SYS::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
|
||||
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
|
||||
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
|
||||
{0x0024, nullptr, "GetImportTitleContextListFiltered"},
|
||||
{0x0023, &AM_SYS::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
|
||||
{0x0024, &AM_SYS::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
|
||||
{0x0025, &AM_SYS::CheckContentRights, "CheckContentRights"},
|
||||
{0x0026, nullptr, "GetTicketLimitInfos"},
|
||||
{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"},
|
||||
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
|
||||
{0x002B, nullptr, "ListExistingContentInfosSystem"},
|
||||
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
|
||||
{0x002C, &AM_SYS::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
|
||||
{0x002D, &AM_SYS::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
|
||||
{0x1001, &AM_SYS::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
|
||||
{0x1002, &AM_SYS::FindDLCContentInfos, "FindDLCContentInfos"},
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "core/hle/service/cfg/cfg_s.h"
|
||||
#include "core/hle/service/cfg/cfg_u.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
|
||||
SERVICE_CONSTRUCT_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) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
u8 ret = 0;
|
||||
if (cfg->secure_info_a_loaded) {
|
||||
ret = cfg->secure_info_a.unknown;
|
||||
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.
|
||||
// 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);
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u8>(ret);
|
||||
@ -232,20 +241,25 @@ void Module::Interface::SecureInfoGetSerialNo(Kernel::HLERequestContext& ctx) {
|
||||
[[maybe_unused]] u32 out_size = rp.Pop<u32>();
|
||||
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);
|
||||
rb.Push(Result(ErrorDescription::InvalidSize, ErrorModule::Config,
|
||||
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.
|
||||
// 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);
|
||||
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
|
||||
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);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -398,27 +412,34 @@ void Module::Interface::GetLocalFriendCodeSeedData(Kernel::HLERequestContext& ct
|
||||
auto out_buffer = rp.PopMappedBuffer();
|
||||
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,
|
||||
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.
|
||||
// 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,
|
||||
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);
|
||||
}
|
||||
|
||||
void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& 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.
|
||||
// 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);
|
||||
rb.Push(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent));
|
||||
@ -426,7 +447,7 @@ void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
|
||||
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) {
|
||||
@ -601,14 +622,6 @@ Result Module::UpdateConfigNANDSavegame() {
|
||||
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 res = DeleteConfigNANDSaveFile();
|
||||
// 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();
|
||||
}
|
||||
|
||||
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() {
|
||||
FileUtil::IOFile mcu_data_file(
|
||||
fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb");
|
||||
@ -768,8 +732,6 @@ Module::Module(Core::System& system_) : system(system_) {
|
||||
SetEULAVersion(default_version);
|
||||
UpdateConfigNANDSavegame();
|
||||
}
|
||||
LoadSecureInfoAFile();
|
||||
LoadLocalFriendCodeSeedBFile();
|
||||
}
|
||||
|
||||
Module::~Module() = default;
|
||||
|
@ -21,6 +21,10 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace HW::UniqueData {
|
||||
enum class SecureDataLoadStatus;
|
||||
}
|
||||
|
||||
namespace Service::CFG {
|
||||
|
||||
enum ConfigBlockID {
|
||||
@ -177,28 +181,6 @@ enum class AccessFlag : u16 {
|
||||
};
|
||||
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 {
|
||||
public:
|
||||
Module(Core::System& system_);
|
||||
@ -635,34 +617,6 @@ public:
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@ -678,10 +632,6 @@ private:
|
||||
std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer;
|
||||
std::unique_ptr<FileSys::ArchiveBackend> cfg_system_save_data_archive;
|
||||
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;
|
||||
MCUData mcu_data{};
|
||||
|
||||
|
@ -2,6 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// 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/assert.h"
|
||||
#include "common/common_types.h"
|
||||
@ -25,6 +29,8 @@
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/fs/archive.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)
|
||||
SERIALIZE_EXPORT_IMPL(Service::FS::FS_USER)
|
||||
@ -1111,6 +1117,47 @@ void FS_USER::GetArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||
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) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
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) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
LOG_DEBUG(Service_FS, "");
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(FileSys::GetSeedCount());
|
||||
}
|
||||
@ -1269,12 +1317,68 @@ void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) {
|
||||
void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
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::AddSeed({title_id, seed, {}});
|
||||
IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
|
||||
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) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const u64 value = rp.Pop<u64>();
|
||||
@ -1729,7 +1833,7 @@ FS_USER::FS_USER(Core::System& system)
|
||||
{0x0847, nullptr, "FormatCtrCardUserSaveData"},
|
||||
{0x0848, nullptr, "GetSdmcCtrRootPath"},
|
||||
{0x0849, &FS_USER::GetArchiveResource, "GetArchiveResource"},
|
||||
{0x084A, nullptr, "ExportIntegrityVerificationSeed"},
|
||||
{0x084A, &FS_USER::ExportIntegrityVerificationSeed, "ExportIntegrityVerificationSeed"},
|
||||
{0x084B, nullptr, "ImportIntegrityVerificationSeed"},
|
||||
{0x084C, &FS_USER::FormatSaveData, "FormatSaveData"},
|
||||
{0x084D, nullptr, "GetLegacySubBannerData"},
|
||||
@ -1767,7 +1871,11 @@ FS_USER::FS_USER(Core::System& system)
|
||||
{0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" },
|
||||
{0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" },
|
||||
{0x087A, &FS_USER::AddSeed, "AddSeed"},
|
||||
{0x087B, &FS_USER::GetSeed, "GetSeed"},
|
||||
{0x087C, &FS_USER::DeleteSeed, "GetSeed"},
|
||||
{0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"},
|
||||
{0x0880, &FS_USER::SetUnknown0x80Data, "SetUnknown0x80Data"},
|
||||
{0x0881, &FS_USER::GetUnknown0x80Data, "GetUnknown0x80Data"},
|
||||
{0x0886, nullptr, "CheckUpdatedDat"},
|
||||
// clang-format on
|
||||
};
|
||||
|
@ -524,6 +524,8 @@ private:
|
||||
*/
|
||||
void GetArchiveResource(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void ExportIntegrityVerificationSeed(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* FS_User::GetFormatInfo service function.
|
||||
* Inputs:
|
||||
@ -610,17 +612,6 @@ private:
|
||||
*/
|
||||
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.
|
||||
* Inputs:
|
||||
@ -633,6 +624,25 @@ private:
|
||||
*/
|
||||
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.
|
||||
* Inputs:
|
||||
|
85
src/core/hle/service/http/ctr-common-1-cert.h
Normal file
85
src/core/hle/service/http/ctr-common-1-cert.h
Normal 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
|
||||
};
|
83
src/core/hle/service/http/ctr-common-1-key.h
Normal file
83
src/core/hle/service/http/ctr-common-1-key.h
Normal 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
|
||||
};
|
@ -28,6 +28,9 @@ SERIALIZE_EXPORT_IMPL(Service::HTTP::SessionData)
|
||||
|
||||
namespace Service::HTTP {
|
||||
|
||||
#include "ctr-common-1-cert.h"
|
||||
#include "ctr-common-1-key.h"
|
||||
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
InvalidRequestState = 22,
|
||||
@ -576,10 +579,15 @@ void HTTP_C::BeginRequest(Kernel::HLERequestContext& ctx) {
|
||||
// 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
|
||||
|
||||
if (http_context.state == RequestState::NotStarted) {
|
||||
http_context.request_future =
|
||||
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context));
|
||||
http_context.current_copied_data = 0;
|
||||
if (http_context.method == RequestMethod::Post && !http_context.post_data_added) {
|
||||
http_context.post_pending_request = true;
|
||||
} 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);
|
||||
@ -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
|
||||
if (http_context.state == RequestState::NotStarted) {
|
||||
http_context.request_future =
|
||||
std::async(std::launch::async, &Context::MakeRequest, std::ref(http_context));
|
||||
http_context.current_copied_data = 0;
|
||||
if (http_context.method == RequestMethod::Post && !http_context.post_data_added) {
|
||||
http_context.post_pending_request = true;
|
||||
} 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);
|
||||
@ -946,6 +958,7 @@ void HTTP_C::AddPostDataAscii(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
Context::Param param_value(name, value);
|
||||
http_context.post_data.emplace(name, param_value);
|
||||
http_context.post_data_added = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1004,6 +1017,7 @@ void HTTP_C::AddPostDataBinary(Kernel::HLERequestContext& ctx) {
|
||||
Context::Param param_value(name, value);
|
||||
http_context.post_data.emplace(name, param_value);
|
||||
http_context.force_multipart = true;
|
||||
http_context.post_data_added = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1053,6 +1067,7 @@ void HTTP_C::AddPostDataRaw(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
http_context.post_data_raw.resize(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);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1144,8 +1159,8 @@ void HTTP_C::SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout)
|
||||
|
||||
Context& http_context = GetContext(context_handle);
|
||||
|
||||
if (http_context.state == RequestState::NotStarted) {
|
||||
LOG_ERROR(Service_HTTP, "Tried to send Post data on a context that has not been started");
|
||||
if (http_context.state != RequestState::NotStarted) {
|
||||
LOG_ERROR(Service_HTTP, "Tried to send Post data on a context that has been started");
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ErrorInvalidRequestState);
|
||||
rb.PushMappedBuffer(value_buffer);
|
||||
@ -1163,6 +1178,7 @@ void HTTP_C::SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout)
|
||||
|
||||
Context::Param param_value(name, value);
|
||||
http_context.post_data.emplace(name, param_value);
|
||||
http_context.post_data_added = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1227,6 +1243,7 @@ void HTTP_C::SendPostDataBinaryImpl(Kernel::HLERequestContext& ctx, bool timeout
|
||||
|
||||
Context::Param param_value(name, value);
|
||||
http_context.post_data.emplace(name, param_value);
|
||||
http_context.post_data_added = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1295,6 +1312,7 @@ void HTTP_C::SendPostDataRawImpl(Kernel::HLERequestContext& ctx, bool timeout) {
|
||||
Context::Param raw_param(value);
|
||||
std::string value_string(value.begin(), value.end());
|
||||
http_context.post_data.emplace(value_string, raw_param);
|
||||
http_context.post_data_added = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1360,20 +1378,129 @@ void HTTP_C::NotifyFinishSendPostData(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
Context& http_context = GetContext(context_handle);
|
||||
|
||||
if (http_context.state == RequestState::NotStarted) {
|
||||
LOG_ERROR(Service_HTTP,
|
||||
"Tried to notfy finish Post on a context that has not been started");
|
||||
if (http_context.state != RequestState::NotStarted) {
|
||||
LOG_ERROR(Service_HTTP, "Tried to notfy finish Post on a context that has 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);
|
||||
rb.Push(ErrorInvalidRequestState);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
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) {
|
||||
GetResponseHeaderImpl(ctx, false);
|
||||
}
|
||||
@ -1746,9 +1873,6 @@ void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
if (!ClCertA.init) {
|
||||
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(),
|
||||
@ -1961,7 +2085,15 @@ bool HTTP_C::PerformStateChecks(Kernel::HLERequestContext& ctx, IPC::RequestPars
|
||||
}
|
||||
|
||||
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;
|
||||
std::vector<u8> cert_file_data;
|
||||
std::vector<u8> key_file_data;
|
||||
|
||||
FileSys::NCCHArchive archive(0x0004001b00010002, Service::FS::MediaType::NAND);
|
||||
|
||||
@ -1972,63 +2104,68 @@ void HTTP_C::DecryptClCertA() {
|
||||
open_mode.read_flag.Assign(1);
|
||||
auto file_result = archive.OpenFile(file_path, open_mode, 0);
|
||||
if (file_result.Failed()) {
|
||||
LOG_ERROR(Service_HTTP, "ClCertA file missing");
|
||||
return;
|
||||
LOG_ERROR(Service_HTTP, "ClCertA file missing, using default");
|
||||
|
||||
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> 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);
|
||||
std::vector<u8> cert_data(cert_file_data.size() - iv_length);
|
||||
|
||||
using CryptoPP::AES;
|
||||
CryptoPP::CBC_Mode<AES>::Decryption aes_cert;
|
||||
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.ProcessData(cert_data.data(), cert_file.Data() + iv_length,
|
||||
cert_file.Length() - iv_length);
|
||||
aes_cert.ProcessData(cert_data.data(), cert_file_data.data() + iv_length,
|
||||
cert_file_data.size() - iv_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;
|
||||
}
|
||||
|
||||
std::vector<u8> key_data(key_file.Length() - iv_length);
|
||||
std::vector<u8> key_data(key_file_data.size() - iv_length);
|
||||
|
||||
CryptoPP::CBC_Mode<AES>::Decryption aes_key;
|
||||
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.ProcessData(key_data.data(), key_file.Data() + iv_length,
|
||||
key_file.Length() - iv_length);
|
||||
aes_key.ProcessData(key_data.data(), key_file_data.data() + iv_length,
|
||||
key_file_data.size() - iv_length);
|
||||
|
||||
ClCertA.certificate = std::move(cert_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"},
|
||||
{0x001E, &HTTP_C::GetResponseHeader, "GetResponseHeader"},
|
||||
{0x001F, &HTTP_C::GetResponseHeaderTimeout, "GetResponseHeaderTimeout"},
|
||||
{0x0020, nullptr, "GetResponseData"},
|
||||
{0x0021, nullptr, "GetResponseDataTimeout"},
|
||||
{0x0020, &HTTP_C::GetResponseData, "GetResponseData"},
|
||||
{0x0021, &HTTP_C::GetResponseDataTimeout, "GetResponseDataTimeout"},
|
||||
{0x0022, &HTTP_C::GetResponseStatusCode, "GetResponseStatusCode"},
|
||||
{0x0023, &HTTP_C::GetResponseStatusCodeTimeout, "GetResponseStatusCodeTimeout"},
|
||||
{0x0024, &HTTP_C::AddTrustedRootCA, "AddTrustedRootCA"},
|
||||
|
@ -274,6 +274,8 @@ public:
|
||||
u32 socket_buffer_size;
|
||||
std::vector<RequestHeader> headers;
|
||||
const ClCertAData* clcert_data;
|
||||
bool post_data_added = false;
|
||||
bool post_pending_request = false;
|
||||
Params post_data;
|
||||
std::string post_data_raw;
|
||||
PostDataEncoding post_data_encoding = PostDataEncoding::Auto;
|
||||
@ -699,6 +701,12 @@ private:
|
||||
*/
|
||||
void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetResponseData(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetResponseDataTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetResponseDataImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||
|
||||
/**
|
||||
* GetResponseHeaderImpl:
|
||||
* Implements GetResponseHeader and GetResponseHeaderTimeout service functions
|
||||
|
@ -7,14 +7,20 @@
|
||||
#include <sstream>
|
||||
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.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/hw/aes/arithmetic128.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/hw/default_keys.h"
|
||||
#include "core/hw/rsa/rsa.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
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
|
||||
// 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.
|
||||
constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45,
|
||||
0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}};
|
||||
AESKey generator_constant;
|
||||
|
||||
AESKey HexToKey(const std::string& hex) {
|
||||
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;
|
||||
AESIV nfc_iv;
|
||||
|
||||
AESKey otp_key;
|
||||
AESIV otp_iv;
|
||||
|
||||
KeySlot movable_key;
|
||||
KeySlot movable_cmac;
|
||||
|
||||
struct KeyDesc {
|
||||
char key_type;
|
||||
std::size_t slot_id;
|
||||
@ -132,96 +143,29 @@ struct KeyDesc {
|
||||
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() {
|
||||
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS;
|
||||
FileUtil::CreateFullPath(filepath); // Create path if not already created
|
||||
auto s = GetKeysStream();
|
||||
|
||||
boost::iostreams::stream<boost::iostreams::file_descriptor_source> file;
|
||||
FileUtil::OpenFStream<std::ios_base::in>(file, filepath);
|
||||
if (!file.is_open()) {
|
||||
return;
|
||||
}
|
||||
std::string mode = "";
|
||||
|
||||
while (!file.eof()) {
|
||||
while (!s.eof()) {
|
||||
std::string line;
|
||||
std::getline(file, 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 != "AES") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto parts = Common::SplitString(line, '=');
|
||||
if (parts.size() != 2) {
|
||||
LOG_ERROR(HW_AES, "Failed to parse {}", line);
|
||||
@ -265,6 +209,31 @@ void LoadPresetKeys() {
|
||||
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") {
|
||||
dlp_nfc_key_y_slots[DlpNfcKeyY::Dlp] = key;
|
||||
continue;
|
||||
@ -310,15 +279,38 @@ void LoadPresetKeys() {
|
||||
|
||||
} // 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) {
|
||||
static bool initialized = false;
|
||||
if (initialized && !force) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
HW::RSA::InitSlots();
|
||||
LoadBootromKeys();
|
||||
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) {
|
||||
@ -371,4 +363,12 @@ const AESIV& GetNfcIv() {
|
||||
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
|
||||
|
@ -6,9 +6,15 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <istream>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
class Certificate;
|
||||
class OTP;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace HW::AES {
|
||||
|
||||
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 AESIV = std::array<u8, AES_BLOCK_SIZE>;
|
||||
|
||||
std::istringstream GetKeysStream();
|
||||
|
||||
void InitKeys(bool force = false);
|
||||
|
||||
void SetKeyX(std::size_t slot_id, const AESKey& key);
|
||||
@ -90,5 +98,8 @@ void SelectDlpNfcKeyYIndex(u8 index);
|
||||
bool NfcSecretsAvailable();
|
||||
const NfcSecret& GetNfcSecret(NfcSecretId secret_id);
|
||||
const AESIV& GetNfcIv();
|
||||
std::pair<AESKey, AESIV> GetOTPKeyIV();
|
||||
|
||||
const AESKey& GetMovableKey(bool cmac_key);
|
||||
|
||||
} // namespace HW::AES
|
||||
|
466
src/core/hw/default_keys.h
Normal file
466
src/core/hw/default_keys.h
Normal 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
212
src/core/hw/ecc.cpp
Normal 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
70
src/core/hw/ecc.h
Normal 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
|
@ -3,6 +3,8 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <sstream>
|
||||
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <cryptopp/hex.h>
|
||||
#include <cryptopp/integer.h>
|
||||
#include <cryptopp/nbtheory.h>
|
||||
@ -11,39 +13,82 @@
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hw/aes/key.h"
|
||||
#include "core/hw/rsa/rsa.h"
|
||||
#include "cryptopp/osrng.h"
|
||||
#include "cryptopp/rsa.h"
|
||||
|
||||
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;
|
||||
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::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
|
||||
CryptoPP::Integer(exponent.data(), exponent.size()),
|
||||
CryptoPP::Integer(modulus.data(), modulus.size()));
|
||||
std::stringstream ss;
|
||||
ss << std::hex << sig;
|
||||
CryptoPP::HexDecoder decoder;
|
||||
decoder.Put(reinterpret_cast<unsigned char*>(ss.str().data()), ss.str().size());
|
||||
decoder.MessageEnd();
|
||||
std::vector<u8> result(decoder.MaxRetrievable());
|
||||
decoder.Get(result.data(), result.size());
|
||||
return HexToBytes(ss.str());
|
||||
|
||||
std::vector<u8> result((out_size_bytes == -1) ? sig.MinEncodedSize() : out_size_bytes);
|
||||
sig.Encode(result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<u8> RsaSlot::Sign(std::span<const u8> message) const {
|
||||
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() {
|
||||
@ -52,62 +97,119 @@ void InitSlots() {
|
||||
return;
|
||||
initialized = true;
|
||||
|
||||
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
|
||||
FileUtil::IOFile file(filepath, "rb");
|
||||
if (!file) {
|
||||
return;
|
||||
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 != "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())
|
||||
return RsaSlot{};
|
||||
return empty_slot;
|
||||
return rsa_slots[slot_id];
|
||||
}
|
||||
|
||||
std::vector<u8> CreateASN1Message(std::span<const u8> data) {
|
||||
static constexpr std::array<u8, 224> asn1_header = {
|
||||
{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}};
|
||||
const RsaSlot& GetTicketWrapSlot() {
|
||||
return ticket_wrap_slot;
|
||||
}
|
||||
|
||||
std::vector<u8> message(asn1_header.begin(), asn1_header.end());
|
||||
CryptoPP::SHA256 sha;
|
||||
message.resize(message.size() + CryptoPP::SHA256::DIGESTSIZE);
|
||||
sha.CalculateDigest(message.data() + asn1_header.size(), data.data(), data.size());
|
||||
return message;
|
||||
const RsaSlot& GetSecureInfoSlot() {
|
||||
return secure_info_slot;
|
||||
}
|
||||
|
||||
const RsaSlot& GetLocalFriendCodeSeedSlot() {
|
||||
return local_friend_code_seed_slot;
|
||||
}
|
||||
|
||||
} // namespace HW::RSA
|
||||
|
@ -15,23 +15,57 @@ public:
|
||||
RsaSlot() = default;
|
||||
RsaSlot(std::vector<u8> exponent, std::vector<u8> 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 {
|
||||
// TODO(B3N30): Maybe check if exponent and modulus are vailid
|
||||
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:
|
||||
bool init = false;
|
||||
std::vector<u8> exponent;
|
||||
std::vector<u8> modulus;
|
||||
std::vector<u8> private_d;
|
||||
};
|
||||
|
||||
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
|
||||
|
228
src/core/hw/unique_data.cpp
Normal file
228
src/core/hw/unique_data.cpp
Normal 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
133
src/core/hw/unique_data.h
Normal 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
|
@ -75,6 +75,7 @@ enum class ResultStatus {
|
||||
ErrorEncrypted,
|
||||
ErrorGbaTitle,
|
||||
ErrorArtic,
|
||||
ErrorNotFound,
|
||||
};
|
||||
|
||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user