diff --git a/CMakeLists.txt b/CMakeLists.txt index f785b5d0c..28a9b115c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON) option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF) +option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" ON) + # Compile options CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f0da8bbe3..15799c0c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,6 +183,9 @@ endif() if(ENABLE_DEVELOPER_OPTIONS) add_compile_definitions(ENABLE_DEVELOPER_OPTIONS) endif() +if(ENABLE_BUILTIN_KEYBLOB) + add_compile_definitions(ENABLE_BUILTIN_KEYBLOB) +endif() add_subdirectory(common) add_subdirectory(core) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index f8cb55874..018fea670 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -258,6 +258,9 @@ object NativeLibrary { external fun nativeFileExists(path: String): Boolean + external fun deleteOpenGLShaderCache(titleId: Long) + external fun deleteVulkanShaderCache(titleId: Long) + private var coreErrorAlertResult = false private val coreErrorAlertLock = Object() diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 5f5215876..ba3156754 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -8,6 +8,7 @@ import android.Manifest.permission import android.annotation.SuppressLint import android.content.Intent import android.content.SharedPreferences +import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle @@ -80,7 +81,9 @@ class EmulationActivity : AppCompatActivity() { return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment } + private var isRotationBlocked: Boolean = true private var isEmulationRunning: Boolean = false + private var isEmulationReady: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { requestWindowFeature(Window.FEATURE_NO_TITLE) @@ -89,12 +92,20 @@ class EmulationActivity : AppCompatActivity() { ThemeUtil.setTheme(this) settingsViewModel.settings.loadSettings() + + screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) + + // Block orientation until emulation is ready to prevent unneccesary + // surface recreation until the renderer is ready. + isRotationBlocked = true + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED + super.onCreate(savedInstanceState) + secondaryDisplay = SecondaryDisplay(this) secondaryDisplay.updateDisplay() binding = ActivityEmulationBinding.inflate(layoutInflater) - screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) setContentView(binding.root) @@ -119,8 +130,6 @@ class EmulationActivity : AppCompatActivity() { isEmulationRunning = true instance = this - applyOrientationSettings() // Check for orientation settings at startup - val game = try { intent.extras?.let { extras -> BundleCompat.getParcelable(extras, "game", Game::class.java) @@ -140,9 +149,18 @@ class EmulationActivity : AppCompatActivity() { // rotations. Here we set full screen immersive repeatedly in onResume and in // onWindowFocusChanged to prevent the unwanted status bar state. override fun onResume() { - super.onResume() enableFullscreenImmersive() - applyOrientationSettings() // Check for orientation settings changes on runtime + if (isEmulationReady) { + // If emulation is ready then unblock rotation + isRotationBlocked = false + applyOrientationSettings() + emulationViewModel.setEmulationStarted(true) + } else { + if (!isRotationBlocked) { + applyOrientationSettings() + } + } + super.onResume() } override fun onStop() { @@ -151,8 +169,8 @@ class EmulationActivity : AppCompatActivity() { } override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) enableFullscreenImmersive() + super.onWindowFocusChanged(hasFocus) } public override fun onRestart() { @@ -164,11 +182,15 @@ class EmulationActivity : AppCompatActivity() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean("isEmulationRunning", isEmulationRunning) + outState.putBoolean("isEmulationReady", isEmulationReady) + outState.putBoolean("isRotationBlocked", isRotationBlocked) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false) + isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false) + isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked) } override fun onDestroy() { @@ -222,6 +244,11 @@ class EmulationActivity : AppCompatActivity() { fun onEmulationStarted() { emulationViewModel.setEmulationStarted(true) + isEmulationReady = true + if (isRotationBlocked) { + isRotationBlocked = false + applyOrientationSettings() + } Toast.makeText( applicationContext, getString(R.string.emulation_menu_help), diff --git a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt index d43ea5a60..392f68661 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/adapters/GameAdapter.kt @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import android.widget.PopupMenu +import androidx.lifecycle.lifecycleScope import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton @@ -514,6 +515,63 @@ class GameAdapter( showUninstallContextMenu(it, game, bottomSheetDialog) } + bottomSheetView.findViewById(R.id.delete_cache).setOnClickListener { + val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles)) + var selectedIndex = -1 + val dialog = MaterialAlertDialogBuilder(context) + .setTitle(R.string.delete_cache_select_backend) + .setSingleChoiceItems(options, -1) { dialog, which -> + selectedIndex = which + } + .setPositiveButton(android.R.string.ok) {_, _ -> + val progToast = Toast.makeText( + CitraApplication.appContext, + R.string.deleting_shader_cache, + Toast.LENGTH_LONG + ) + progToast.show() + + activity.lifecycleScope.launch(Dispatchers.IO) { + + when (selectedIndex) { + 0 -> { + NativeLibrary.deleteVulkanShaderCache(game.titleId) + } + 1 -> { + NativeLibrary.deleteOpenGLShaderCache(game.titleId) + } + } + + activity.runOnUiThread { + progToast.cancel() + Toast.makeText( + CitraApplication.appContext, + R.string.shader_cache_deleted, + Toast.LENGTH_SHORT + ).show() + } + } + } + .setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + } + .create() + + dialog.setOnShowListener { + val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE) + + positiveButton.isEnabled = false + + val listView = dialog.listView + listView.setOnItemClickListener { _, _, position, _ -> + selectedIndex = position + positiveButton.isEnabled = true + } + } + + dialog.show() + } + val bottomSheetBehavior = bottomSheetDialog.getBehavior() bottomSheetBehavior.skipCollapsed = true bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index f4a30610c..42ac888d2 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1130,4 +1130,44 @@ jboolean Java_org_citra_citra_1emu_NativeLibrary_nativeFileExists(JNIEnv* env, j return FileUtil::Exists(path); } -} // extern "C" +void Java_org_citra_citra_1emu_NativeLibrary_deleteOpenGLShaderCache(JNIEnv* env, jobject obj, + jlong title_id) { + for (const std::string_view cache_type : {"separable", "conventional"}) { + const std::string path = + fmt::format("{}opengl/precompiled/{}/{:016X}.bin", + FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir), cache_type, title_id); + LOG_INFO(Frontend, "Deleting shader file: {}", path); + FileUtil::Delete(path); + } + const std::string path = + fmt::format("{}opengl/transferable/{:016X}.bin", + FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir), title_id); + LOG_INFO(Frontend, "Deleting shader file: {}", path); + FileUtil::Delete(path); +} + +void Java_org_citra_citra_1emu_NativeLibrary_deleteVulkanShaderCache(JNIEnv* env, jobject obj, + jlong title_id) { + for (const std::string_view cache_type : {"vs", "fs", "gs", "pl"}) { + const std::string path = + fmt::format("{}vulkan/transferable/{:016X}_{}.vkch", + FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir), title_id, cache_type); + LOG_INFO(Frontend, "Deleting shader file: {}", path); + FileUtil::Delete(path); + } + + FileUtil::ForeachDirectoryEntry( + nullptr, + fmt::format("{}vulkan/pipeline", FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir)), + [title_id]([[maybe_unused]] u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) { + if (virtual_name.starts_with(fmt::format("{:016X}", title_id))) { + std::string path = directory + DIR_SEP + virtual_name; + LOG_INFO(Frontend, "Deleting shader file: {}", path); + FileUtil::Delete(path); + } + return true; + }); +} + +} // extern "C" \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/dialog_about_game.xml b/src/android/app/src/main/res/layout/dialog_about_game.xml index cb7eea8de..6bcc2a08e 100644 --- a/src/android/app/src/main/res/layout/dialog_about_game.xml +++ b/src/android/app/src/main/res/layout/dialog_about_game.xml @@ -161,7 +161,7 @@ + app:layout_constraintTop_toBottomOf="@+id/horizontal_layout_2"> + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 750677fd5..08e927081 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -586,6 +586,10 @@ Preparing Shaders Building %s + Delete Shader Cache + Select which graphics API to delete the shader cache + Deleting shader cache for title, please wait… + Shader cache deleted Play diff --git a/src/citra_libretro/citra_libretro.cpp b/src/citra_libretro/citra_libretro.cpp index 9263fc46b..c93931196 100644 --- a/src/citra_libretro/citra_libretro.cpp +++ b/src/citra_libretro/citra_libretro.cpp @@ -105,31 +105,6 @@ unsigned retro_api_version() { return RETRO_API_VERSION; } -void LibRetro::OnConfigureEnvironment() { - -#ifdef HAVE_LIBRETRO_VFS - struct retro_vfs_interface_info vfs_iface_info{1, nullptr}; - LibRetro::SetVFSCallback(&vfs_iface_info); -#endif - - LibRetro::RegisterCoreOptions(); - - static const struct retro_controller_description controllers[] = { - {"Nintendo 3DS", RETRO_DEVICE_JOYPAD}, - }; - - static const struct retro_controller_info ports[] = { - {controllers, 1}, - {nullptr, 0}, - }; - - LibRetro::SetControllerInfo(ports); -} - -uintptr_t LibRetro::GetFramebuffer() { - return emu_instance->hw_render.get_current_framebuffer(); -} - /** * Updates Citra's settings with Libretro's. */ @@ -598,6 +573,7 @@ bool retro_load_game(const struct retro_game_info* info) { LibRetro::DisplayMessage("Failed to set HW renderer"); return false; } + LibRetro::SetFramebufferCallback(emu_instance->hw_render.get_current_framebuffer); #endif break; case Settings::GraphicsAPI::Vulkan: diff --git a/src/citra_libretro/environment.cpp b/src/citra_libretro/environment.cpp index c3deddacc..03bf51053 100644 --- a/src/citra_libretro/environment.cpp +++ b/src/citra_libretro/environment.cpp @@ -8,6 +8,7 @@ #include "audio_core/libretro_sink.h" #include "common/scm_rev.h" #include "core/3ds.h" +#include "core_settings.h" #include "emu_window/libretro_window.h" #include "environment.h" @@ -26,6 +27,7 @@ static retro_audio_sample_batch_t audio_batch_cb; static retro_environment_t environ_cb; static retro_input_poll_t input_poll_cb; static retro_input_state_t input_state_cb; +static retro_hw_get_current_framebuffer_t framebuffer_cb; } // namespace @@ -232,6 +234,34 @@ bool CanUseJIT() { } #endif +void OnConfigureEnvironment() { +#ifdef HAVE_LIBRETRO_VFS + struct retro_vfs_interface_info vfs_iface_info{1, nullptr}; + SetVFSCallback(&vfs_iface_info); +#endif + + RegisterCoreOptions(); + + static const struct retro_controller_description controllers[] = { + {"Nintendo 3DS", RETRO_DEVICE_JOYPAD}, + }; + + static const struct retro_controller_info ports[] = { + {controllers, 1}, + {nullptr, 0}, + }; + + SetControllerInfo(ports); +} + +void SetFramebufferCallback(retro_hw_get_current_framebuffer_t cb) { + framebuffer_cb = cb; +} + +uintptr_t GetFramebuffer() { + return framebuffer_cb ? framebuffer_cb() : 0; +} + }; // namespace LibRetro void retro_get_system_info(struct retro_system_info* info) { diff --git a/src/citra_libretro/environment.h b/src/citra_libretro/environment.h index 2b84d8b85..18f2b04bd 100644 --- a/src/citra_libretro/environment.h +++ b/src/citra_libretro/environment.h @@ -109,6 +109,9 @@ bool HasUpdatedConfig(); /// Returns the current framebuffer. uintptr_t GetFramebuffer(); +/// Sets the callback used by GetFramebuffer(). +void SetFramebufferCallback(retro_hw_get_current_framebuffer_t cb); + /// Tells the frontend that we are done. bool Shutdown(); diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 20971828a..8be0aff56 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -174,6 +174,8 @@ add_library(citra_qt STATIC EXCLUDE_FROM_ALL multiplayer/state.cpp multiplayer/state.h multiplayer/validation.h + notification_led.cpp + notification_led.h precompiled_headers.h qt_image_interface.cpp qt_image_interface.h diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 9bb0b2e34..8c2d373c7 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -352,6 +352,8 @@ GMainWindow::GMainWindow(Core::System& system_) Camera::RegisterFactory("image", std::make_unique()); Camera::RegisterFactory("qt", std::make_unique(qt_cameras)); + system.RegisterInfoLEDColorChanged([this]() { emit InfoLEDColorChanged(); }); + LoadTranslation(); Pica::g_debug_context = Pica::DebugContext::Construct(); @@ -606,6 +608,20 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(multiplayer_state->GetStatusText()); statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon()); + QFrame* sep = new QFrame(this); + sep->setFrameShape(QFrame::VLine); + sep->setFrameShadow(QFrame::Sunken); + sep->setFixedHeight(16); + statusBar()->addPermanentWidget(sep); + + notification_led = new LedWidget(); + notification_led->setToolTip(tr("Emulated notification LED")); + statusBar()->addPermanentWidget(notification_led); + connect(this, &GMainWindow::InfoLEDColorChanged, this, [this] { + auto led_color = system.GetInfoLEDColor(); + notification_led->setColor(QColor(led_color.r(), led_color.g(), led_color.b())); + }); + statusBar()->setVisible(true); // Removes an ugly inner border from the status bar widgets under Linux @@ -1600,6 +1616,7 @@ void GMainWindow::ShutdownGame() { emu_speed_label->setVisible(false); game_fps_label->setVisible(false); emu_frametime_label->setVisible(false); + notification_led->setColor(QColor(0, 0, 0)); UpdateSaveStates(); diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h index cdf9eaff6..f0092cc19 100644 --- a/src/citra_qt/citra_qt.h +++ b/src/citra_qt/citra_qt.h @@ -21,6 +21,7 @@ #include #include "citra_qt/compatibility_list.h" #include "citra_qt/hotkeys.h" +#include "citra_qt/notification_led.h" #include "citra_qt/user_data_migration.h" #include "core/core.h" #include "core/savestate.h" @@ -147,6 +148,7 @@ signals: void CIAInstallReport(Service::AM::InstallStatus status, QString filepath); void CompressFinished(bool is_compress, bool success); void CIAInstallFinished(); + void InfoLEDColorChanged(); // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); @@ -364,6 +366,8 @@ private: MultiplayerState* multiplayer_state = nullptr; + LedWidget* notification_led = nullptr; + // Created before `config` to ensure that emu data directory // isn't created before the check is performed UserDataMigrator user_data_migrator; diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 9ed8bebfa..6b47a28d1 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -670,12 +670,16 @@ void ConfigureSystem::RefreshSecureDataStatus() { return tr("Status: Loaded (Invalid Signature)"); case HW::UniqueData::SecureDataLoadStatus::RegionChanged: return tr("Status: Loaded (Region Changed)"); + case HW::UniqueData::SecureDataLoadStatus::CannotValidateSignature: + return tr("Status: Loaded (Cannot Validate Signature)"); case HW::UniqueData::SecureDataLoadStatus::NotFound: return tr("Status: Not Found"); case HW::UniqueData::SecureDataLoadStatus::Invalid: return tr("Status: Invalid"); case HW::UniqueData::SecureDataLoadStatus::IOError: return tr("Status: IO Error"); + case HW::UniqueData::SecureDataLoadStatus::NoCryptoKeys: + return tr("Status: Missing Crypto Keys"); default: return QString(); } diff --git a/src/citra_qt/notification_led.cpp b/src/citra_qt/notification_led.cpp new file mode 100644 index 000000000..e7d9e0d33 --- /dev/null +++ b/src/citra_qt/notification_led.cpp @@ -0,0 +1,87 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "citra_qt/notification_led.h" + +LedWidget::LedWidget(QWidget* parent) : QWidget(parent), color(0, 0, 0) { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +QSize LedWidget::sizeHint() const { + return QSize(16, 16); +} + +QSize LedWidget::minimumSizeHint() const { + return QSize(16, 16); +} + +void LedWidget::setColor(const QColor& _color) { + if (color == _color) + return; + + color = _color; + update(); +} + +QColor LedWidget::lerpColor(const QColor& a, const QColor& b, float t) { + t = std::clamp(t, 0.0f, 1.0f); + + return QColor(int(a.red() + (b.red() - a.red()) * t), + int(a.green() + (b.green() - a.green()) * t), + int(a.blue() + (b.blue() - a.blue()) * t)); +} + +QColor LedWidget::blendLedColor(int r, int g, int b) const { + // Default "off" color + const QColor off_color(64, 64, 64); + + // If completely off, just show gray and skip further calculations + if (r == 0 && g == 0 && b == 0) + return off_color; + + // Normalize lit color so hue stays pure + int max_c = std::max({r, g, b}); + QColor lit_color((r * 255) / max_c, (g * 255) / max_c, (b * 255) / max_c); + + // Convert PWM duty to perceived brightness. + // This gives better results as LED RGB values + // are not linear. + constexpr float gamma = 2.4f; + float pwm = max_c / 255.0; + float t = std::powf(pwm, 1.f / gamma); + + return lerpColor(off_color, lit_color, t * 0.8f); +} + +void LedWidget::paintEvent(QPaintEvent*) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QRectF rect = this->rect().adjusted(0, 2, 0, -2); + + qreal size = std::min(rect.width(), rect.height()); + QRectF circle((rect.center().x() - size / 2.f) - 2, rect.center().y() - size / 2.f, size, size); + + QPointF center = circle.center(); + qreal radius = circle.width() / 2.f; + + QColor base = blendLedColor(color.red(), color.green(), color.blue()); + + QRadialGradient g(center, radius); + + QColor inner = base.lighter(135); + QColor outer = base.darker(125); + + g.setColorAt(0.f, inner); + g.setColorAt(0.7f, base); + g.setColorAt(1.f, outer); + + p.setPen(Qt::NoPen); + p.setBrush(g); + p.drawEllipse(circle); +} diff --git a/src/citra_qt/notification_led.h b/src/citra_qt/notification_led.h new file mode 100644 index 000000000..3a139a202 --- /dev/null +++ b/src/citra_qt/notification_led.h @@ -0,0 +1,30 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class LedWidget : public QWidget { + Q_OBJECT + +public: + explicit LedWidget(QWidget* parent = nullptr); + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + void setColor(const QColor& color); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + QColor blendLedColor(int r, int g, int b) const; + static QColor lerpColor(const QColor& a, const QColor& b, float t); + +private: + QColor color; +}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fd8212dcf..ec7a45bfe 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -350,6 +350,8 @@ add_library(citra_core STATIC hle/service/ldr_ro/ldr_ro.h hle/service/mcu/mcu_hwc.cpp hle/service/mcu/mcu_hwc.h + hle/service/mcu/mcu_rtc.cpp + hle/service/mcu/mcu_rtc.h hle/service/mcu/mcu.cpp hle/service/mcu/mcu.h hle/service/mic/mic_u.cpp @@ -462,7 +464,6 @@ add_library(citra_core STATIC 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 @@ -500,6 +501,10 @@ add_library(citra_core STATIC tracer/recorder.h ) +if (ENABLE_BUILTIN_KEYBLOB) + target_sources(citra_core PRIVATE hw/default_keys.h) +endif() + create_target_directory_groups(citra_core) target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core) diff --git a/src/core/core.cpp b/src/core/core.cpp index 42cba3160..6d7f7d009 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -511,6 +511,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, Kernel::MemoryMode memory_mode, u32 num_cores) { LOG_DEBUG(HW_Memory, "initialized OK"); + is_powered_on = true; + memory = std::make_unique(*this); timing = std::make_unique(num_cores, Settings::values.cpu_clock_percentage.GetValue(), @@ -590,9 +592,9 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, plg_ldr->SetAllowGameChangeState(Settings::values.allow_plugin_loader.GetValue()); } - LOG_DEBUG(Core, "Initialized OK"); + SetInfoLEDColor({}); - is_powered_on = true; + LOG_DEBUG(Core, "Initialized OK"); return ResultStatus::Success; } @@ -687,9 +689,6 @@ void System::RegisterImageInterface(std::shared_ptr im void System::Shutdown(bool is_deserializing) { - // Shutdown emulation session - is_powered_on = false; - gpu.reset(); if (!is_deserializing) { lle_modules.clear(); @@ -720,7 +719,12 @@ void System::Shutdown(bool is_deserializing) { memory.reset(); + SetInfoLEDColor({}); + LOG_DEBUG(Core, "Shutdown OK"); + + // Shutdown emulation session + is_powered_on = false; } void System::Reset() { diff --git a/src/core/core.h b/src/core/core.h index ca61e8262..05620da32 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -12,6 +12,7 @@ #include #include #include "common/common_types.h" +#include "common/vector_math.h" #include "core/arm/arm_interface.h" #include "core/cheats/cheats.h" #include "core/hle/service/apt/applet_manager.h" @@ -381,6 +382,27 @@ public: bool IsInitialSetup(); + // This returns the 3DS notification LED RGB value. + // Keep in mind this is used as a PWM duty cycle on real HW, + // so the percieved LED brightness is not linear. + const Common::Vec3& GetInfoLEDColor() const { + return info_led_color; + } + + void SetInfoLEDColor(const Common::Vec3& color) { + if (color == info_led_color) + return; + + info_led_color = color; + if (info_led_color_changed) { + info_led_color_changed(); + } + } + + void RegisterInfoLEDColorChanged(const std::function& func) { + info_led_color_changed = func; + } + private: /** * Initialize the emulated system. @@ -487,6 +509,9 @@ private: std::vector lle_modules; + Common::Vec3 info_led_color; + std::function info_led_color_changed; + friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int file_version); diff --git a/src/core/hle/service/mcu/mcu.cpp b/src/core/hle/service/mcu/mcu.cpp index dff4ee3e9..83b0742b6 100644 --- a/src/core/hle/service/mcu/mcu.cpp +++ b/src/core/hle/service/mcu/mcu.cpp @@ -1,16 +1,18 @@ -// Copyright 2024 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "core/core.h" #include "core/hle/service/mcu/mcu.h" #include "core/hle/service/mcu/mcu_hwc.h" +#include "core/hle/service/mcu/mcu_rtc.h" namespace Service::MCU { void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::MCU diff --git a/src/core/hle/service/mcu/mcu_hwc.cpp b/src/core/hle/service/mcu/mcu_hwc.cpp index c2a5d7514..dbdf6b2af 100644 --- a/src/core/hle/service/mcu/mcu_hwc.cpp +++ b/src/core/hle/service/mcu/mcu_hwc.cpp @@ -1,15 +1,18 @@ -// Copyright 2024 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "common/archives.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/mcu/mcu_hwc.h" +#include "core/hle/service/mcu/mcu_rtc.h" +SERVICE_CONSTRUCT_IMPL(Service::MCU::HWC) SERIALIZE_EXPORT_IMPL(Service::MCU::HWC) namespace Service::MCU { -HWC::HWC() : ServiceFramework("mcu::HWC", 1) { +HWC::HWC(Core::System& _system) : ServiceFramework("mcu::HWC", 1), system(_system) { static const FunctionInfo functions[] = { // clang-format off {0x0001, nullptr, "ReadRegister"}, @@ -21,7 +24,7 @@ HWC::HWC() : ServiceFramework("mcu::HWC", 1) { {0x0007, nullptr, "SetWifiLEDState"}, {0x0008, nullptr, "SetCameraLEDPattern"}, {0x0009, nullptr, "Set3DLEDState"}, - {0x000A, nullptr, "SetInfoLEDPattern"}, + {0x000A, &HWC::SetInfoLEDPattern, "SetInfoLEDPattern"}, {0x000B, nullptr, "GetSoundVolume"}, {0x000C, nullptr, "SetTopScreenFlicker"}, {0x000D, nullptr, "SetBottomScreenFlicker"}, @@ -33,4 +36,19 @@ HWC::HWC() : ServiceFramework("mcu::HWC", 1) { RegisterHandlers(functions); } +void HWC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto pat = rp.PopRaw(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto mcu_rtc = MCU::RTC::GetService(system); + if (mcu_rtc) { + mcu_rtc->UpdateInfoLEDPattern(pat); + rb.Push(ResultSuccess); + } else { + rb.Push(ResultUnknown); + } +} + } // namespace Service::MCU diff --git a/src/core/hle/service/mcu/mcu_hwc.h b/src/core/hle/service/mcu/mcu_hwc.h index fdcd7bf2f..8e117e9f9 100644 --- a/src/core/hle/service/mcu/mcu_hwc.h +++ b/src/core/hle/service/mcu/mcu_hwc.h @@ -1,4 +1,4 @@ -// Copyright 2024 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -10,12 +10,17 @@ namespace Service::MCU { class HWC final : public ServiceFramework { public: - explicit HWC(); + explicit HWC(Core::System& _system); private: + Core::System& system; + + void SetInfoLEDPattern(Kernel::HLERequestContext& ctx); + SERVICE_SERIALIZATION_SIMPLE }; } // namespace Service::MCU +SERVICE_CONSTRUCT(Service::MCU::HWC) BOOST_CLASS_EXPORT_KEY(Service::MCU::HWC) diff --git a/src/core/hle/service/mcu/mcu_rtc.cpp b/src/core/hle/service/mcu/mcu_rtc.cpp new file mode 100644 index 000000000..294f4c41f --- /dev/null +++ b/src/core/hle/service/mcu/mcu_rtc.cpp @@ -0,0 +1,293 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/archives.h" +#include "common/vector_math.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/mcu/mcu.h" +#include "core/hle/service/mcu/mcu_rtc.h" + +SERVICE_CONSTRUCT_IMPL(Service::MCU::RTC) +SERIALIZE_EXPORT_IMPL(Service::MCU::RTC) + +namespace Service::MCU { + +class InfoLedHandler { +public: + InfoLedHandler() = default; + ~InfoLedHandler() = default; + + static constexpr s64 CALLBACK_PERIOD_NS = 1'000'000'000ll / 60; // 60Hz (~16ms) + static constexpr s64 MCU_TICK_PERIOD_NS = 1'000'000'000ll / 512; // 512Hz (~2ms) + + void SetPattern(const InfoLedPattern& p) { + current_pattern = p; + pattern_changed = true; + } + + void SetHeader(const InfoLedPattern::Header& header) { + current_pattern.header = header; + pattern_changed = true; + } + + // The MCU led code is updated with a frequency of 512Hz on real hardware. However + // it is not a very relevant feature for emulation, so to prevent slicing the core + // timing too much let's update it every frame instead (60Hz) and adjust for it. + void Tick(s64 cycles_late) { + + const s64 late_ns = cyclesToNs(cycles_late); + + // Accumulate elapsed time. + arm_time_ns += CALLBACK_PERIOD_NS + late_ns; + if (arm_time_ns < 0) + arm_time_ns = 0; + + // Sync the MCU state up to the current ARM time + while (arm_time_ns >= MCU_TICK_PERIOD_NS) { + arm_time_ns -= MCU_TICK_PERIOD_NS; + TickMCULed(); + } + } + + Common::Vec3 Color() const { + return result_color; + } + + // To save CPU time, do not tick if all smooth state has finished + // and the pattern is all zero. + bool NeedsTicking() { + auto patAllZero = [this]() -> bool { + u32* data = reinterpret_cast(¤t_pattern); + for (size_t i = 0; i < sizeof(InfoLedPattern) / sizeof(u32); i++) { + if (data[i]) + return false; + } + return true; + }; + + return !patAllZero() || !state_r.Finished() || !state_g.Finished() || !state_b.Finished(); + } + + bool Status() const { + return status_finished; + } + +private: + struct LedSmoothState { + s16 target = 0; + s16 increment = 0; + s16 current = 0; + + bool Finished() { + return current == target; + } + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int) { + ar & target; + ar & increment; + ar & current; + } + }; + + // Decompilation of MCU function at address 0x2f44 + void setSmoothState(LedSmoothState& state, u8 color) { + // Looks like the color is multiplied for better precision + state.target = static_cast(color) * 128; + + // Real HW makes sure ticks_to_progress is not 0 when the led pattern + // is set through I2C. We check for it here instead as it's equivalent. + const u8 ticks = std::max(current_pattern.header.ticks_to_progress, 1); + state.increment = (state.target - state.current) / ticks; + } + + // Decompilation of MCU function at address 0x2dc0 + static u8 updateSmoothState(LedSmoothState& status) { + if (!status.Finished()) { + if (std::abs(status.target - status.current) > std::abs(status.increment)) { + status.current += status.increment; + } else { + status.current = status.target; + } + } + + return static_cast(status.current / 128); + } + + // Decompilation of MCU function at address 0x2f6b + // This function is called every 1/512 seconds + void TickMCULed() { + + // Here, a few things happen. + // If a global variable is set to 2 (0xff904), the led state is cleared. + // If a global variable bit 0 is set (0xffe98), this function does not run at all. + // If a global variable bit 7 is set (0xffe97), this function takes another path which + // runs function 0x2f1d instead of setSmoothState() to set the LED smooth status. + // This function seems to setup smooth to fade to off state. + // TODO(PabloMK7): Figure out what those mean. Maybe power on/off related + + if (pattern_changed) { + pattern_changed = false; + status_finished = false; + ticks_to_next_index = 0; + index = 0; + } else { + if (ticks_to_next_index == 0) { + ticks_to_next_index = current_pattern.header.ticks_per_index; + + if (index < InfoLedPattern::PATTERN_INDEX_COUNT - 1) { + status_finished = false; + index = (index + 1) % InfoLedPattern::PATTERN_INDEX_COUNT; + last_index_repeat_times = 0; + } else { + status_finished = true; + if (current_pattern.header.last_index_repeat_times != 0xFF) { + last_index_repeat_times++; + if (last_index_repeat_times > + current_pattern.header.last_index_repeat_times) { + index = 0; + } + } + } + + // Set smooth for the next index + setSmoothState(state_r, current_pattern.r[index]); + setSmoothState(state_g, current_pattern.g[index]); + setSmoothState(state_b, current_pattern.b[index]); + } + ticks_to_next_index--; + } + + // Update smooth state + result_color.r() = updateSmoothState(state_r); + result_color.g() = updateSmoothState(state_g); + result_color.b() = updateSmoothState(state_b); + } + +private: + InfoLedPattern current_pattern{}; + + bool pattern_changed = false; + bool status_finished = false; + + u8 ticks_to_next_index = 0; + u8 index = 0; + u8 last_index_repeat_times = 0; + + LedSmoothState state_r{}; + LedSmoothState state_g{}; + LedSmoothState state_b{}; + + Common::Vec3 result_color{}; + + s64 arm_time_ns = 0; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int) { + ar & current_pattern; + ar & pattern_changed; + ar & status_finished; + ar & ticks_to_next_index; + ar & index; + ar & last_index_repeat_times; + ar & state_r; + ar & state_g; + ar & state_b; + ar & result_color; + ar & arm_time_ns; + } +}; + +RTC::RTC(Core::System& _system) : ServiceFramework("mcu::RTC", 1), system(_system) { + static const FunctionInfo functions[] = { + // clang-format off + {0x003B, &RTC::SetInfoLEDPattern, "SetInfoLEDPattern"}, + {0x003C, &RTC::SetInfoLEDPatternHeader, "SetInfoLEDPattern"}, + {0x003D, &RTC::GetInfoLEDStatus, "SetInfoLEDPattern"}, + // clang-format on + }; + RegisterHandlers(functions); + + info_led = std::make_unique(); + info_led_tick_event = + system.Kernel().timing.RegisterEvent("MCUTickInfoLED", [this](u64, s64 cycles_late) { + info_led->Tick(cycles_late); + system.SetInfoLEDColor(info_led->Color()); + if (info_led->NeedsTicking()) { + system.Kernel().timing.ScheduleEvent(nsToCycles(InfoLedHandler::CALLBACK_PERIOD_NS), + info_led_tick_event, 0, 1); + } else { + info_led_ticking = false; + } + }); +} + +RTC::~RTC() {} + +void RTC::UpdateInfoLEDPattern(const InfoLedPattern& pat) { + info_led->SetPattern(pat); + if (!info_led_ticking) { + system.Kernel().timing.ScheduleEvent(0, info_led_tick_event, 0, 1); + info_led_ticking = true; + } +} + +void RTC::UpdateInfoLEDHeader(const InfoLedPattern::Header& header) { + info_led->SetHeader(header); + if (!info_led_ticking) { + system.Kernel().timing.ScheduleEvent(0, info_led_tick_event, 0, 1); + info_led_ticking = true; + } +} + +bool RTC::GetInfoLEDStatusFinished() { + return info_led->Status(); +} + +std::shared_ptr RTC::GetService(Core::System& system) { + return system.ServiceManager().GetService("mcu::RTC"); +} + +void RTC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto pat = rp.PopRaw(); + + UpdateInfoLEDPattern(pat); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void RTC::SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto head = rp.PopRaw(); + + UpdateInfoLEDHeader(head); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void RTC::GetInfoLEDStatus(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(static_cast(GetInfoLEDStatusFinished())); +} + +template +void RTC::serialize(Archive& ar, const unsigned int) { + DEBUG_SERIALIZATION_POINT; + ar& boost::serialization::base_object(*this); + ar & info_led; + ar & info_led_ticking; +} + +} // namespace Service::MCU diff --git a/src/core/hle/service/mcu/mcu_rtc.h b/src/core/hle/service/mcu/mcu_rtc.h new file mode 100644 index 000000000..a5a3cd692 --- /dev/null +++ b/src/core/hle/service/mcu/mcu_rtc.h @@ -0,0 +1,83 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/core_timing.h" +#include "core/hle/service/service.h" + +namespace Service::MCU { +class InfoLedHandler; + +struct InfoLedPattern { + static constexpr size_t PATTERN_INDEX_COUNT = 32; + + struct Header { + u8 ticks_per_index{}; // Amount of ticks to stay in the current index (1 tick == 1/512 s) + u8 ticks_to_progress{}; // Amount of ticks to go from the previous value to the current + // index value. Normally, this only makes sense to be set to 0 to + // disable interpolation, or equal to "ticks_per_index" for linear + // interpolation. Any other value breaks the interpolation math. + u8 last_index_repeat_times{}; // Amount of times to repeat the last index, as if the color + // array had "last_index_repeat_times" more elements equal to + // the last array value. (0xFF means repeat forever) + u8 padding{}; + } header; + + // RGB color elements, corresponding to the LED PWM duty cycle. + // (0x0 -> fully off, 0xFF -> fully on) + std::array r{}; + std::array g{}; + std::array b{}; + + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int) { + ar & header.ticks_per_index; + ar & header.ticks_to_progress; + ar & header.last_index_repeat_times; + ar & header.padding; + + ar & r; + ar & g; + ar & b; + } +}; +static_assert(sizeof(InfoLedPattern) == 0x64); + +class RTC final : public ServiceFramework { +public: + explicit RTC(Core::System& _system); + ~RTC(); + + void UpdateInfoLEDPattern(const InfoLedPattern& pat); + + void UpdateInfoLEDHeader(const InfoLedPattern::Header& header); + + bool GetInfoLEDStatusFinished(); + + static std::shared_ptr GetService(Core::System& system); + +private: + void SetInfoLEDPattern(Kernel::HLERequestContext& ctx); + + void SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx); + + void GetInfoLEDStatus(Kernel::HLERequestContext& ctx); + + Core::System& system; + + std::unique_ptr info_led; + Core::TimingEventType* info_led_tick_event{}; + bool info_led_ticking{}; + + template + void serialize(Archive& ar, const unsigned int); + friend class boost::serialization::access; +}; + +} // namespace Service::MCU + +SERVICE_CONSTRUCT(Service::MCU::RTC) +BOOST_CLASS_EXPORT_KEY(Service::MCU::RTC) diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 1cdb64c88..f0e5769e1 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -12,6 +12,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/file_backend.h" #include "core/hle/kernel/shared_page.h" +#include "core/hle/service/mcu/mcu_rtc.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/ptm/ptm_gets.h" #include "core/hle/service/ptm/ptm_play.h" @@ -133,6 +134,51 @@ void Module::Interface::CheckNew3DS(Kernel::HLERequestContext& ctx) { Service::PTM::CheckNew3DS(rb); } +void Module::Interface::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto pat = rp.PopRaw(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto mcu_rtc = MCU::RTC::GetService(ptm->system); + if (mcu_rtc) { + mcu_rtc->UpdateInfoLEDPattern(pat); + rb.Push(ResultSuccess); + } else { + rb.Push(ResultUnknown); + } +} + +void Module::Interface::SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto head = rp.PopRaw(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto mcu_rtc = MCU::RTC::GetService(ptm->system); + if (mcu_rtc) { + mcu_rtc->UpdateInfoLEDHeader(head); + rb.Push(ResultSuccess); + } else { + rb.Push(ResultUnknown); + } +} + +void Module::Interface::GetInfoLEDStatus(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + auto mcu_rtc = MCU::RTC::GetService(ptm->system); + if (mcu_rtc) { + rb.Push(ResultSuccess); + rb.Push(static_cast(mcu_rtc->GetInfoLEDStatusFinished())); + } else { + rb.Push(ResultUnknown); + rb.Push(u8{}); + } +} + void Module::Interface::GetSystemTime(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 2e3e5af55..95863350a 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -139,6 +139,12 @@ public: */ void CheckNew3DS(Kernel::HLERequestContext& ctx); + void SetInfoLEDPattern(Kernel::HLERequestContext& ctx); + + void SetInfoLEDPatternHeader(Kernel::HLERequestContext& ctx); + + void GetInfoLEDStatus(Kernel::HLERequestContext& ctx); + /** * PTM::GetSystemTime service function * Outputs: diff --git a/src/core/hle/service/ptm/ptm_sysm.cpp b/src/core/hle/service/ptm/ptm_sysm.cpp index f827517d3..aced50518 100644 --- a/src/core/hle/service/ptm/ptm_sysm.cpp +++ b/src/core/hle/service/ptm/ptm_sysm.cpp @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -41,9 +41,9 @@ PTM_S_Common::PTM_S_Common(std::shared_ptr ptm, const char* name) {0x0408, nullptr, "Awake"}, {0x0409, nullptr, "RebootAsync"}, {0x040A, &PTM_S_Common::CheckNew3DS, "CheckNew3DS"}, - {0x0801, nullptr, "SetInfoLEDPattern"}, - {0x0802, nullptr, "SetInfoLEDPatternHeader"}, - {0x0803, nullptr, "GetInfoLEDStatus"}, + {0x0801, &PTM_S_Common::SetInfoLEDPattern, "SetInfoLEDPattern"}, + {0x0802, &PTM_S_Common::SetInfoLEDPatternHeader, "SetInfoLEDPatternHeader"}, + {0x0803, &PTM_S_Common::GetInfoLEDStatus, "GetInfoLEDStatus"}, {0x0804, nullptr, "SetBatteryEmptyLEDPattern"}, {0x0805, nullptr, "ClearStepHistory"}, {0x0806, nullptr, "SetStepHistory"}, diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 2e46de838..80aa84d36 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -18,7 +18,9 @@ #include "core/hle/service/fs/archive.h" #include "core/hw/aes/arithmetic128.h" #include "core/hw/aes/key.h" +#ifdef ENABLE_BUILTIN_KEYBLOB #include "core/hw/default_keys.h" +#endif // ENABLE_BUILTIN_KEYBLOB #include "core/hw/rsa/rsa.h" #include "core/loader/loader.h" @@ -130,8 +132,8 @@ std::array, NumDlpNfcKeyYs> dlp_nfc_key_y_slots; std::array nfc_secrets; AESIV nfc_iv; -AESKey otp_key; -AESIV otp_iv; +AESKey otp_key{}; +AESIV otp_iv{}; // gets xor'd with the mac address to produce the final iv AESIV dlp_checksum_mod_iv; @@ -297,6 +299,7 @@ std::istringstream GetKeysStream() { if (file.is_open()) { return std::istringstream(std::string(std::istreambuf_iterator(file), {})); } else { +#ifdef ENABLE_BUILTIN_KEYBLOB // The key data is encrypted in the source to prevent easy access to it for unintended // purposes. std::vector kiv(16); @@ -304,6 +307,9 @@ std::istringstream GetKeysStream() { CryptoPP::CBC_Mode::Decryption(kiv.data(), kiv.size(), kiv.data()) .ProcessData(reinterpret_cast(s.data()), default_keys_enc, s.size()); return std::istringstream(s); +#else + return std::istringstream(""); +#endif // ENABLE_BUILTIN_KEYBLOB } } diff --git a/src/core/hw/default_keys.h b/src/core/hw/default_keys.h index 554b74ae1..22fdcdc64 100644 --- a/src/core/hw/default_keys.h +++ b/src/core/hw/default_keys.h @@ -2,7 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -unsigned char default_keys_enc[] = { +#pragma once + +#ifndef ENABLE_BUILTIN_KEYBLOB +#error Attempting to include default_keys.h, but ENABLE_BUILTIN_KEYBLOB is disabled. +#endif + +constexpr unsigned char default_keys_enc[] = { 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, @@ -468,4 +474,4 @@ unsigned char default_keys_enc[] = { 0x14, 0x79, 0xD0, 0xA8, 0x3C, 0xB3, 0x46, 0xC3, 0xDA, 0x6C, 0x0C, 0xEC, 0x2A, 0xB2, 0x9B, 0x21, 0xB2, 0xAD, 0x8C, 0x0C, 0x85, 0x9A, 0x8D, 0x7C, 0x10, 0xEA, 0x51, 0x1D, 0x2D, 0xDE, 0x7D, 0x8F}; -const long int default_keys_enc_size = sizeof(default_keys_enc); +constexpr long int default_keys_enc_size = sizeof(default_keys_enc); diff --git a/src/core/hw/rsa/rsa.h b/src/core/hw/rsa/rsa.h index b6dd2c1ca..54e944caa 100644 --- a/src/core/hw/rsa/rsa.h +++ b/src/core/hw/rsa/rsa.h @@ -1,4 +1,4 @@ -// Copyright 2020 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -14,7 +14,8 @@ class RsaSlot { public: RsaSlot() = default; RsaSlot(std::vector exponent, std::vector modulus) - : init(true), exponent(std::move(exponent)), modulus(std::move(modulus)) {} + : init_exponent(true), init_modulus(true), exponent(std::move(exponent)), + modulus(std::move(modulus)) {} std::vector ModularExponentiation(std::span message, int out_size_bytes = -1) const; @@ -25,11 +26,12 @@ public: explicit operator bool() const { // TODO(B3N30): Maybe check if exponent and modulus are vailid - return init; + return init_exponent && init_modulus; } void SetExponent(const std::vector& e) { exponent = e; + init_exponent = true; } const std::vector& GetExponent() const { @@ -38,6 +40,7 @@ public: void SetModulus(const std::vector& m) { modulus = m; + init_modulus = true; } const std::vector& GetModulus() const { @@ -46,6 +49,7 @@ public: void SetPrivateD(const std::vector& d) { private_d = d; + init_private_d = true; } const std::vector& GetPrivateD() const { @@ -53,7 +57,9 @@ public: } private: - bool init = false; + bool init_exponent = false; + bool init_modulus = false; + bool init_private_d = false; std::vector exponent; std::vector modulus; std::vector private_d; diff --git a/src/core/hw/unique_data.cpp b/src/core/hw/unique_data.cpp index 8d7ac9035..4ebc2091a 100644 --- a/src/core/hw/unique_data.cpp +++ b/src/core/hw/unique_data.cpp @@ -27,13 +27,17 @@ static MovableSedFull movable; static bool movable_signature_valid = false; bool SecureInfoA::VerifySignature() const { - return HW::RSA::GetSecureInfoSlot().Verify( - std::span(reinterpret_cast(&body), sizeof(body)), signature); + auto sec_info_slot = HW::RSA::GetSecureInfoSlot(); + return sec_info_slot && + sec_info_slot.Verify( + std::span(reinterpret_cast(&body), sizeof(body)), signature); } bool LocalFriendCodeSeedB::VerifySignature() const { - return HW::RSA::GetLocalFriendCodeSeedSlot().Verify( - std::span(reinterpret_cast(&body), sizeof(body)), signature); + auto lfcs_slot = HW::RSA::GetLocalFriendCodeSeedSlot(); + return lfcs_slot && + HW::RSA::GetLocalFriendCodeSeedSlot().Verify( + std::span(reinterpret_cast(&body), sizeof(body)), signature); } bool MovableSed::VerifySignature() const { @@ -42,6 +46,9 @@ bool MovableSed::VerifySignature() const { SecureDataLoadStatus LoadSecureInfoA() { if (secure_info_a.IsValid()) { + if (!HW::RSA::GetSecureInfoSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } return secure_info_a_signature_valid ? SecureDataLoadStatus::Loaded : (secure_info_a_region_changed ? SecureDataLoadStatus::RegionChanged @@ -63,8 +70,11 @@ SecureDataLoadStatus LoadSecureInfoA() { return SecureDataLoadStatus::IOError; } - HW::AES::InitKeys(); secure_info_a_region_changed = false; + HW::AES::InitKeys(); + if (!HW::RSA::GetSecureInfoSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } secure_info_a_signature_valid = secure_info_a.VerifySignature(); if (!secure_info_a_signature_valid) { // Check if the file has been region changed @@ -93,6 +103,9 @@ SecureDataLoadStatus LoadSecureInfoA() { SecureDataLoadStatus LoadLocalFriendCodeSeedB() { if (local_friend_code_seed_b.IsValid()) { + if (!HW::RSA::GetLocalFriendCodeSeedSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } return local_friend_code_seed_b_signature_valid ? SecureDataLoadStatus::Loaded : SecureDataLoadStatus::InvalidSignature; } @@ -114,6 +127,9 @@ SecureDataLoadStatus LoadLocalFriendCodeSeedB() { } HW::AES::InitKeys(); + if (!HW::RSA::GetLocalFriendCodeSeedSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } 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"); @@ -128,10 +144,17 @@ SecureDataLoadStatus LoadOTP() { return SecureDataLoadStatus::Loaded; } + auto is_all_zero = [](const auto& arr) { + return std::all_of(arr.begin(), arr.end(), [](auto x) { return x == 0; }); + }; + const std::string filepath = GetOTPPath(); HW::AES::InitKeys(); auto otp_keyiv = HW::AES::GetOTPKeyIV(); + if (is_all_zero(otp_keyiv.first) || is_all_zero(otp_keyiv.second)) { + return SecureDataLoadStatus::NoCryptoKeys; + } auto loader_status = otp.Load(filepath, otp_keyiv.first, otp_keyiv.second); if (loader_status != Loader::ResultStatus::Success) { @@ -169,6 +192,9 @@ SecureDataLoadStatus LoadOTP() { SecureDataLoadStatus LoadMovable() { if (movable.IsValid()) { + if (!HW::RSA::GetLocalFriendCodeSeedSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } return movable_signature_valid ? SecureDataLoadStatus::Loaded : SecureDataLoadStatus::InvalidSignature; } @@ -193,6 +219,9 @@ SecureDataLoadStatus LoadMovable() { } HW::AES::InitKeys(); + if (!HW::RSA::GetLocalFriendCodeSeedSlot()) { + return SecureDataLoadStatus::CannotValidateSignature; + } movable_signature_valid = movable.VerifySignature(); if (!movable_signature_valid) { LOG_WARNING(HW, "movable.sed signature check failed"); diff --git a/src/core/hw/unique_data.h b/src/core/hw/unique_data.h index dd3a4bb8b..3b4f8efbe 100644 --- a/src/core/hw/unique_data.h +++ b/src/core/hw/unique_data.h @@ -136,10 +136,12 @@ enum class SecureDataLoadStatus { Loaded = 0, InvalidSignature = 1, RegionChanged = 2, + CannotValidateSignature = 3, NotFound = -1, Invalid = -2, IOError = -3, + NoCryptoKeys = -4, }; SecureDataLoadStatus LoadSecureInfoA(); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index abab77c4e..f772fa5f5 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -140,6 +140,7 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore& RasterizerVulkan::~RasterizerVulkan() = default; void RasterizerVulkan::TickFrame() { + scheduler.WaitWorker(); res_cache.TickFrame(); }