Merge branch 'azahar-emu:master' into master

This commit is contained in:
bug 2026-04-13 01:04:43 +01:00 committed by GitHub
commit ff170959af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 900 additions and 68 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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<MaterialButton>(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

View file

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

View file

@ -161,7 +161,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/game_button_tray"
android:id="@+id/horizontal_layout_2"
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -199,19 +199,24 @@
</LinearLayout>
<LinearLayout
android:id="@+id/compress_tray"
android:id="@+id/horizontal_layout_3"
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start|center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/game_button_tray">
app:layout_constraintTop_toBottomOf="@+id/horizontal_layout_2">
<com.google.android.material.button.MaterialButton
android:id="@+id/delete_cache"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/delete_shader_cache"
android:text="@string/delete_shader_cache" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -586,6 +586,10 @@
<!-- Disk Shader Cache -->
<string name="preparing_shaders">Preparing Shaders</string>
<string name="building_shaders">Building %s</string>
<string name="delete_shader_cache">Delete Shader Cache</string>
<string name="delete_cache_select_backend">Select which graphics API to delete the shader cache</string>
<string name="deleting_shader_cache">Deleting shader cache for title, please wait…</string>
<string name="shader_cache_deleted">Shader cache deleted</string>
<!-- About Game Dialog -->
<string name="play">Play</string>

View file

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

View file

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

View file

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

View file

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

View file

@ -352,6 +352,8 @@ GMainWindow::GMainWindow(Core::System& system_)
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>(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();

View file

@ -21,6 +21,7 @@
#include <QTranslator>
#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;

View file

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

View file

@ -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 <algorithm>
#include <cmath>
#include <QPainter>
#include <QRadialGradient>
#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);
}

View file

@ -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 <QColor>
#include <QWidget>
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;
};

View file

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

View file

@ -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<Memory::MemorySystem>(*this);
timing = std::make_unique<Timing>(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<Frontend::ImageInterface> 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() {

View file

@ -12,6 +12,7 @@
#include <boost/optional.hpp>
#include <boost/serialization/version.hpp>
#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<u8>& GetInfoLEDColor() const {
return info_led_color;
}
void SetInfoLEDColor(const Common::Vec3<u8>& 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<void()>& func) {
info_led_color_changed = func;
}
private:
/**
* Initialize the emulated system.
@ -487,6 +509,9 @@ private:
std::vector<u64> lle_modules;
Common::Vec3<u8> info_led_color;
std::function<void()> info_led_color_changed;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned int file_version);

View file

@ -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<HWC>()->InstallAsService(service_manager);
std::make_shared<HWC>(system)->InstallAsService(service_manager);
std::make_shared<RTC>(system)->InstallAsService(service_manager);
}
} // namespace Service::MCU

View file

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

View file

@ -1,4 +1,4 @@
// Copyright 2024 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -10,12 +10,17 @@ namespace Service::MCU {
class HWC final : public ServiceFramework<HWC> {
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)

View file

@ -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 <algorithm>
#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<u8> 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<u32*>(&current_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 <class Archive>
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<s16>(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<u8>(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<u8>(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<u8> result_color{};
s64 arm_time_ns = 0;
friend class boost::serialization::access;
template <class Archive>
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<InfoLedHandler>();
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> RTC::GetService(Core::System& system) {
return system.ServiceManager().GetService<RTC>("mcu::RTC");
}
void RTC::SetInfoLEDPattern(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto pat = rp.PopRaw<InfoLedPattern>();
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<InfoLedPattern::Header>();
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<u8>(GetInfoLEDStatusFinished()));
}
template <class Archive>
void RTC::serialize(Archive& ar, const unsigned int) {
DEBUG_SERIALIZATION_POINT;
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
ar & info_led;
ar & info_led_ticking;
}
} // namespace Service::MCU

View file

@ -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<u8, PATTERN_INDEX_COUNT> r{};
std::array<u8, PATTERN_INDEX_COUNT> g{};
std::array<u8, PATTERN_INDEX_COUNT> b{};
friend class boost::serialization::access;
template <class Archive>
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<RTC> {
public:
explicit RTC(Core::System& _system);
~RTC();
void UpdateInfoLEDPattern(const InfoLedPattern& pat);
void UpdateInfoLEDHeader(const InfoLedPattern::Header& header);
bool GetInfoLEDStatusFinished();
static std::shared_ptr<RTC> 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<InfoLedHandler> info_led;
Core::TimingEventType* info_led_tick_event{};
bool info_led_ticking{};
template <class Archive>
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)

View file

@ -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<MCU::InfoLedPattern>();
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<MCU::InfoLedPattern::Header>();
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<u8>(mcu_rtc->GetInfoLEDStatusFinished()));
} else {
rb.Push(ResultUnknown);
rb.Push(u8{});
}
}
void Module::Interface::GetSystemTime(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);

View file

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

View file

@ -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<Module> 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"},

View file

@ -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<std::optional<AESKey>, NumDlpNfcKeyYs> dlp_nfc_key_y_slots;
std::array<NfcSecret, NumNfcSecrets> 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<char>(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<u8> kiv(16);
@ -304,6 +307,9 @@ std::istringstream GetKeysStream() {
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);
#else
return std::istringstream("");
#endif // ENABLE_BUILTIN_KEYBLOB
}
}

View file

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

View file

@ -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<u8> exponent, std::vector<u8> 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<u8> ModularExponentiation(std::span<const u8> 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<u8>& e) {
exponent = e;
init_exponent = true;
}
const std::vector<u8>& GetExponent() const {
@ -38,6 +40,7 @@ public:
void SetModulus(const std::vector<u8>& m) {
modulus = m;
init_modulus = true;
}
const std::vector<u8>& GetModulus() const {
@ -46,6 +49,7 @@ public:
void SetPrivateD(const std::vector<u8>& d) {
private_d = d;
init_private_d = true;
}
const std::vector<u8>& 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<u8> exponent;
std::vector<u8> modulus;
std::vector<u8> private_d;

View file

@ -27,13 +27,17 @@ 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);
auto sec_info_slot = HW::RSA::GetSecureInfoSlot();
return sec_info_slot &&
sec_info_slot.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);
auto lfcs_slot = HW::RSA::GetLocalFriendCodeSeedSlot();
return lfcs_slot &&
HW::RSA::GetLocalFriendCodeSeedSlot().Verify(
std::span<const u8>(reinterpret_cast<const u8*>(&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");

View file

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

View file

@ -140,6 +140,7 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore&
RasterizerVulkan::~RasterizerVulkan() = default;
void RasterizerVulkan::TickFrame() {
scheduler.WaitWorker();
res_cache.TickFrame();
}