diff options
Diffstat (limited to 'src')
114 files changed, 3229 insertions, 1979 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 115f72710..e2c5b6acd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu import android.app.Dialog import android.content.DialogInterface +import android.net.Uri import android.os.Bundle import android.text.Html import android.text.method.LinkMovementMethod @@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.lang.ref.WeakReference import org.yuzu.yuzu_emu.activities.EmulationActivity -import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath +import org.yuzu.yuzu_emu.utils.DocumentsTree import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable @@ -68,7 +69,7 @@ object NativeLibrary { @Keep @JvmStatic fun openContentUri(path: String?, openmode: String?): Int { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.openContentUri(path, openmode) } else { FileUtil.openContentUri(path, openmode) @@ -78,7 +79,7 @@ object NativeLibrary { @Keep @JvmStatic fun getSize(path: String?): Long { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.getFileSize(path) } else { FileUtil.getFileSize(path) @@ -88,23 +89,41 @@ object NativeLibrary { @Keep @JvmStatic fun exists(path: String?): Boolean { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.exists(path) } else { - FileUtil.exists(path) + FileUtil.exists(path, suppressLog = true) } } @Keep @JvmStatic fun isDirectory(path: String?): Boolean { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.isDirectory(path) } else { FileUtil.isDirectory(path) } } + @Keep + @JvmStatic + fun getParentDirectory(path: String): String = + if (DocumentsTree.isNativePath(path)) { + YuzuApplication.documentsTree!!.getParentDirectory(path) + } else { + path + } + + @Keep + @JvmStatic + fun getFilename(path: String): String = + if (DocumentsTree.isNativePath(path)) { + YuzuApplication.documentsTree!!.getFilename(path) + } else { + FileUtil.getFilename(Uri.parse(path)) + } + /** * Returns true if pro controller isn't available and handheld is */ @@ -215,32 +234,6 @@ object NativeLibrary { external fun initGameIni(gameID: String?) - /** - * Gets the embedded icon within the given ROM. - * - * @param filename the file path to the ROM. - * @return a byte array containing the JPEG data for the icon. - */ - external fun getIcon(filename: String): ByteArray - - /** - * Gets the embedded title of the given ISO/ROM. - * - * @param filename The file path to the ISO/ROM. - * @return the embedded title of the ISO/ROM. - */ - external fun getTitle(filename: String): String - - external fun getDescription(filename: String): String - - external fun getGameId(filename: String): String - - external fun getRegions(filename: String): String - - external fun getCompany(filename: String): String - - external fun isHomebrew(filename: String): Boolean - external fun setAppDirectory(directory: String) /** @@ -294,11 +287,6 @@ object NativeLibrary { external fun stopEmulation() /** - * Resets the in-memory ROM metadata cache. - */ - external fun resetRomMetadata() - - /** * Returns true if emulation is running (or is paused). */ external fun isRunning(): Boolean diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index f9f88a1d2..0c82cdba8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -147,7 +147,7 @@ class GameAdapter(private val activity: AppCompatActivity) : private class DiffCallback : DiffUtil.ItemCallback<Game>() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { - return oldItem.gameId == newItem.gameId + return oldItem.programId == newItem.programId } override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 598a9d42b..07bd78bf7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -15,6 +15,7 @@ import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper +import android.os.SystemClock import android.view.* import android.widget.TextView import android.widget.Toast @@ -25,6 +26,7 @@ import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.drawerlayout.widget.DrawerLayout +import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle @@ -156,6 +158,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.showFpsText.setTextColor(Color.YELLOW) binding.doneControlConfig.setOnClickListener { stopConfiguringControls() } + binding.drawerLayout.addDrawerListener(object : DrawerListener { + override fun onDrawerSlide(drawerView: View, slideOffset: Float) { + binding.surfaceInputOverlay.dispatchTouchEvent( + MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis() + 100, + MotionEvent.ACTION_UP, + 0f, + 0f, + 0 + ) + ) + } + + override fun onDrawerOpened(drawerView: View) { + // No op + } + + override fun onDrawerClosed(drawerView: View) { + // No op + } + + override fun onDrawerStateChanged(newState: Int) { + // No op + } + }) binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text = game.title diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index fd9785075..f273c880a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -26,7 +26,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.HomeNavigationDirections @@ -186,7 +186,8 @@ class HomeSettingsFragment : Fragment() { } binding.homeSettingsList.apply { - layoutManager = LinearLayoutManager(requireContext()) + layoutManager = + GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns)) adapter = HomeSettingAdapter( requireActivity() as AppCompatActivity, viewLifecycleOwner, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 6527c64ab..b43978fce 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -12,15 +12,14 @@ import kotlinx.serialization.Serializable @Serializable class Game( val title: String, - val description: String, - val regions: String, val path: String, - val gameId: String, - val company: String, + val programId: String, + val developer: String, + val version: String, val isHomebrew: Boolean ) : Parcelable { - val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" - val keyLastPlayedTime get() = "${gameId}_LastPlayed" + val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" + val keyLastPlayedTime get() = "${programId}_LastPlayed" override fun equals(other: Any?): Boolean { if (other !is Game) { @@ -32,11 +31,9 @@ class Game( override fun hashCode(): Int { var result = title.hashCode() - result = 31 * result + description.hashCode() - result = 31 * result + regions.hashCode() result = 31 * result + path.hashCode() - result = 31 * result + gameId.hashCode() - result = 31 * result + company.hashCode() + result = 31 * result + programId.hashCode() + result = 31 * result + developer.hashCode() result = 31 * result + isHomebrew.hashCode() return result } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index 6e09fa81d..8512ed17c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt @@ -14,15 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.MissingFieldException import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.GameHelper +import org.yuzu.yuzu_emu.utils.GameMetadata -@OptIn(ExperimentalSerializationApi::class) class GamesViewModel : ViewModel() { val games: StateFlow<List<Game>> get() = _games private val _games = MutableStateFlow(emptyList<Game>()) @@ -49,26 +47,34 @@ class GamesViewModel : ViewModel() { // Retrieve list of cached games val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) .getStringSet(GameHelper.KEY_GAMES, emptySet()) - if (storedGames!!.isNotEmpty()) { - val deserializedGames = mutableSetOf<Game>() - storedGames.forEach { - val game: Game - try { - game = Json.decodeFromString(it) - } catch (e: MissingFieldException) { - return@forEach - } - val gameExists = - DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path)) - ?.exists() - if (gameExists == true) { - deserializedGames.add(game) + viewModelScope.launch { + withContext(Dispatchers.IO) { + if (storedGames!!.isNotEmpty()) { + val deserializedGames = mutableSetOf<Game>() + storedGames.forEach { + val game: Game + try { + game = Json.decodeFromString(it) + } catch (e: Exception) { + // We don't care about any errors related to parsing the game cache + return@forEach + } + + val gameExists = + DocumentFile.fromSingleUri( + YuzuApplication.appContext, + Uri.parse(game.path) + )?.exists() + if (gameExists == true) { + deserializedGames.add(game) + } + } + setGames(deserializedGames.toList()) } + reloadGames(false) } - setGames(deserializedGames.toList()) } - reloadGames(false) } fun setGames(games: List<Game>) { @@ -106,7 +112,7 @@ class GamesViewModel : ViewModel() { viewModelScope.launch { withContext(Dispatchers.IO) { - NativeLibrary.resetRomMetadata() + GameMetadata.resetMetadata() setGames(GameHelper.getGames()) _isReloading.value = false diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index eafcf9e42..738275297 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt @@ -42,6 +42,23 @@ class DocumentsTree { return node != null && node.isDirectory } + fun getParentDirectory(filepath: String): String { + val node = resolvePath(filepath)!! + val parentNode = node.parent + if (parentNode != null && parentNode.isDirectory) { + return parentNode.uri!!.toString() + } + return node.uri!!.toString() + } + + fun getFilename(filepath: String): String { + val node = resolvePath(filepath) + if (node != null) { + return node.name!! + } + return filepath + } + private fun resolvePath(filepath: String): DocumentsNode? { val tokens = StringTokenizer(filepath, File.separator, false) var iterator = root diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 5ee74a52c..8c3268e9c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt @@ -144,7 +144,7 @@ object FileUtil { * @param path Native content uri path * @return bool */ - fun exists(path: String?): Boolean { + fun exists(path: String?, suppressLog: Boolean = false): Boolean { var c: Cursor? = null try { val mUri = Uri.parse(path) @@ -152,7 +152,9 @@ object FileUtil { c = context.contentResolver.query(mUri, columns, null, null, null) return c!!.count > 0 } catch (e: Exception) { - Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) + if (!suppressLog) { + Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) + } } finally { closeQuietly(c) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 9001ca9ab..e6aca6b44 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -71,27 +71,26 @@ object GameHelper { fun getGame(uri: Uri, addedToLibrary: Boolean): Game { val filePath = uri.toString() - var name = NativeLibrary.getTitle(filePath) + var name = GameMetadata.getTitle(filePath) // If the game's title field is empty, use the filename. if (name.isEmpty()) { name = FileUtil.getFilename(uri) } - var gameId = NativeLibrary.getGameId(filePath) + var programId = GameMetadata.getProgramId(filePath) // If the game's ID field is empty, use the filename without extension. - if (gameId.isEmpty()) { - gameId = name.substring(0, name.lastIndexOf(".")) + if (programId.isEmpty()) { + programId = name.substring(0, name.lastIndexOf(".")) } val newGame = Game( name, - NativeLibrary.getDescription(filePath).replace("\n", " "), - NativeLibrary.getRegions(filePath), filePath, - gameId, - NativeLibrary.getCompany(filePath), - NativeLibrary.isHomebrew(filePath) + programId, + GameMetadata.getDeveloper(filePath), + GameMetadata.getVersion(filePath), + GameMetadata.getIsHomebrew(filePath) ) if (addedToLibrary) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt index 9fe99fab1..654d62f52 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt @@ -18,7 +18,6 @@ import coil.key.Keyer import coil.memory.MemoryCache import coil.request.ImageRequest import coil.request.Options -import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.model.Game @@ -36,7 +35,7 @@ class GameIconFetcher( } private fun decodeGameIcon(uri: String): Bitmap? { - val data = NativeLibrary.getIcon(uri) + val data = GameMetadata.getIcon(uri) return BitmapFactory.decodeByteArray( data, 0, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt new file mode 100644 index 000000000..0f3542ac6 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +object GameMetadata { + external fun getTitle(path: String): String + + external fun getProgramId(path: String): String + + external fun getDeveloper(path: String): String + + external fun getVersion(path: String): String + + external fun getIcon(path: String): ByteArray + + external fun getIsHomebrew(path: String): Boolean + + external fun resetMetadata() +} diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index e15d1480b..1c36661f5 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -14,8 +14,10 @@ add_library(yuzu-android SHARED id_cache.cpp id_cache.h native.cpp + native.h native_config.cpp uisettings.cpp + game_metadata.cpp ) set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp new file mode 100644 index 000000000..24d9df702 --- /dev/null +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <core/core.h> +#include <core/file_sys/patch_manager.h> +#include <core/loader/nro.h> +#include <jni.h> +#include "core/loader/loader.h" +#include "jni/android_common/android_common.h" +#include "native.h" + +struct RomMetadata { + std::string title; + u64 programId; + std::string developer; + std::string version; + std::vector<u8> icon; + bool isHomebrew; +}; + +std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; + +RomMetadata CacheRomMetadata(const std::string& path) { + const auto file = + Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); + auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + + RomMetadata entry; + loader->ReadTitle(entry.title); + loader->ReadProgramId(entry.programId); + loader->ReadIcon(entry.icon); + + const FileSys::PatchManager pm{ + entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(), + EmulationSession::GetInstance().System().GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + entry.developer = control.first->GetDeveloperName(); + entry.version = control.first->GetVersionString(); + } else { + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { + entry.developer = nacp.GetDeveloperName(); + } else { + entry.developer = ""; + } + + entry.version = "1.0.0"; + } + + if (loader->GetFileType() == Loader::FileType::NRO) { + auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get()); + entry.isHomebrew = loader_nro->IsHomebrew(); + } else { + entry.isHomebrew = false; + } + + m_rom_metadata_cache[path] = entry; + + return entry; +} + +RomMetadata GetRomMetadata(const std::string& path) { + if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { + return search->second; + } + + return CacheRomMetadata(path); +} + +extern "C" { + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId)); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version); +} + +jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, + jstring jpath) { + auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon; + jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size())); + env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), + reinterpret_cast<jbyte*>(icon_data.data())); + return icon; +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, + jstring jpath) { + return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew); +} + +void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) { + return m_rom_metadata_cache.clear(); +} + +} // extern "C" diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 598f4e8bf..686b73588 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -33,7 +33,6 @@ #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/registered_cache.h" #include "core/file_sys/submission_package.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" @@ -48,514 +47,416 @@ #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "core/hid/hid_types.h" -#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" -#include "core/perf_stats.h" #include "jni/android_common/android_common.h" -#include "jni/applets/software_keyboard.h" #include "jni/config.h" -#include "jni/emu_window/emu_window.h" #include "jni/id_cache.h" -#include "video_core/rasterizer_interface.h" +#include "jni/native.h" #include "video_core/renderer_base.h" #define jconst [[maybe_unused]] const auto #define jauto [[maybe_unused]] auto -namespace { +static EmulationSession s_instance; -class EmulationSession final { -public: - EmulationSession() { - m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); - } - - ~EmulationSession() = default; - - static EmulationSession& GetInstance() { - return s_instance; - } - - const Core::System& System() const { - return m_system; - } +EmulationSession::EmulationSession() { + m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); +} - Core::System& System() { - return m_system; - } +EmulationSession& EmulationSession::GetInstance() { + return s_instance; +} - const EmuWindow_Android& Window() const { - return *m_window; - } +const Core::System& EmulationSession::System() const { + return m_system; +} - EmuWindow_Android& Window() { - return *m_window; - } +Core::System& EmulationSession::System() { + return m_system; +} - ANativeWindow* NativeWindow() const { - return m_native_window; - } +const EmuWindow_Android& EmulationSession::Window() const { + return *m_window; +} - void SetNativeWindow(ANativeWindow* native_window) { - m_native_window = native_window; - } +EmuWindow_Android& EmulationSession::Window() { + return *m_window; +} - int InstallFileToNand(std::string filename, std::string file_extension) { - jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, - std::size_t block_size) { - if (src == nullptr || dest == nullptr) { - return false; - } - if (!dest->Resize(src->GetSize())) { - return false; - } +ANativeWindow* EmulationSession::NativeWindow() const { + return m_native_window; +} - using namespace Common::Literals; - [[maybe_unused]] std::vector<u8> buffer(1_MiB); +void EmulationSession::SetNativeWindow(ANativeWindow* native_window) { + m_native_window = native_window; +} - for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - jconst read = src->Read(buffer.data(), buffer.size(), i); - dest->Write(buffer.data(), read, i); - } - return true; - }; - - enum InstallResult { - Success = 0, - SuccessFileOverwritten = 1, - InstallError = 2, - ErrorBaseGame = 3, - ErrorFilenameExtension = 4, - }; - - m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); - m_system.GetFileSystemController().CreateFactories(*m_vfs); - - [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; - if (file_extension == "nsp") { - nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); - if (nsp->IsExtractedType()) { - return InstallError; - } - } else { - return ErrorFilenameExtension; +int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) { + jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, + std::size_t block_size) { + if (src == nullptr || dest == nullptr) { + return false; } - - if (!nsp) { - return InstallError; + if (!dest->Resize(src->GetSize())) { + return false; } - if (nsp->GetStatus() != Loader::ResultStatus::Success) { - return InstallError; - } + using namespace Common::Literals; + [[maybe_unused]] std::vector<u8> buffer(1_MiB); - jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( - *nsp, true, copy_func); - - switch (res) { - case FileSys::InstallResult::Success: - return Success; - case FileSys::InstallResult::OverwriteExisting: - return SuccessFileOverwritten; - case FileSys::InstallResult::ErrorBaseInstall: - return ErrorBaseGame; - default: - return InstallError; + for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { + jconst read = src->Read(buffer.data(), buffer.size(), i); + dest->Write(buffer.data(), read, i); } - } + return true; + }; - void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, - const std::string& custom_driver_name, - const std::string& file_redirect_dir) { -#ifdef ARCHITECTURE_arm64 - void* handle{}; - const char* file_redirect_dir_{}; - int featureFlags{}; - - // Enable driver file redirection when renderer debugging is enabled. - if (Settings::values.renderer_debug && file_redirect_dir.size()) { - featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; - file_redirect_dir_ = file_redirect_dir.c_str(); - } + enum InstallResult { + Success = 0, + SuccessFileOverwritten = 1, + InstallError = 2, + ErrorBaseGame = 3, + ErrorFilenameExtension = 4, + }; - // Try to load a custom driver. - if (custom_driver_name.size()) { - handle = adrenotools_open_libvulkan( - RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), - custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); - } + m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); - // Try to load the system driver. - if (!handle) { - handle = - adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), - nullptr, nullptr, file_redirect_dir_, nullptr); + [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp; + if (file_extension == "nsp") { + nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read)); + if (nsp->IsExtractedType()) { + return InstallError; } - - m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); -#endif + } else { + return ErrorFilenameExtension; } - bool IsRunning() const { - return m_is_running; + if (!nsp) { + return InstallError; } - bool IsPaused() const { - return m_is_running && m_is_paused; + if (nsp->GetStatus() != Loader::ResultStatus::Success) { + return InstallError; } - const Core::PerfStatsResults& PerfStats() const { - std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); - return m_perf_stats; - } + jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true, + copy_func); - void SurfaceChanged() { - if (!IsRunning()) { - return; - } - m_window->OnSurfaceChanged(m_native_window); + switch (res) { + case FileSys::InstallResult::Success: + return Success; + case FileSys::InstallResult::OverwriteExisting: + return SuccessFileOverwritten; + case FileSys::InstallResult::ErrorBaseInstall: + return ErrorBaseGame; + default: + return InstallError; } +} - void ConfigureFilesystemProvider(const std::string& filepath) { - const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); - if (!file) { - return; - } - - auto loader = Loader::GetLoader(m_system, file); - if (!loader) { - return; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return; - } +void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir, + const std::string& custom_driver_dir, + const std::string& custom_driver_name, + const std::string& file_redirect_dir) { +#ifdef ARCHITECTURE_arm64 + void* handle{}; + const char* file_redirect_dir_{}; + int featureFlags{}; - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - m_manual_provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), - program_id, file); - } else if (res2 == Loader::ResultStatus::Success && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - const auto nsp = file_type == Loader::FileType::NSP - ? std::make_shared<FileSys::NSP>(file) - : FileSys::XCI{file}.GetSecurePartitionNSP(); - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } - } + // Enable driver file redirection when renderer debugging is enabled. + if (Settings::values.renderer_debug && file_redirect_dir.size()) { + featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT; + file_redirect_dir_ = file_redirect_dir.c_str(); } - Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { - std::scoped_lock lock(m_mutex); - - // Create the render window. - m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, - m_vulkan_library); - - m_system.SetFilesystem(m_vfs); - m_system.GetUserChannel().clear(); - - // Initialize system. - jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); - m_software_keyboard = android_keyboard.get(); - m_system.SetShuttingDown(false); - m_system.ApplySettings(); - Settings::LogSettings(); - m_system.HIDCore().ReloadInputDevices(); - m_system.SetAppletFrontendSet({ - nullptr, // Amiibo Settings - nullptr, // Controller Selector - nullptr, // Error Display - nullptr, // Mii Editor - nullptr, // Parental Controls - nullptr, // Photo Viewer - nullptr, // Profile Selector - std::move(android_keyboard), // Software Keyboard - nullptr, // Web Browser - }); - - // Initialize filesystem. - m_manual_provider = std::make_unique<FileSys::ManualContentProvider>(); - m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); - m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, - m_manual_provider.get()); - m_system.GetFileSystemController().CreateFactories(*m_vfs); - ConfigureFilesystemProvider(filepath); - - // Initialize account manager - m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); - - // Load the ROM. - m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); - if (m_load_result != Core::SystemResultStatus::Success) { - return m_load_result; - } - - // Complete initialization. - m_system.GPU().Start(); - m_system.GetCpuManager().OnGpuReady(); - m_system.RegisterExitCallback([&] { HaltEmulation(); }); + // Try to load a custom driver. + if (custom_driver_name.size()) { + handle = adrenotools_open_libvulkan( + RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(), + custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr); + } - return Core::SystemResultStatus::Success; + // Try to load the system driver. + if (!handle) { + handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), + nullptr, nullptr, file_redirect_dir_, nullptr); } - void ShutdownEmulation() { - std::scoped_lock lock(m_mutex); + m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); +#endif +} - m_is_running = false; +bool EmulationSession::IsRunning() const { + return m_is_running; +} - // Unload user input. - m_system.HIDCore().UnloadInputDevices(); +bool EmulationSession::IsPaused() const { + return m_is_running && m_is_paused; +} - // Shutdown the main emulated process - if (m_load_result == Core::SystemResultStatus::Success) { - m_system.DetachDebugger(); - m_system.ShutdownMainProcess(); - m_detached_tasks.WaitForAllTasks(); - m_load_result = Core::SystemResultStatus::ErrorNotInitialized; - m_window.reset(); - OnEmulationStopped(Core::SystemResultStatus::Success); - return; - } +const Core::PerfStatsResults& EmulationSession::PerfStats() const { + std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); + return m_perf_stats; +} - // Tear down the render window. - m_window.reset(); +void EmulationSession::SurfaceChanged() { + if (!IsRunning()) { + return; } + m_window->OnSurfaceChanged(m_native_window); +} - void PauseEmulation() { - std::scoped_lock lock(m_mutex); - m_system.Pause(); - m_is_paused = true; +void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) { + const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); + if (!file) { + return; } - void UnPauseEmulation() { - std::scoped_lock lock(m_mutex); - m_system.Run(); - m_is_paused = false; + auto loader = Loader::GetLoader(m_system, file); + if (!loader) { + return; } - void HaltEmulation() { - std::scoped_lock lock(m_mutex); - m_is_running = false; - m_cv.notify_one(); + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; } - void RunEmulation() { - { - std::scoped_lock lock(m_mutex); - m_is_running = true; - } - - // Load the disk shader cache. - if (Settings::values.use_disk_shader_cache.GetValue()) { - LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - m_system.Renderer().ReadRasterizer()->LoadDiskResources( - m_system.GetApplicationProcessProgramID(), std::stop_token{}, - LoadDiskCacheProgress); - LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + m_manual_provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared<FileSys::NSP>(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } } + } +} - void(m_system.Run()); - - if (m_system.DebuggerEnabled()) { - m_system.InitializeDebugger(); - } +Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) { + std::scoped_lock lock(m_mutex); + + // Create the render window. + m_window = + std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library); + + m_system.SetFilesystem(m_vfs); + m_system.GetUserChannel().clear(); + + // Initialize system. + jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); + m_software_keyboard = android_keyboard.get(); + m_system.SetShuttingDown(false); + m_system.ApplySettings(); + Settings::LogSettings(); + m_system.HIDCore().ReloadInputDevices(); + m_system.SetAppletFrontendSet({ + nullptr, // Amiibo Settings + nullptr, // Controller Selector + nullptr, // Error Display + nullptr, // Mii Editor + nullptr, // Parental Controls + nullptr, // Photo Viewer + nullptr, // Profile Selector + std::move(android_keyboard), // Software Keyboard + nullptr, // Web Browser + }); + + // Initialize filesystem. + m_manual_provider = std::make_unique<FileSys::ManualContentProvider>(); + m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); + m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + m_manual_provider.get()); + m_system.GetFileSystemController().CreateFactories(*m_vfs); + ConfigureFilesystemProvider(filepath); + + // Initialize account manager + m_profile_manager = std::make_unique<Service::Account::ProfileManager>(); + + // Load the ROM. + m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); + if (m_load_result != Core::SystemResultStatus::Success) { + return m_load_result; + } + + // Complete initialization. + m_system.GPU().Start(); + m_system.GetCpuManager().OnGpuReady(); + m_system.RegisterExitCallback([&] { HaltEmulation(); }); - OnEmulationStarted(); + return Core::SystemResultStatus::Success; +} - while (true) { - { - [[maybe_unused]] std::unique_lock lock(m_mutex); - if (m_cv.wait_for(lock, std::chrono::milliseconds(800), - [&]() { return !m_is_running; })) { - // Emulation halted. - break; - } - } - { - // Refresh performance stats. - std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); - m_perf_stats = m_system.GetAndResetPerfStats(); - } - } - } +void EmulationSession::ShutdownEmulation() { + std::scoped_lock lock(m_mutex); - std::string GetRomTitle(const std::string& path) { - return GetRomMetadata(path).title; - } + m_is_running = false; - std::vector<u8> GetRomIcon(const std::string& path) { - return GetRomMetadata(path).icon; - } + // Unload user input. + m_system.HIDCore().UnloadInputDevices(); - bool GetIsHomebrew(const std::string& path) { - return GetRomMetadata(path).isHomebrew; + // Shutdown the main emulated process + if (m_load_result == Core::SystemResultStatus::Success) { + m_system.DetachDebugger(); + m_system.ShutdownMainProcess(); + m_detached_tasks.WaitForAllTasks(); + m_load_result = Core::SystemResultStatus::ErrorNotInitialized; + m_window.reset(); + OnEmulationStopped(Core::SystemResultStatus::Success); + return; } - void ResetRomMetadata() { - m_rom_metadata_cache.clear(); - } + // Tear down the render window. + m_window.reset(); +} - bool IsHandheldOnly() { - jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); +void EmulationSession::PauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Pause(); + m_is_paused = true; +} - if (npad_style_set.fullkey == 1) { - return false; - } +void EmulationSession::UnPauseEmulation() { + std::scoped_lock lock(m_mutex); + m_system.Run(); + m_is_paused = false; +} - if (npad_style_set.handheld == 0) { - return false; - } +void EmulationSession::HaltEmulation() { + std::scoped_lock lock(m_mutex); + m_is_running = false; + m_cv.notify_one(); +} - return !Settings::IsDockedMode(); +void EmulationSession::RunEmulation() { + { + std::scoped_lock lock(m_mutex); + m_is_running = true; } - void SetDeviceType([[maybe_unused]] int index, int type) { - jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); - controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); + // Load the disk shader cache. + if (Settings::values.use_disk_shader_cache.GetValue()) { + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress); + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); } - void OnGamepadConnectEvent([[maybe_unused]] int index) { - jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); - - // Ensure that player1 is configured correctly and handheld disconnected - if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { - jauto handheld = - m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + void(m_system.Run()); - if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { - handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); - controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); - handheld->Disconnect(); - } - } + if (m_system.DebuggerEnabled()) { + m_system.InitializeDebugger(); + } - // Ensure that handheld is configured correctly and player 1 disconnected - if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { - jauto player1 = - m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + OnEmulationStarted(); - if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { - player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); - controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); - player1->Disconnect(); + while (true) { + { + [[maybe_unused]] std::unique_lock lock(m_mutex); + if (m_cv.wait_for(lock, std::chrono::milliseconds(800), + [&]() { return !m_is_running; })) { + // Emulation halted. + break; } } - - if (!controller->IsConnected()) { - controller->Connect(); + { + // Refresh performance stats. + std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); + m_perf_stats = m_system.GetAndResetPerfStats(); } } +} + +bool EmulationSession::IsHandheldOnly() { + jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); - void OnGamepadDisconnectEvent([[maybe_unused]] int index) { - jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); - controller->Disconnect(); + if (npad_style_set.fullkey == 1) { + return false; } - SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { - return m_software_keyboard; + if (npad_style_set.handheld == 0) { + return false; } -private: - struct RomMetadata { - std::string title; - std::vector<u8> icon; - bool isHomebrew; - }; + return !Settings::IsDockedMode(); +} - RomMetadata GetRomMetadata(const std::string& path) { - if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { - return search->second; - } +void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); +} - return CacheRomMetadata(path); - } +void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); - RomMetadata CacheRomMetadata(const std::string& path) { - jconst file = Core::GetGameFileFromPath(m_vfs, path); - jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + // Ensure that player1 is configured correctly and handheld disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { + jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); - RomMetadata entry; - loader->ReadTitle(entry.title); - loader->ReadIcon(entry.icon); - if (loader->GetFileType() == Loader::FileType::NRO) { - jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get()); - entry.isHomebrew = loader_nro->IsHomebrew(); - } else { - entry.isHomebrew = false; + if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { + handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + handheld->Disconnect(); } - - m_rom_metadata_cache[path] = entry; - - return entry; } -private: - static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { - JNIEnv* env = IDCache::GetEnvForThread(); - env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), - IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), - static_cast<jint>(progress), static_cast<jint>(max)); - } + // Ensure that handheld is configured correctly and player 1 disconnected + if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { + jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); - static void OnEmulationStarted() { - JNIEnv* env = IDCache::GetEnvForThread(); - env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), - IDCache::GetOnEmulationStarted()); + if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { + player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + player1->Disconnect(); + } } - static void OnEmulationStopped(Core::SystemResultStatus result) { - JNIEnv* env = IDCache::GetEnvForThread(); - env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), - IDCache::GetOnEmulationStopped(), static_cast<jint>(result)); + if (!controller->IsConnected()) { + controller->Connect(); } +} -private: - static EmulationSession s_instance; - - // Frontend management - std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; - - // Window management - std::unique_ptr<EmuWindow_Android> m_window; - ANativeWindow* m_native_window{}; - - // Core emulation - Core::System m_system; - InputCommon::InputSubsystem m_input_subsystem; - Common::DetachedTasks m_detached_tasks; - Core::PerfStatsResults m_perf_stats{}; - std::shared_ptr<FileSys::VfsFilesystem> m_vfs; - Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; - std::atomic<bool> m_is_running = false; - std::atomic<bool> m_is_paused = false; - SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; - std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; - std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; +void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { + jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->Disconnect(); +} - // GPU driver parameters - std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; +SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { + return m_software_keyboard; +} - // Synchronization - std::condition_variable_any m_cv; - mutable std::mutex m_perf_stats_mutex; - mutable std::mutex m_mutex; -}; +void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, + int max) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), + IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage), + static_cast<jint>(progress), static_cast<jint>(max)); +} -/*static*/ EmulationSession EmulationSession::s_instance; +void EmulationSession::OnEmulationStarted() { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted()); +} -} // Anonymous namespace +void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(), + static_cast<jint>(result)); +} static Core::SystemResultStatus RunEmulation(const std::string& filepath) { Common::Log::Initialize(); @@ -657,10 +558,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla EmulationSession::GetInstance().HaltEmulation(); } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) { - EmulationSession::GetInstance().ResetRomMetadata(); -} - jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) { return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); } @@ -766,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c } } -jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz, - jstring j_filename) { - jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename)); - jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size())); - env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), - reinterpret_cast<jbyte*>(icon_data.data())); - return icon; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz, - jstring j_filename) { - jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename)); - return env->NewStringUTF(title.c_str()); -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz, - jstring j_filename) { - return j_filename; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz, - jstring j_filename) { - return j_filename; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz, - jstring j_filename) { - return env->NewStringUTF(""); -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz, - jstring j_filename) { - return env->NewStringUTF(""); -} - -jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz, - jstring j_filename) { - return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename)); -} - void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) { // Create the default config.ini. Config{}; diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h new file mode 100644 index 000000000..b1db87e41 --- /dev/null +++ b/src/android/app/src/main/jni/native.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <android/native_window_jni.h> +#include "common/detached_tasks.h" +#include "core/core.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/perf_stats.h" +#include "jni/applets/software_keyboard.h" +#include "jni/emu_window/emu_window.h" +#include "video_core/rasterizer_interface.h" + +#pragma once + +class EmulationSession final { +public: + explicit EmulationSession(); + ~EmulationSession() = default; + + static EmulationSession& GetInstance(); + const Core::System& System() const; + Core::System& System(); + + const EmuWindow_Android& Window() const; + EmuWindow_Android& Window(); + ANativeWindow* NativeWindow() const; + void SetNativeWindow(ANativeWindow* native_window); + void SurfaceChanged(); + + int InstallFileToNand(std::string filename, std::string file_extension); + void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, + const std::string& custom_driver_name, + const std::string& file_redirect_dir); + + bool IsRunning() const; + bool IsPaused() const; + void PauseEmulation(); + void UnPauseEmulation(); + void HaltEmulation(); + void RunEmulation(); + void ShutdownEmulation(); + + const Core::PerfStatsResults& PerfStats() const; + void ConfigureFilesystemProvider(const std::string& filepath); + Core::SystemResultStatus InitializeEmulation(const std::string& filepath); + + bool IsHandheldOnly(); + void SetDeviceType([[maybe_unused]] int index, int type); + void OnGamepadConnectEvent([[maybe_unused]] int index); + void OnGamepadDisconnectEvent([[maybe_unused]] int index); + SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard(); + +private: + static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); + static void OnEmulationStarted(); + static void OnEmulationStopped(Core::SystemResultStatus result); + +private: + // Window management + std::unique_ptr<EmuWindow_Android> m_window; + ANativeWindow* m_native_window{}; + + // Core emulation + Core::System m_system; + InputCommon::InputSubsystem m_input_subsystem; + Common::DetachedTasks m_detached_tasks; + Core::PerfStatsResults m_perf_stats{}; + std::shared_ptr<FileSys::VfsFilesystem> m_vfs; + Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; + std::atomic<bool> m_is_running = false; + std::atomic<bool> m_is_paused = false; + SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; + std::unique_ptr<Service::Account::ProfileManager> m_profile_manager; + std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider; + + // GPU driver parameters + std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; + + // Synchronization + std::condition_variable_any m_cv; + mutable std::mutex m_perf_stats_mutex; + mutable std::mutex m_mutex; +}; diff --git a/src/android/app/src/main/res/layout/card_home_option.xml b/src/android/app/src/main/res/layout/card_home_option.xml index f9f1d89fb..6e8a232f9 100644 --- a/src/android/app/src/main/res/layout/card_home_option.xml +++ b/src/android/app/src/main/res/layout/card_home_option.xml @@ -16,7 +16,8 @@ <LinearLayout android:id="@+id/option_layout" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical"> <ImageView android:id="@+id/option_icon" diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 8a1861051..e216eb3de 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(common STATIC socket_types.h spin_lock.cpp spin_lock.h + stb.cpp + stb.h steady_clock.cpp steady_clock.h stream.cpp @@ -208,6 +210,8 @@ if (MSVC) /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) +else() + set_source_files_properties(stb.cpp PROPERTIES COMPILE_OPTIONS "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers") endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") @@ -223,7 +227,7 @@ endif() create_target_directory_groups(common) -target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile Threads::Threads) +target_link_libraries(common PUBLIC Boost::context Boost::headers fmt::fmt microprofile stb::headers Threads::Threads) target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle) if (ANDROID) diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp index 298a79bac..1dd826a4a 100644 --- a/src/common/fs/fs_android.cpp +++ b/src/common/fs/fs_android.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/fs/fs_android.h" +#include "common/string_util.h" namespace Common::FS::Android { @@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) { env->GetJavaVM(&g_jvm); native_library = clazz; +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ F(JMethodID, JMethodName, Signature) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ F(JMethodID, JMethodName, Signature) #define F(JMethodID, JMethodName, Signature) \ JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); + ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH } void UnRegisterCallbacks() { +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define F(JMethodID) JMethodID = nullptr; + ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH } bool IsContentUri(const std::string& path) { @@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) #undef F #undef FR +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, JMethodID, Caller) +#define F(FunctionName, JMethodID, Caller) \ + std::string FunctionName(const std::string& filepath) { \ + if (JMethodID == nullptr) { \ + return 0; \ + } \ + auto env = GetEnvForThread(); \ + jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ + jstring j_return = \ + static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \ + if (!j_return) { \ + return {}; \ + } \ + const jchar* jchars = env->GetStringChars(j_return, nullptr); \ + const jsize length = env->GetStringLength(j_return); \ + const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \ + const std::string converted_string = Common::UTF16ToUTF8(string_view); \ + env->ReleaseStringChars(j_return, jchars); \ + return converted_string; \ + } +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) +#undef F +#undef FH + } // namespace Common::FS::Android diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index b441c2a12..2c9234313 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h @@ -17,19 +17,28 @@ "(Ljava/lang/String;)Z") \ V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") +#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \ + V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \ + "(Ljava/lang/String;)Ljava/lang/String;") \ + V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \ + "(Ljava/lang/String;)Ljava/lang/String;") + namespace Common::FS::Android { static JavaVM* g_jvm = nullptr; static jclass native_library = nullptr; +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define F(JMethodID) static jmethodID JMethodID = nullptr; +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH enum class OpenMode { Read, @@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) #undef F #undef FR +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName) +#define F(FunctionName) std::string FunctionName(const std::string& filepath); +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) +#undef F +#undef FH + } // namespace Common::FS::Android diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 441c8af97..bcf447089 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -13,6 +13,7 @@ #define AMIIBO_DIR "amiibo" #define CACHE_DIR "cache" #define CONFIG_DIR "config" +#define CRASH_DUMPS_DIR "crash_dumps" #define DUMP_DIR "dump" #define KEYS_DIR "keys" #define LOAD_DIR "load" diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 0abd81a45..c3a81f9a9 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -119,6 +119,7 @@ public: GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR); GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache); GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config); + GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR); GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR); GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); @@ -400,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se } std::string_view GetParentPath(std::string_view path) { + if (path.empty()) { + return path; + } + +#ifdef ANDROID + if (path[0] != '/') { + std::string path_string{path}; + return FS::Android::GetParentDirectory(path_string); + } +#endif const auto name_bck_index = path.rfind('\\'); const auto name_fwd_index = path.rfind('/'); std::size_t name_index; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 63801c924..2874ea738 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -15,6 +15,7 @@ enum class YuzuPath { AmiiboDir, // Where Amiibo backups are stored. CacheDir, // Where cached filesystem data is stored. ConfigDir, // Where config files are stored. + CrashDumpsDir, // Where crash dumps are stored. DumpDir, // Where dumped data is stored. KeysDir, // Where key files are stored. LoadDir, // Where cheat/mod files are stored. diff --git a/src/common/nvidia_flags.cpp b/src/common/nvidia_flags.cpp index 7ed7690ee..fa3747782 100644 --- a/src/common/nvidia_flags.cpp +++ b/src/common/nvidia_flags.cpp @@ -25,6 +25,7 @@ void ConfigureNvidiaEnvironmentFlags() { void(_putenv(fmt::format("__GL_SHADER_DISK_CACHE_PATH={}", windows_path_string).c_str())); void(_putenv("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1")); + void(_putenv("__GL_THREADED_OPTIMIZATIONS=1")); #endif } diff --git a/src/common/settings.h b/src/common/settings.h index 236e33bee..9317075f7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -505,7 +505,6 @@ struct Values { linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false}; Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers", Category::Debugging}; - Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging}; Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging}; // Miscellaneous diff --git a/src/common/stb.cpp b/src/common/stb.cpp new file mode 100644 index 000000000..d3b16665d --- /dev/null +++ b/src/common/stb.cpp @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#include "common/stb.h" diff --git a/src/common/stb.h b/src/common/stb.h new file mode 100644 index 000000000..e5c197c11 --- /dev/null +++ b/src/common/stb.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <stb_image.h> +#include <stb_image_resize.h> +#include <stb_image_write.h> diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 4c7aba3f5..72c481798 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,6 +14,10 @@ #include <windows.h> #endif +#ifdef ANDROID +#include <common/fs/fs_android.h> +#endif + namespace Common { /// Make a string lowercase @@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ if (full_path.empty()) return false; +#ifdef ANDROID + if (full_path[0] != '/') { + *_pPath = Common::FS::Android::GetParentDirectory(full_path); + *_pFilename = Common::FS::Android::GetFilename(full_path); + return true; + } +#endif + std::size_t dir_end = full_path.find_last_of("/" // windows needs the : included for something like just "C:" to be considered a directory #ifdef _WIN32 diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 0c012f094..5e27dde58 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -86,9 +86,9 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt std::map<std::string, Symbols::Symbols> symbols; for (const auto& module : modules) { - symbols.insert_or_assign( - module.second, Symbols::GetSymbols(module.first, system.ApplicationMemory(), - system.ApplicationProcess()->Is64BitProcess())); + symbols.insert_or_assign(module.second, + Symbols::GetSymbols(module.first, system.ApplicationMemory(), + system.ApplicationProcess()->Is64Bit())); } for (auto& entry : out) { diff --git a/src/core/core.cpp b/src/core/core.cpp index d7e2efbd7..14d6c8c27 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -309,17 +309,10 @@ struct System::Impl { telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider); - // Create a resource limit for the process. - const auto physical_memory_size = - kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application); - auto* resource_limit = Kernel::CreateResourceLimitForProcess(system, physical_memory_size); - // Create the process. auto main_process = Kernel::KProcess::Create(system.Kernel()); - ASSERT(Kernel::KProcess::Initialize(main_process, system, "main", - Kernel::KProcess::ProcessType::Userland, resource_limit) - .IsSuccess()); Kernel::KProcess::Register(system.Kernel(), main_process); + kernel.AppendNewProcess(main_process); kernel.MakeApplicationProcess(main_process); const auto [load_result, load_parameters] = app_loader->Load(*main_process, system); if (load_result != Loader::ResultStatus::Success) { @@ -418,6 +411,7 @@ struct System::Impl { services->KillNVNFlinger(); } kernel.CloseServices(); + kernel.ShutdownCores(); services.reset(); service_manager.reset(); cheat_engine.reset(); @@ -429,7 +423,6 @@ struct System::Impl { gpu_core.reset(); host1x_core.reset(); perf_stats.reset(); - kernel.ShutdownCores(); cpu_manager.Shutdown(); debugger.reset(); kernel.Shutdown(); diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index a1589fecb..0e270eb50 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -258,20 +258,20 @@ private: Kernel::KScopedSchedulerLock sl{system.Kernel()}; // Put all threads to sleep on next scheduler round. - for (auto* thread : ThreadList()) { - thread->RequestSuspend(Kernel::SuspendType::Debug); + for (auto& thread : ThreadList()) { + thread.RequestSuspend(Kernel::SuspendType::Debug); } } void ResumeEmulation(Kernel::KThread* except = nullptr) { // Wake up all threads. - for (auto* thread : ThreadList()) { - if (thread == except) { + for (auto& thread : ThreadList()) { + if (std::addressof(thread) == except) { continue; } - thread->SetStepState(Kernel::StepState::NotStepping); - thread->Resume(Kernel::SuspendType::Debug); + thread.SetStepState(Kernel::StepState::NotStepping); + thread.Resume(Kernel::SuspendType::Debug); } } @@ -283,13 +283,17 @@ private: } void UpdateActiveThread() { - const auto& threads{ThreadList()}; - if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) { - state->active_thread = threads.front(); + auto& threads{ThreadList()}; + for (auto& thread : threads) { + if (std::addressof(thread) == state->active_thread) { + // Thread is still alive, no need to update. + return; + } } + state->active_thread = std::addressof(threads.front()); } - const std::list<Kernel::KThread*>& ThreadList() { + Kernel::KProcess::ThreadList& ThreadList() { return system.ApplicationProcess()->GetThreadList(); } diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 2076aa8a2..6f5f5156b 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -109,7 +109,7 @@ static std::string EscapeXML(std::string_view data) { GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) : DebuggerFrontend(backend_), system{system_} { - if (system.ApplicationProcess()->Is64BitProcess()) { + if (system.ApplicationProcess()->Is64Bit()) { arch = std::make_unique<GDBStubA64>(); } else { arch = std::make_unique<GDBStubA32>(); @@ -446,10 +446,10 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) { // See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, - const Kernel::KThread* thread) { + const Kernel::KThread& thread) { // Read thread type from TLS - const VAddr tls_thread_type{memory.Read32(thread->GetTlsAddress() + 0x1fc)}; - const VAddr argument_thread_type{thread->GetArgument()}; + const VAddr tls_thread_type{memory.Read32(thread.GetTlsAddress() + 0x1fc)}; + const VAddr argument_thread_type{thread.GetArgument()}; if (argument_thread_type && tls_thread_type != argument_thread_type) { // Probably not created by nnsdk, no name available. @@ -477,10 +477,10 @@ static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& } static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, - const Kernel::KThread* thread) { + const Kernel::KThread& thread) { // Read thread type from TLS - const VAddr tls_thread_type{memory.Read64(thread->GetTlsAddress() + 0x1f8)}; - const VAddr argument_thread_type{thread->GetArgument()}; + const VAddr tls_thread_type{memory.Read64(thread.GetTlsAddress() + 0x1f8)}; + const VAddr argument_thread_type{thread.GetArgument()}; if (argument_thread_type && tls_thread_type != argument_thread_type) { // Probably not created by nnsdk, no name available. @@ -508,16 +508,16 @@ static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& } static std::optional<std::string> GetThreadName(Core::System& system, - const Kernel::KThread* thread) { - if (system.ApplicationProcess()->Is64BitProcess()) { + const Kernel::KThread& thread) { + if (system.ApplicationProcess()->Is64Bit()) { return GetNameFromThreadType64(system.ApplicationMemory(), thread); } else { return GetNameFromThreadType32(system.ApplicationMemory(), thread); } } -static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { - switch (thread->GetWaitReasonForDebugging()) { +static std::string_view GetThreadWaitReason(const Kernel::KThread& thread) { + switch (thread.GetWaitReasonForDebugging()) { case Kernel::ThreadWaitReasonForDebugging::Sleep: return "Sleep"; case Kernel::ThreadWaitReasonForDebugging::IPC: @@ -535,8 +535,8 @@ static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { } } -static std::string GetThreadState(const Kernel::KThread* thread) { - switch (thread->GetState()) { +static std::string GetThreadState(const Kernel::KThread& thread) { + switch (thread.GetState()) { case Kernel::ThreadState::Initialized: return "Initialized"; case Kernel::ThreadState::Waiting: @@ -604,7 +604,7 @@ void GDBStub::HandleQuery(std::string_view command) { const auto& threads = system.ApplicationProcess()->GetThreadList(); std::vector<std::string> thread_ids; for (const auto& thread : threads) { - thread_ids.push_back(fmt::format("{:x}", thread->GetThreadId())); + thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId())); } SendReply(fmt::format("m{}", fmt::join(thread_ids, ","))); } else if (command.starts_with("sThreadInfo")) { @@ -616,14 +616,14 @@ void GDBStub::HandleQuery(std::string_view command) { buffer += "<threads>"; const auto& threads = system.ApplicationProcess()->GetThreadList(); - for (const auto* thread : threads) { + for (const auto& thread : threads) { auto thread_name{GetThreadName(system, thread)}; if (!thread_name) { - thread_name = fmt::format("Thread {:d}", thread->GetThreadId()); + thread_name = fmt::format("Thread {:d}", thread.GetThreadId()); } buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)", - thread->GetThreadId(), thread->GetActiveCore(), + thread.GetThreadId(), thread.GetActiveCore(), EscapeXML(*thread_name), GetThreadState(thread)); } @@ -850,10 +850,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) { } Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { - const auto& threads{system.ApplicationProcess()->GetThreadList()}; - for (auto* thread : threads) { - if (thread->GetThreadId() == thread_id) { - return thread; + auto& threads{system.ApplicationProcess()->GetThreadList()}; + for (auto& thread : threads) { + if (thread.GetThreadId() == thread_id) { + return std::addressof(thread); } } diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 8e291ff67..763a44fee 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -104,16 +104,16 @@ Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) { } /*static*/ ProgramMetadata ProgramMetadata::GetDefault() { - // Allow use of cores 0~3 and thread priorities 1~63. - constexpr u32 default_thread_info_capability = 0x30007F7; + // Allow use of cores 0~3 and thread priorities 16~63. + constexpr u32 default_thread_info_capability = 0x30043F7; ProgramMetadata result; result.LoadManual( true /*is_64_bit*/, FileSys::ProgramAddressSpaceType::Is39Bit /*address_space*/, - 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x00100000 /*main_thread_stack_size*/, - 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, - 0x1FE00000 /*system_resource_size*/, {default_thread_info_capability} /*capabilities*/); + 0x2c /*main_thread_prio*/, 0 /*main_thread_core*/, 0x100000 /*main_thread_stack_size*/, + 0 /*title_id*/, 0xFFFFFFFFFFFFFFFF /*filesystem_permissions*/, 0 /*system_resource_size*/, + {default_thread_info_capability} /*capabilities*/); return result; } diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 9f8e74b13..76ee97d78 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -73,6 +73,9 @@ public: u64 GetFilesystemPermissions() const; u32 GetSystemResourceSize() const; const KernelCapabilityDescriptors& GetKernelCapabilities() const; + const std::array<u8, 0x10>& GetName() const { + return npdm_header.application_name; + } void Print() const; @@ -164,14 +167,14 @@ private: u32_le unk_size_2; }; - Header npdm_header; - AciHeader aci_header; - AcidHeader acid_header; + Header npdm_header{}; + AciHeader aci_header{}; + AcidHeader acid_header{}; - FileAccessControl acid_file_access; - FileAccessHeader aci_file_access; + FileAccessControl acid_file_access{}; + FileAccessHeader aci_file_access{}; - KernelCapabilityDescriptors aci_kernel_capabilities; + KernelCapabilityDescriptors aci_kernel_capabilities{}; }; } // namespace FileSys diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp index 4cfdf4558..59364efa1 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -8,7 +8,11 @@ #include "core/hle/kernel/board/nintendo/nx/k_system_control.h" #include "core/hle/kernel/board/nintendo/nx/secure_monitor.h" +#include "core/hle/kernel/k_memory_manager.h" +#include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_trace.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/svc_results.h" namespace Kernel::Board::Nintendo::Nx { @@ -30,6 +34,8 @@ constexpr const std::size_t RequiredNonSecureSystemMemorySize = constexpr const std::size_t RequiredNonSecureSystemMemorySizeWithFatal = RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal; +constexpr const std::size_t SecureAlignment = 128_KiB; + namespace { using namespace Common::Literals; @@ -183,4 +189,57 @@ u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) { return GenerateUniformRange(min, max, GenerateRandomU64); } +size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) { + if (pool == static_cast<u32>(KMemoryManager::Pool::Applet)) { + return 0; + } else { + // return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool); + return size; + } +} + +Result KSystemControl::AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size, + u32 pool) { + // Applet secure memory is handled separately. + UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet)); + + // Ensure the size is aligned. + const size_t alignment = + (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment); + R_UNLESS(Common::IsAligned(size, alignment), ResultInvalidSize); + + // Allocate the memory. + const size_t num_pages = size / PageSize; + const KPhysicalAddress paddr = kernel.MemoryManager().AllocateAndOpenContinuous( + num_pages, alignment / PageSize, + KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool), + KMemoryManager::Direction::FromFront)); + R_UNLESS(paddr != 0, ResultOutOfMemory); + + // Ensure we don't leak references to the memory on error. + ON_RESULT_FAILURE { + kernel.MemoryManager().Close(paddr, num_pages); + }; + + // We succeeded. + *out = KPageTable::GetHeapVirtualAddress(kernel.MemoryLayout(), paddr); + R_SUCCEED(); +} + +void KSystemControl::FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size, + u32 pool) { + // Applet secure memory is handled separately. + UNIMPLEMENTED_IF(pool == static_cast<u32>(KMemoryManager::Pool::Applet)); + + // Ensure the size is aligned. + const size_t alignment = + (pool == static_cast<u32>(KMemoryManager::Pool::System) ? PageSize : SecureAlignment); + ASSERT(Common::IsAligned(GetInteger(address), alignment)); + ASSERT(Common::IsAligned(size, alignment)); + + // Close the secure region's pages. + kernel.MemoryManager().Close(KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), address), + size / PageSize); +} + } // namespace Kernel::Board::Nintendo::Nx diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h index b477e8193..ff1feec70 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h @@ -4,6 +4,11 @@ #pragma once #include "core/hle/kernel/k_typed_address.h" +#include "core/hle/result.h" + +namespace Kernel { +class KernelCore; +} namespace Kernel::Board::Nintendo::Nx { @@ -25,8 +30,16 @@ public: static std::size_t GetMinimumNonSecureSystemPoolSize(); }; + // Randomness. static u64 GenerateRandomRange(u64 min, u64 max); static u64 GenerateRandomU64(); + + // Secure Memory. + static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool); + static Result AllocateSecureMemory(KernelCore& kernel, KVirtualAddress* out, size_t size, + u32 pool); + static void FreeSecureMemory(KernelCore& kernel, KVirtualAddress address, size_t size, + u32 pool); }; } // namespace Kernel::Board::Nintendo::Nx diff --git a/src/core/hle/kernel/k_capabilities.h b/src/core/hle/kernel/k_capabilities.h index de766c811..ebd4eedb1 100644 --- a/src/core/hle/kernel/k_capabilities.h +++ b/src/core/hle/kernel/k_capabilities.h @@ -200,8 +200,8 @@ private: RawCapabilityValue raw; BitField<0, 15, CapabilityType> id; - BitField<15, 4, u32> major_version; - BitField<19, 13, u32> minor_version; + BitField<15, 4, u32> minor_version; + BitField<19, 13, u32> major_version; }; union HandleTable { diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index efbac0e6a..7633a51fb 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -107,12 +107,12 @@ KConditionVariable::KConditionVariable(Core::System& system) KConditionVariable::~KConditionVariable() = default; -Result KConditionVariable::SignalToAddress(KProcessAddress addr) { - KThread* owner_thread = GetCurrentThreadPointer(m_kernel); +Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress addr) { + KThread* owner_thread = GetCurrentThreadPointer(kernel); // Signal the address. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Remove waiter thread. bool has_waiters{}; @@ -133,7 +133,7 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) { // Write the value to userspace. Result result{ResultSuccess}; - if (WriteToUser(m_kernel, addr, std::addressof(next_value))) [[likely]] { + if (WriteToUser(kernel, addr, std::addressof(next_value))) [[likely]] { result = ResultSuccess; } else { result = ResultInvalidCurrentMemory; @@ -148,28 +148,28 @@ Result KConditionVariable::SignalToAddress(KProcessAddress addr) { } } -Result KConditionVariable::WaitForAddress(Handle handle, KProcessAddress addr, u32 value) { - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); - ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(m_kernel); +Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, + u32 value) { + KThread* cur_thread = GetCurrentThreadPointer(kernel); + ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel); // Wait for the address. KThread* owner_thread{}; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Check if the thread should terminate. R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested); // Read the tag from userspace. u32 test_tag{}; - R_UNLESS(ReadFromUser(m_kernel, std::addressof(test_tag), addr), - ResultInvalidCurrentMemory); + R_UNLESS(ReadFromUser(kernel, std::addressof(test_tag), addr), ResultInvalidCurrentMemory); // If the tag isn't the handle (with wait mask), we're done. R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask)); // Get the lock owner thread. - owner_thread = GetCurrentProcess(m_kernel) + owner_thread = GetCurrentProcess(kernel) .GetHandleTable() .GetObjectWithoutPseudoHandle<KThread>(handle) .ReleasePointerUnsafe(); diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h index 8c2f3ae51..2620c8e39 100644 --- a/src/core/hle/kernel/k_condition_variable.h +++ b/src/core/hle/kernel/k_condition_variable.h @@ -24,11 +24,12 @@ public: explicit KConditionVariable(Core::System& system); ~KConditionVariable(); - // Arbitration - Result SignalToAddress(KProcessAddress addr); - Result WaitForAddress(Handle handle, KProcessAddress addr, u32 value); + // Arbitration. + static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr); + static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, + u32 value); - // Condition variable + // Condition variable. void Signal(u64 cv_key, s32 count); Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout); diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index fe6a20168..22d79569a 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -22,7 +22,7 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { KScopedSchedulerLock sl{kernel}; // Pin the current thread. - process->PinCurrentThread(core_id); + process->PinCurrentThread(); // Set the interrupt flag for the thread. GetCurrentThread(kernel).SetInterruptFlag(); diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index 637558e10..cdc5572d8 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/initial_process.h" #include "core/hle/kernel/k_memory_manager.h" #include "core/hle/kernel/k_page_group.h" +#include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/svc_results.h" @@ -168,11 +169,37 @@ void KMemoryManager::Initialize(KVirtualAddress management_region, size_t manage } Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) { - UNREACHABLE(); + const u32 pool_index = static_cast<u32>(pool); + + // Lock the pool. + KScopedLightLock lk(m_pool_locks[pool_index]); + + // Check that we don't already have an optimized process. + R_UNLESS(!m_has_optimized_process[pool_index], ResultBusy); + + // Set the optimized process id. + m_optimized_process_ids[pool_index] = process_id; + m_has_optimized_process[pool_index] = true; + + // Clear the management area for the optimized process. + for (auto* manager = this->GetFirstManager(pool, Direction::FromFront); manager != nullptr; + manager = this->GetNextManager(manager, Direction::FromFront)) { + manager->InitializeOptimizedMemory(m_system.Kernel()); + } + + R_SUCCEED(); } void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) { - UNREACHABLE(); + const u32 pool_index = static_cast<u32>(pool); + + // Lock the pool. + KScopedLightLock lk(m_pool_locks[pool_index]); + + // If the process was optimized, clear it. + if (m_has_optimized_process[pool_index] && m_optimized_process_ids[pool_index] == process_id) { + m_has_optimized_process[pool_index] = false; + } } KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, @@ -207,7 +234,7 @@ KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, siz // Maintain the optimized memory bitmap, if we should. if (m_has_optimized_process[static_cast<size_t>(pool)]) { - UNIMPLEMENTED(); + chosen_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block, num_pages); } // Open the first reference to the pages. @@ -255,7 +282,8 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, // Maintain the optimized memory bitmap, if we should. if (unoptimized) { - UNIMPLEMENTED(); + cur_manager->TrackUnoptimizedAllocation(m_system.Kernel(), allocated_block, + pages_per_alloc); } num_pages -= pages_per_alloc; @@ -358,8 +386,8 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32 // Process part or all of the block. const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); - any_new = - manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern); + any_new = manager.ProcessOptimizedAllocation(m_system.Kernel(), cur_address, + cur_pages, fill_pattern); // Advance. cur_address += cur_pages * PageSize; @@ -382,7 +410,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32 // Track some or all of the current pages. const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); - manager.TrackOptimizedAllocation(cur_address, cur_pages); + manager.TrackOptimizedAllocation(m_system.Kernel(), cur_address, cur_pages); // Advance. cur_address += cur_pages * PageSize; @@ -427,17 +455,86 @@ size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size, return total_management_size; } -void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) { - UNREACHABLE(); +void KMemoryManager::Impl::InitializeOptimizedMemory(KernelCore& kernel) { + auto optimize_pa = + KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region); + auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa); + + std::memset(optimize_map, 0, CalculateOptimizedProcessOverheadSize(m_heap.GetSize())); } -void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) { - UNREACHABLE(); +void KMemoryManager::Impl::TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, + size_t num_pages) { + auto optimize_pa = + KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region); + auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa); + + // Get the range we're tracking. + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + // Track. + while (offset <= last) { + // Mark the page as not being optimized-allocated. + optimize_map[offset / Common::BitSize<u64>()] &= + ~(u64(1) << (offset % Common::BitSize<u64>())); + + offset++; + } +} + +void KMemoryManager::Impl::TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, + size_t num_pages) { + auto optimize_pa = + KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region); + auto* optimize_map = kernel.System().DeviceMemory().GetPointer<u64>(optimize_pa); + + // Get the range we're tracking. + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + // Track. + while (offset <= last) { + // Mark the page as being optimized-allocated. + optimize_map[offset / Common::BitSize<u64>()] |= + (u64(1) << (offset % Common::BitSize<u64>())); + + offset++; + } } -bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, - u8 fill_pattern) { - UNREACHABLE(); +bool KMemoryManager::Impl::ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, + size_t num_pages, u8 fill_pattern) { + auto& device_memory = kernel.System().DeviceMemory(); + auto optimize_pa = + KPageTable::GetHeapPhysicalAddress(kernel.MemoryLayout(), m_management_region); + auto* optimize_map = device_memory.GetPointer<u64>(optimize_pa); + + // We want to return whether any pages were newly allocated. + bool any_new = false; + + // Get the range we're processing. + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + // Process. + while (offset <= last) { + // Check if the page has been optimized-allocated before. + if ((optimize_map[offset / Common::BitSize<u64>()] & + (u64(1) << (offset % Common::BitSize<u64>()))) == 0) { + // If not, it's new. + any_new = true; + + // Fill the page. + auto* ptr = device_memory.GetPointer<u8>(m_heap.GetAddress()); + std::memset(ptr + offset * PageSize, fill_pattern, PageSize); + } + + offset++; + } + + // Return the number of pages we processed. + return any_new; } size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) { diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h index 7e4b41319..c5a487af9 100644 --- a/src/core/hle/kernel/k_memory_manager.h +++ b/src/core/hle/kernel/k_memory_manager.h @@ -216,14 +216,14 @@ private: m_heap.SetInitialUsedSize(reserved_size); } - void InitializeOptimizedMemory() { - UNIMPLEMENTED(); - } + void InitializeOptimizedMemory(KernelCore& kernel); - void TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages); - void TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages); + void TrackUnoptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, + size_t num_pages); + void TrackOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, size_t num_pages); - bool ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern); + bool ProcessOptimizedAllocation(KernelCore& kernel, KPhysicalAddress block, + size_t num_pages, u8 fill_pattern); constexpr Pool GetPool() const { return m_pool; diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 217ccbae3..1d47bdf6b 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -82,14 +82,14 @@ public: using namespace Common::Literals; -constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) { +constexpr size_t GetAddressSpaceWidthFromType(Svc::CreateProcessFlag as_type) { switch (as_type) { - case FileSys::ProgramAddressSpaceType::Is32Bit: - case FileSys::ProgramAddressSpaceType::Is32BitNoMap: + case Svc::CreateProcessFlag::AddressSpace32Bit: + case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias: return 32; - case FileSys::ProgramAddressSpaceType::Is36Bit: + case Svc::CreateProcessFlag::AddressSpace64BitDeprecated: return 36; - case FileSys::ProgramAddressSpaceType::Is39Bit: + case Svc::CreateProcessFlag::AddressSpace64Bit: return 39; default: ASSERT(false); @@ -105,7 +105,7 @@ KPageTable::KPageTable(Core::System& system_) KPageTable::~KPageTable() = default; -Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, +Result KPageTable::InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_addr, size_t code_size, KSystemResource* system_resource, @@ -133,7 +133,7 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type ASSERT(code_addr + code_size - 1 <= end - 1); // Adjust heap/alias size if we don't have an alias region - if (as_type == FileSys::ProgramAddressSpaceType::Is32BitNoMap) { + if (as_type == Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias) { heap_region_size += alias_region_size; alias_region_size = 0; } diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 3d64b6fb0..66f16faaf 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h @@ -63,7 +63,7 @@ public: explicit KPageTable(Core::System& system_); ~KPageTable(); - Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr, + Result InitializeForProcess(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_addr, size_t code_size, KSystemResource* system_resource, KResourceLimit* resource_limit, @@ -400,7 +400,7 @@ public: constexpr size_t GetAliasCodeRegionSize() const { return m_alias_code_region_end - m_alias_code_region_start; } - size_t GetNormalMemorySize() { + size_t GetNormalMemorySize() const { KScopedLightLock lk(m_general_lock); return GetHeapSize() + m_mapped_physical_memory_size; } diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 7fa34d693..1f4b0755d 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -1,515 +1,598 @@ -// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <algorithm> -#include <bitset> -#include <ctime> -#include <memory> #include <random> -#include "common/alignment.h" -#include "common/assert.h" -#include "common/logging/log.h" #include "common/scope_exit.h" #include "common/settings.h" #include "core/core.h" -#include "core/file_sys/program_metadata.h" -#include "core/hle/kernel/code_set.h" -#include "core/hle/kernel/k_memory_block_manager.h" -#include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" -#include "core/hle/kernel/k_resource_limit.h" -#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/kernel/k_shared_memory_info.h" -#include "core/hle/kernel/k_thread.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/svc_results.h" -#include "core/memory.h" +#include "core/hle/kernel/k_thread_local_page.h" +#include "core/hle/kernel/k_thread_queue.h" +#include "core/hle/kernel/k_worker_task_manager.h" namespace Kernel { -namespace { -/** - * Sets up the primary application thread - * - * @param system The system instance to create the main thread under. - * @param owner_process The parent process for the main thread - * @param priority The priority to give the main thread - */ -void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, - KProcessAddress stack_top) { - const KProcessAddress entry_point = owner_process.GetEntryPoint(); - ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); - - KThread* thread = KThread::Create(system.Kernel()); - SCOPE_EXIT({ thread->Close(); }); - - ASSERT(KThread::InitializeUserThread(system, thread, entry_point, 0, stack_top, priority, - owner_process.GetIdealCoreId(), - std::addressof(owner_process)) - .IsSuccess()); - - // Register 1 must be a handle to the main thread - Handle thread_handle{}; - owner_process.GetHandleTable().Add(std::addressof(thread_handle), thread); - - thread->GetContext32().cpu_registers[0] = 0; - thread->GetContext64().cpu_registers[0] = 0; - thread->GetContext32().cpu_registers[1] = thread_handle; - thread->GetContext64().cpu_registers[1] = thread_handle; - - if (system.DebuggerEnabled()) { - thread->RequestSuspend(SuspendType::Debug); - } - // Run our thread. - void(thread->Run()); -} -} // Anonymous namespace +namespace { -Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process_name, - ProcessType type, KResourceLimit* res_limit) { - auto& kernel = system.Kernel(); +Result TerminateChildren(KernelCore& kernel, KProcess* process, + const KThread* thread_to_not_terminate) { + // Request that all children threads terminate. + { + KScopedLightLock proc_lk(process->GetListLock()); + KScopedSchedulerLock sl(kernel); + + if (thread_to_not_terminate != nullptr && + process->GetPinnedThread(GetCurrentCoreId(kernel)) == thread_to_not_terminate) { + // NOTE: Here Nintendo unpins the current thread instead of the thread_to_not_terminate. + // This is valid because the only caller which uses non-nullptr as argument uses + // GetCurrentThreadPointer(), but it's still notable because it seems incorrect at + // first glance. + process->UnpinCurrentThread(); + } - process->name = std::move(process_name); - process->m_resource_limit = res_limit; - process->m_system_resource_address = 0; - process->m_state = State::Created; - process->m_program_id = 0; - process->m_process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() - : kernel.CreateNewUserProcessID(); - process->m_capabilities.InitializeForMetadatalessProcess(); - process->m_is_initialized = true; + auto& thread_list = process->GetThreadList(); + for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { + if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) { + if (thread->GetState() != ThreadState::Terminated) { + thread->RequestTerminate(); + } + } + } + } - std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue() - : static_cast<u32>(std::time(nullptr))); - std::uniform_int_distribution<u64> distribution; - std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(), - [&] { return distribution(rng); }); + // Wait for all children threads to terminate. + while (true) { + // Get the next child. + KThread* cur_child = nullptr; + { + KScopedLightLock proc_lk(process->GetListLock()); + + auto& thread_list = process->GetThreadList(); + for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { + if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) { + if (thread->GetState() != ThreadState::Terminated) { + if (thread->Open()) { + cur_child = thread; + break; + } + } + } + } + } - kernel.AppendNewProcess(process); + // If we didn't find any non-terminated children, we're done. + if (cur_child == nullptr) { + break; + } - // Clear remaining fields. - process->m_num_running_threads = 0; - process->m_is_signaled = false; - process->m_exception_thread = nullptr; - process->m_is_suspended = false; - process->m_schedule_count = 0; - process->m_is_handle_table_initialized = false; - process->m_is_hbl = false; + // Terminate and close the thread. + SCOPE_EXIT({ cur_child->Close(); }); - // Open a reference to the resource limit. - process->m_resource_limit->Open(); + if (const Result terminate_result = cur_child->Terminate(); + ResultTerminationRequested == terminate_result) { + R_THROW(terminate_result); + } + } R_SUCCEED(); } -void KProcess::DoWorkerTaskImpl() { - UNIMPLEMENTED(); -} - -KResourceLimit* KProcess::GetResourceLimit() const { - return m_resource_limit; -} +class ThreadQueueImplForKProcessEnterUserException final : public KThreadQueue { +private: + KThread** m_exception_thread; -void KProcess::IncrementRunningThreadCount() { - ASSERT(m_num_running_threads.load() >= 0); - ++m_num_running_threads; -} +public: + explicit ThreadQueueImplForKProcessEnterUserException(KernelCore& kernel, KThread** t) + : KThreadQueue(kernel), m_exception_thread(t) {} -void KProcess::DecrementRunningThreadCount() { - ASSERT(m_num_running_threads.load() > 0); + virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + // Set the exception thread. + *m_exception_thread = waiting_thread; - if (const auto prev = m_num_running_threads--; prev == 1) { - // TODO(bunnei): Process termination to be implemented when multiprocess is supported. + // Invoke the base end wait handler. + KThreadQueue::EndWait(waiting_thread, wait_result); } -} -u64 KProcess::GetTotalPhysicalMemoryAvailable() { - const u64 capacity{m_resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) + - m_page_table.GetNormalMemorySize() + GetSystemResourceSize() + m_image_size + - m_main_thread_stack_size}; - if (const auto pool_size = m_kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application); - capacity != pool_size) { - LOG_WARNING(Kernel, "capacity {} != application pool size {}", capacity, pool_size); - } - if (capacity < m_memory_usage_capacity) { - return capacity; + virtual void CancelWait(KThread* waiting_thread, Result wait_result, + bool cancel_timer_task) override { + // Remove the thread as a waiter on its mutex owner. + waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread); + + // Invoke the base cancel wait handler. + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); } - return m_memory_usage_capacity; -} +}; -u64 KProcess::GetTotalPhysicalMemoryAvailableWithoutSystemResource() { - return this->GetTotalPhysicalMemoryAvailable() - this->GetSystemResourceSize(); +void GenerateRandom(std::span<u64> out_random) { + std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue() + : static_cast<u32>(std::time(nullptr))); + std::uniform_int_distribution<u64> distribution; + std::generate(out_random.begin(), out_random.end(), [&] { return distribution(rng); }); } -u64 KProcess::GetTotalPhysicalMemoryUsed() { - return m_image_size + m_main_thread_stack_size + m_page_table.GetNormalMemorySize() + - this->GetSystemResourceSize(); -} +} // namespace -u64 KProcess::GetTotalPhysicalMemoryUsedWithoutSystemResource() { - return this->GetTotalPhysicalMemoryUsed() - this->GetSystemResourceSize(); -} +void KProcess::Finalize() { + // Delete the process local region. + this->DeleteThreadLocalRegion(m_plr_address); -bool KProcess::ReleaseUserException(KThread* thread) { - KScopedSchedulerLock sl{m_kernel}; + // Get the used memory size. + const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize(); - if (m_exception_thread == thread) { - m_exception_thread = nullptr; + // Finalize the page table. + m_page_table.Finalize(); - // Remove waiter thread. - bool has_waiters{}; - if (KThread* next = thread->RemoveKernelWaiterByKey( - std::addressof(has_waiters), - reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread))); - next != nullptr) { - next->EndWait(ResultSuccess); + // Finish using our system resource. + if (m_system_resource) { + if (m_system_resource->IsSecureResource()) { + // Finalize optimized memory. If memory wasn't optimized, this is a no-op. + m_kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool); } - KScheduler::SetSchedulerUpdateNeeded(m_kernel); - - return true; - } else { - return false; + m_system_resource->Close(); + m_system_resource = nullptr; } -} - -void KProcess::PinCurrentThread(s32 core_id) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - // Get the current thread. - KThread* cur_thread = - m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread(); + // Free all shared memory infos. + { + auto it = m_shared_memory_list.begin(); + while (it != m_shared_memory_list.end()) { + KSharedMemoryInfo* info = std::addressof(*it); + KSharedMemory* shmem = info->GetSharedMemory(); - // If the thread isn't terminated, pin it. - if (!cur_thread->IsTerminationRequested()) { - // Pin it. - this->PinThread(core_id, cur_thread); - cur_thread->Pin(core_id); + while (!info->Close()) { + shmem->Close(); + } + shmem->Close(); - // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + it = m_shared_memory_list.erase(it); + KSharedMemoryInfo::Free(m_kernel, info); + } } -} -void KProcess::UnpinCurrentThread(s32 core_id) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - - // Get the current thread. - KThread* cur_thread = - m_kernel.Scheduler(static_cast<std::size_t>(core_id)).GetSchedulerCurrentThread(); + // Our thread local page list must be empty at this point. + ASSERT(m_partially_used_tlp_tree.empty()); + ASSERT(m_fully_used_tlp_tree.empty()); - // Unpin it. - cur_thread->Unpin(); - this->UnpinThread(core_id, cur_thread); + // Release memory to the resource limit. + if (m_resource_limit != nullptr) { + ASSERT(used_memory_size >= m_memory_release_hint); + m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, used_memory_size, + used_memory_size - m_memory_release_hint); + m_resource_limit->Close(); + } - // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + // Perform inherited finalization. + KSynchronizationObject::Finalize(); } -void KProcess::UnpinThread(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - - // Get the thread's core id. - const auto core_id = thread->GetActiveCore(); +Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, + bool is_real) { + // TODO: remove this special case + if (is_real) { + // Create and clear the process local region. + R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address))); + this->GetMemory().ZeroBlock(m_plr_address, Svc::ThreadLocalRegionSize); + } - // Unpin it. - this->UnpinThread(core_id, thread); - thread->Unpin(); + // Copy in the name from parameters. + static_assert(sizeof(params.name) < sizeof(m_name)); + std::memcpy(m_name.data(), params.name.data(), sizeof(params.name)); + m_name[sizeof(params.name)] = 0; + + // Set misc fields. + m_state = State::Created; + m_main_thread_stack_size = 0; + m_used_kernel_memory_size = 0; + m_ideal_core_id = 0; + m_flags = params.flags; + m_version = params.version; + m_program_id = params.program_id; + m_code_address = params.code_address; + m_code_size = params.code_num_pages * PageSize; + m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication); + + // Set thread fields. + for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + m_running_threads[i] = nullptr; + m_pinned_threads[i] = nullptr; + m_running_thread_idle_counts[i] = 0; + m_running_thread_switch_counts[i] = 0; + } - // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); -} + // Set max memory based on address space type. + switch ((params.flags & Svc::CreateProcessFlag::AddressSpaceMask)) { + case Svc::CreateProcessFlag::AddressSpace32Bit: + case Svc::CreateProcessFlag::AddressSpace64BitDeprecated: + case Svc::CreateProcessFlag::AddressSpace64Bit: + m_max_process_memory = m_page_table.GetHeapRegionSize(); + break; + case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias: + m_max_process_memory = m_page_table.GetHeapRegionSize() + m_page_table.GetAliasRegionSize(); + break; + default: + UNREACHABLE(); + } -Result KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address, - [[maybe_unused]] size_t size) { - // Lock ourselves, to prevent concurrent access. - KScopedLightLock lk(m_state_lock); + // Generate random entropy. + GenerateRandom(m_entropy); - // Try to find an existing info for the memory. - KSharedMemoryInfo* shemen_info = nullptr; - const auto iter = std::find_if( - m_shared_memory_list.begin(), m_shared_memory_list.end(), - [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; }); - if (iter != m_shared_memory_list.end()) { - shemen_info = *iter; - } + // Clear remaining fields. + m_num_running_threads = 0; + m_num_process_switches = 0; + m_num_thread_switches = 0; + m_num_fpu_switches = 0; + m_num_supervisor_calls = 0; + m_num_ipc_messages = 0; - if (shemen_info == nullptr) { - shemen_info = KSharedMemoryInfo::Allocate(m_kernel); - R_UNLESS(shemen_info != nullptr, ResultOutOfMemory); + m_is_signaled = false; + m_exception_thread = nullptr; + m_is_suspended = false; + m_memory_release_hint = 0; + m_schedule_count = 0; + m_is_handle_table_initialized = false; - shemen_info->Initialize(shmem); - m_shared_memory_list.push_back(shemen_info); - } + // Open a reference to our resource limit. + m_resource_limit = res_limit; + m_resource_limit->Open(); - // Open a reference to the shared memory and its info. - shmem->Open(); - shemen_info->Open(); + // We're initialized! + m_is_initialized = true; R_SUCCEED(); } -void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] KProcessAddress address, - [[maybe_unused]] size_t size) { - // Lock ourselves, to prevent concurrent access. - KScopedLightLock lk(m_state_lock); +Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg, + std::span<const u32> caps, KResourceLimit* res_limit, + KMemoryManager::Pool pool, bool immortal) { + ASSERT(res_limit != nullptr); + ASSERT((params.code_num_pages * PageSize) / PageSize == + static_cast<size_t>(params.code_num_pages)); + + // Set members. + m_memory_pool = pool; + m_is_default_application_system_resource = false; + m_is_immortal = immortal; + + // Setup our system resource. + if (const size_t system_resource_num_pages = params.system_resource_num_pages; + system_resource_num_pages != 0) { + // Create a secure system resource. + KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel); + R_UNLESS(secure_resource != nullptr, ResultOutOfResource); + + ON_RESULT_FAILURE { + secure_resource->Close(); + }; + + // Initialize the secure resource. + R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, res_limit, + m_memory_pool)); + + // Set our system resource. + m_system_resource = secure_resource; + } else { + // Use the system-wide system resource. + const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication); + m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource() + : m_kernel.GetSystemSystemResource()); - KSharedMemoryInfo* shemen_info = nullptr; - const auto iter = std::find_if( - m_shared_memory_list.begin(), m_shared_memory_list.end(), - [shmem](const KSharedMemoryInfo* info) { return info->GetSharedMemory() == shmem; }); - if (iter != m_shared_memory_list.end()) { - shemen_info = *iter; + m_is_default_application_system_resource = is_app; + + // Open reference to the system resource. + m_system_resource->Open(); } - ASSERT(shemen_info != nullptr); + // Ensure we clean up our secure resource, if we fail. + ON_RESULT_FAILURE { + m_system_resource->Close(); + m_system_resource = nullptr; + }; - if (shemen_info->Close()) { - m_shared_memory_list.erase(iter); - KSharedMemoryInfo::Free(m_kernel, shemen_info); + // Setup page table. + { + const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask; + const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr); + const bool enable_das_merge = + False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge); + R_TRY(m_page_table.InitializeForProcess( + as_type, enable_aslr, enable_das_merge, !enable_aslr, pool, params.code_address, + params.code_num_pages * PageSize, m_system_resource, res_limit, this->GetMemory())); } + ON_RESULT_FAILURE_2 { + m_page_table.Finalize(); + }; - // Close a reference to the shared memory. - shmem->Close(); -} + // Ensure we can insert the code region. + R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize, + KMemoryState::Code), + ResultInvalidMemoryRegion); -void KProcess::RegisterThread(KThread* thread) { - KScopedLightLock lk{m_list_lock}; + // Map the code region. + R_TRY(m_page_table.MapPageGroup(params.code_address, pg, KMemoryState::Code, + KMemoryPermission::KernelRead)); - m_thread_list.push_back(thread); -} + // Initialize capabilities. + R_TRY(m_capabilities.InitializeForKip(caps, std::addressof(m_page_table))); -void KProcess::UnregisterThread(KThread* thread) { - KScopedLightLock lk{m_list_lock}; + // Initialize the process id. + m_process_id = m_kernel.CreateNewUserProcessID(); + ASSERT(InitialProcessIdMin <= m_process_id); + ASSERT(m_process_id <= InitialProcessIdMax); - m_thread_list.remove(thread); -} + // Initialize the rest of the process. + R_TRY(this->Initialize(params, res_limit, true)); -u64 KProcess::GetFreeThreadCount() const { - if (m_resource_limit != nullptr) { - const auto current_value = - m_resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax); - const auto limit_value = m_resource_limit->GetLimitValue(LimitableResource::ThreadCountMax); - return limit_value - current_value; - } else { - return 0; - } + // We succeeded! + R_SUCCEED(); } -Result KProcess::Reset() { - // Lock the process and the scheduler. - KScopedLightLock lk(m_state_lock); - KScopedSchedulerLock sl{m_kernel}; +Result KProcess::Initialize(const Svc::CreateProcessParameter& params, + std::span<const u32> user_caps, KResourceLimit* res_limit, + KMemoryManager::Pool pool) { + ASSERT(res_limit != nullptr); - // Validate that we're in a state that we can reset. - R_UNLESS(m_state != State::Terminated, ResultInvalidState); - R_UNLESS(m_is_signaled, ResultInvalidState); + // Set members. + m_memory_pool = pool; + m_is_default_application_system_resource = false; + m_is_immortal = false; - // Clear signaled. - m_is_signaled = false; - R_SUCCEED(); -} + // Get the memory sizes. + const size_t code_num_pages = params.code_num_pages; + const size_t system_resource_num_pages = params.system_resource_num_pages; + const size_t code_size = code_num_pages * PageSize; + const size_t system_resource_size = system_resource_num_pages * PageSize; -Result KProcess::SetActivity(ProcessActivity activity) { - // Lock ourselves and the scheduler. - KScopedLightLock lk{m_state_lock}; - KScopedLightLock list_lk{m_list_lock}; - KScopedSchedulerLock sl{m_kernel}; + // Reserve memory for our code resource. + KScopedResourceReservation memory_reservation( + res_limit, Svc::LimitableResource::PhysicalMemoryMax, code_size); + R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); - // Validate our state. - R_UNLESS(m_state != State::Terminating, ResultInvalidState); - R_UNLESS(m_state != State::Terminated, ResultInvalidState); + // Setup our system resource. + if (system_resource_num_pages != 0) { + // Create a secure system resource. + KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel); + R_UNLESS(secure_resource != nullptr, ResultOutOfResource); - // Either pause or resume. - if (activity == ProcessActivity::Paused) { - // Verify that we're not suspended. - R_UNLESS(!m_is_suspended, ResultInvalidState); + ON_RESULT_FAILURE { + secure_resource->Close(); + }; - // Suspend all threads. - for (auto* thread : this->GetThreadList()) { - thread->RequestSuspend(SuspendType::Process); - } + // Initialize the secure resource. + R_TRY(secure_resource->Initialize(system_resource_size, res_limit, m_memory_pool)); + + // Set our system resource. + m_system_resource = secure_resource; - // Set ourselves as suspended. - this->SetSuspended(true); } else { - ASSERT(activity == ProcessActivity::Runnable); + // Use the system-wide system resource. + const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication); + m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource() + : m_kernel.GetSystemSystemResource()); - // Verify that we're suspended. - R_UNLESS(m_is_suspended, ResultInvalidState); + m_is_default_application_system_resource = is_app; - // Resume all threads. - for (auto* thread : this->GetThreadList()) { - thread->Resume(SuspendType::Process); - } + // Open reference to the system resource. + m_system_resource->Open(); + } - // Set ourselves as resumed. - this->SetSuspended(false); + // Ensure we clean up our secure resource, if we fail. + ON_RESULT_FAILURE { + m_system_resource->Close(); + m_system_resource = nullptr; + }; + + // Setup page table. + { + const auto as_type = params.flags & Svc::CreateProcessFlag::AddressSpaceMask; + const bool enable_aslr = True(params.flags & Svc::CreateProcessFlag::EnableAslr); + const bool enable_das_merge = + False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge); + R_TRY(m_page_table.InitializeForProcess(as_type, enable_aslr, enable_das_merge, + !enable_aslr, pool, params.code_address, code_size, + m_system_resource, res_limit, this->GetMemory())); + } + ON_RESULT_FAILURE_2 { + m_page_table.Finalize(); + }; + + // Ensure we can insert the code region. + R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code), + ResultInvalidMemoryRegion); + + // Map the code region. + R_TRY(m_page_table.MapPages(params.code_address, code_num_pages, KMemoryState::Code, + KMemoryPermission::KernelRead | KMemoryPermission::NotMapped)); + + // Initialize capabilities. + R_TRY(m_capabilities.InitializeForUser(user_caps, std::addressof(m_page_table))); + + // Initialize the process id. + m_process_id = m_kernel.CreateNewUserProcessID(); + ASSERT(ProcessIdMin <= m_process_id); + ASSERT(m_process_id <= ProcessIdMax); + + // If we should optimize memory allocations, do so. + if (m_system_resource->IsSecureResource() && + True(params.flags & Svc::CreateProcessFlag::OptimizeMemoryAllocation)) { + R_TRY(m_kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool)); } + // Initialize the rest of the process. + R_TRY(this->Initialize(params, res_limit, true)); + + // We succeeded, so commit our memory reservation. + memory_reservation.Commit(); R_SUCCEED(); } -Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, - bool is_hbl) { - m_program_id = metadata.GetTitleID(); - m_ideal_core = metadata.GetMainThreadCore(); - m_is_64bit_process = metadata.Is64BitProgram(); - m_system_resource_size = metadata.GetSystemResourceSize(); - m_image_size = code_size; - m_is_hbl = is_hbl; +void KProcess::DoWorkerTaskImpl() { + // Terminate child threads. + TerminateChildren(m_kernel, this, nullptr); - if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { - // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. - // However, some (buggy) programs/libraries like skyline incorrectly depend on the - // existence of ASLR pages before the entry point, so we will adjust the load address - // to point to about 2GiB into the ASLR region. - m_code_address = 0x8000'0000; - } else { - // All other processes can be mapped at the beginning of the code region. - if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) { - m_code_address = 0x800'0000; - } else { - m_code_address = 0x20'0000; - } + // Finalize the handle table, if we're not immortal. + if (!m_is_immortal && m_is_handle_table_initialized) { + this->FinalizeHandleTable(); } - KScopedResourceReservation memory_reservation( - m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); - if (!memory_reservation.Succeeded()) { - LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes", - code_size + m_system_resource_size); - R_RETURN(ResultLimitReached); - } - // Initialize process address space - if (const Result result{m_page_table.InitializeForProcess( - metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, - this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()), - m_resource_limit, m_kernel.System().ApplicationMemory())}; - result.IsError()) { - R_RETURN(result); - } - - // Map process code region - if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize, - KMemoryState::Code, - KMemoryPermission::None)}; - result.IsError()) { - R_RETURN(result); - } - - // Initialize process capabilities - const auto& caps{metadata.GetKernelCapabilities()}; - if (const Result result{ - m_capabilities.InitializeForUserProcess(caps.data(), caps.size(), m_page_table)}; - result.IsError()) { - R_RETURN(result); - } - - // Set memory usage capacity - switch (metadata.GetAddressSpaceType()) { - case FileSys::ProgramAddressSpaceType::Is32Bit: - case FileSys::ProgramAddressSpaceType::Is36Bit: - case FileSys::ProgramAddressSpaceType::Is39Bit: - m_memory_usage_capacity = - m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart(); - break; + // Finish termination. + this->FinishTermination(); +} - case FileSys::ProgramAddressSpaceType::Is32BitNoMap: - m_memory_usage_capacity = - (m_page_table.GetHeapRegionEnd() - m_page_table.GetHeapRegionStart()) + - (m_page_table.GetAliasRegionEnd() - m_page_table.GetAliasRegionStart()); - break; +Result KProcess::StartTermination() { + // Finalize the handle table when we're done, if the process isn't immortal. + SCOPE_EXIT({ + if (!m_is_immortal) { + this->FinalizeHandleTable(); + } + }); - default: - ASSERT(false); - break; - } + // Terminate child threads other than the current one. + R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel))); +} - // Create TLS region - R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address))); - memory_reservation.Commit(); +void KProcess::FinishTermination() { + // Only allow termination to occur if the process isn't immortal. + if (!m_is_immortal) { + // Release resource limit hint. + if (m_resource_limit != nullptr) { + m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize(); + m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, 0, + m_memory_release_hint); + } + + // Change state. + { + KScopedSchedulerLock sl(m_kernel); + this->ChangeState(State::Terminated); + } - R_RETURN(m_handle_table.Initialize(m_capabilities.GetHandleTableSize())); + // Close. + this->Close(); + } } -void KProcess::Run(s32 main_thread_priority, u64 stack_size) { - ASSERT(this->AllocateMainThreadStack(stack_size) == ResultSuccess); - m_resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); +void KProcess::Exit() { + // Determine whether we need to start terminating + bool needs_terminate = false; + { + KScopedLightLock lk(m_state_lock); + KScopedSchedulerLock sl(m_kernel); + + ASSERT(m_state != State::Created); + ASSERT(m_state != State::CreatedAttached); + ASSERT(m_state != State::Crashed); + ASSERT(m_state != State::Terminated); + if (m_state == State::Running || m_state == State::RunningAttached || + m_state == State::DebugBreak) { + this->ChangeState(State::Terminating); + needs_terminate = true; + } + } - const std::size_t heap_capacity{m_memory_usage_capacity - - (m_main_thread_stack_size + m_image_size)}; - ASSERT(!m_page_table.SetMaxHeapSize(heap_capacity).IsError()); + // If we need to start termination, do so. + if (needs_terminate) { + this->StartTermination(); - this->ChangeState(State::Running); + // Register the process as a work task. + m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this); + } - SetupMainThread(m_kernel.System(), *this, main_thread_priority, m_main_thread_stack_top); + // Exit the current thread. + GetCurrentThread(m_kernel).Exit(); } -void KProcess::PrepareForTermination() { - this->ChangeState(State::Terminating); +Result KProcess::Terminate() { + // Determine whether we need to start terminating. + bool needs_terminate = false; + { + KScopedLightLock lk(m_state_lock); - const auto stop_threads = [this](const std::vector<KThread*>& in_thread_list) { - for (auto* thread : in_thread_list) { - if (thread->GetOwnerProcess() != this) - continue; + // Check whether we're allowed to terminate. + R_UNLESS(m_state != State::Created, ResultInvalidState); + R_UNLESS(m_state != State::CreatedAttached, ResultInvalidState); - if (thread == GetCurrentThreadPointer(m_kernel)) - continue; + KScopedSchedulerLock sl(m_kernel); - // TODO(Subv): When are the other running/ready threads terminated? - ASSERT_MSG(thread->GetState() == ThreadState::Waiting, - "Exiting processes with non-waiting threads is currently unimplemented"); + if (m_state == State::Running || m_state == State::RunningAttached || + m_state == State::Crashed || m_state == State::DebugBreak) { + this->ChangeState(State::Terminating); + needs_terminate = true; + } + } - thread->Exit(); + // If we need to terminate, do so. + if (needs_terminate) { + // Start termination. + if (R_SUCCEEDED(this->StartTermination())) { + // Finish termination. + this->FinishTermination(); + } else { + // Register the process as a work task. + m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, + this); } - }; + } - stop_threads(m_kernel.System().GlobalSchedulerContext().GetThreadList()); + R_SUCCEED(); +} - this->DeleteThreadLocalRegion(m_plr_address); - m_plr_address = 0; +Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) { + // Lock ourselves, to prevent concurrent access. + KScopedLightLock lk(m_state_lock); - if (m_resource_limit) { - m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, - m_main_thread_stack_size + m_image_size); + // Try to find an existing info for the memory. + KSharedMemoryInfo* info = nullptr; + for (auto it = m_shared_memory_list.begin(); it != m_shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; + } } - this->ChangeState(State::Terminated); -} + // If we didn't find an info, create one. + if (info == nullptr) { + // Allocate a new info. + info = KSharedMemoryInfo::Allocate(m_kernel); + R_UNLESS(info != nullptr, ResultOutOfResource); -void KProcess::Finalize() { - // Free all shared memory infos. - { - auto it = m_shared_memory_list.begin(); - while (it != m_shared_memory_list.end()) { - KSharedMemoryInfo* info = *it; - KSharedMemory* shmem = info->GetSharedMemory(); + // Initialize the info and add it to our list. + info->Initialize(shmem); + m_shared_memory_list.push_back(*info); + } - while (!info->Close()) { - shmem->Close(); - } + // Open a reference to the shared memory and its info. + shmem->Open(); + info->Open(); - shmem->Close(); + R_SUCCEED(); +} - it = m_shared_memory_list.erase(it); - KSharedMemoryInfo::Free(m_kernel, info); +void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) { + // Lock ourselves, to prevent concurrent access. + KScopedLightLock lk(m_state_lock); + + // Find an existing info for the memory. + KSharedMemoryInfo* info = nullptr; + auto it = m_shared_memory_list.begin(); + for (; it != m_shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; } } + ASSERT(info != nullptr); - // Release memory to the resource limit. - if (m_resource_limit != nullptr) { - m_resource_limit->Close(); - m_resource_limit = nullptr; + // Close a reference to the info and its memory. + if (info->Close()) { + m_shared_memory_list.erase(it); + KSharedMemoryInfo::Free(m_kernel, info); } - // Finalize the page table. - m_page_table.Finalize(); - - // Perform inherited finalization. - KSynchronizationObject::Finalize(); + shmem->Close(); } Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { @@ -518,7 +601,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { // See if we can get a region from a partially used TLP. { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl(m_kernel); if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) { tlr = it->Reserve(); @@ -538,7 +621,9 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { // Allocate a new page. tlp = KThreadLocalPage::Allocate(m_kernel); R_UNLESS(tlp != nullptr, ResultOutOfMemory); - auto tlp_guard = SCOPE_GUARD({ KThreadLocalPage::Free(m_kernel, tlp); }); + ON_RESULT_FAILURE { + KThreadLocalPage::Free(m_kernel, tlp); + }; // Initialize the new page. R_TRY(tlp->Initialize(m_kernel, this)); @@ -549,7 +634,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { // Insert into our tree. { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl(m_kernel); if (tlp->IsAllUsed()) { m_fully_used_tlp_tree.insert(*tlp); } else { @@ -558,7 +643,6 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { } // We succeeded! - tlp_guard.Cancel(); *out = tlr; R_SUCCEED(); } @@ -568,7 +652,7 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) { // Release the region. { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl(m_kernel); // Try to find the page in the partially used list. auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize)); @@ -611,95 +695,213 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) { R_SUCCEED(); } -bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { - const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { - return wp.type == DebugWatchpointType::None; - })}; +bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value) { + if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { + return rl->Reserve(which, value); + } else { + return true; + } +} - if (watch == m_watchpoints.end()) { - return false; +bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout) { + if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { + return rl->Reserve(which, value, timeout); + } else { + return true; } +} - watch->start_address = addr; - watch->end_address = addr + size; - watch->type = type; +void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value) { + if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { + rl->Release(which, value); + } +} - for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; - page += PageSize) { - m_debug_page_refcounts[page]++; - this->GetMemory().MarkRegionDebug(page, PageSize, true); +void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint) { + if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { + rl->Release(which, value, hint); } +} - return true; +void KProcess::IncrementRunningThreadCount() { + ASSERT(m_num_running_threads.load() >= 0); + + ++m_num_running_threads; } -bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { - const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { - return wp.start_address == addr && wp.end_address == addr + size && wp.type == type; - })}; +void KProcess::DecrementRunningThreadCount() { + ASSERT(m_num_running_threads.load() > 0); - if (watch == m_watchpoints.end()) { + if (const auto prev = m_num_running_threads--; prev == 1) { + this->Terminate(); + } +} + +bool KProcess::EnterUserException() { + // Get the current thread. + KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + ASSERT(this == cur_thread->GetOwnerProcess()); + + // Check that we haven't already claimed the exception thread. + if (m_exception_thread == cur_thread) { return false; } - watch->start_address = 0; - watch->end_address = 0; - watch->type = DebugWatchpointType::None; + // Create the wait queue we'll be using. + ThreadQueueImplForKProcessEnterUserException wait_queue(m_kernel, + std::addressof(m_exception_thread)); - for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; - page += PageSize) { - m_debug_page_refcounts[page]--; - if (!m_debug_page_refcounts[page]) { - this->GetMemory().MarkRegionDebug(page, PageSize, false); + // Claim the exception thread. + { + // Lock the scheduler. + KScopedSchedulerLock sl(m_kernel); + + // Check that we're not terminating. + if (cur_thread->IsTerminationRequested()) { + return false; + } + + // If we don't have an exception thread, we can just claim it directly. + if (m_exception_thread == nullptr) { + m_exception_thread = cur_thread; + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + return true; } + + // Otherwise, we need to wait until we don't have an exception thread. + + // Add the current thread as a waiter on the current exception thread. + cur_thread->SetKernelAddressKey( + reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1); + m_exception_thread->AddWaiter(cur_thread); + + // Wait to claim the exception thread. + cur_thread->BeginWait(std::addressof(wait_queue)); } - return true; + // If our wait didn't end due to thread termination, we succeeded. + return ResultTerminationRequested != cur_thread->GetWaitResult(); } -void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { - const auto ReprotectSegment = [&](const CodeSet::Segment& segment, - Svc::MemoryPermission permission) { - m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission); - }; +bool KProcess::LeaveUserException() { + return this->ReleaseUserException(GetCurrentThreadPointer(m_kernel)); +} - this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size()); +bool KProcess::ReleaseUserException(KThread* thread) { + KScopedSchedulerLock sl(m_kernel); - ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute); - ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read); - ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); + if (m_exception_thread == thread) { + m_exception_thread = nullptr; + + // Remove waiter thread. + bool has_waiters; + if (KThread* next = thread->RemoveKernelWaiterByKey( + std::addressof(has_waiters), + reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1); + next != nullptr) { + next->EndWait(ResultSuccess); + } + + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + + return true; + } else { + return false; + } } -bool KProcess::IsSignaled() const { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - return m_is_signaled; +void KProcess::RegisterThread(KThread* thread) { + KScopedLightLock lk(m_list_lock); + + m_thread_list.push_back(*thread); } -KProcess::KProcess(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer{kernel}, m_page_table{m_kernel.System()}, - m_handle_table{m_kernel}, m_address_arbiter{m_kernel.System()}, - m_condition_var{m_kernel.System()}, m_state_lock{m_kernel}, m_list_lock{m_kernel} {} +void KProcess::UnregisterThread(KThread* thread) { + KScopedLightLock lk(m_list_lock); -KProcess::~KProcess() = default; + m_thread_list.erase(m_thread_list.iterator_to(*thread)); +} + +size_t KProcess::GetUsedUserPhysicalMemorySize() const { + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault(); -void KProcess::ChangeState(State new_state) { - if (m_state == new_state) { - return; + return norm_size + other_size + sec_size; +} + +size_t KProcess::GetTotalUserPhysicalMemorySize() const { + // Get the amount of free and used size. + const size_t free_size = + m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax); + const size_t max_size = m_max_process_memory; + + // Determine used size. + // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike + // GetUsedUserPhysicalMemorySize(). + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySize(); + const size_t used_size = norm_size + other_size + sec_size; + + // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo + // does it this way. + if (used_size + free_size > max_size) { + return max_size; + } else { + return free_size + this->GetUsedUserPhysicalMemorySize(); } +} - m_state = new_state; - m_is_signaled = true; - this->NotifyAvailable(); +size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const { + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + + return norm_size + other_size; +} + +size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const { + // Get the amount of free and used size. + const size_t free_size = + m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax); + const size_t max_size = m_max_process_memory; + + // Determine used size. + // NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike + // GetUsedUserPhysicalMemorySize(). + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySize(); + const size_t used_size = norm_size + other_size + sec_size; + + // NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo + // does it this way. + if (used_size + free_size > max_size) { + return max_size - this->GetRequiredSecureMemorySizeNonDefault(); + } else { + return free_size + this->GetUsedNonSystemUserPhysicalMemorySize(); + } } -Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { +Result KProcess::Run(s32 priority, size_t stack_size) { + // Lock ourselves, to prevent concurrent access. + KScopedLightLock lk(m_state_lock); + + // Validate that we're in a state where we can initialize. + const auto state = m_state; + R_UNLESS(state == State::Created || state == State::CreatedAttached, ResultInvalidState); + + // Place a tentative reservation of a thread for this process. + KScopedResourceReservation thread_reservation(this, Svc::LimitableResource::ThreadCountMax); + R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); + // Ensure that we haven't already allocated stack. ASSERT(m_main_thread_stack_size == 0); // Ensure that we're allocating a valid stack. stack_size = Common::AlignUp(stack_size, PageSize); - // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory); - R_UNLESS(stack_size + m_image_size >= m_image_size, ResultOutOfMemory); + R_UNLESS(stack_size + m_code_size <= m_max_process_memory, ResultOutOfMemory); + R_UNLESS(stack_size + m_code_size >= m_code_size, ResultOutOfMemory); // Place a tentative reservation of memory for our new stack. KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax, @@ -707,21 +909,359 @@ Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached); // Allocate and map our stack. + KProcessAddress stack_top = 0; if (stack_size) { KProcessAddress stack_bottom; R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize, KMemoryState::Stack, KMemoryPermission::UserReadWrite)); - m_main_thread_stack_top = stack_bottom + stack_size; + stack_top = stack_bottom + stack_size; m_main_thread_stack_size = stack_size; } + // Ensure our stack is safe to clean up on exit. + ON_RESULT_FAILURE { + if (m_main_thread_stack_size) { + ASSERT(R_SUCCEEDED(m_page_table.UnmapPages(stack_top - m_main_thread_stack_size, + m_main_thread_stack_size / PageSize, + KMemoryState::Stack))); + m_main_thread_stack_size = 0; + } + }; + + // Set our maximum heap size. + R_TRY(m_page_table.SetMaxHeapSize(m_max_process_memory - + (m_main_thread_stack_size + m_code_size))); + + // Initialize our handle table. + R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize())); + ON_RESULT_FAILURE_2 { + this->FinalizeHandleTable(); + }; + + // Create a new thread for the process. + KThread* main_thread = KThread::Create(m_kernel); + R_UNLESS(main_thread != nullptr, ResultOutOfResource); + SCOPE_EXIT({ main_thread->Close(); }); + + // Initialize the thread. + R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0, + stack_top, priority, m_ideal_core_id, this)); + + // Register the thread, and commit our reservation. + KThread::Register(m_kernel, main_thread); + thread_reservation.Commit(); + + // Add the thread to our handle table. + Handle thread_handle; + R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread)); + + // Set the thread arguments. + main_thread->GetContext32().cpu_registers[0] = 0; + main_thread->GetContext64().cpu_registers[0] = 0; + main_thread->GetContext32().cpu_registers[1] = thread_handle; + main_thread->GetContext64().cpu_registers[1] = thread_handle; + + // Update our state. + this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached); + ON_RESULT_FAILURE_2 { + this->ChangeState(state); + }; + + // Suspend for debug, if we should. + if (m_kernel.System().DebuggerEnabled()) { + main_thread->RequestSuspend(SuspendType::Debug); + } + + // Run our thread. + R_TRY(main_thread->Run()); + + // Open a reference to represent that we're running. + this->Open(); + // We succeeded! Commit our memory reservation. mem_reservation.Commit(); R_SUCCEED(); } +Result KProcess::Reset() { + // Lock the process and the scheduler. + KScopedLightLock lk(m_state_lock); + KScopedSchedulerLock sl(m_kernel); + + // Validate that we're in a state that we can reset. + R_UNLESS(m_state != State::Terminated, ResultInvalidState); + R_UNLESS(m_is_signaled, ResultInvalidState); + + // Clear signaled. + m_is_signaled = false; + R_SUCCEED(); +} + +Result KProcess::SetActivity(Svc::ProcessActivity activity) { + // Lock ourselves and the scheduler. + KScopedLightLock lk(m_state_lock); + KScopedLightLock list_lk(m_list_lock); + KScopedSchedulerLock sl(m_kernel); + + // Validate our state. + R_UNLESS(m_state != State::Terminating, ResultInvalidState); + R_UNLESS(m_state != State::Terminated, ResultInvalidState); + + // Either pause or resume. + if (activity == Svc::ProcessActivity::Paused) { + // Verify that we're not suspended. + R_UNLESS(!m_is_suspended, ResultInvalidState); + + // Suspend all threads. + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->RequestSuspend(SuspendType::Process); + } + + // Set ourselves as suspended. + this->SetSuspended(true); + } else { + ASSERT(activity == Svc::ProcessActivity::Runnable); + + // Verify that we're suspended. + R_UNLESS(m_is_suspended, ResultInvalidState); + + // Resume all threads. + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->Resume(SuspendType::Process); + } + + // Set ourselves as resumed. + this->SetSuspended(false); + } + + R_SUCCEED(); +} + +void KProcess::PinCurrentThread() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + + // Get the current thread. + const s32 core_id = GetCurrentCoreId(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + + // If the thread isn't terminated, pin it. + if (!cur_thread->IsTerminationRequested()) { + // Pin it. + this->PinThread(core_id, cur_thread); + cur_thread->Pin(core_id); + + // An update is needed. + KScheduler::SetSchedulerUpdateNeeded(m_kernel); + } +} + +void KProcess::UnpinCurrentThread() { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + + // Get the current thread. + const s32 core_id = GetCurrentCoreId(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + + // Unpin it. + cur_thread->Unpin(); + this->UnpinThread(core_id, cur_thread); + + // An update is needed. + KScheduler::SetSchedulerUpdateNeeded(m_kernel); +} + +void KProcess::UnpinThread(KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + + // Get the thread's core id. + const auto core_id = thread->GetActiveCore(); + + // Unpin it. + this->UnpinThread(core_id, thread); + thread->Unpin(); + + // An update is needed. + KScheduler::SetSchedulerUpdateNeeded(m_kernel); +} + +Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, + s32 max_out_count) { + // TODO: use current memory reference + auto& memory = m_kernel.System().ApplicationMemory(); + + // Lock the list. + KScopedLightLock lk(m_list_lock); + + // Iterate over the list. + s32 count = 0; + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + // If we're within array bounds, write the id. + if (count < max_out_count) { + // Get the thread id. + KThread* thread = std::addressof(*it); + const u64 id = thread->GetId(); + + // Copy the id to userland. + memory.Write64(out_thread_ids + count * sizeof(u64), id); + } + + // Increment the count. + ++count; + } + + // We successfully iterated the list. + *out_num_threads = count; + R_SUCCEED(); +} + +void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {} + +KProcess::KProcess(KernelCore& kernel) + : KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel.System()}, + m_state_lock{kernel}, m_list_lock{kernel}, m_cond_var{kernel.System()}, + m_address_arbiter{kernel.System()}, m_handle_table{kernel} {} +KProcess::~KProcess() = default; + +Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl) { + // Create a resource limit for the process. + const auto physical_memory_size = + m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application); + auto* res_limit = + Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size); + + // Ensure we maintain a clean state on exit. + SCOPE_EXIT({ res_limit->Close(); }); + + // Declare flags and code address. + Svc::CreateProcessFlag flag{}; + u64 code_address{}; + + // We are an application. + flag |= Svc::CreateProcessFlag::IsApplication; + + // If we are 64-bit, create as such. + if (metadata.Is64BitProgram()) { + flag |= Svc::CreateProcessFlag::Is64Bit; + } + + // Set the address space type and code address. + switch (metadata.GetAddressSpaceType()) { + case FileSys::ProgramAddressSpaceType::Is39Bit: + flag |= Svc::CreateProcessFlag::AddressSpace64Bit; + + // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. + // However, some (buggy) programs/libraries like skyline incorrectly depend on the + // existence of ASLR pages before the entry point, so we will adjust the load address + // to point to about 2GiB into the ASLR region. + code_address = 0x8000'0000; + break; + case FileSys::ProgramAddressSpaceType::Is36Bit: + flag |= Svc::CreateProcessFlag::AddressSpace64BitDeprecated; + code_address = 0x800'0000; + break; + case FileSys::ProgramAddressSpaceType::Is32Bit: + flag |= Svc::CreateProcessFlag::AddressSpace32Bit; + code_address = 0x20'0000; + break; + case FileSys::ProgramAddressSpaceType::Is32BitNoMap: + flag |= Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias; + code_address = 0x20'0000; + break; + } + + Svc::CreateProcessParameter params{ + .name = {}, + .version = {}, + .program_id = metadata.GetTitleID(), + .code_address = code_address, + .code_num_pages = static_cast<s32>(code_size / PageSize), + .flags = flag, + .reslimit = Svc::InvalidHandle, + .system_resource_num_pages = static_cast<s32>(metadata.GetSystemResourceSize() / PageSize), + }; + + // Set the process name. + const auto& name = metadata.GetName(); + static_assert(sizeof(params.name) <= sizeof(name)); + std::memcpy(params.name.data(), name.data(), sizeof(params.name)); + + // Initialize for application process. + R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit, + KMemoryManager::Pool::Application)); + + // Assign remaining properties. + m_is_hbl = is_hbl; + m_ideal_core_id = metadata.GetMainThreadCore(); + + // We succeeded. + R_SUCCEED(); +} + +void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { + const auto ReprotectSegment = [&](const CodeSet::Segment& segment, + Svc::MemoryPermission permission) { + m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission); + }; + + this->GetMemory().WriteBlock(base_addr, code_set.memory.data(), code_set.memory.size()); + + ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute); + ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read); + ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); +} + +bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { + const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { + return wp.type == DebugWatchpointType::None; + })}; + + if (watch == m_watchpoints.end()) { + return false; + } + + watch->start_address = addr; + watch->end_address = addr + size; + watch->type = type; + + for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; + page += PageSize) { + m_debug_page_refcounts[page]++; + this->GetMemory().MarkRegionDebug(page, PageSize, true); + } + + return true; +} + +bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { + const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { + return wp.start_address == addr && wp.end_address == addr + size && wp.type == type; + })}; + + if (watch == m_watchpoints.end()) { + return false; + } + + watch->start_address = 0; + watch->end_address = 0; + watch->type = DebugWatchpointType::None; + + for (KProcessAddress page = Common::AlignDown(GetInteger(addr), PageSize); page < addr + size; + page += PageSize) { + m_debug_page_refcounts[page]--; + if (!m_debug_page_refcounts[page]) { + this->GetMemory().MarkRegionDebug(page, PageSize, false); + } + } + + return true; +} + Core::Memory::Memory& KProcess::GetMemory() const { // TODO: per-process memory return m_kernel.System().ApplicationMemory(); diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 146e07a57..f9f755afa 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -1,59 +1,23 @@ -// SPDX-FileCopyrightText: 2015 Citra Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include <array> -#include <cstddef> -#include <list> #include <map> -#include <string> + +#include "core/hle/kernel/code_set.h" #include "core/hle/kernel/k_address_arbiter.h" -#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/k_capabilities.h" #include "core/hle/kernel/k_condition_variable.h" #include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/k_page_table.h" -#include "core/hle/kernel/k_synchronization_object.h" +#include "core/hle/kernel/k_page_table_manager.h" +#include "core/hle/kernel/k_system_resource.h" +#include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/k_thread_local_page.h" -#include "core/hle/kernel/k_typed_address.h" -#include "core/hle/kernel/k_worker_task.h" -#include "core/hle/kernel/process_capability.h" -#include "core/hle/kernel/slab_helpers.h" -#include "core/hle/result.h" - -namespace Core { -namespace Memory { -class Memory; -}; - -class System; -} // namespace Core - -namespace FileSys { -class ProgramMetadata; -} namespace Kernel { -class KernelCore; -class KResourceLimit; -class KThread; -class KSharedMemoryInfo; -class TLSPage; - -struct CodeSet; - -enum class MemoryRegion : u16 { - APPLICATION = 1, - SYSTEM = 2, - BASE = 3, -}; - -enum class ProcessActivity : u32 { - Runnable, - Paused, -}; - enum class DebugWatchpointType : u8 { None = 0, Read = 1 << 0, @@ -72,9 +36,6 @@ class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWor KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); public: - explicit KProcess(KernelCore& kernel); - ~KProcess() override; - enum class State { Created = static_cast<u32>(Svc::ProcessState::Created), CreatedAttached = static_cast<u32>(Svc::ProcessState::CreatedAttached), @@ -86,470 +47,493 @@ public: DebugBreak = static_cast<u32>(Svc::ProcessState::DebugBreak), }; - enum : u64 { - /// Lowest allowed process ID for a kernel initial process. - InitialKIPIDMin = 1, - /// Highest allowed process ID for a kernel initial process. - InitialKIPIDMax = 80, - - /// Lowest allowed process ID for a userland process. - ProcessIDMin = 81, - /// Highest allowed process ID for a userland process. - ProcessIDMax = 0xFFFFFFFFFFFFFFFF, - }; + using ThreadList = Common::IntrusiveListMemberTraits<&KThread::m_process_list_node>::ListType; - // Used to determine how process IDs are assigned. - enum class ProcessType { - KernelInternal, - Userland, - }; + static constexpr size_t AslrAlignment = 2_MiB; - static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; +public: + static constexpr u64 InitialProcessIdMin = 1; + static constexpr u64 InitialProcessIdMax = 0x50; - static Result Initialize(KProcess* process, Core::System& system, std::string process_name, - ProcessType type, KResourceLimit* res_limit); + static constexpr u64 ProcessIdMin = InitialProcessIdMax + 1; + static constexpr u64 ProcessIdMax = std::numeric_limits<u64>::max(); - /// Gets a reference to the process' page table. - KPageTable& GetPageTable() { - return m_page_table; - } +private: + using SharedMemoryInfoList = Common::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType; + using TLPTree = + Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>; + using TLPIterator = TLPTree::iterator; - /// Gets const a reference to the process' page table. - const KPageTable& GetPageTable() const { - return m_page_table; - } +private: + KPageTable m_page_table; + std::atomic<size_t> m_used_kernel_memory_size{}; + TLPTree m_fully_used_tlp_tree{}; + TLPTree m_partially_used_tlp_tree{}; + s32 m_ideal_core_id{}; + KResourceLimit* m_resource_limit{}; + KSystemResource* m_system_resource{}; + size_t m_memory_release_hint{}; + State m_state{}; + KLightLock m_state_lock; + KLightLock m_list_lock; + KConditionVariable m_cond_var; + KAddressArbiter m_address_arbiter; + std::array<u64, 4> m_entropy{}; + bool m_is_signaled{}; + bool m_is_initialized{}; + bool m_is_application{}; + bool m_is_default_application_system_resource{}; + bool m_is_hbl{}; + std::array<char, 13> m_name{}; + std::atomic<u16> m_num_running_threads{}; + Svc::CreateProcessFlag m_flags{}; + KMemoryManager::Pool m_memory_pool{}; + s64 m_schedule_count{}; + KCapabilities m_capabilities{}; + u64 m_program_id{}; + u64 m_process_id{}; + KProcessAddress m_code_address{}; + size_t m_code_size{}; + size_t m_main_thread_stack_size{}; + size_t m_max_process_memory{}; + u32 m_version{}; + KHandleTable m_handle_table; + KProcessAddress m_plr_address{}; + KThread* m_exception_thread{}; + ThreadList m_thread_list{}; + SharedMemoryInfoList m_shared_memory_list{}; + bool m_is_suspended{}; + bool m_is_immortal{}; + bool m_is_handle_table_initialized{}; + std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{}; + std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{}; + std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_switch_counts{}; + std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{}; + std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{}; + std::map<KProcessAddress, u64> m_debug_page_refcounts{}; + std::atomic<s64> m_cpu_time{}; + std::atomic<s64> m_num_process_switches{}; + std::atomic<s64> m_num_thread_switches{}; + std::atomic<s64> m_num_fpu_switches{}; + std::atomic<s64> m_num_supervisor_calls{}; + std::atomic<s64> m_num_ipc_messages{}; + std::atomic<s64> m_num_ipc_replies{}; + std::atomic<s64> m_num_ipc_receives{}; - /// Gets a reference to the process' handle table. - KHandleTable& GetHandleTable() { - return m_handle_table; - } +private: + Result StartTermination(); + void FinishTermination(); - /// Gets a const reference to the process' handle table. - const KHandleTable& GetHandleTable() const { - return m_handle_table; + void PinThread(s32 core_id, KThread* thread) { + ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + ASSERT(thread != nullptr); + ASSERT(m_pinned_threads[core_id] == nullptr); + m_pinned_threads[core_id] = thread; } - /// Gets a reference to process's memory. - Core::Memory::Memory& GetMemory() const; - - Result SignalToAddress(KProcessAddress address) { - return m_condition_var.SignalToAddress(address); + void UnpinThread(s32 core_id, KThread* thread) { + ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); + ASSERT(thread != nullptr); + ASSERT(m_pinned_threads[core_id] == thread); + m_pinned_threads[core_id] = nullptr; } - Result WaitForAddress(Handle handle, KProcessAddress address, u32 tag) { - return m_condition_var.WaitForAddress(handle, address, tag); - } +public: + explicit KProcess(KernelCore& kernel); + ~KProcess() override; - void SignalConditionVariable(u64 cv_key, int32_t count) { - return m_condition_var.Signal(cv_key, count); - } + Result Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, + bool is_real); - Result WaitConditionVariable(KProcessAddress address, u64 cv_key, u32 tag, s64 ns) { - R_RETURN(m_condition_var.Wait(address, cv_key, tag, ns)); - } + Result Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg, + std::span<const u32> caps, KResourceLimit* res_limit, + KMemoryManager::Pool pool, bool immortal); + Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps, + KResourceLimit* res_limit, KMemoryManager::Pool pool); + void Exit(); - Result SignalAddressArbiter(uint64_t address, Svc::SignalType signal_type, s32 value, - s32 count) { - R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count)); + const char* GetName() const { + return m_name.data(); } - Result WaitAddressArbiter(uint64_t address, Svc::ArbitrationType arb_type, s32 value, - s64 timeout) { - R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout)); + u64 GetProgramId() const { + return m_program_id; } - KProcessAddress GetProcessLocalRegionAddress() const { - return m_plr_address; + u64 GetProcessId() const { + return m_process_id; } - /// Gets the current status of the process State GetState() const { return m_state; } - /// Gets the unique ID that identifies this particular process. - u64 GetProcessId() const { - return m_process_id; + u64 GetCoreMask() const { + return m_capabilities.GetCoreMask(); + } + u64 GetPhysicalCoreMask() const { + return m_capabilities.GetPhysicalCoreMask(); + } + u64 GetPriorityMask() const { + return m_capabilities.GetPriorityMask(); } - /// Gets the program ID corresponding to this process. - u64 GetProgramId() const { - return m_program_id; + s32 GetIdealCoreId() const { + return m_ideal_core_id; + } + void SetIdealCoreId(s32 core_id) { + m_ideal_core_id = core_id; } - KProcessAddress GetEntryPoint() const { - return m_code_address; + bool CheckThreadPriority(s32 prio) const { + return ((1ULL << prio) & this->GetPriorityMask()) != 0; } - /// Gets the resource limit descriptor for this process - KResourceLimit* GetResourceLimit() const; + u32 GetCreateProcessFlags() const { + return static_cast<u32>(m_flags); + } - /// Gets the ideal CPU core ID for this process - u8 GetIdealCoreId() const { - return m_ideal_core; + bool Is64Bit() const { + return True(m_flags & Svc::CreateProcessFlag::Is64Bit); } - /// Checks if the specified thread priority is valid. - bool CheckThreadPriority(s32 prio) const { - return ((1ULL << prio) & GetPriorityMask()) != 0; + KProcessAddress GetEntryPoint() const { + return m_code_address; } - /// Gets the bitmask of allowed cores that this process' threads can run on. - u64 GetCoreMask() const { - return m_capabilities.GetCoreMask(); + size_t GetMainStackSize() const { + return m_main_thread_stack_size; } - /// Gets the bitmask of allowed thread priorities. - u64 GetPriorityMask() const { - return m_capabilities.GetPriorityMask(); + KMemoryManager::Pool GetMemoryPool() const { + return m_memory_pool; } - /// Gets the amount of secure memory to allocate for memory management. - u32 GetSystemResourceSize() const { - return m_system_resource_size; + u64 GetRandomEntropy(size_t i) const { + return m_entropy[i]; } - /// Gets the amount of secure memory currently in use for memory management. - u32 GetSystemResourceUsage() const { - // On hardware, this returns the amount of system resource memory that has - // been used by the kernel. This is problematic for Yuzu to emulate, because - // system resource memory is used for page tables -- and yuzu doesn't really - // have a way to calculate how much memory is required for page tables for - // the current process at any given time. - // TODO: Is this even worth implementing? Games may retrieve this value via - // an SDK function that gets used + available system resource size for debug - // or diagnostic purposes. However, it seems unlikely that a game would make - // decisions based on how much system memory is dedicated to its page tables. - // Is returning a value other than zero wise? - return 0; + bool IsApplication() const { + return m_is_application; } - /// Whether this process is an AArch64 or AArch32 process. - bool Is64BitProcess() const { - return m_is_64bit_process; + bool IsDefaultApplicationSystemResource() const { + return m_is_default_application_system_resource; } bool IsSuspended() const { return m_is_suspended; } - void SetSuspended(bool suspended) { m_is_suspended = suspended; } - /// Gets the total running time of the process instance in ticks. - u64 GetCPUTimeTicks() const { - return m_total_process_running_time_ticks; + Result Terminate(); + + bool IsTerminated() const { + return m_state == State::Terminated; } - /// Updates the total running time, adding the given ticks to it. - void UpdateCPUTimeTicks(u64 ticks) { - m_total_process_running_time_ticks += ticks; + bool IsPermittedSvc(u32 svc_id) const { + return m_capabilities.IsPermittedSvc(svc_id); } - /// Gets the process schedule count, used for thread yielding - s64 GetScheduledCount() const { - return m_schedule_count; + bool IsPermittedInterrupt(s32 interrupt_id) const { + return m_capabilities.IsPermittedInterrupt(interrupt_id); } - /// Increments the process schedule count, used for thread yielding. - void IncrementScheduledCount() { - ++m_schedule_count; + bool IsPermittedDebug() const { + return m_capabilities.IsPermittedDebug(); } - void IncrementRunningThreadCount(); - void DecrementRunningThreadCount(); + bool CanForceDebug() const { + return m_capabilities.CanForceDebug(); + } - void SetRunningThread(s32 core, KThread* thread, u64 idle_count) { - m_running_threads[core] = thread; - m_running_thread_idle_counts[core] = idle_count; + bool IsHbl() const { + return m_is_hbl; } - void ClearRunningThread(KThread* thread) { - for (size_t i = 0; i < m_running_threads.size(); ++i) { - if (m_running_threads[i] == thread) { - m_running_threads[i] = nullptr; - } - } + Kernel::KMemoryManager::Direction GetAllocateOption() const { + // TODO: property of the KPageTableBase + return KMemoryManager::Direction::FromFront; } - [[nodiscard]] KThread* GetRunningThread(s32 core) const { - return m_running_threads[core]; + ThreadList& GetThreadList() { + return m_thread_list; + } + const ThreadList& GetThreadList() const { + return m_thread_list; } + bool EnterUserException(); + bool LeaveUserException(); bool ReleaseUserException(KThread* thread); - [[nodiscard]] KThread* GetPinnedThread(s32 core_id) const { + KThread* GetPinnedThread(s32 core_id) const { ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); return m_pinned_threads[core_id]; } - /// Gets 8 bytes of random data for svcGetInfo RandomEntropy - u64 GetRandomEntropy(std::size_t index) const { - return m_random_entropy.at(index); + const Svc::SvcAccessFlagSet& GetSvcPermissions() const { + return m_capabilities.GetSvcPermissions(); } - /// Retrieves the total physical memory available to this process in bytes. - u64 GetTotalPhysicalMemoryAvailable(); - - /// Retrieves the total physical memory available to this process in bytes, - /// without the size of the personal system resource heap added to it. - u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource(); - - /// Retrieves the total physical memory used by this process in bytes. - u64 GetTotalPhysicalMemoryUsed(); - - /// Retrieves the total physical memory used by this process in bytes, - /// without the size of the personal system resource heap added to it. - u64 GetTotalPhysicalMemoryUsedWithoutSystemResource(); - - /// Gets the list of all threads created with this process as their owner. - std::list<KThread*>& GetThreadList() { - return m_thread_list; + KResourceLimit* GetResourceLimit() const { + return m_resource_limit; } - /// Registers a thread as being created under this process, - /// adding it to this process' thread list. - void RegisterThread(KThread* thread); + bool ReserveResource(Svc::LimitableResource which, s64 value); + bool ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout); + void ReleaseResource(Svc::LimitableResource which, s64 value); + void ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint); - /// Unregisters a thread from this process, removing it - /// from this process' thread list. - void UnregisterThread(KThread* thread); + KLightLock& GetStateLock() { + return m_state_lock; + } + KLightLock& GetListLock() { + return m_list_lock; + } - /// Retrieves the number of available threads for this process. - u64 GetFreeThreadCount() const; - - /// Clears the signaled state of the process if and only if it's signaled. - /// - /// @pre The process must not be already terminated. If this is called on a - /// terminated process, then ResultInvalidState will be returned. - /// - /// @pre The process must be in a signaled state. If this is called on a - /// process instance that is not signaled, ResultInvalidState will be - /// returned. - Result Reset(); + KPageTable& GetPageTable() { + return m_page_table; + } + const KPageTable& GetPageTable() const { + return m_page_table; + } - /** - * Loads process-specifics configuration info with metadata provided - * by an executable. - * - * @param metadata The provided metadata to load process specific info from. - * - * @returns ResultSuccess if all relevant metadata was able to be - * loaded and parsed. Otherwise, an error code is returned. - */ - Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, - bool is_hbl); + KHandleTable& GetHandleTable() { + return m_handle_table; + } + const KHandleTable& GetHandleTable() const { + return m_handle_table; + } - /** - * Starts the main application thread for this process. - * - * @param main_thread_priority The priority for the main thread. - * @param stack_size The stack size for the main thread in bytes. - */ - void Run(s32 main_thread_priority, u64 stack_size); + size_t GetUsedUserPhysicalMemorySize() const; + size_t GetTotalUserPhysicalMemorySize() const; + size_t GetUsedNonSystemUserPhysicalMemorySize() const; + size_t GetTotalNonSystemUserPhysicalMemorySize() const; - /** - * Prepares a process for termination by stopping all of its threads - * and clearing any other resources. - */ - void PrepareForTermination(); + Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); + void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); - void LoadModule(CodeSet code_set, KProcessAddress base_addr); + Result CreateThreadLocalRegion(KProcessAddress* out); + Result DeleteThreadLocalRegion(KProcessAddress addr); - bool IsInitialized() const override { - return m_is_initialized; + KProcessAddress GetProcessLocalRegionAddress() const { + return m_plr_address; } - static void PostDestroy(uintptr_t arg) {} - - void Finalize() override; - - u64 GetId() const override { - return GetProcessId(); + KThread* GetExceptionThread() const { + return m_exception_thread; } - bool IsHbl() const { - return m_is_hbl; + void AddCpuTime(s64 diff) { + m_cpu_time += diff; + } + s64 GetCpuTime() { + return m_cpu_time.load(); } - bool IsSignaled() const override; - - void DoWorkerTaskImpl(); + s64 GetScheduledCount() const { + return m_schedule_count; + } + void IncrementScheduledCount() { + ++m_schedule_count; + } - Result SetActivity(ProcessActivity activity); + void IncrementRunningThreadCount(); + void DecrementRunningThreadCount(); - void PinCurrentThread(s32 core_id); - void UnpinCurrentThread(s32 core_id); - void UnpinThread(KThread* thread); + size_t GetRequiredSecureMemorySizeNonDefault() const { + if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) { + auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource); + return secure_system_resource->CalculateRequiredSecureMemorySize(); + } - KLightLock& GetStateLock() { - return m_state_lock; + return 0; } - Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); - void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Thread-local storage management - - // Marks the next available region as used and returns the address of the slot. - [[nodiscard]] Result CreateThreadLocalRegion(KProcessAddress* out); + size_t GetRequiredSecureMemorySize() const { + if (m_system_resource->IsSecureResource()) { + auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource); + return secure_system_resource->CalculateRequiredSecureMemorySize(); + } - // Frees a used TLS slot identified by the given address - Result DeleteThreadLocalRegion(KProcessAddress addr); + return 0; + } - /////////////////////////////////////////////////////////////////////////////////////////////// - // Debug watchpoint management + size_t GetTotalSystemResourceSize() const { + if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) { + auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource); + return secure_system_resource->GetSize(); + } - // Attempts to insert a watchpoint into a free slot. Returns false if none are available. - bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); + return 0; + } - // Attempts to remove the watchpoint specified by the given parameters. - bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); + size_t GetUsedSystemResourceSize() const { + if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) { + auto* secure_system_resource = static_cast<KSecureSystemResource*>(m_system_resource); + return secure_system_resource->GetUsedSize(); + } - const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const { - return m_watchpoints; + return 0; } - const std::string& GetName() { - return name; + void SetRunningThread(s32 core, KThread* thread, u64 idle_count, u64 switch_count) { + m_running_threads[core] = thread; + m_running_thread_idle_counts[core] = idle_count; + m_running_thread_switch_counts[core] = switch_count; } -private: - void PinThread(s32 core_id, KThread* thread) { - ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); - ASSERT(thread != nullptr); - ASSERT(m_pinned_threads[core_id] == nullptr); - m_pinned_threads[core_id] = thread; + void ClearRunningThread(KThread* thread) { + for (size_t i = 0; i < m_running_threads.size(); ++i) { + if (m_running_threads[i] == thread) { + m_running_threads[i] = nullptr; + } + } } - void UnpinThread(s32 core_id, KThread* thread) { - ASSERT(0 <= core_id && core_id < static_cast<s32>(Core::Hardware::NUM_CPU_CORES)); - ASSERT(thread != nullptr); - ASSERT(m_pinned_threads[core_id] == thread); - m_pinned_threads[core_id] = nullptr; + const KSystemResource& GetSystemResource() const { + return *m_system_resource; } - void FinalizeHandleTable() { - // Finalize the table. - m_handle_table.Finalize(); - - // Note that the table is finalized. - m_is_handle_table_initialized = false; + const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const { + return m_system_resource->GetMemoryBlockSlabManager(); + } + const KBlockInfoManager& GetBlockInfoManager() const { + return m_system_resource->GetBlockInfoManager(); + } + const KPageTableManager& GetPageTableManager() const { + return m_system_resource->GetPageTableManager(); } - void ChangeState(State new_state); - - /// Allocates the main thread stack for the process, given the stack size in bytes. - Result AllocateMainThreadStack(std::size_t stack_size); - - /// Memory manager for this process - KPageTable m_page_table; - - /// Current status of the process - State m_state{}; + KThread* GetRunningThread(s32 core) const { + return m_running_threads[core]; + } + u64 GetRunningThreadIdleCount(s32 core) const { + return m_running_thread_idle_counts[core]; + } + u64 GetRunningThreadSwitchCount(s32 core) const { + return m_running_thread_switch_counts[core]; + } - /// The ID of this process - u64 m_process_id = 0; + void RegisterThread(KThread* thread); + void UnregisterThread(KThread* thread); - /// Title ID corresponding to the process - u64 m_program_id = 0; + Result Run(s32 priority, size_t stack_size); - /// Specifies additional memory to be reserved for the process's memory management by the - /// system. When this is non-zero, secure memory is allocated and used for page table allocation - /// instead of using the normal global page tables/memory block management. - u32 m_system_resource_size = 0; + Result Reset(); - /// Resource limit descriptor for this process - KResourceLimit* m_resource_limit{}; + void SetDebugBreak() { + if (m_state == State::RunningAttached) { + this->ChangeState(State::DebugBreak); + } + } - KVirtualAddress m_system_resource_address{}; + void SetAttached() { + if (m_state == State::DebugBreak) { + this->ChangeState(State::RunningAttached); + } + } - /// The ideal CPU core for this process, threads are scheduled on this core by default. - u8 m_ideal_core = 0; + Result SetActivity(Svc::ProcessActivity activity); - /// Contains the parsed process capability descriptors. - ProcessCapabilities m_capabilities; + void PinCurrentThread(); + void UnpinCurrentThread(); + void UnpinThread(KThread* thread); - /// Whether or not this process is AArch64, or AArch32. - /// By default, we currently assume this is true, unless otherwise - /// specified by metadata provided to the process during loading. - bool m_is_64bit_process = true; + void SignalConditionVariable(uintptr_t cv_key, int32_t count) { + return m_cond_var.Signal(cv_key, count); + } - /// Total running time for the process in ticks. - std::atomic<u64> m_total_process_running_time_ticks = 0; + Result WaitConditionVariable(KProcessAddress address, uintptr_t cv_key, u32 tag, s64 ns) { + R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns)); + } - /// Per-process handle table for storing created object handles in. - KHandleTable m_handle_table; + Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value, + s32 count) { + R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count)); + } - /// Per-process address arbiter. - KAddressArbiter m_address_arbiter; + Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value, + s64 timeout) { + R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout)); + } - /// The per-process mutex lock instance used for handling various - /// forms of services, such as lock arbitration, and condition - /// variable related facilities. - KConditionVariable m_condition_var; + Result GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count); - /// Address indicating the location of the process' dedicated TLS region. - KProcessAddress m_plr_address = 0; + static void Switch(KProcess* cur_process, KProcess* next_process); - /// Address indicating the location of the process's entry point. - KProcessAddress m_code_address = 0; +public: + // Attempts to insert a watchpoint into a free slot. Returns false if none are available. + bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); - /// Random values for svcGetInfo RandomEntropy - std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; + // Attempts to remove the watchpoint specified by the given parameters. + bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); - /// List of threads that are running with this process as their owner. - std::list<KThread*> m_thread_list; + const std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS>& GetWatchpoints() const { + return m_watchpoints; + } - /// List of shared memory that are running with this process as their owner. - std::list<KSharedMemoryInfo*> m_shared_memory_list; +public: + Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl); - /// Address of the top of the main thread's stack - KProcessAddress m_main_thread_stack_top{}; + void LoadModule(CodeSet code_set, KProcessAddress base_addr); - /// Size of the main thread's stack - std::size_t m_main_thread_stack_size{}; + Core::Memory::Memory& GetMemory() const; - /// Memory usage capacity for the process - std::size_t m_memory_usage_capacity{}; +public: + // Overridden parent functions. + bool IsInitialized() const override { + return m_is_initialized; + } - /// Process total image size - std::size_t m_image_size{}; + static void PostDestroy(uintptr_t arg) {} - /// Schedule count of this process - s64 m_schedule_count{}; + void Finalize() override; - size_t m_memory_release_hint{}; + u64 GetIdImpl() const { + return this->GetProcessId(); + } + u64 GetId() const override { + return this->GetIdImpl(); + } - std::string name{}; + virtual bool IsSignaled() const override { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + return m_is_signaled; + } - bool m_is_signaled{}; - bool m_is_suspended{}; - bool m_is_immortal{}; - bool m_is_handle_table_initialized{}; - bool m_is_initialized{}; - bool m_is_hbl{}; + void DoWorkerTaskImpl(); - std::atomic<u16> m_num_running_threads{}; +private: + void ChangeState(State new_state) { + if (m_state != new_state) { + m_state = new_state; + m_is_signaled = true; + this->NotifyAvailable(); + } + } - std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{}; - std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{}; - std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{}; - std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{}; - std::map<KProcessAddress, u64> m_debug_page_refcounts; + Result InitializeHandleTable(s32 size) { + // Try to initialize the handle table. + R_TRY(m_handle_table.Initialize(size)); - KThread* m_exception_thread{}; + // We succeeded, so note that we did. + m_is_handle_table_initialized = true; + R_SUCCEED(); + } - KLightLock m_state_lock; - KLightLock m_list_lock; + void FinalizeHandleTable() { + // Finalize the table. + m_handle_table.Finalize(); - using TLPTree = - Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>; - using TLPIterator = TLPTree::iterator; - TLPTree m_fully_used_tlp_tree; - TLPTree m_partially_used_tlp_tree; + // Note that the table is finalized. + m_is_handle_table_initialized = false; + } }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index d8143c650..1bce63a56 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -190,7 +190,7 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { if (m_state.should_count_idle) { if (highest_thread != nullptr) [[likely]] { if (KProcess* process = highest_thread->GetOwnerProcess(); process != nullptr) { - process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count); + process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, 0); } } else { m_state.idle_count++; @@ -356,7 +356,7 @@ void KScheduler::SwitchThread(KThread* next_thread) { const s64 tick_diff = cur_tick - prev_tick; cur_thread->AddCpuTime(m_core_id, tick_diff); if (cur_process != nullptr) { - cur_process->UpdateCPUTimeTicks(tick_diff); + cur_process->AddCpuTime(tick_diff); } m_last_context_switch_time = cur_tick; diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp index e6c8d589a..07e92aa80 100644 --- a/src/core/hle/kernel/k_system_resource.cpp +++ b/src/core/hle/kernel/k_system_resource.cpp @@ -1,25 +1,100 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "core/core.h" +#include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_system_resource.h" namespace Kernel { Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool) { - // Unimplemented - UNREACHABLE(); + // Set members. + m_resource_limit = resource_limit; + m_resource_size = size; + m_resource_pool = pool; + + // Determine required size for our secure resource. + const size_t secure_size = this->CalculateRequiredSecureMemorySize(); + + // Reserve memory for our secure resource. + KScopedResourceReservation memory_reservation( + m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size); + R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); + + // Allocate secure memory. + R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address), + m_resource_size, static_cast<u32>(m_resource_pool))); + ASSERT(m_resource_address != 0); + + // Ensure we clean up the secure memory, if we fail past this point. + ON_RESULT_FAILURE { + KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size, + static_cast<u32>(m_resource_pool)); + }; + + // Check that our allocation is bigger than the reference counts needed for it. + const size_t rc_size = + Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize); + R_UNLESS(m_resource_size > rc_size, ResultOutOfMemory); + + // Get resource pointer. + KPhysicalAddress resource_paddr = + KPageTable::GetHeapPhysicalAddress(m_kernel.MemoryLayout(), m_resource_address); + auto* resource = + m_kernel.System().DeviceMemory().GetPointer<KPageTableManager::RefCount>(resource_paddr); + + // Initialize slab heaps. + m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size, + PageSize); + m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, resource); + m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0); + m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0); + + // Initialize managers. + m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager), + std::addressof(m_page_table_heap)); + m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager), + std::addressof(m_memory_block_heap)); + m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager), + std::addressof(m_block_info_heap)); + + // Set our managers. + this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager); + + // Commit the memory reservation. + memory_reservation.Commit(); + + // Open reference to our resource limit. + m_resource_limit->Open(); + + // Set ourselves as initialized. + m_is_initialized = true; + + R_SUCCEED(); } void KSecureSystemResource::Finalize() { - // Unimplemented - UNREACHABLE(); + // Check that we have no outstanding allocations. + ASSERT(m_memory_block_slab_manager.GetUsed() == 0); + ASSERT(m_block_info_manager.GetUsed() == 0); + ASSERT(m_page_table_manager.GetUsed() == 0); + + // Free our secure memory. + KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size, + static_cast<u32>(m_resource_pool)); + + // Release the memory reservation. + m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + this->CalculateRequiredSecureMemorySize()); + + // Close reference to our resource limit. + m_resource_limit->Close(); } size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool) { - // Unimplemented - UNREACHABLE(); + return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast<u32>(pool)); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 7df8fd7f7..a6deb50ec 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -122,16 +122,15 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress case ThreadType::Main: ASSERT(arg == 0); [[fallthrough]]; - case ThreadType::HighPriority: - [[fallthrough]]; - case ThreadType::Dummy: - [[fallthrough]]; case ThreadType::User: ASSERT(((owner == nullptr) || (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask())); ASSERT(((owner == nullptr) || (prio > Svc::LowestThreadPriority) || (owner->GetPriorityMask() | (1ULL << prio)) == owner->GetPriorityMask())); break; + case ThreadType::HighPriority: + case ThreadType::Dummy: + break; case ThreadType::Kernel: UNIMPLEMENTED(); break; @@ -216,6 +215,7 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress // Setup the TLS, if needed. if (type == ThreadType::User) { R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); + owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize); } m_parent = owner; @@ -403,7 +403,7 @@ void KThread::StartTermination() { if (m_parent != nullptr) { m_parent->ReleaseUserException(this); if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) { - m_parent->UnpinCurrentThread(m_core_id); + m_parent->UnpinCurrentThread(); } } @@ -415,10 +415,6 @@ void KThread::StartTermination() { m_parent->ClearRunningThread(this); } - // Signal. - m_signaled = true; - KSynchronizationObject::NotifyAvailable(); - // Clear previous thread in KScheduler. KScheduler::ClearPreviousThread(m_kernel, this); @@ -437,6 +433,13 @@ void KThread::FinishTermination() { } } + // Acquire the scheduler lock. + KScopedSchedulerLock sl{m_kernel}; + + // Signal. + m_signaled = true; + KSynchronizationObject::NotifyAvailable(); + // Close the thread. this->Close(); } @@ -820,7 +823,7 @@ void KThread::CloneFpuStatus() { ASSERT(this->GetOwnerProcess() != nullptr); ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel)); - if (this->GetOwnerProcess()->Is64BitProcess()) { + if (this->GetOwnerProcess()->Is64Bit()) { // Clone FPSR and FPCR. ThreadContext64 cur_ctx{}; m_kernel.System().CurrentArmInterface().SaveContext(cur_ctx); @@ -923,7 +926,7 @@ Result KThread::GetThreadContext3(Common::ScratchBuffer<u8>& out) { // If we're not terminating, get the thread's user context. if (!this->IsTerminationRequested()) { - if (m_parent->Is64BitProcess()) { + if (m_parent->Is64Bit()) { // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. auto context = GetContext64(); context.pstate &= 0xFF0FFE20; @@ -1174,6 +1177,9 @@ Result KThread::Run() { owner->IncrementRunningThreadCount(); } + // Open a reference, now that we're running. + this->Open(); + // Set our state and finish. this->SetState(ThreadState::Runnable); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index d178c2453..e1f80b04f 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -721,6 +721,7 @@ private: // For core KThread implementation ThreadContext32 m_thread_context_32{}; ThreadContext64 m_thread_context_64{}; + Common::IntrusiveListNode m_process_list_node; Common::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node{}; s32 m_priority{}; using ConditionVariableThreadTreeTraits = diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 24433d32b..4a1559291 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -101,35 +101,31 @@ struct KernelCore::Impl { void InitializeCores() { for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { - cores[core_id]->Initialize((*application_process).Is64BitProcess()); + cores[core_id]->Initialize((*application_process).Is64Bit()); system.ApplicationMemory().SetCurrentPageTable(*application_process, core_id); } } - void CloseApplicationProcess() { - KProcess* old_process = application_process.exchange(nullptr); - if (old_process == nullptr) { - return; - } - - // old_process->Close(); - // TODO: The process should be destroyed based on accurate ref counting after - // calling Close(). Adding a manual Destroy() call instead to avoid a memory leak. - old_process->Finalize(); - old_process->Destroy(); + void TerminateApplicationProcess() { + application_process.load()->Terminate(); } void Shutdown() { is_shutting_down.store(true, std::memory_order_relaxed); SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); }); - process_list.clear(); - CloseServices(); + auto* old_process = application_process.exchange(nullptr); + if (old_process) { + old_process->Close(); + } + + process_list.clear(); + next_object_id = 0; - next_kernel_process_id = KProcess::InitialKIPIDMin; - next_user_process_id = KProcess::ProcessIDMin; + next_kernel_process_id = KProcess::InitialProcessIdMin; + next_user_process_id = KProcess::ProcessIdMin; next_thread_id = 1; global_handle_table->Finalize(); @@ -176,8 +172,6 @@ struct KernelCore::Impl { } } - CloseApplicationProcess(); - // Track kernel objects that were not freed on shutdown { std::scoped_lock lk{registered_objects_lock}; @@ -344,6 +338,8 @@ struct KernelCore::Impl { // Create the system page table managers. app_system_resource = std::make_unique<KSystemResource>(kernel); sys_system_resource = std::make_unique<KSystemResource>(kernel); + KAutoObject::Create(std::addressof(*app_system_resource)); + KAutoObject::Create(std::addressof(*sys_system_resource)); // Set the managers for the system resources. app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager, @@ -792,8 +788,8 @@ struct KernelCore::Impl { std::mutex registered_in_use_objects_lock; std::atomic<u32> next_object_id{0}; - std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin}; - std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin}; + std::atomic<u64> next_kernel_process_id{KProcess::InitialProcessIdMin}; + std::atomic<u64> next_user_process_id{KProcess::ProcessIdMin}; std::atomic<u64> next_thread_id{1}; // Lists all processes that exist in the current session. @@ -924,10 +920,6 @@ const KProcess* KernelCore::ApplicationProcess() const { return impl->application_process; } -void KernelCore::CloseApplicationProcess() { - impl->CloseApplicationProcess(); -} - const std::vector<KProcess*>& KernelCore::GetProcessList() const { return impl->process_list; } @@ -1128,8 +1120,8 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name, std::function<void()> func) { // Make a new process. KProcess* process = KProcess::Create(*this); - ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland, - GetSystemResourceLimit()))); + ASSERT(R_SUCCEEDED( + process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); // Ensure that we don't hold onto any extra references. SCOPE_EXIT({ process->Close(); }); @@ -1156,8 +1148,8 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function // Make a new process. KProcess* process = KProcess::Create(*this); - ASSERT(R_SUCCEEDED(KProcess::Initialize(process, System(), "", KProcess::ProcessType::Userland, - GetSystemResourceLimit()))); + ASSERT(R_SUCCEEDED( + process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); // Ensure that we don't hold onto any extra references. SCOPE_EXIT({ process->Close(); }); @@ -1266,7 +1258,8 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const { void KernelCore::SuspendApplication(bool suspended) { const bool should_suspend{exception_exited || suspended}; - const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; + const auto activity = + should_suspend ? Svc::ProcessActivity::Paused : Svc::ProcessActivity::Runnable; // Get the application process. KScopedAutoObject<KProcess> process = ApplicationProcess(); @@ -1300,6 +1293,8 @@ void KernelCore::SuspendApplication(bool suspended) { } void KernelCore::ShutdownCores() { + impl->TerminateApplicationProcess(); + KScopedSchedulerLock lk{*this}; for (auto* thread : impl->shutdown_threads) { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index d5b08eeb5..d8086c0ea 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -134,9 +134,6 @@ public: /// Retrieves a const pointer to the application process. const KProcess* ApplicationProcess() const; - /// Closes the application process. - void CloseApplicationProcess(); - /// Retrieves the list of processes. const std::vector<KProcess*>& GetProcessList() const; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 871d541d4..b76683969 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -4426,7 +4426,7 @@ void Call(Core::System& system, u32 imm) { auto& kernel = system.Kernel(); kernel.EnterSVCProfile(); - if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) { + if (GetCurrentProcess(system.Kernel()).Is64Bit()) { Call64(system, imm); } else { Call32(system, imm); diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp index f99964028..ada998772 100644 --- a/src/core/hle/kernel/svc/svc_info.cpp +++ b/src/core/hle/kernel/svc/svc_info.cpp @@ -86,20 +86,19 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle R_SUCCEED(); case InfoType::TotalMemorySize: - *result = process->GetTotalPhysicalMemoryAvailable(); + *result = process->GetTotalUserPhysicalMemorySize(); R_SUCCEED(); case InfoType::UsedMemorySize: - *result = process->GetTotalPhysicalMemoryUsed(); + *result = process->GetUsedUserPhysicalMemorySize(); R_SUCCEED(); case InfoType::SystemResourceSizeTotal: - *result = process->GetSystemResourceSize(); + *result = process->GetTotalSystemResourceSize(); R_SUCCEED(); case InfoType::SystemResourceSizeUsed: - LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); - *result = process->GetSystemResourceUsage(); + *result = process->GetUsedSystemResourceSize(); R_SUCCEED(); case InfoType::ProgramId: @@ -111,20 +110,29 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle R_SUCCEED(); case InfoType::TotalNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); + *result = process->GetTotalNonSystemUserPhysicalMemorySize(); R_SUCCEED(); case InfoType::UsedNonSystemMemorySize: - *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); + *result = process->GetUsedNonSystemUserPhysicalMemorySize(); R_SUCCEED(); case InfoType::IsApplication: LOG_WARNING(Kernel_SVC, "(STUBBED) Assuming process is application"); - *result = true; + *result = process->IsApplication(); R_SUCCEED(); case InfoType::FreeThreadCount: - *result = process->GetFreeThreadCount(); + if (KResourceLimit* resource_limit = process->GetResourceLimit(); + resource_limit != nullptr) { + const auto current_value = + resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax); + const auto limit_value = + resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax); + *result = limit_value - current_value; + } else { + *result = 0; + } R_SUCCEED(); default: @@ -161,7 +169,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle case InfoType::RandomEntropy: R_UNLESS(handle == 0, ResultInvalidHandle); - R_UNLESS(info_sub_id < KProcess::RANDOM_ENTROPY_SIZE, ResultInvalidCombination); + R_UNLESS(info_sub_id < 4, ResultInvalidCombination); *result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id); R_SUCCEED(); diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp index 1d7bc4246..5f0833fcb 100644 --- a/src/core/hle/kernel/svc/svc_lock.cpp +++ b/src/core/hle/kernel/svc/svc_lock.cpp @@ -17,7 +17,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3 R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress); - R_RETURN(GetCurrentProcess(system.Kernel()).WaitForAddress(thread_handle, address, tag)); + R_RETURN(KConditionVariable::WaitForAddress(system.Kernel(), thread_handle, address, tag)); } /// Unlock a mutex @@ -28,7 +28,7 @@ Result ArbitrateUnlock(Core::System& system, u64 address) { R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); R_UNLESS(Common::IsAligned(address, sizeof(u32)), ResultInvalidAddress); - R_RETURN(GetCurrentProcess(system.Kernel()).SignalToAddress(address)); + R_RETURN(KConditionVariable::SignalToAddress(system.Kernel(), address)); } Result ArbitrateLock64(Core::System& system, Handle thread_handle, uint64_t address, uint32_t tag) { diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp index d3545f232..99330d02a 100644 --- a/src/core/hle/kernel/svc/svc_physical_memory.cpp +++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp @@ -46,7 +46,7 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) { KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())}; auto& page_table{current_process->GetPageTable()}; - if (current_process->GetSystemResourceSize() == 0) { + if (current_process->GetTotalSystemResourceSize() == 0) { LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); R_THROW(ResultInvalidState); } @@ -95,7 +95,7 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) { KProcess* const current_process{GetCurrentProcessPointer(system.Kernel())}; auto& page_table{current_process->GetPageTable()}; - if (current_process->GetSystemResourceSize() == 0) { + if (current_process->GetTotalSystemResourceSize() == 0) { LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); R_THROW(ResultInvalidState); } diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 8ebc1bd1c..6c79cfd8d 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -132,7 +132,7 @@ void SynchronizePreemptionState(Core::System& system) { GetCurrentThread(kernel).ClearInterruptFlag(); // Unpin the current thread. - cur_process->UnpinCurrentThread(core_id); + cur_process->UnpinCurrentThread(); } } diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 933b82e30..755fd62b5 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -85,10 +85,6 @@ Result StartThread(Core::System& system, Handle thread_handle) { // Try to start the thread. R_TRY(thread->Run()); - // If we succeeded, persist a reference to the thread. - thread->Open(); - system.Kernel().RegisterInUseObject(thread.GetPointerUnsafe()); - R_SUCCEED(); } @@ -99,7 +95,6 @@ void ExitThread(Core::System& system) { auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); system.GlobalSchedulerContext().RemoveThread(current_thread); current_thread->Exit(); - system.Kernel().UnregisterInUseObject(current_thread); } /// Sleep the current thread @@ -260,7 +255,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_ auto list_iter = thread_list.cbegin(); for (std::size_t i = 0; i < copy_amount; ++i, ++list_iter) { - memory.Write64(out_thread_ids, (*list_iter)->GetThreadId()); + memory.Write64(out_thread_ids, list_iter->GetThreadId()); out_thread_ids += sizeof(u64); } diff --git a/src/core/hle/kernel/svc_generator.py b/src/core/hle/kernel/svc_generator.py index 7fcbb1ba1..5531faac6 100644 --- a/src/core/hle/kernel/svc_generator.py +++ b/src/core/hle/kernel/svc_generator.py @@ -592,7 +592,7 @@ void Call(Core::System& system, u32 imm) { auto& kernel = system.Kernel(); kernel.EnterSVCProfile(); - if (GetCurrentProcess(system.Kernel()).Is64BitProcess()) { + if (GetCurrentProcess(system.Kernel()).Is64Bit()) { Call64(system, imm); } else { Call32(system, imm); diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 251e6013c..50de02e36 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -604,13 +604,57 @@ enum class ProcessActivity : u32 { Paused, }; +enum class CreateProcessFlag : u32 { + // Is 64 bit? + Is64Bit = (1 << 0), + + // What kind of address space? + AddressSpaceShift = 1, + AddressSpaceMask = (7 << AddressSpaceShift), + AddressSpace32Bit = (0 << AddressSpaceShift), + AddressSpace64BitDeprecated = (1 << AddressSpaceShift), + AddressSpace32BitWithoutAlias = (2 << AddressSpaceShift), + AddressSpace64Bit = (3 << AddressSpaceShift), + + // Should JIT debug be done on crash? + EnableDebug = (1 << 4), + + // Should ASLR be enabled for the process? + EnableAslr = (1 << 5), + + // Is the process an application? + IsApplication = (1 << 6), + + // 4.x deprecated: Should use secure memory? + DeprecatedUseSecureMemory = (1 << 7), + + // 5.x+ Pool partition type. + PoolPartitionShift = 7, + PoolPartitionMask = (0xF << PoolPartitionShift), + PoolPartitionApplication = (0 << PoolPartitionShift), + PoolPartitionApplet = (1 << PoolPartitionShift), + PoolPartitionSystem = (2 << PoolPartitionShift), + PoolPartitionSystemNonSecure = (3 << PoolPartitionShift), + + // 7.x+ Should memory allocation be optimized? This requires IsApplication. + OptimizeMemoryAllocation = (1 << 11), + + // 11.x+ DisableDeviceAddressSpaceMerge. + DisableDeviceAddressSpaceMerge = (1 << 12), + + // Mask of all flags. + All = Is64Bit | AddressSpaceMask | EnableDebug | EnableAslr | IsApplication | + PoolPartitionMask | OptimizeMemoryAllocation | DisableDeviceAddressSpaceMerge, +}; +DECLARE_ENUM_FLAG_OPERATORS(CreateProcessFlag); + struct CreateProcessParameter { std::array<char, 12> name; u32 version; u64 program_id; u64 code_address; s32 code_num_pages; - u32 flags; + CreateProcessFlag flags; Handle reslimit; s32 system_resource_num_pages; }; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 98765b81a..ff067c8d9 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -796,7 +796,9 @@ ILockAccessor::ILockAccessor(Core::System& system_) lock_event = service_context.CreateEvent("ILockAccessor::LockEvent"); } -ILockAccessor::~ILockAccessor() = default; +ILockAccessor::~ILockAccessor() { + service_context.CloseEvent(lock_event); +}; void ILockAccessor::TryLock(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -909,7 +911,9 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); } -ICommonStateGetter::~ICommonStateGetter() = default; +ICommonStateGetter::~ICommonStateGetter() { + service_context.CloseEvent(sleep_lock_event); +}; void ICommonStateGetter::GetBootMode(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp index 19ed184e8..b379dadeb 100644 --- a/src/core/hle/service/am/applets/applet_cabinet.cpp +++ b/src/core/hle/service/am/applets/applet_cabinet.cpp @@ -25,7 +25,9 @@ Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_, service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent"); } -Cabinet::~Cabinet() = default; +Cabinet::~Cabinet() { + service_context.CloseEvent(availability_change_event); +}; void Cabinet::Initialize() { Applet::Initialize(); diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index 31dd98140..cd1dfe993 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -25,11 +25,12 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService( "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager)); - server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system)); + server_manager->RegisterNamedService( + "caps:ss", std::make_shared<IScreenShotService>(system, album_manager)); server_manager->RegisterNamedService("caps:sc", std::make_shared<IScreenShotControlService>(system)); - server_manager->RegisterNamedService("caps:su", - std::make_shared<IScreenShotApplicationService>(system)); + server_manager->RegisterNamedService( + "caps:su", std::make_shared<IScreenShotApplicationService>(system, album_manager)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp index 2b4e3f076..7d733eb54 100644 --- a/src/core/hle/service/caps/caps_manager.cpp +++ b/src/core/hle/service/caps/caps_manager.cpp @@ -2,12 +2,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <sstream> -#include <stb_image.h> -#include <stb_image_resize.h> #include "common/fs/file.h" #include "common/fs/path_util.h" #include "common/logging/log.h" +#include "common/stb.h" #include "core/core.h" #include "core/hle/service/caps/caps_manager.h" #include "core/hle/service/caps/caps_result.h" @@ -227,6 +226,49 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail( +static_cast<int>(out_image_output.height), decoder_options.flags); } +Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, + const ScreenShotAttribute& attribute, + std::span<const u8> image_data, u64 aruid) { + return SaveScreenShot(out_entry, attribute, {}, image_data, aruid); +} + +Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, + const ScreenShotAttribute& attribute, + const ApplicationData& app_data, std::span<const u8> image_data, + u64 aruid) { + const u64 title_id = system.GetApplicationProcessProgramID(); + const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore(); + + s64 posix_time{}; + Result result = user_clock.GetCurrentTime(system, posix_time); + + if (result.IsError()) { + return result; + } + + const auto date = ConvertToAlbumDateTime(posix_time); + + return SaveImage(out_entry, image_data, title_id, date); +} + +Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, + const ScreenShotAttribute& attribute, + const AlbumFileId& file_id, + std::span<const u8> image_data) { + const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore(); + + s64 posix_time{}; + Result result = user_clock.GetCurrentTime(system, posix_time); + + if (result.IsError()) { + return result; + } + + const auto date = ConvertToAlbumDateTime(posix_time); + + return SaveImage(out_entry, image_data, file_id.application_id, date); +} + Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const { const auto file = album_files.find(file_id); @@ -365,6 +407,47 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p return ResultSuccess; } +static void PNGToMemory(void* context, void* png, int len) { + std::vector<u8>* png_image = static_cast<std::vector<u8>*>(context); + png_image->reserve(len); + std::memcpy(png_image->data(), png, len); +} + +Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, + u64 title_id, const AlbumFileDateTime& date) const { + const auto screenshot_path = + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir); + const std::string formatted_date = + fmt::format("{:04}-{:02}-{:02}_{:02}-{:02}-{:02}-{:03}", date.year, date.month, date.day, + date.hour, date.minute, date.second, 0); + const std::string file_path = + fmt::format("{}/{:016x}_{}.png", screenshot_path, title_id, formatted_date); + + const Common::FS::IOFile db_file{file_path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + + std::vector<u8> png_image; + if (!stbi_write_png_to_func(PNGToMemory, &png_image, 1280, 720, STBI_rgb_alpha, image.data(), + 0)) { + return ResultFileCountLimit; + } + + if (db_file.Write(png_image) != png_image.size()) { + return ResultFileCountLimit; + } + + out_entry = { + .size = png_image.size(), + .hash = {}, + .datetime = date, + .storage = AlbumStorage::Sd, + .content = ContentType::Screenshot, + .unknown = 1, + }; + + return ResultSuccess; +} + AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const { Time::TimeZone::CalendarInfo calendar_date{}; const auto& time_zone_manager = diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index f65eb12c1..44d85117f 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h @@ -58,6 +58,15 @@ public: std::vector<u8>& out_image, const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const; + Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, + std::span<const u8> image_data, u64 aruid); + Result SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, + const ApplicationData& app_data, std::span<const u8> image_data, + u64 aruid); + Result SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, + const ScreenShotAttribute& attribute, const AlbumFileId& file_id, + std::span<const u8> image_data); + private: static constexpr std::size_t NandAlbumFileLimit = 1000; static constexpr std::size_t SdAlbumFileLimit = 10000; @@ -67,6 +76,8 @@ private: Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const; Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width, int height, ScreenShotDecoderFlag flag) const; + Result SaveImage(ApplicationAlbumEntry& out_entry, std::span<const u8> image, u64 title_id, + const AlbumFileDateTime& date) const; AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const; diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index d0d1b5425..1ba2b7972 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -1,19 +1,25 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/logging/log.h" +#include "core/hle/service/caps/caps_manager.h" +#include "core/hle/service/caps/caps_types.h" +#include "core/hle/service/ipc_helpers.h" + #include "core/hle/service/caps/caps_ss.h" namespace Service::Capture { -IScreenShotService::IScreenShotService(Core::System& system_) - : ServiceFramework{system_, "caps:ss"} { +IScreenShotService::IScreenShotService(Core::System& system_, + std::shared_ptr<AlbumManager> album_manager) + : ServiceFramework{system_, "caps:ss"}, manager{album_manager} { // clang-format off static const FunctionInfo functions[] = { {201, nullptr, "SaveScreenShot"}, {202, nullptr, "SaveEditedScreenShot"}, - {203, nullptr, "SaveScreenShotEx0"}, + {203, &IScreenShotService::SaveScreenShotEx0, "SaveScreenShotEx0"}, {204, nullptr, "SaveEditedScreenShotEx0"}, - {206, nullptr, "Unknown206"}, + {206, &IScreenShotService::SaveEditedScreenShotEx1, "SaveEditedScreenShotEx1"}, {208, nullptr, "SaveScreenShotOfMovieEx1"}, {1000, nullptr, "Unknown1000"}, }; @@ -24,4 +30,65 @@ IScreenShotService::IScreenShotService(Core::System& system_) IScreenShotService::~IScreenShotService() = default; +void IScreenShotService::SaveScreenShotEx0(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + ScreenShotAttribute attribute{}; + u32 report_option{}; + INSERT_PADDING_BYTES(0x4); + u64 applet_resource_user_id{}; + }; + static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto image_data_buffer = ctx.ReadBuffer(); + + LOG_INFO(Service_Capture, + "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}", + parameters.report_option, image_data_buffer.size(), + parameters.applet_resource_user_id); + + ApplicationAlbumEntry entry{}; + const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(result); + rb.PushRaw(entry); +} +void IScreenShotService::SaveEditedScreenShotEx1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + ScreenShotAttribute attribute; + u64 width; + u64 height; + u64 thumbnail_width; + u64 thumbnail_height; + AlbumFileId file_id; + }; + static_assert(sizeof(Parameters) == 0x78, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto application_data_buffer = ctx.ReadBuffer(0); + const auto image_data_buffer = ctx.ReadBuffer(1); + const auto thumbnail_image_data_buffer = ctx.ReadBuffer(2); + + LOG_INFO(Service_Capture, + "called, width={}, height={}, thumbnail_width={}, thumbnail_height={}, " + "application_id={:016x}, storage={}, type={}, app_data_buffer_size={}, " + "image_data_buffer_size={}, thumbnail_image_buffer_size={}", + parameters.width, parameters.height, parameters.thumbnail_width, + parameters.thumbnail_height, parameters.file_id.application_id, + parameters.file_id.storage, parameters.file_id.type, application_data_buffer.size(), + image_data_buffer.size(), thumbnail_image_data_buffer.size()); + + ApplicationAlbumEntry entry{}; + const auto result = manager->SaveEditedScreenShot(entry, parameters.attribute, + parameters.file_id, image_data_buffer); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(result); + rb.PushRaw(entry); +} + } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h index 381e44fd4..a7e9972ab 100644 --- a/src/core/hle/service/caps/caps_ss.h +++ b/src/core/hle/service/caps/caps_ss.h @@ -13,8 +13,14 @@ namespace Service::Capture { class IScreenShotService final : public ServiceFramework<IScreenShotService> { public: - explicit IScreenShotService(Core::System& system_); + explicit IScreenShotService(Core::System& system_, std::shared_ptr<AlbumManager> album_manager); ~IScreenShotService() override; + +private: + void SaveScreenShotEx0(HLERequestContext& ctx); + void SaveEditedScreenShotEx1(HLERequestContext& ctx); + + std::shared_ptr<AlbumManager> manager; }; } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index cad173dc7..e85625ee4 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -2,19 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "core/hle/service/caps/caps_manager.h" #include "core/hle/service/caps/caps_su.h" +#include "core/hle/service/caps/caps_types.h" #include "core/hle/service/ipc_helpers.h" namespace Service::Capture { -IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_) - : ServiceFramework{system_, "caps:su"} { +IScreenShotApplicationService::IScreenShotApplicationService( + Core::System& system_, std::shared_ptr<AlbumManager> album_manager) + : ServiceFramework{system_, "caps:su"}, manager{album_manager} { // clang-format off static const FunctionInfo functions[] = { {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"}, {201, nullptr, "SaveScreenShot"}, - {203, nullptr, "SaveScreenShotEx0"}, - {205, nullptr, "SaveScreenShotEx1"}, + {203, &IScreenShotApplicationService::SaveScreenShotEx0, "SaveScreenShotEx0"}, + {205, &IScreenShotApplicationService::SaveScreenShotEx1, "SaveScreenShotEx1"}, {210, nullptr, "SaveScreenShotEx2"}, }; // clang-format on @@ -36,4 +39,62 @@ void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx rb.Push(ResultSuccess); } +void IScreenShotApplicationService::SaveScreenShotEx0(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + ScreenShotAttribute attribute{}; + AlbumReportOption report_option{}; + INSERT_PADDING_BYTES(0x4); + u64 applet_resource_user_id{}; + }; + static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto image_data_buffer = ctx.ReadBuffer(); + + LOG_INFO(Service_Capture, + "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}", + parameters.report_option, image_data_buffer.size(), + parameters.applet_resource_user_id); + + ApplicationAlbumEntry entry{}; + const auto result = manager->SaveScreenShot(entry, parameters.attribute, image_data_buffer, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(result); + rb.PushRaw(entry); +} + +void IScreenShotApplicationService::SaveScreenShotEx1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + ScreenShotAttribute attribute{}; + AlbumReportOption report_option{}; + INSERT_PADDING_BYTES(0x4); + u64 applet_resource_user_id{}; + }; + static_assert(sizeof(Parameters) == 0x50, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + const auto app_data_buffer = ctx.ReadBuffer(0); + const auto image_data_buffer = ctx.ReadBuffer(1); + + LOG_INFO(Service_Capture, + "called, report_option={}, image_data_buffer_size={}, applet_resource_user_id={}", + parameters.report_option, image_data_buffer.size(), + parameters.applet_resource_user_id); + + ApplicationAlbumEntry entry{}; + ApplicationData app_data{}; + std::memcpy(&app_data, app_data_buffer.data(), sizeof(ApplicationData)); + const auto result = + manager->SaveScreenShot(entry, parameters.attribute, app_data, image_data_buffer, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(result); + rb.PushRaw(entry); +} + } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 647e3059d..89e71f506 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -10,14 +10,20 @@ class System; } namespace Service::Capture { +class AlbumManager; class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> { public: - explicit IScreenShotApplicationService(Core::System& system_); + explicit IScreenShotApplicationService(Core::System& system_, + std::shared_ptr<AlbumManager> album_manager); ~IScreenShotApplicationService() override; private: void SetShimLibraryVersion(HLERequestContext& ctx); + void SaveScreenShotEx0(HLERequestContext& ctx); + void SaveScreenShotEx1(HLERequestContext& ctx); + + std::shared_ptr<AlbumManager> manager; }; } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h index 7fd357954..589ac28d3 100644 --- a/src/core/hle/service/caps/caps_types.h +++ b/src/core/hle/service/caps/caps_types.h @@ -20,6 +20,8 @@ enum class AlbumImageOrientation { enum class AlbumReportOption : s32 { Disable, Enable, + Unknown2, + Unknown3, }; enum class ContentType : u8 { diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp index 14c67e454..73a2a2b91 100644 --- a/src/core/hle/service/hid/controllers/palma.cpp +++ b/src/core/hle/service/hid/controllers/palma.cpp @@ -19,7 +19,9 @@ Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent"); } -Controller_Palma::~Controller_Palma() = default; +Controller_Palma::~Controller_Palma() { + service_context.CloseEvent(operation_complete_event); +}; void Controller_Palma::OnInit() {} diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 4d70006c1..929dd5f03 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2757,6 +2757,10 @@ public: joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); } + ~HidSys() { + service_context.CloseEvent(joy_detach_event); + }; + private: void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { LOG_WARNING(Service_HID, "called"); diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp index ee522c36e..8c44f93e8 100644 --- a/src/core/hle/service/hid/hidbus/hidbus_base.cpp +++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp @@ -13,7 +13,10 @@ HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& ser : system(system_), service_context(service_context_) { send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent"); } -HidbusBase::~HidbusBase() = default; + +HidbusBase::~HidbusBase() { + service_context.CloseEvent(send_command_async_event); +}; void HidbusBase::ActivateDevice() { if (is_activated) { diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp index 6f3ae3cc4..ff374ae39 100644 --- a/src/core/hle/service/hle_ipc.cpp +++ b/src/core/hle/service/hle_ipc.cpp @@ -27,10 +27,12 @@ namespace { static thread_local std::array read_buffer_data_a{ Common::ScratchBuffer<u8>(), Common::ScratchBuffer<u8>(), + Common::ScratchBuffer<u8>(), }; static thread_local std::array read_buffer_data_x{ Common::ScratchBuffer<u8>(), Common::ScratchBuffer<u8>(), + Common::ScratchBuffer<u8>(), }; } // Anonymous namespace @@ -343,6 +345,7 @@ std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) con static thread_local std::array read_buffer_a{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), + Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), }; ASSERT_OR_EXECUTE_MSG( @@ -358,6 +361,7 @@ std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) con static thread_local std::array read_buffer_x{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), + Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), }; ASSERT_OR_EXECUTE_MSG( @@ -373,10 +377,12 @@ std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) cons static thread_local std::array read_buffer_a{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), + Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), }; static thread_local std::array read_buffer_x{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), + Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), }; const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp index 6a313a03b..f51e63564 100644 --- a/src/core/hle/service/kernel_helpers.cpp +++ b/src/core/hle/service/kernel_helpers.cpp @@ -21,10 +21,8 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_) // Create the process. process = Kernel::KProcess::Create(kernel); - ASSERT(Kernel::KProcess::Initialize(process, system_, std::move(name_), - Kernel::KProcess::ProcessType::KernelInternal, - kernel.GetSystemResourceLimit()) - .IsSuccess()); + ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{}, + kernel.GetSystemResourceLimit(), false))); // Register the process. Kernel::KProcess::Register(kernel, process); diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp index 2dbe29616..ed66f6f5b 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp @@ -41,7 +41,7 @@ bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const { // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer. if (!use_async_buffer) { - return max_acquired_buffer_count; + return 0; } if (dequeue_buffer_cannot_block || async) { @@ -52,7 +52,7 @@ s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const { } s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { - return GetMinUndequeuedBufferCountLocked(async) + 1; + return GetMinUndequeuedBufferCountLocked(async); } s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { @@ -61,7 +61,7 @@ s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { if (override_max_buffer_count != 0) { ASSERT(override_max_buffer_count >= min_buffer_count); - max_buffer_count = override_max_buffer_count; + return override_max_buffer_count; } // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index dc6917d5d..6e7a49658 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -134,7 +134,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St const s32 max_buffer_count = core->GetMaxBufferCountLocked(async); if (async && core->override_max_buffer_count) { if (core->override_max_buffer_count < max_buffer_count) { - LOG_ERROR(Service_Nvnflinger, "async mode is invalid with buffer count override"); + *found = BufferQueueCore::INVALID_BUFFER_SLOT; return Status::BadValue; } } @@ -142,7 +142,8 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, St // Free up any buffers that are in slots beyond the max buffer count for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) { ASSERT(slots[s].buffer_state == BufferState::Free); - if (slots[s].graphic_buffer != nullptr) { + if (slots[s].graphic_buffer != nullptr && slots[s].buffer_state == BufferState::Free && + !slots[s].is_preallocated) { core->FreeBufferLocked(s); *return_flags |= Status::ReleaseAllBuffers; } diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index a07c621d9..bebb45eae 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -66,7 +66,6 @@ Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_ "ScreenComposition", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { - { const auto lock_guard = Lock(); } vsync_signal.Set(); return std::chrono::nanoseconds(GetNextTicks()); }); @@ -99,6 +98,7 @@ Nvnflinger::~Nvnflinger() { } ShutdownLayers(); + vsync_thread = {}; if (nvdrv) { nvdrv->Close(disp_fd); @@ -106,6 +106,7 @@ Nvnflinger::~Nvnflinger() { } void Nvnflinger::ShutdownLayers() { + const auto lock_guard = Lock(); for (auto& display : displays) { for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) { display.GetLayer(layer).Core().NotifyShutdown(); @@ -229,16 +230,6 @@ VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) { return display->FindLayer(layer_id); } -const VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) const { - const auto* const display = FindDisplay(display_id); - - if (display == nullptr) { - return nullptr; - } - - return display->FindLayer(layer_id); -} - VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) { auto* const display = FindDisplay(display_id); @@ -288,7 +279,6 @@ void Nvnflinger::Compose() { auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd); ASSERT(nvdisp); - guard->unlock(); Common::Rectangle<int> crop_rect{ static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()), static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())}; @@ -299,7 +289,6 @@ void Nvnflinger::Compose() { buffer.fence.fences, buffer.fence.num_fences); MicroProfileFlip(); - guard->lock(); swap_interval = buffer.swap_interval; diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index 14c783582..959d8b46b 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -117,9 +117,6 @@ private: /// Finds the layer identified by the specified ID in the desired display. [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); - /// Finds the layer identified by the specified ID in the desired display. - [[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const; - /// Finds the layer identified by the specified ID in the desired display, /// or creates the layer if it is not found. /// To be used when the system expects the specified ID to already exist. diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp index 938330dd0..6a7fd72bc 100644 --- a/src/core/hle/service/pctl/pctl_module.cpp +++ b/src/core/hle/service/pctl/pctl_module.cpp @@ -141,6 +141,12 @@ public: service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent"); } + ~IParentalControlService() { + service_context.CloseEvent(synchronization_event); + service_context.CloseEvent(unlinked_event); + service_context.CloseEvent(request_suspension_event); + }; + private: bool CheckFreeCommunicationPermissionImpl() const { if (states.temporary_unlocked) { diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index f9cf2dda3..d92499f05 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -37,7 +37,7 @@ std::optional<Kernel::KProcess*> SearchProcessList( void GetApplicationPidGeneric(HLERequestContext& ctx, const std::vector<Kernel::KProcess*>& process_list) { const auto process = SearchProcessList(process_list, [](const auto& proc) { - return proc->GetProcessId() == Kernel::KProcess::ProcessIDMin; + return proc->GetProcessId() == Kernel::KProcess::ProcessIdMin; }); IPC::ResponseBuilder rb{ctx, 4}; diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index a06e99166..53a89cc8f 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -19,16 +19,23 @@ namespace Core::Memory { namespace { constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; -std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) { +std::string_view ExtractName(std::size_t& out_name_size, std::string_view data, + std::size_t start_index, char match) { auto end_index = start_index; while (data[end_index] != match) { ++end_index; - if (end_index > data.size() || - (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { + if (end_index > data.size()) { return {}; } } + out_name_size = end_index - start_index; + + // Clamp name if it's too big + if (out_name_size > sizeof(CheatDefinition::readable_name)) { + end_index = start_index + sizeof(CheatDefinition::readable_name); + } + return data.substr(start_index, end_index - start_index); } } // Anonymous namespace @@ -113,7 +120,8 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { return {}; } - const auto name = ExtractName(data, i + 1, '}'); + std::size_t name_size{}; + const auto name = ExtractName(name_size, data, i + 1, '}'); if (name.empty()) { return {}; } @@ -125,12 +133,13 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = '\0'; - i += name.length() + 1; + i += name_size + 1; } else if (data[i] == '[') { current_entry = out.size(); out.emplace_back(); - const auto name = ExtractName(data, i + 1, ']'); + std::size_t name_size{}; + const auto name = ExtractName(name_size, data, i + 1, ']'); if (name.empty()) { return {}; } @@ -142,7 +151,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = '\0'; - i += name.length() + 1; + i += name_size + 1; } else if (::isxdigit(data[i])) { if (!current_entry || out[*current_entry].definition.num_opcodes >= out[*current_entry].definition.opcodes.size()) { diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index ed875d444..5d168cbc1 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -116,7 +116,7 @@ json GetProcessorStateDataAuto(Core::System& system) { Core::ARM_Interface::ThreadContext64 context{}; arm.SaveContext(context); - return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", + return GetProcessorStateData(process->Is64Bit() ? "AArch64" : "AArch32", GetInteger(process->GetEntryPoint()), context.sp, context.pc, context.pstate, context.cpu_registers); } diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index cf51f3481..c9f903213 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp @@ -139,7 +139,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) { input_thread_running = true; // Max update rate is 5ms, ensure we are always able to read a bit faster - constexpr int ThreadDelay = 2; + constexpr int ThreadDelay = 3; std::vector<u8> buffer(MaxBufferSize); while (!stop_token.stop_requested()) { @@ -163,6 +163,17 @@ void JoyconDriver::InputThread(std::stop_token stop_token) { OnNewData(buffer); } + if (!vibration_queue.Empty()) { + VibrationValue vibration_value; + vibration_queue.Pop(vibration_value); + last_vibration_result = rumble_protocol->SendVibration(vibration_value); + } + + // We can't keep up with vibrations. Start skipping. + while (vibration_queue.Size() > 6) { + vibration_queue.Pop(); + } + std::this_thread::yield(); } @@ -402,7 +413,8 @@ Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vib if (disable_input_thread) { return Common::Input::DriverResult::HandleInUse; } - return rumble_protocol->SendVibration(vibration); + vibration_queue.Push(vibration); + return last_vibration_result; } Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index 335e12cc3..5355780fb 100644 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h @@ -9,6 +9,7 @@ #include <span> #include <thread> +#include "common/threadsafe_queue.h" #include "input_common/helpers/joycon_protocol/joycon_types.h" namespace Common::Input { @@ -152,6 +153,10 @@ private: SerialNumber handle_serial_number{}; // Serial number type reported by hidapi SupportedFeatures supported_features{}; + /// Queue of vibration request to controllers + Common::Input::DriverResult last_vibration_result{Common::Input::DriverResult::Success}; + Common::SPSCQueue<VibrationValue> vibration_queue; + // Thread related mutable std::mutex mutex; std::jthread input_thread; diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp index d91e04446..66ecfc9f7 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp @@ -242,6 +242,7 @@ std::string EmitGLSL(const Profile& profile, const RuntimeInfo& runtime_info, IR } if (program.info.uses_subgroup_shuffles) { ctx.header += "bool shfl_in_bounds;"; + ctx.header += "uint shfl_result;"; } ctx.code.insert(0, ctx.header); ctx.code += '}'; diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp index 1245c9429..f9be5de1c 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp @@ -141,7 +141,8 @@ void EmitShuffleIndex(EmitContext& ctx, IR::Inst& inst, std::string_view value, const auto src_thread_id{fmt::format("({})|({})", lhs, min_thread_id)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); - ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); + ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id); + ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); } void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index, @@ -158,7 +159,8 @@ void EmitShuffleUp(EmitContext& ctx, IR::Inst& inst, std::string_view value, std const auto src_thread_id{fmt::format("({}-{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})>=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); - ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); + ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id); + ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); } void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, @@ -175,7 +177,8 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, const auto src_thread_id{fmt::format("({}+{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); - ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); + ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id); + ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); } void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value, @@ -193,7 +196,8 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val const auto src_thread_id{fmt::format("({}^{})", THREAD_ID, index)}; ctx.Add("shfl_in_bounds=int({})<=int({});", src_thread_id, max_thread_id); SetInBoundsFlag(ctx, inst); - ctx.AddU32("{}=shfl_in_bounds?readInvocationARB({},{}):{};", inst, value, src_thread_id, value); + ctx.Add("shfl_result=readInvocationARB({},{});", value, src_thread_id); + ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); } void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9b2698fad..081a574e8 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1067,8 +1067,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() { template <class P> void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { - do { - channel_state->has_deleted_buffers = false; + BufferOperations([&]() { if (is_indexed) { UpdateIndexBuffer(); } @@ -1082,14 +1081,16 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { if (current_draw_indirect) { UpdateDrawIndirect(); } - } while (channel_state->has_deleted_buffers); + }); } template <class P> void BufferCache<P>::DoUpdateComputeBuffers() { - UpdateComputeUniformBuffers(); - UpdateComputeStorageBuffers(); - UpdateComputeTextureBuffers(); + BufferOperations([&]() { + UpdateComputeUniformBuffers(); + UpdateComputeStorageBuffers(); + UpdateComputeTextureBuffers(); + }); } template <class P> diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 804b95989..22bf8cc77 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -358,7 +358,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device .has_broken_spirv_subgroup_mask_vector_extract_dynamic = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, .has_broken_robust = - device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Maxwell, + device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, }; host_info = Shader::HostTranslateInfo{ diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 465eac37e..059b7cb40 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -13,6 +13,7 @@ #include "common/microprofile.h" #include "common/scope_exit.h" #include "common/settings.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/control/channel_state.h" #include "video_core/engines/draw_manager.h" #include "video_core/engines/kepler_compute.h" @@ -285,6 +286,7 @@ void RasterizerVulkan::DrawTexture() { query_cache.NotifySegment(true); + std::scoped_lock l{texture_cache.mutex}; texture_cache.SynchronizeGraphicsDescriptors(); texture_cache.UpdateRenderTargets(false); diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index d56558a83..daaea2979 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -190,7 +190,7 @@ void SetupDirtySpecialOps(Tables& tables) { void SetupDirtyViewportSwizzles(Tables& tables) { static constexpr size_t swizzle_offset = 6; for (size_t index = 0; index < Regs::NumViewports; ++index) { - tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = + tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = ViewportSwizzles; } } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 81ef98f61..821f44f1a 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -147,6 +147,9 @@ bool Swapchain::AcquireNextImage() { case VK_ERROR_OUT_OF_DATE_KHR: is_outdated = true; break; + case VK_ERROR_SURFACE_LOST_KHR: + vk::Check(result); + break; default: LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result)); break; @@ -180,6 +183,9 @@ void Swapchain::Present(VkSemaphore render_semaphore) { case VK_ERROR_OUT_OF_DATE_KHR: is_outdated = true; break; + case VK_ERROR_SURFACE_LOST_KHR: + vk::Check(result); + break; default: LOG_CRITICAL(Render_Vulkan, "Failed to present with error {}", vk::ToString(result)); break; diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 2f3254a97..70cf14afa 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -522,7 +522,7 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char .applicationVersion = VK_MAKE_VERSION(0, 1, 0), .pEngineName = "yuzu Emulator", .engineVersion = VK_MAKE_VERSION(0, 1, 0), - .apiVersion = version, + .apiVersion = VK_API_VERSION_1_3, }; const VkInstanceCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 34208ed74..33e1fb663 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -227,14 +227,14 @@ add_executable(yuzu yuzu.rc ) -if (WIN32 AND YUZU_CRASH_DUMPS) +if (YUZU_CRASH_DUMPS) target_sources(yuzu PRIVATE - mini_dump.cpp - mini_dump.h + breakpad.cpp + breakpad.h ) - target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) - target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) + target_link_libraries(yuzu PRIVATE libbreakpad_client) + target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index ca0e14fad..515cb7ce6 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -155,18 +155,27 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( UpdateBorderColor(i); connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { - if (checked) { - // Hide eventual error message about number of controllers - ui->labelError->setVisible(false); - for (std::size_t index = 0; index <= i; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } else { - for (std::size_t index = i; index < NUM_PLAYERS; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 && + player_groupboxes[i + 1]->isChecked(); + + // Ensures that connecting a controller changes the number of players + if (connected_controller_checkboxes[i]->isChecked() != checked) { + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); } }); + connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) { + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && + i < connected_controller_checkboxes.size() - 1 && + connected_controller_checkboxes[i + 1]->isChecked(); + + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); + }); connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), [this, i](int) { @@ -668,6 +677,29 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { } } +void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current) { + connected_controller_checkboxes[player_index]->setChecked(checked); + // Hide eventual error message about number of controllers + ui->labelError->setVisible(false); + + if (checked) { + // Check all previous buttons when checked + if (player_index > 0) { + PropagatePlayerNumberChanged(player_index - 1, checked); + } + } else { + // Unchecked all following buttons when unchecked + if (player_index < connected_controller_checkboxes.size() - 1) { + PropagatePlayerNumberChanged(player_index + 1, checked); + } + } + + if (reconnect_current) { + connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked); + } +} + void QtControllerSelectorDialog::DisableUnsupportedPlayers() { const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 7f0673d06..e5372495d 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -100,6 +100,10 @@ private: // Updates the console mode. void UpdateDockedState(bool is_handheld); + // Enable preceding controllers or disable following ones + void PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current = false); + // Disables and disconnects unsupported players based on the given parameters. void DisableUnsupportedPlayers(); diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp new file mode 100644 index 000000000..0f6a71ab0 --- /dev/null +++ b/src/yuzu/breakpad.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <ranges> + +#if defined(_WIN32) +#include <client/windows/handler/exception_handler.h> +#elif defined(__linux__) +#include <client/linux/handler/exception_handler.h> +#else +#error Minidump creation not supported on this platform +#endif + +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" +#include "yuzu/breakpad.h" + +namespace Breakpad { + +static void PruneDumpDirectory(const std::filesystem::path& dump_path) { + // Code in this function should be exception-safe. + struct Entry { + std::filesystem::path path; + std::filesystem::file_time_type last_write_time; + }; + std::vector<Entry> existing_dumps; + + // Get existing entries. + std::error_code ec; + std::filesystem::directory_iterator dir(dump_path, ec); + for (auto& entry : dir) { + if (entry.is_regular_file()) { + existing_dumps.push_back(Entry{ + .path = entry.path(), + .last_write_time = entry.last_write_time(ec), + }); + } + } + + // Sort descending by creation date. + std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) { + return a.last_write_time > b.last_write_time; + }); + + // Delete older dumps. + for (size_t i = 5; i < existing_dumps.size(); i++) { + std::filesystem::remove(existing_dumps[i].path, ec); + } +} + +#if defined(__linux__) +[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, + bool succeeded) { + // Prevent time- and space-consuming core dumps from being generated, as we have + // already generated a minidump and a core file will not be useful anyway. + _exit(1); +} +#endif + +void InstallCrashHandler() { + // Write crash dumps to profile directory. + const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir); + PruneDumpDirectory(dump_path); + +#if defined(_WIN32) + // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API. + static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr, + google_breakpad::ExceptionHandler::HANDLER_ALL}; +#elif defined(__linux__) + static google_breakpad::MinidumpDescriptor descriptor{dump_path}; + static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback, + nullptr, true, -1}; +#endif +} + +} // namespace Breakpad diff --git a/src/yuzu/breakpad.h b/src/yuzu/breakpad.h new file mode 100644 index 000000000..0f911aa9c --- /dev/null +++ b/src/yuzu/breakpad.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Breakpad { + +void InstallCrashHandler(); + +} diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d5157c502..baa3e55f3 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -114,7 +114,7 @@ const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_ma // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ +const std::array<UISettings::Shortcut, 23> Config::default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"), QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"), QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="), QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}}, @@ -136,6 +136,7 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, + {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral(""), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut, false}}, }}; // clang-format on diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 727feebfb..74ec4f771 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -48,7 +48,7 @@ public: default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; - static const std::array<UISettings::Shortcut, 22> default_hotkeys; + static const std::array<UISettings::Shortcut, 23> default_hotkeys; static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map; static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map; diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index b22fda746..ef421c754 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) connect(ui->toggle_gdbstub, &QCheckBox::toggled, [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); - - connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { - if (crash_dump_warning_shown) { - return; - } - QMessageBox::warning(this, tr("Restart Required"), - tr("yuzu is required to restart in order to apply this setting."), - QMessageBox::Ok, QMessageBox::Ok); - crash_dump_warning_shown = true; - }); } ConfigureDebug::~ConfigureDebug() = default; @@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() { ui->disable_web_applet->setEnabled(false); ui->disable_web_applet->setText(tr("Web applet not compiled")); #endif - -#ifdef YUZU_DBGHELP - ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); -#else - ui->create_crash_dumps->setEnabled(false); - ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); -#endif } void ConfigureDebug::ApplyConfiguration() { @@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); - Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 66b8b7459..76fe98924 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -471,13 +471,6 @@ </property> </widget> </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="create_crash_dumps"> - <property name="text"> - <string>Create Minidump After Crash</string> - </property> - </widget> - </item> <item row="3" column="0"> <widget class="QCheckBox" name="dump_audio_commands"> <property name="toolTip"> diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 0b2a965f8..68e21cd84 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -319,6 +319,13 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { void ConfigureHotkeys::RestoreDefaults() { for (int r = 0; r < model->rowCount(); ++r) { const QStandardItem* parent = model->item(r, 0); + const int hotkey_size = static_cast<int>(Config::default_hotkeys.size()); + + if (hotkey_size != parent->rowCount()) { + QMessageBox::warning(this, tr("Invalid hotkey settings"), + tr("An error occurred. Please report this issue on github.")); + return; + } for (int r2 = 0; r2 < parent->rowCount(); ++r2) { model->item(r, 0) diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 5a48e388b..3dcad2701 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -101,13 +101,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, }; - player_connected = { + connected_controller_checkboxes = { ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; - std::array<QLabel*, 8> player_connected_labels = { + std::array<QLabel*, 8> connected_controller_labels = { ui->label, ui->label_3, ui->label_4, ui->label_5, ui->label_6, ui->label_7, ui->label_8, ui->label_9, }; @@ -115,23 +115,37 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); - connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) { - // Ensures that the controllers are always connected in sequential order - this->propagateMouseClickOnPlayers(i, checked, true); + connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) { + // Ensures that connecting a controller changes the number of players + if (connected_controller_checkboxes[i]->isChecked() != checked) { + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked); + } + }); + connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) { + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && + i < connected_controller_checkboxes.size() - 1 && + connected_controller_checkboxes[i + 1]->isChecked(); + + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); }); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, &ConfigureInput::UpdateAllInputDevices); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); - connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + // Keep activated controllers synced with the "Connected Controllers" checkboxes player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); // Remove/hide all the elements that exceed max_players, if applicable. if (i >= max_players) { ui->tabWidget->removeTab(static_cast<int>(max_players)); - player_connected[i]->hide(); - player_connected_labels[i]->hide(); + connected_controller_checkboxes[i]->hide(); + connected_controller_labels[i]->hide(); } } // Only the first player can choose handheld mode so connect the signal just to player 1 @@ -175,28 +189,25 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, LoadConfiguration(); } -void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) { - // Origin has already been toggled - if (!origin) { - player_connected[player_index]->setChecked(checked); - } +void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current) { + connected_controller_checkboxes[player_index]->setChecked(checked); if (checked) { // Check all previous buttons when checked if (player_index > 0) { - propagateMouseClickOnPlayers(player_index - 1, checked, false); + PropagatePlayerNumberChanged(player_index - 1, checked); } } else { // Unchecked all following buttons when unchecked - if (player_index < player_tabs.size() - 1) { - // Reconnect current player if it was the last one checked - // (player number was reduced by more than one) - if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) { - player_connected[player_index]->setCheckState(Qt::Checked); - } - propagateMouseClickOnPlayers(player_index + 1, checked, false); + if (player_index < connected_controller_checkboxes.size() - 1) { + PropagatePlayerNumberChanged(player_index + 1, checked); } } + + if (reconnect_current) { + connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked); + } } QList<QWidget*> ConfigureInput::GetSubTabs() const { @@ -249,17 +260,17 @@ void ConfigureInput::LoadConfiguration() { } void ConfigureInput::LoadPlayerControllerIndices() { - for (std::size_t i = 0; i < player_connected.size(); ++i) { + for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) { if (i == 0) { auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); if (handheld->IsConnected()) { - player_connected[i]->setChecked(true); + connected_controller_checkboxes[i]->setChecked(true); continue; } } const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i); - player_connected[i]->setChecked(controller->IsConnected()); + connected_controller_checkboxes[i]->setChecked(controller->IsConnected()); } } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index abb7f7089..136cd3a0a 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -56,7 +56,9 @@ private: void UpdateDockedState(bool is_handheld); void UpdateAllInputDevices(); void UpdateAllInputProfiles(std::size_t player_index); - void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked); + // Enable preceding controllers or disable following ones + void PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current = false); /// Load configuration settings. void LoadConfiguration(); @@ -71,7 +73,8 @@ private: std::array<ConfigureInputPlayer*, 8> player_controllers; std::array<QWidget*, 8> player_tabs; - std::array<QCheckBox*, 8> player_connected; + // Checkboxes representing the "Connected Controllers". + std::array<QCheckBox*, 8> connected_controller_checkboxes; ConfigureInputAdvanced* advanced; Core::System& system; diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index d4df43d73..d3255d2b4 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -75,7 +75,7 @@ public: void ClearAll(); signals: - /// Emitted when this controller is connected by the user. + /// Emitted when this controller is (dis)connected by the user. void Connected(bool connected); /// Emitted when the Handheld mode is selected (undocked with dual joycons attached). void HandheldStateChanged(bool is_handheld); @@ -183,9 +183,6 @@ private: /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs; - static constexpr int PLAYER_COUNT = 8; - std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; - /// This will be the the setting function when an input is awaiting configuration. std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index d765e808a..68c28b320 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -89,7 +89,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type auto& player = Settings::values.players.GetValue()[player_index]; auto controller = hid_core.GetEmulatedControllerByIndex(player_index); - const int vibration_strenght = vibration_spinboxes[player_index]->value(); + const int vibration_strength = vibration_spinboxes[player_index]->value(); const auto& buttons = controller->GetButtonsValues(); bool button_is_pressed = false; @@ -105,10 +105,10 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type return; } - const int old_vibration_enabled = player.vibration_enabled; - const bool old_vibration_strenght = player.vibration_strength; + const bool old_vibration_enabled = player.vibration_enabled; + const int old_vibration_strength = player.vibration_strength; player.vibration_enabled = true; - player.vibration_strength = vibration_strenght; + player.vibration_strength = vibration_strength; const Core::HID::VibrationValue vibration{ .low_amplitude = 1.0f, @@ -121,7 +121,7 @@ void ConfigureVibration::VibrateController(Core::HID::ControllerTriggerType type // Restore previous values player.vibration_enabled = old_vibration_enabled; - player.vibration_strength = old_vibration_strenght; + player.vibration_strength = old_vibration_strength; } void ConfigureVibration::StopVibrations() { diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 0783a2430..7049c57b6 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -127,7 +127,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons return list; } - if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) { + if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) { return list; } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 2bb1a0239..7e7d8e252 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -380,7 +380,6 @@ void GameList::UnloadController() { GameList::~GameList() { UnloadController(); - emit ShouldCancelWorker(); } void GameList::SetFilterFocus() { @@ -397,6 +396,10 @@ void GameList::ClearFilter() { search_field->clear(); } +void GameList::WorkerEvent() { + current_worker->ProcessEvents(this); +} + void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); tree_view->setExpanded( @@ -826,28 +829,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - // Before deleting rows, cancel the worker so that it is not using them - emit ShouldCancelWorker(); + // Cancel any existing worker. + current_worker.reset(); // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); search_field->clear(); - GameListWorker* worker = - new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); + current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list, + play_time_manager, system); - connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); - connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, - Qt::QueuedConnection); - connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, + // Get events from the worker as data becomes available + connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, Qt::QueuedConnection); - // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to - // cancel without delay. - connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, - Qt::DirectConnection); - QThreadPool::globalInstance()->start(worker); - current_worker = std::move(worker); + QThreadPool::globalInstance()->start(current_worker.get()); } void GameList::SaveInterfaceLayout() { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 712570cea..563a3a35b 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -109,7 +109,6 @@ signals: void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, StartGameType type, AmLaunchType launch_type); void GameChosen(const QString& game_path, const u64 title_id = 0); - void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target, const std::string& game_path); void OpenTransferableShaderCacheRequested(u64 program_id); @@ -138,11 +137,16 @@ private slots: void OnUpdateThemedIcons(); private: + friend class GameListWorker; + void WorkerEvent(); + void AddDirEntry(GameListDir* entry_items); void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); - void ValidateEntry(const QModelIndex& item); void DonePopulating(const QStringList& watch_list); +private: + void ValidateEntry(const QModelIndex& item); + void RefreshGameDirectory(); void ToggleFavorite(u64 program_id); @@ -165,7 +169,7 @@ private: QVBoxLayout* layout = nullptr; QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; - GameListWorker* current_worker = nullptr; + std::unique_ptr<GameListWorker> current_worker; QFileSystemWatcher* watcher = nullptr; ControllerNavigation* controller_navigation = nullptr; CompatibilityList compatibility_list; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 077ced12b..69be21027 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, const PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_) : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, - play_time_manager{play_time_manager_}, system{system_} {} + compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ + system_} { + // We want the game list to manage our lifetime. + setAutoDelete(false); +} + +GameListWorker::~GameListWorker() { + this->disconnect(); + stop_requested.store(true); + processing_completed.Wait(); +} + +void GameListWorker::ProcessEvents(GameList* game_list) { + while (true) { + std::function<void(GameList*)> func; + { + // Lock queue to protect concurrent modification. + std::scoped_lock lk(lock); + + // If we can't pop a function, return. + if (queued_events.empty()) { + return; + } + + // Pop a function. + func = std::move(queued_events.back()); + queued_events.pop_back(); + } + + // Run the function. + func(game_list); + } +} + +template <typename F> +void GameListWorker::RecordEvent(F&& func) { + { + // Lock queue to protect concurrent modification. + std::scoped_lock lk(lock); -GameListWorker::~GameListWorker() = default; + // Add the function into the front of the queue. + queued_events.emplace_front(std::move(func)); + } + + // Data now available. + emit DataAvailable(); +} void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { using namespace FileSys; @@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { GetMetadataFromControlNCA(patch, *control, icon, name); } - emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); + RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } @@ -360,11 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()}; - emit EntryReady(MakeGameListEntry(physical_name, name, - Common::FS::GetSize(physical_name), icon, - *loader, id, compatibility_list, - play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + id, compatibility_list, play_time_manager, patch); + + RecordEvent( + [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } else { std::vector<u8> icon; @@ -376,11 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; - emit EntryReady(MakeGameListEntry(physical_name, name, - Common::FS::GetSize(physical_name), icon, - *loader, program_id, compatibility_list, - play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); + + RecordEvent( + [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } } else if (is_dir) { @@ -399,25 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } void GameListWorker::run() { + watch_list.clear(); provider->ClearAllEntries(); + const auto DirEntryReady = [&](GameListDir* game_list_dir) { + RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); }); + }; + for (UISettings::GameDir& game_dir : game_dirs) { + if (stop_requested) { + break; + } + if (game_dir.path == QStringLiteral("SDMC")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else if (game_dir.path == QStringLiteral("UserNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else if (game_dir.path == QStringLiteral("SysNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else { watch_list.append(game_dir.path); auto* const game_list_dir = new GameListDir(game_dir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), game_dir.deep_scan, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), @@ -425,12 +479,6 @@ void GameListWorker::run() { } } - emit Finished(watch_list); + RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); }); processing_completed.Set(); } - -void GameListWorker::Cancel() { - this->disconnect(); - stop_requested.store(true); - processing_completed.Wait(); -} diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 54dc05e30..d5990fcde 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -4,6 +4,7 @@ #pragma once #include <atomic> +#include <deque> #include <memory> #include <string> @@ -20,6 +21,7 @@ namespace Core { class System; } +class GameList; class QStandardItem; namespace FileSys { @@ -46,24 +48,22 @@ public: /// Starts the processing of directory tree information. void run() override; - /// Tells the worker that it should no longer continue processing. Thread-safe. - void Cancel(); - -signals: +public: /** - * The `EntryReady` signal is emitted once an entry has been prepared and is ready - * to be added to the game list. - * @param entry_items a list with `QStandardItem`s that make up the columns of the new - * entry. + * Synchronously processes any events queued by the worker. + * + * AddDirEntry is called on the game list for every discovered directory. + * AddEntry is called on the game list for every discovered program. + * DonePopulating is called on the game list when processing completes. */ - void DirEntryReady(GameListDir* entry_items); - void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); + void ProcessEvents(GameList* game_list); - /** - * After the worker has traversed the game directory looking for entries, this signal is - * emitted with a list of folders that should be watched for changes as well. - */ - void Finished(QStringList watch_list); +signals: + void DataAvailable(); + +private: + template <typename F> + void RecordEvent(F&& func); private: void AddTitlesToGameList(GameListDir* parent_dir); @@ -84,8 +84,11 @@ private: QStringList watch_list; - Common::Event processing_completed; + std::mutex lock; + std::condition_variable cv; + std::deque<std::function<void(GameList*)>> queued_events; std::atomic_bool stop_requested = false; + Common::Event processing_completed; Core::System& system; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 1431cf2fe..9eafacea7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -159,8 +159,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/util/clickable_label.h" #include "yuzu/vk_device_info.h" -#ifdef YUZU_DBGHELP -#include "yuzu/mini_dump.h" +#ifdef YUZU_CRASH_DUMPS +#include "yuzu/breakpad.h" #endif using namespace Common::Literals; @@ -2019,7 +2019,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} .filename()); } - const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess(); + const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") .arg(QString::fromStdString(title_name), instruction_set_suffix) @@ -2906,7 +2906,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga const std::string game_file_name = std::filesystem::path(game_path).filename().string(); // Determine full paths for icon and shortcut -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) const char* home = std::getenv("HOME"); const std::filesystem::path home_path = (home == nullptr ? "~" : home); const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); @@ -2963,7 +2963,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga QImage icon_data = QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) // Convert and write the icon as a PNG if (!icon_data.save(QString::fromStdString(icon_path.string()))) { LOG_ERROR(Frontend, "Could not write icon as PNG to file"); @@ -4002,7 +4002,7 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st const std::string& comment, const std::string& icon_path, const std::string& command, const std::string& arguments, const std::string& categories, const std::string& keywords) { -#if defined(__linux__) || defined(__FreeBSD__) +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) // This desktop file template was writing referencing // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html std::string shortcut_contents{}; @@ -5187,22 +5187,15 @@ int main(int argc, char* argv[]) { return 0; } -#ifdef YUZU_DBGHELP - PROCESS_INFORMATION pi; - if (!is_child && Settings::values.create_crash_dumps.GetValue() && - MiniDump::SpawnDebuggee(argv[0], pi)) { - // Delete the config object so that it doesn't save when the program exits - config.reset(nullptr); - MiniDump::DebugDebuggee(pi); - return 0; - } -#endif - if (StartupChecks(argv[0], &has_broken_vulkan, Settings::values.perform_vulkan_check.GetValue())) { return 0; } +#ifdef YUZU_CRASH_DUMPS + Breakpad::InstallCrashHandler(); +#endif + Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp deleted file mode 100644 index a34dc6a9c..000000000 --- a/src/yuzu/mini_dump.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-FileCopyrightText: 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <cstdio> -#include <cstring> -#include <ctime> -#include <filesystem> -#include <fmt/format.h> -#include <windows.h> -#include "yuzu/mini_dump.h" -#include "yuzu/startup_checks.h" - -// dbghelp.h must be included after windows.h -#include <dbghelp.h> - -namespace MiniDump { - -void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, - EXCEPTION_POINTERS* pep) { - char file_name[255]; - const std::time_t the_time = std::time(nullptr); - std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); - - // Open the file - HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { - fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); - return; - } - - // Create the minidump - const MINIDUMP_TYPE dump_type = MiniDumpNormal; - - const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, - dump_type, (pep != 0) ? info : 0, 0, 0); - - if (write_dump_status) { - fmt::print(stderr, "MiniDump created: {}", file_name); - } else { - fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); - } - - // Close the file - CloseHandle(file_handle); -} - -void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { - EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - - HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); - if (thread_handle == nullptr) { - fmt::print(stderr, "OpenThread failed ({})", GetLastError()); - return; - } - - // Get child process context - CONTEXT context = {}; - context.ContextFlags = CONTEXT_ALL; - if (!GetThreadContext(thread_handle, &context)) { - fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); - return; - } - - // Create exception pointers for minidump - EXCEPTION_POINTERS ep; - ep.ExceptionRecord = &record; - ep.ContextRecord = &context; - - MINIDUMP_EXCEPTION_INFORMATION info; - info.ThreadId = deb_ev.dwThreadId; - info.ExceptionPointers = &ep; - info.ClientPointers = false; - - CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); - - if (CloseHandle(thread_handle) == 0) { - fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); - } -} - -bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { - std::memset(&pi, 0, sizeof(pi)); - - // Don't debug if we are already being debugged - if (IsDebuggerPresent()) { - return false; - } - - if (!SpawnChild(arg0, &pi, 0)) { - fmt::print(stderr, "warning: continuing without crash dumps"); - return false; - } - - const bool can_debug = DebugActiveProcess(pi.dwProcessId); - if (!can_debug) { - fmt::print(stderr, - "warning: DebugActiveProcess failed ({}), continuing without crash dumps", - GetLastError()); - return false; - } - - return true; -} - -static const char* ExceptionName(DWORD exception) { - switch (exception) { - case EXCEPTION_ACCESS_VIOLATION: - return "EXCEPTION_ACCESS_VIOLATION"; - case EXCEPTION_DATATYPE_MISALIGNMENT: - return "EXCEPTION_DATATYPE_MISALIGNMENT"; - case EXCEPTION_BREAKPOINT: - return "EXCEPTION_BREAKPOINT"; - case EXCEPTION_SINGLE_STEP: - return "EXCEPTION_SINGLE_STEP"; - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; - case EXCEPTION_FLT_DENORMAL_OPERAND: - return "EXCEPTION_FLT_DENORMAL_OPERAND"; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; - case EXCEPTION_FLT_INEXACT_RESULT: - return "EXCEPTION_FLT_INEXACT_RESULT"; - case EXCEPTION_FLT_INVALID_OPERATION: - return "EXCEPTION_FLT_INVALID_OPERATION"; - case EXCEPTION_FLT_OVERFLOW: - return "EXCEPTION_FLT_OVERFLOW"; - case EXCEPTION_FLT_STACK_CHECK: - return "EXCEPTION_FLT_STACK_CHECK"; - case EXCEPTION_FLT_UNDERFLOW: - return "EXCEPTION_FLT_UNDERFLOW"; - case EXCEPTION_INT_DIVIDE_BY_ZERO: - return "EXCEPTION_INT_DIVIDE_BY_ZERO"; - case EXCEPTION_INT_OVERFLOW: - return "EXCEPTION_INT_OVERFLOW"; - case EXCEPTION_PRIV_INSTRUCTION: - return "EXCEPTION_PRIV_INSTRUCTION"; - case EXCEPTION_IN_PAGE_ERROR: - return "EXCEPTION_IN_PAGE_ERROR"; - case EXCEPTION_ILLEGAL_INSTRUCTION: - return "EXCEPTION_ILLEGAL_INSTRUCTION"; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; - case EXCEPTION_STACK_OVERFLOW: - return "EXCEPTION_STACK_OVERFLOW"; - case EXCEPTION_INVALID_DISPOSITION: - return "EXCEPTION_INVALID_DISPOSITION"; - case EXCEPTION_GUARD_PAGE: - return "EXCEPTION_GUARD_PAGE"; - case EXCEPTION_INVALID_HANDLE: - return "EXCEPTION_INVALID_HANDLE"; - default: - return "unknown exception type"; - } -} - -void DebugDebuggee(PROCESS_INFORMATION& pi) { - DEBUG_EVENT deb_ev = {}; - - while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { - const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); - if (!wait_success) { - fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError()); - return; - } - - switch (deb_ev.dwDebugEventCode) { - case OUTPUT_DEBUG_STRING_EVENT: - case CREATE_PROCESS_DEBUG_EVENT: - case CREATE_THREAD_DEBUG_EVENT: - case EXIT_PROCESS_DEBUG_EVENT: - case EXIT_THREAD_DEBUG_EVENT: - case LOAD_DLL_DEBUG_EVENT: - case RIP_EVENT: - case UNLOAD_DLL_DEBUG_EVENT: - // Continue on all other debug events - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); - break; - case EXCEPTION_DEBUG_EVENT: - EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - - // We want to generate a crash dump if we are seeing the same exception again. - if (!deb_ev.u.Exception.dwFirstChance) { - fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n", - record.ExceptionCode, ExceptionName(record.ExceptionCode)); - DumpFromDebugEvent(deb_ev, pi); - } - - // Continue without handling the exception. - // Lets the debuggee use its own exception handler. - // - If one does not exist, we will see the exception once more where we make a minidump - // for. Then when it reaches here again, yuzu will probably crash. - // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an - // infinite loop of exceptions. - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); - break; - } - } -} - -} // namespace MiniDump diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h deleted file mode 100644 index d6b6cca84..000000000 --- a/src/yuzu/mini_dump.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <windows.h> - -#include <dbghelp.h> - -namespace MiniDump { - -void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, - EXCEPTION_POINTERS* pep); - -void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); -bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); -void DebugDebuggee(PROCESS_INFORMATION& pi); - -} // namespace MiniDump |