Compare commits

...

53 Commits

Author SHA1 Message Date
yuzubot
7b098dc0ac Android #71 2023-09-15 00:57:25 +00:00
Charles Lombardo
0c55248f92 Merge pull request #11503 from t895/stateflow-patch
android: Delay collecting UI state in games fragment
2023-09-14 20:23:49 -04:00
Charles Lombardo
b394389170 android: Delay collecting UI state in games fragment 2023-09-14 20:02:48 -04:00
Charles Lombardo
5eceab3ce6 Merge pull request #11425 from t895/stateflows
android: Use StateFlow instead of LiveData
2023-09-14 16:54:21 -04:00
Charles Lombardo
8baed5d95d android: Refactor menu tags to enum 2023-09-14 15:18:56 -04:00
Charles Lombardo
4a3cbf0021 android: Use StateFlow instead of LiveData 2023-09-14 15:18:56 -04:00
liamwhite
04352a9aef Merge pull request #11496 from liamwhite/ngc
ngc: implement service
2023-09-14 09:24:46 -04:00
liamwhite
48dec7e0c9 Merge pull request #11479 from Kelebek1/check_all_fbs
Look for the most recently modified image for present
2023-09-14 09:24:27 -04:00
liamwhite
b5f99164f1 Merge pull request #11433 from liamwhite/shutdown-oopsie
polyfill_thread: ensure mutex was locked before signaling stop
2023-09-14 09:24:20 -04:00
liamwhite
eb4ddb2868 shader_recompiler: skip sampler for buffer textures (#11435) 2023-09-14 15:23:50 +02:00
Liam
9d7eebde7b ngc: implement service 2023-09-14 09:14:08 -04:00
liamwhite
8fb9f78e83 Merge pull request #11385 from liamwhite/acceptcancel
internal_network: cancel pending socket operations on application process termination
2023-09-13 15:40:58 -04:00
liamwhite
3f52b5167b Merge pull request #11486 from liamwhite/system-verification
qt: add verification for installed contents
2023-09-13 09:39:27 -04:00
liamwhite
5b5c69b8f6 Merge pull request #11480 from german77/mii_service
service: mii: Update implementation Part1
2023-09-13 09:39:16 -04:00
liamwhite
9a0ea90018 Merge pull request #11473 from liamwhite/fix-launch-param
am: Implement UserChannel parameters
2023-09-13 09:39:06 -04:00
Liam
f8985d1cc5 qt: add verification for installed contents 2023-09-12 09:20:50 -04:00
liamwhite
ce5320c49f Merge pull request #11447 from xcfrg/portable-compile-out
common: add a compile time option to allow disabling portable mode
2023-09-12 09:17:50 -04:00
Narr the Reg
4d138b760b service: mii: Remove most magic values 2023-09-11 22:07:55 -06:00
liamwhite
66f2947854 ci: fix msvc when used with LTO (#11459) 2023-09-11 23:25:21 +02:00
german77
ec25f847d8 mii: service: Address review 2023-09-11 09:54:32 -06:00
german77
bd169f417f mii: Prepare Interface for new implementation 2023-09-11 00:58:46 -06:00
german77
571399930c service: mii: Fix ver3 inconsistencies 2023-09-11 00:23:46 -06:00
german77
36290f9a0a service: mii: move char info operations 2023-09-10 23:18:08 -06:00
german77
d6037efe5e service: mii: Move store data operations 2023-09-10 23:18:03 -06:00
german77
81f50d5132 service: mii: Move core data operations 2023-09-10 22:52:33 -06:00
german77
8d7d62dc24 service: mii: Move ver3 operations 2023-09-10 22:42:38 -06:00
german77
27929d7ca2 service: mii: separate mii types into their own file 2023-09-10 22:18:25 -06:00
german77
63b239f5c6 service: mii: Move all raw data to it's file 2023-09-10 22:14:37 -06:00
german77
0cdc8b13b7 service: mii: Add mii util and result 2023-09-10 20:43:26 -06:00
Kelebek1
baad1238c3 Look for the most recently modified image for present 2023-09-11 03:11:29 +01:00
FearlessTobi
87c0ba129c am: Implement UserChannel parameters
Used by the Super Mairo 3D All-Stars collection.
2023-09-10 15:39:25 -04:00
liamwhite
eb9e847380 Merge pull request #11450 from lat9nq/no-vk-device-fix
configure_graphics: Fix handling of broken Vulkan
2023-09-10 13:41:10 -04:00
liamwhite
5b8fdedf4d Merge pull request #11436 from liamwhite/bad-format
shader_recompiler: always declare image format for image buffers
2023-09-10 13:40:47 -04:00
liamwhite
64130d9f01 Merge pull request #11456 from liamwhite/worse-integrity-verification
core: implement basic integrity verification
2023-09-10 13:40:39 -04:00
liamwhite
3df56dc790 Merge pull request #11465 from Kelebek1/skip_remaining_reset
[Audio] Do not reset the remaining command count each time
2023-09-10 13:40:32 -04:00
liamwhite
3b7d112c83 Merge pull request #11467 from Kelebek1/fix_decode
[audio] Fix data source version 1 command looping
2023-09-10 13:40:25 -04:00
liamwhite
b011ce023d Merge pull request #11470 from GPUCode/bundle-vvl
android: Add option to bundle validation layer
2023-09-10 13:40:18 -04:00
FearlessTobi
36917d8a8f am: Remove bcat from PopLaunchParameter
This never belonged here and has no use anymore since the Boxcat backend was removed.

.
2023-09-09 20:44:05 -04:00
GPUCode
24ab10c2f6 vk_buffer_cache: Respect max vertex bindings in BindVertexBuffers (#11471) 2023-09-10 02:19:45 +02:00
GPUCode
cad28abe61 renderer_vulkan: Remove debug report
* VVL has implemented the more modern alternative, thus we don't need to support it anymore
2023-09-08 23:28:46 +03:00
GPUCode
254b2bd9df cmake: Add option to fetch validation layer binary on android 2023-09-08 23:13:52 +03:00
Liam
7bec8d1c5b internal_network: log error on interrupt pipe read failure 2023-09-08 14:00:07 -04:00
Kelebek1
800d6f7d0d Fix data source version 1 command looping 2023-09-08 15:03:21 +01:00
Kelebek1
4baaaf6a99 Do not reset the command buffer command count each time 2023-09-07 20:53:48 +01:00
xcfrg
a02d641042 add a compile time option to allow disabling portable mode 2023-09-06 18:53:39 -04:00
Liam
716e0a126a core: implement basic integrity verification 2023-09-06 16:49:27 -04:00
lat9nq
d8943e5bac yuzu-qt: Use Null when OpenGL is not compiled 2023-09-05 17:59:44 -04:00
lat9nq
e4ebabcd5b yuzu-qt: Update API Text for broken Vulkan
Otherwise caused a blue Vulkan badge to appear in the status bar.
2023-09-05 17:59:10 -04:00
lat9nq
d078cff269 configure_graphics: Capture by reference
Small optimization.
2023-09-05 17:50:55 -04:00
lat9nq
ea46efd9a2 configure_graphics: Fix handling of broken Vulkan
The VSync combobox wouldn't populate if there was no Vulkan device,
which caused issues with trying to set VSync on other backends.

This also adds another layer to GetCurrentGraphicsBackend to check for
broken Vulkan and return OpenGL instead of Vulkan.
2023-09-04 20:21:14 -04:00
Liam
ba4b65e4bc shader_recompiler: always declare image format for image buffers 2023-09-02 17:25:00 -04:00
Liam
bdd09d6844 polyfill_thread: ensure mutex was locked before signaling stop 2023-09-02 11:51:40 -04:00
Liam
531572b411 internal_network: cancel pending socket operations on application process termination 2023-08-26 18:19:51 -04:00
103 changed files with 5768 additions and 2524 deletions

View File

@@ -49,6 +49,8 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
@@ -61,6 +63,8 @@ option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF) option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
option(YUZU_ENABLE_PORTABLE "Allow yuzu to enable portable mode if a user folder is found in the CWD" ON)
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF) CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
@@ -77,6 +81,24 @@ if (ANDROID OR WIN32 OR APPLE)
endif() endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
set(vvl_version "sdk-1.3.261.1")
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
if (NOT EXISTS "${vvl_zip_file}")
# Download and extract validation layer release to externals directory
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version}-android.zip"
"${vvl_zip_file}" SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif()
# Copy the arm64 binary to src/android/app/main/jniLibs
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
DESTINATION "${vvl_lib_path}")
endif()
# On Android, fetch and compile libcxx before doing anything else # On Android, fetch and compile libcxx before doing anything else
if (ANDROID) if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON) set(CMAKE_SKIP_INSTALL_RULES ON)

View File

@@ -1,3 +1,11 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
End of merge log. You can find the original README.md below the break.
-----
<!-- <!--
SPDX-FileCopyrightText: 2018 yuzu Emulator Project SPDX-FileCopyrightText: 2018 yuzu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later SPDX-License-Identifier: GPL-2.0-or-later

View File

@@ -85,6 +85,7 @@ if (MSVC)
/wd4100 # 'identifier': unreferenced formal parameter /wd4100 # 'identifier': unreferenced formal parameter
/wd4324 # 'struct_name': structure was padded due to __declspec(align()) /wd4324 # 'struct_name': structure was padded due to __declspec(align())
/wd4201 # nonstandard extension used : nameless struct/union /wd4201 # nonstandard extension used : nameless struct/union
/wd4702 # unreachable code (when used with LTO)
) )
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)

View File

@@ -10,8 +10,12 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
binding.optionIcon.alpha = 0.5f binding.optionIcon.alpha = 0.5f
} }
option.details.observe(viewLifecycle) { updateOptionDetails(it) } viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
option.details.collect { updateOptionDetails(it) }
}
}
binding.optionDetail.postDelayed( binding.optionDetail.postDelayed(
{ {
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE

View File

@@ -80,6 +80,17 @@ object Settings {
const val SECTION_THEME = "Theme" const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug" const val SECTION_DEBUG = "Debug"
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_GENERAL(R.string.preferences_general),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_CPU(R.string.cpu),
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
const val PREF_OVERLAY_VERSION = "OverlayVersion" const val PREF_OVERLAY_VERSION = "OverlayVersion"

View File

@@ -3,10 +3,12 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.Settings
class SubmenuSetting( class SubmenuSetting(
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,
val menuKey: String val menuKey: Settings.MenuTag
) : SettingsItem(emptySetting, titleId, descriptionId) { ) : SettingsItem(emptySetting, titleId, descriptionId) {
override val type = TYPE_SUBMENU override val type = TYPE_SUBMENU
} }

View File

@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
) )
} }
settingsViewModel.shouldRecreate.observe(this) { lifecycleScope.apply {
if (it) { launch {
settingsViewModel.setShouldRecreate(false) repeatOnLifecycle(Lifecycle.State.CREATED) {
recreate() settingsViewModel.shouldRecreate.collectLatest {
if (it) {
settingsViewModel.setShouldRecreate(false)
recreate()
}
}
}
} }
} launch {
settingsViewModel.shouldNavigateBack.observe(this) { repeatOnLifecycle(Lifecycle.State.CREATED) {
if (it) { settingsViewModel.shouldNavigateBack.collectLatest {
settingsViewModel.setShouldNavigateBack(false) if (it) {
navigateBack() settingsViewModel.setShouldNavigateBack(false)
navigateBack()
}
}
}
} }
} launch {
settingsViewModel.shouldShowResetSettingsDialog.observe(this) { repeatOnLifecycle(Lifecycle.State.CREATED) {
if (it) { settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
settingsViewModel.setShouldShowResetSettingsDialog(false) if (it) {
ResetSettingsDialogFragment().show( settingsViewModel.setShouldShowResetSettingsDialog(false)
supportFragmentManager, ResetSettingsDialogFragment().show(
ResetSettingsDialogFragment.TAG supportFragmentManager,
) ResetSettingsDialogFragment.TAG
)
}
}
}
} }
} }

View File

@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.model.SettingsViewModel
class SettingsFragment : Fragment() { class SettingsFragment : Fragment() {
@@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireContext()) settingsAdapter = SettingsAdapter(this, requireContext())
presenter = SettingsFragmentPresenter( presenter = SettingsFragmentPresenter(
settingsViewModel, settingsViewModel,
settingsAdapter!!, settingsAdapter!!,
args.menuTag, args.menuTag
args.game?.gameId ?: ""
) )
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
val dividerDecoration = MaterialDividerItemDecoration( val dividerDecoration = MaterialDividerItemDecoration(
requireContext(), requireContext(),
LinearLayoutManager.VERTICAL LinearLayoutManager.VERTICAL
@@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
settingsViewModel.setShouldNavigateBack(true) settingsViewModel.setShouldNavigateBack(true)
} }
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.apply {
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it launch {
} repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.shouldReloadSettingsList.collectLatest {
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { if (it) {
if (it) { settingsViewModel.setShouldReloadSettingsList(false)
settingsViewModel.setShouldReloadSettingsList(false) presenter.loadSettingsList()
presenter.loadSettingsList() }
}
}
}
launch {
settingsViewModel.isUsingSearch.collectLatest {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
} }
} }
settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
if (it) {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
} else {
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
}
}
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
binding.toolbarSettings.inflateMenu(R.menu.menu_settings) binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
binding.toolbarSettings.setOnMenuItemClickListener { binding.toolbarSettings.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {

View File

@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.SettingsViewModel import org.yuzu.yuzu_emu.model.SettingsViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
class SettingsFragmentPresenter( class SettingsFragmentPresenter(
private val settingsViewModel: SettingsViewModel, private val settingsViewModel: SettingsViewModel,
private val adapter: SettingsAdapter, private val adapter: SettingsAdapter,
private var menuTag: String, private var menuTag: Settings.MenuTag
private var gameId: String
) { ) {
private var settingsList = ArrayList<SettingsItem>() private var settingsList = ArrayList<SettingsItem>()
@@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
} }
fun loadSettingsList() { fun loadSettingsList() {
if (!TextUtils.isEmpty(gameId)) {
settingsViewModel.setToolbarTitle(
context.getString(
R.string.advanced_settings_game,
gameId
)
)
}
val sl = ArrayList<SettingsItem>() val sl = ArrayList<SettingsItem>()
when (menuTag) { when (menuTag) {
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
Settings.SECTION_GENERAL -> addGeneralSettings(sl) Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
Settings.SECTION_SYSTEM -> addSystemSettings(sl) Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl) Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl) Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl) Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl) Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
else -> { else -> {
val context = YuzuApplication.appContext val context = YuzuApplication.appContext
Toast.makeText( Toast.makeText(
@@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
} }
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
sl.apply { sl.apply {
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
add( add(
RunnableSetting(R.string.reset_to_default, 0, false) { RunnableSetting(R.string.reset_to_default, 0, false) {
settingsViewModel.setShouldShowResetSettingsDialog(true) settingsViewModel.setShouldShowResetSettingsDialog(true)
@@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
} }
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
sl.apply { sl.apply {
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
add(ShortSetting.RENDERER_SPEED_LIMIT.key) add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
} }
private fun addSystemSettings(sl: ArrayList<SettingsItem>) { private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
sl.apply { sl.apply {
add(BooleanSetting.USE_DOCKED_MODE.key) add(BooleanSetting.USE_DOCKED_MODE.key)
add(IntSetting.REGION_INDEX.key) add(IntSetting.REGION_INDEX.key)
@@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
} }
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
sl.apply { sl.apply {
add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_RESOLUTION.key)
@@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
} }
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
sl.apply { sl.apply {
add(IntSetting.AUDIO_OUTPUT_ENGINE.key) add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
add(ByteSetting.AUDIO_VOLUME.key) add(ByteSetting.AUDIO_VOLUME.key)
@@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
} }
private fun addThemeSettings(sl: ArrayList<SettingsItem>) { private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
sl.apply { sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting { val theme: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int override val int: Int
@@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
} }
private fun addDebugSettings(sl: ArrayList<SettingsItem>) { private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
sl.apply { sl.apply {
add(HeaderSetting(R.string.gpu)) add(HeaderSetting(R.string.gpu))
add(IntSetting.RENDERER_BACKEND.key) add(IntSetting.RENDERER_BACKEND.key)

View File

@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
@@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.surfaceEmulation.holder.addCallback(this) binding.surfaceEmulation.holder.addCallback(this)
binding.showFpsText.setTextColor(Color.YELLOW) binding.showFpsText.setTextColor(Color.YELLOW)
@@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_settings -> { R.id.menu_settings -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
SettingsFile.FILE_NAME_CONFIG Settings.MenuTag.SECTION_ROOT
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
true true
@@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
) )
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
.windowLayoutInfo(requireActivity())
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
}
}
GameIconUtils.loadGameIcon(game, binding.loadingImage) GameIconUtils.loadGameIcon(game, binding.loadingImage)
binding.loadingTitle.text = game.title binding.loadingTitle.text = game.title
binding.loadingTitle.isSelected = true binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true binding.loadingText.isSelected = true
emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.apply {
if (it > 0 && it != emulationViewModel.totalShaders.value!!) { launch {
binding.loadingProgressIndicator.isIndeterminate = false repeatOnLifecycle(Lifecycle.State.STARTED) {
WindowInfoTracker.getOrCreate(requireContext())
if (it < binding.loadingProgressIndicator.max) { .windowLayoutInfo(requireActivity())
binding.loadingProgressIndicator.progress = it .collect {
updateFoldableLayout(requireActivity() as EmulationActivity, it)
}
} }
} }
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.shaderProgress.collectLatest {
if (it > 0 && it != emulationViewModel.totalShaders.value) {
binding.loadingProgressIndicator.isIndeterminate = false
if (it == emulationViewModel.totalShaders.value!!) { if (it < binding.loadingProgressIndicator.max) {
binding.loadingText.setText(R.string.loading) binding.loadingProgressIndicator.progress = it
binding.loadingProgressIndicator.isIndeterminate = true }
}
if (it == emulationViewModel.totalShaders.value) {
binding.loadingText.setText(R.string.loading)
binding.loadingProgressIndicator.isIndeterminate = true
}
}
}
} }
} launch {
emulationViewModel.totalShaders.observe(viewLifecycleOwner) { repeatOnLifecycle(Lifecycle.State.CREATED) {
binding.loadingProgressIndicator.max = it emulationViewModel.totalShaders.collectLatest {
} binding.loadingProgressIndicator.max = it
emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { }
if (it.isNotEmpty()) { }
binding.loadingText.text = it
} }
} launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> emulationViewModel.shaderMessage.collectLatest {
if (started) { if (it.isNotEmpty()) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) binding.loadingText.text = it
ViewUtils.showView(binding.surfaceInputOverlay) }
ViewUtils.hideView(binding.loadingIndicator) }
}
// Setup overlay
updateShowFpsOverlay()
} }
} launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.emulationStarted.collectLatest {
if (it) {
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
ViewUtils.showView(binding.surfaceInputOverlay)
ViewUtils.hideView(binding.loadingIndicator)
emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { // Setup overlay
if (it) { updateShowFpsOverlay()
binding.loadingText.setText(R.string.shutting_down) }
ViewUtils.showView(binding.loadingIndicator) }
ViewUtils.hideView(binding.inputContainer) }
ViewUtils.hideView(binding.showFpsText) }
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
emulationViewModel.isEmulationStopping.collectLatest {
if (it) {
binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer)
ViewUtils.hideView(binding.showFpsText)
}
}
}
} }
} }
} }
@@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
} else { } else {
if (EmulationMenuSettings.showOverlay && if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
emulationViewModel.emulationStarted.value == true
) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE binding.surfaceInputOverlay.visibility = View.VISIBLE
} }

View File

@@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
{ {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
SettingsFile.FILE_NAME_CONFIG Settings.MenuTag.SECTION_ROOT
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
} }
@@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
{ {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
Settings.SECTION_THEME Settings.MenuTag.SECTION_THEME
) )
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
} }

View File

@@ -9,8 +9,12 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
.create() .create()
dialog.setCanceledOnTouchOutside(false) dialog.setCanceledOnTouchOutside(false)
taskViewModel.isComplete.observe(this) { complete -> viewLifecycleOwner.lifecycleScope.launch {
if (complete) { repeatOnLifecycle(Lifecycle.State.CREATED) {
dialog.dismiss() taskViewModel.isComplete.collect {
when (val result = taskViewModel.result.value) { if (it) {
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() dialog.dismiss()
is MessageDialogFragment -> result.show( when (val result = taskViewModel.result.value) {
requireActivity().supportFragmentManager, is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
MessageDialogFragment.TAG .show()
)
is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
}
taskViewModel.clear()
}
} }
taskViewModel.clear()
} }
} }
if (taskViewModel.isRunning.value == false) { if (!taskViewModel.isRunning.value) {
taskViewModel.runTask() taskViewModel.runTask()
} }
return dialog return dialog

View File

@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false) homeViewModel.setNavigationVisibility(visible = true, animated = false)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
filterAndSearch() filterAndSearch()
} }
gamesViewModel.apply { viewLifecycleOwner.lifecycleScope.apply {
searchFocused.observe(viewLifecycleOwner) { searchFocused -> launch {
if (searchFocused) { repeatOnLifecycle(Lifecycle.State.CREATED) {
focusSearch() gamesViewModel.searchFocused.collect {
gamesViewModel.setSearchFocused(false) if (it) {
focusSearch()
gamesViewModel.setSearchFocused(false)
}
}
} }
} }
launch {
games.observe(viewLifecycleOwner) { filterAndSearch() } repeatOnLifecycle(Lifecycle.State.CREATED) {
searchedGames.observe(viewLifecycleOwner) { gamesViewModel.games.collect { filterAndSearch() }
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it) }
if (it.isEmpty()) { }
binding.noResultsView.visibility = View.VISIBLE launch {
} else { repeatOnLifecycle(Lifecycle.State.CREATED) {
binding.noResultsView.visibility = View.GONE gamesViewModel.searchedGames.collect {
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noResultsView.visibility = View.VISIBLE
} else {
binding.noResultsView.visibility = View.GONE
}
}
} }
} }
} }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
private inner class ScoredGame(val score: Double, val item: Game) private inner class ScoredGame(val score: Double, val item: Game)
private fun filterAndSearch() { private fun filterAndSearch() {
val baseList = gamesViewModel.games.value!! val baseList = gamesViewModel.games.value
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
R.id.chip_recently_played -> { R.id.chip_recently_played -> {
baseList.filter { baseList.filter {

View File

@@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.divider.MaterialDividerItemDecoration import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import info.debatty.java.stringsimilarity.Cosine import info.debatty.java.stringsimilarity.Cosine
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
search() search()
binding.settingsList.smoothScrollToPosition(0) binding.settingsList.smoothScrollToPosition(0)
} }
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
if (it) { repeatOnLifecycle(Lifecycle.State.CREATED) {
settingsViewModel.setShouldReloadSettingsList(false) settingsViewModel.shouldReloadSettingsList.collect {
search() if (it) {
settingsViewModel.setShouldReloadSettingsList(false)
search()
}
}
} }
} }

View File

@@ -22,10 +22,14 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
) )
} }
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
if (it) { repeatOnLifecycle(Lifecycle.State.CREATED) {
pageForward() homeViewModel.shouldPageForward.collect {
homeViewModel.setShouldPageForward(false) if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
} }
} }

View File

@@ -3,28 +3,28 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class EmulationViewModel : ViewModel() { class EmulationViewModel : ViewModel() {
private val _emulationStarted = MutableLiveData(false) val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
val emulationStarted: LiveData<Boolean> get() = _emulationStarted private val _emulationStarted = MutableStateFlow(false)
private val _isEmulationStopping = MutableLiveData(false) val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping private val _isEmulationStopping = MutableStateFlow(false)
private val _shaderProgress = MutableLiveData(0) val shaderProgress: StateFlow<Int> get() = _shaderProgress
val shaderProgress: LiveData<Int> get() = _shaderProgress private val _shaderProgress = MutableStateFlow(0)
private val _totalShaders = MutableLiveData(0) val totalShaders: StateFlow<Int> get() = _totalShaders
val totalShaders: LiveData<Int> get() = _totalShaders private val _totalShaders = MutableStateFlow(0)
private val _shaderMessage = MutableLiveData("") val shaderMessage: StateFlow<String> get() = _shaderMessage
val shaderMessage: LiveData<String> get() = _shaderMessage private val _shaderMessage = MutableStateFlow("")
fun setEmulationStarted(started: Boolean) { fun setEmulationStarted(started: Boolean) {
_emulationStarted.postValue(started) _emulationStarted.value = started
} }
fun setIsEmulationStopping(value: Boolean) { fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
} }
fun clear() { fun clear() {
_emulationStarted.value = false setEmulationStarted(false)
_isEmulationStopping.value = false setIsEmulationStopping(false)
_shaderProgress.value = 0 setShaderProgress(0)
_totalShaders.value = 0 setTotalShaders(0)
_shaderMessage.value = "" setShaderMessage("")
}
companion object {
const val KEY_EMULATION_STARTED = "EmulationStarted"
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
const val KEY_SHADER_PROGRESS = "ShaderProgress"
const val KEY_TOTAL_SHADERS = "TotalShaders"
const val KEY_SHADER_MESSAGE = "ShaderMessage"
} }
} }

View File

@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.Locale import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList()) val games: StateFlow<List<Game>> get() = _games
val games: LiveData<List<Game>> get() = _games private val _games = MutableStateFlow(emptyList<Game>())
private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) val searchedGames: StateFlow<List<Game>> get() = _searchedGames
val searchedGames: LiveData<List<Game>> get() = _searchedGames private val _searchedGames = MutableStateFlow(emptyList<Game>())
private val _isReloading = MutableLiveData(false) val isReloading: StateFlow<Boolean> get() = _isReloading
val isReloading: LiveData<Boolean> get() = _isReloading private val _isReloading = MutableStateFlow(false)
private val _shouldSwapData = MutableLiveData(false) val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData private val _shouldSwapData = MutableStateFlow(false)
private val _shouldScrollToTop = MutableLiveData(false) val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop private val _shouldScrollToTop = MutableStateFlow(false)
private val _searchFocused = MutableLiveData(false) val searchFocused: StateFlow<Boolean> get() = _searchFocused
val searchFocused: LiveData<Boolean> get() = _searchFocused private val _searchFocused = MutableStateFlow(false)
init { init {
// Ensure keys are loaded so that ROM metadata can be decrypted. // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
) )
) )
_games.postValue(sortedList) _games.value = sortedList
} }
fun setSearchedGames(games: List<Game>) { fun setSearchedGames(games: List<Game>) {
_searchedGames.postValue(games) _searchedGames.value = games
} }
fun setShouldSwapData(shouldSwap: Boolean) { fun setShouldSwapData(shouldSwap: Boolean) {
_shouldSwapData.postValue(shouldSwap) _shouldSwapData.value = shouldSwap
} }
fun setShouldScrollToTop(shouldScroll: Boolean) { fun setShouldScrollToTop(shouldScroll: Boolean) {
_shouldScrollToTop.postValue(shouldScroll) _shouldScrollToTop.value = shouldScroll
} }
fun setSearchFocused(searchFocused: Boolean) { fun setSearchFocused(searchFocused: Boolean) {
_searchFocused.postValue(searchFocused) _searchFocused.value = searchFocused
} }
fun reloadGames(directoryChanged: Boolean) { fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value == true) { if (isReloading.value) {
return return
} }
_isReloading.postValue(true) _isReloading.value = true
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
NativeLibrary.resetRomMetadata() NativeLibrary.resetRomMetadata()
setGames(GameHelper.getGames()) setGames(GameHelper.getGames())
_isReloading.postValue(false) _isReloading.value = false
if (directoryChanged) { if (directoryChanged) {
setShouldSwapData(true) setShouldSwapData(true)

View File

@@ -3,8 +3,8 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData import kotlinx.coroutines.flow.MutableStateFlow
import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.flow.StateFlow
data class HomeSetting( data class HomeSetting(
val titleId: Int, val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
val isEnabled: () -> Boolean = { true }, val isEnabled: () -> Boolean = { true },
val disabledTitleId: Int = 0, val disabledTitleId: Int = 0,
val disabledMessageId: Int = 0, val disabledMessageId: Int = 0,
val details: LiveData<String> = MutableLiveData("") val details: StateFlow<String> = MutableStateFlow("")
) )

View File

@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
import android.net.Uri import android.net.Uri
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible private val _navigationVisible = MutableStateFlow(Pair(false, false))
private val _statusBarShadeVisible = MutableLiveData(true) val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible private val _statusBarShadeVisible = MutableStateFlow(true)
private val _shouldPageForward = MutableLiveData(false) val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward private val _shouldPageForward = MutableStateFlow(false)
private val _gamesDir = MutableLiveData( val gamesDir: StateFlow<String> get() = _gamesDir
private val _gamesDir = MutableStateFlow(
Uri.parse( Uri.parse(
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getString(GameHelper.KEY_GAME_PATH, "") .getString(GameHelper.KEY_GAME_PATH, "")
).path ?: "" ).path ?: ""
) )
val gamesDir: LiveData<String> get() = _gamesDir
var navigatedToSetup = false var navigatedToSetup = false
init {
_navigationVisible.value = Pair(false, false)
}
fun setNavigationVisibility(visible: Boolean, animated: Boolean) { fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
if (_navigationVisible.value?.first == visible) { if (navigationVisible.value.first == visible) {
return return
} }
_navigationVisible.value = Pair(visible, animated) _navigationVisible.value = Pair(visible, animated)
} }
fun setStatusBarShadeVisibility(visible: Boolean) { fun setStatusBarShadeVisibility(visible: Boolean) {
if (_statusBarShadeVisible.value == visible) { if (statusBarShadeVisible.value == visible) {
return return
} }
_statusBarShadeVisible.value = visible _statusBarShadeVisible.value = visible

View File

@@ -3,48 +3,43 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { class SettingsViewModel : ViewModel() {
var game: Game? = null var game: Game? = null
var shouldSave = false var shouldSave = false
var clickedItem: SettingsItem? = null var clickedItem: SettingsItem? = null
private val _toolbarTitle = MutableLiveData("") val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
val toolbarTitle: LiveData<String> get() = _toolbarTitle private val _shouldRecreate = MutableStateFlow(false)
private val _shouldRecreate = MutableLiveData(false) val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate private val _shouldNavigateBack = MutableStateFlow(false)
private val _shouldNavigateBack = MutableLiveData(false) val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
private val _shouldShowResetSettingsDialog = MutableLiveData(false) val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog private val _shouldReloadSettingsList = MutableStateFlow(false)
private val _shouldReloadSettingsList = MutableLiveData(false) val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList private val _isUsingSearch = MutableStateFlow(false)
private val _isUsingSearch = MutableLiveData(false) val sliderProgress: StateFlow<Int> get() = _sliderProgress
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch private val _sliderProgress = MutableStateFlow(-1)
val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) val sliderTextValue: StateFlow<String> get() = _sliderTextValue
private val _sliderTextValue = MutableStateFlow("")
val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
private val _adapterItemChanged = MutableStateFlow(-1)
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
fun setToolbarTitle(value: String) {
_toolbarTitle.value = value
}
fun setShouldRecreate(value: Boolean) { fun setShouldRecreate(value: Boolean) {
_shouldRecreate.value = value _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
} }
fun setSliderTextValue(value: Float, units: String) { fun setSliderTextValue(value: Float, units: String) {
savedStateHandle[KEY_SLIDER_PROGRESS] = value _sliderProgress.value = value.toInt()
savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( _sliderTextValue.value = String.format(
YuzuApplication.appContext.getString(R.string.value_with_units), YuzuApplication.appContext.getString(R.string.value_with_units),
value.toInt().toString(), value.toInt().toString(),
units units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
} }
fun setSliderProgress(value: Float) { fun setSliderProgress(value: Float) {
savedStateHandle[KEY_SLIDER_PROGRESS] = value _sliderProgress.value = value.toInt()
} }
fun setAdapterItemChanged(value: Int) { fun setAdapterItemChanged(value: Int) {
savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value _adapterItemChanged.value = value
} }
fun clear() { fun clear() {
game = null game = null
shouldSave = false shouldSave = false
} }
companion object {
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
const val KEY_SLIDER_PROGRESS = "SliderProgress"
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
}
} }

View File

@@ -3,29 +3,25 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() { class TaskViewModel : ViewModel() {
private val _result = MutableLiveData<Any>() val result: StateFlow<Any> get() = _result
val result: LiveData<Any> = _result private val _result = MutableStateFlow(Any())
private val _isComplete = MutableLiveData<Boolean>() val isComplete: StateFlow<Boolean> get() = _isComplete
val isComplete: LiveData<Boolean> = _isComplete private val _isComplete = MutableStateFlow(false)
private val _isRunning = MutableLiveData<Boolean>() val isRunning: StateFlow<Boolean> get() = _isRunning
val isRunning: LiveData<Boolean> = _isRunning private val _isRunning = MutableStateFlow(false)
lateinit var task: () -> Any lateinit var task: () -> Any
init {
clear()
}
fun clear() { fun clear() {
_result.value = Any() _result.value = Any()
_isComplete.value = false _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
} }
fun runTask() { fun runTask() {
if (_isRunning.value == true) { if (isRunning.value) {
return return
} }
_isRunning.value = true _isRunning.value = true
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val res = task() val res = task()
_result.postValue(res) _result.value = res
_isComplete.postValue(true) _isComplete.value = true
_isRunning.value = false
} }
} }
} }

View File

@@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false) homeViewModel.setNavigationVisibility(visible = true, animated = false)
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
if (_binding == null) { if (_binding == null) {
return@post return@post
} }
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
} }
} }
gamesViewModel.apply { viewLifecycleOwner.lifecycleScope.apply {
// Watch for when we get updates to any of our games lists launch {
isReloading.observe(viewLifecycleOwner) { isReloading -> repeatOnLifecycle(Lifecycle.State.RESUMED) {
binding.swipeRefresh.isRefreshing = isReloading gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
}
games.observe(viewLifecycleOwner) {
(binding.gridGames.adapter as GameAdapter).submitList(it)
if (it.isEmpty()) {
binding.noticeText.visibility = View.VISIBLE
} else {
binding.noticeText.visibility = View.GONE
} }
} }
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> launch {
if (shouldSwapData) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
(binding.gridGames.adapter as GameAdapter).submitList( gamesViewModel.games.collect {
gamesViewModel.games.value!! (binding.gridGames.adapter as GameAdapter).submitList(it)
) if (it.isEmpty()) {
gamesViewModel.setShouldSwapData(false) binding.noticeText.visibility = View.VISIBLE
} else {
binding.noticeText.visibility = View.GONE
}
}
} }
} }
launch {
// Check if the user reselected the games menu item and then scroll to top of the list repeatOnLifecycle(Lifecycle.State.RESUMED) {
shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> gamesViewModel.shouldSwapData.collect {
if (shouldScroll) { if (it) {
scrollToTop() (binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.setShouldScrollToTop(false) gamesViewModel.games.value
)
gamesViewModel.setShouldSwapData(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
gamesViewModel.shouldScrollToTop.collect {
if (it) {
scrollToTop()
gamesViewModel.setShouldScrollToTop(false)
}
}
} }
} }
} }

View File

@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
@@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.id.homeSettingsFragment -> { R.id.homeSettingsFragment -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
SettingsFile.FILE_NAME_CONFIG Settings.MenuTag.SECTION_ROOT
) )
navHostFragment.navController.navigate(action) navHostFragment.navController.navigate(action)
} }
@@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
// Prevents navigation from being drawn for a short time on recreation if set to hidden // Prevents navigation from being drawn for a short time on recreation if set to hidden
if (!homeViewModel.navigationVisible.value?.first!!) { if (!homeViewModel.navigationVisible.value.first) {
binding.navigationView.visibility = View.INVISIBLE binding.navigationView.visibility = View.INVISIBLE
binding.statusBarShade.visibility = View.INVISIBLE binding.statusBarShade.visibility = View.INVISIBLE
} }
homeViewModel.navigationVisible.observe(this) { lifecycleScope.apply {
showNavigation(it.first, it.second) launch {
} repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.statusBarShadeVisible.observe(this) { visible -> homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
showStatusBarShade(visible) }
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
}
}
} }
// Dismiss previous notifications (should not happen unless a crash occurred) // Dismiss previous notifications (should not happen unless a crash occurred)

View File

@@ -270,6 +270,7 @@ public:
m_vulkan_library); m_vulkan_library);
m_system.SetFilesystem(m_vfs); m_system.SetFilesystem(m_vfs);
m_system.GetUserChannel().clear();
// Initialize system. // Initialize system.
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>(); jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();

View File

@@ -82,7 +82,7 @@
app:nullable="true" /> app:nullable="true" />
<argument <argument
android:name="menuTag" android:name="menuTag"
app:argType="string" /> app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
</activity> </activity>
<action <action

View File

@@ -10,7 +10,7 @@
android:label="SettingsFragment"> android:label="SettingsFragment">
<argument <argument
android:name="menuTag" android:name="menuTag"
app:argType="string" /> app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
<argument <argument
android:name="game" android:name="game"
app:argType="org.yuzu.yuzu_emu.model.Game" app:argType="org.yuzu.yuzu_emu.model.Game"

View File

@@ -88,8 +88,13 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
return mailbox.Receive(dir, block); return mailbox.Receive(dir, block);
} }
void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept { void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
command_buffers[session_id] = buffer; u64 applet_resource_user_id, bool reset) noexcept {
command_buffers[session_id].buffer = buffer;
command_buffers[session_id].size = size;
command_buffers[session_id].time_limit = time_limit;
command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
command_buffers[session_id].reset_buffer = reset;
} }
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {

View File

@@ -75,7 +75,8 @@ public:
void Send(Direction dir, MailboxMessage message); void Send(Direction dir, MailboxMessage message);
MailboxMessage Receive(Direction dir, bool block = true); MailboxMessage Receive(Direction dir, bool block = true);
void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept; void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
u64 applet_resource_user_id, bool reset) noexcept;
u32 GetRemainCommandCount(s32 session_id) const noexcept; u32 GetRemainCommandCount(s32 session_id) const noexcept;
void ClearRemainCommandCount(s32 session_id) noexcept; void ClearRemainCommandCount(s32 session_id) noexcept;
u64 GetRenderingStartTick(s32 session_id) const noexcept; u64 GetRenderingStartTick(s32 session_id) const noexcept;

View File

@@ -37,11 +37,6 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
return command_count - processed_command_count; return command_count - processed_command_count;
} }
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
commands_buffer_size = size;
}
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
return stream; return stream;
} }

View File

@@ -56,14 +56,6 @@ public:
*/ */
u32 GetRemainingCommandCount() const; u32 GetRemainingCommandCount() const;
/**
* Set the command buffer.
*
* @param buffer - The buffer to use.
* @param size - The size of the buffer.
*/
void SetBuffer(CpuAddr buffer, u64 size);
/** /**
* Get the stream for this command list. * Get the stream for this command list.
* *

View File

@@ -20,6 +20,12 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)}; processor.sample_count)};
for (auto& wave_buffer : wave_buffers) {
wave_buffer.loop_start_offset = wave_buffer.start_offset;
wave_buffer.loop_end_offset = wave_buffer.end_offset;
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
}
DecodeFromWaveBuffersArgs args{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm}, .sample_format{SampleFormat::Adpcm},
.output{out_buffer}, .output{out_buffer},

View File

@@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
return 0; return 0;
} }
auto samples_to_process{ auto start_pos{req.start_offset + req.offset};
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
if (samples_to_process == 0) {
return 0;
}
auto samples_to_read{samples_to_process}; auto samples_to_read{samples_to_process};
auto start_pos{req.start_offset + req.offset};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame}; samples_remaining_in_frame};
@@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
* @param args - The wavebuffer data, and information for how to decode it. * @param args - The wavebuffer data, and information for how to decode it.
*/ */
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
auto& played_samples, auto& consumed) -> void {
voice_state.wave_buffer_valid[index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_samples = 0;
}
index = (index + 1) % MaxWaveBuffers;
consumed++;
};
auto& voice_state{*args.voice_state}; auto& voice_state{*args.voice_state};
auto remaining_sample_count{args.sample_count}; auto remaining_sample_count{args.sample_count};
auto fraction{voice_state.fraction}; auto fraction{voice_state.fraction};
const auto sample_rate_ratio{ const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * (f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
args.pitch};
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
if (size_required < 0) { if (size_required < 0) {
@@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
auto end_offset{wavebuffer.end_offset}; auto end_offset{wavebuffer.end_offset};
if (wavebuffer.loop && voice_state.loop_count > 0 && if (wavebuffer.loop && voice_state.loop_count > 0 &&
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
start_offset = wavebuffer.loop_start_offset; start_offset = wavebuffer.loop_start_offset;
end_offset = wavebuffer.loop_end_offset; end_offset = wavebuffer.loop_end_offset;
} }
DecodeArg decode_arg{.buffer{wavebuffer.buffer}, DecodeArg decode_arg{
.buffer_size{wavebuffer.buffer_size}, .buffer{wavebuffer.buffer},
.start_offset{start_offset}, .buffer_size{wavebuffer.buffer_size},
.end_offset{end_offset}, .start_offset{start_offset},
.channel_count{args.channel_count}, .end_offset{end_offset},
.coefficients{}, .channel_count{args.channel_count},
.adpcm_context{nullptr}, .coefficients{},
.target_channel{args.channel}, .adpcm_context{nullptr},
.offset{offset}, .target_channel{args.channel},
.samples_to_read{samples_to_read - samples_read}}; .offset{offset},
.samples_to_read{samples_to_read - samples_read},
};
s32 samples_decoded{0}; s32 samples_decoded{0};
@@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
temp_buffer_pos += samples_decoded; temp_buffer_pos += samples_decoded;
offset += samples_decoded; offset += samples_decoded;
if (samples_decoded == 0 || offset >= end_offset - start_offset) { if (samples_decoded && offset < end_offset - start_offset) {
offset = 0; continue;
if (!wavebuffer.loop) { }
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) { offset = 0;
played_sample_count = 0; if (wavebuffer.loop) {
} voice_state.loop_count++;
if (wavebuffer.loop_count >= 0 &&
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
wavebuffers_consumed++; EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
} else { wavebuffers_consumed);
voice_state.loop_count++;
if (wavebuffer.loop_count >= 0 &&
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_sample_count = 0;
}
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
wavebuffers_consumed++;
}
if (samples_decoded == 0) {
is_buffer_starved = true;
break;
}
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
played_sample_count = 0;
}
} }
if (samples_decoded == 0) {
is_buffer_starved = true;
break;
}
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
played_sample_count = 0;
}
} else {
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
wavebuffers_consumed);
} }
} }

View File

@@ -21,6 +21,12 @@ void PcmFloatDataSourceVersion1Command::Process(
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count); processor.sample_count);
for (auto& wave_buffer : wave_buffers) {
wave_buffer.loop_start_offset = wave_buffer.start_offset;
wave_buffer.loop_end_offset = wave_buffer.end_offset;
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
}
DecodeFromWaveBuffersArgs args{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmFloat}, .sample_format{SampleFormat::PcmFloat},
.output{out_buffer}, .output{out_buffer},

View File

@@ -23,6 +23,12 @@ void PcmInt16DataSourceVersion1Command::Process(
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count); processor.sample_count);
for (auto& wave_buffer : wave_buffers) {
wave_buffer.loop_start_offset = wave_buffer.start_offset;
wave_buffer.loop_end_offset = wave_buffer.end_offset;
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
}
DecodeFromWaveBuffersArgs args{ DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmInt16}, .sample_format{SampleFormat::PcmInt16},
.output{out_buffer}, .output{out_buffer},

View File

@@ -609,17 +609,11 @@ void System::SendCommandToDsp() {
time_limit_percent = 70.0f; time_limit_percent = 70.0f;
} }
AudioRenderer::CommandBuffer command_buffer{ auto time_limit{
.buffer{translated_addr}, static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
.size{command_size}, (static_cast<f32>(render_time_limit_percent) / 100.0f))};
.time_limit{ audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 * applet_resource_user_id, reset_command_buffers);
(static_cast<f32>(render_time_limit_percent) / 100.0f))},
.applet_resource_user_id{applet_resource_user_id},
.reset_buffer{reset_command_buffers},
};
audio_renderer.SetCommandBuffer(session_id, command_buffer);
reset_command_buffers = false; reset_command_buffers = false;
command_buffer_size = command_size; command_buffer_size = command_size;
if (remaining_command_count == 0) { if (remaining_command_count == 0) {

View File

@@ -151,6 +151,10 @@ add_library(common STATIC
zstd_compression.h zstd_compression.h
) )
if (YUZU_ENABLE_PORTABLE)
add_compile_definitions(YUZU_ENABLE_PORTABLE)
endif()
if (WIN32) if (WIN32)
target_sources(common PRIVATE target_sources(common PRIVATE
windows/timer_resolution.cpp windows/timer_resolution.cpp

View File

@@ -88,8 +88,9 @@ public:
fs::path yuzu_path_config; fs::path yuzu_path_config;
#ifdef _WIN32 #ifdef _WIN32
#ifdef YUZU_ENABLE_PORTABLE
yuzu_path = GetExeDirectory() / PORTABLE_DIR; yuzu_path = GetExeDirectory() / PORTABLE_DIR;
#endif
if (!IsDir(yuzu_path)) { if (!IsDir(yuzu_path)) {
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR; yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
} }
@@ -101,8 +102,9 @@ public:
yuzu_path_cache = yuzu_path / CACHE_DIR; yuzu_path_cache = yuzu_path / CACHE_DIR;
yuzu_path_config = yuzu_path / CONFIG_DIR; yuzu_path_config = yuzu_path / CONFIG_DIR;
#else #else
#ifdef YUZU_ENABLE_PORTABLE
yuzu_path = GetCurrentDir() / PORTABLE_DIR; yuzu_path = GetCurrentDir() / PORTABLE_DIR;
#endif
if (Exists(yuzu_path) && IsDir(yuzu_path)) { if (Exists(yuzu_path) && IsDir(yuzu_path)) {
yuzu_path_cache = yuzu_path / CACHE_DIR; yuzu_path_cache = yuzu_path / CACHE_DIR;
yuzu_path_config = yuzu_path / CONFIG_DIR; yuzu_path_config = yuzu_path / CONFIG_DIR;

View File

@@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Service, NCM) \ SUB(Service, NCM) \
SUB(Service, NFC) \ SUB(Service, NFC) \
SUB(Service, NFP) \ SUB(Service, NFP) \
SUB(Service, NGCT) \ SUB(Service, NGC) \
SUB(Service, NIFM) \ SUB(Service, NIFM) \
SUB(Service, NIM) \ SUB(Service, NIM) \
SUB(Service, NOTIF) \ SUB(Service, NOTIF) \

View File

@@ -80,7 +80,7 @@ enum class Class : u8 {
Service_NCM, ///< The NCM service Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service Service_NFP, ///< The NFP service
Service_NGCT, ///< The NGCT (No Good Content for Terra) service Service_NGC, ///< The NGC (No Good Content) service
Service_NIFM, ///< The NIFM (Network interface) service Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service Service_NIM, ///< The NIM service
Service_NOTIF, ///< The NOTIF (Notification) service Service_NOTIF, ///< The NOTIF (Notification) service

View File

@@ -19,8 +19,8 @@
namespace Common { namespace Common {
template <typename Condvar, typename Lock, typename Pred> template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lock, token, std::move(pred)); cv.wait(lk, token, std::move(pred));
} }
template <typename Rep, typename Period> template <typename Rep, typename Period>
@@ -332,13 +332,17 @@ private:
namespace Common { namespace Common {
template <typename Condvar, typename Lock, typename Pred> template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) { if (token.stop_requested()) {
return; return;
} }
std::stop_callback callback(token, [&] { cv.notify_all(); }); std::stop_callback callback(token, [&] {
cv.wait(lock, [&] { return pred() || token.stop_requested(); }); { std::scoped_lock lk2{*lk.mutex()}; }
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
} }
template <typename Rep, typename Period> template <typename Rep, typename Period>
@@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
std::stop_callback cb(token, [&] { std::stop_callback cb(token, [&] {
// Wake up the waiting thread. // Wake up the waiting thread.
std::unique_lock lk{m}; {
stop_requested = true; std::scoped_lock lk{m};
stop_requested = true;
}
cv.notify_one(); cv.notify_one();
}); });

View File

@@ -584,13 +584,23 @@ add_library(core STATIC
hle/service/lm/lm.h hle/service/lm/lm.h
hle/service/mig/mig.cpp hle/service/mig/mig.cpp
hle/service/mig/mig.h hle/service/mig/mig.h
hle/service/mii/types/char_info.cpp
hle/service/mii/types/char_info.h
hle/service/mii/types/core_data.cpp
hle/service/mii/types/core_data.h
hle/service/mii/types/raw_data.cpp
hle/service/mii/types/raw_data.h
hle/service/mii/types/store_data.cpp
hle/service/mii/types/store_data.h
hle/service/mii/types/ver3_store_data.cpp
hle/service/mii/types/ver3_store_data.h
hle/service/mii/mii.cpp hle/service/mii/mii.cpp
hle/service/mii/mii.h hle/service/mii/mii.h
hle/service/mii/mii_manager.cpp hle/service/mii/mii_manager.cpp
hle/service/mii/mii_manager.h hle/service/mii/mii_manager.h
hle/service/mii/raw_data.cpp hle/service/mii/mii_result.h
hle/service/mii/raw_data.h hle/service/mii/mii_types.h
hle/service/mii/types.h hle/service/mii/mii_util.h
hle/service/mm/mm_u.cpp hle/service/mm/mm_u.cpp
hle/service/mm/mm_u.h hle/service/mm/mm_u.h
hle/service/mnpp/mnpp_app.cpp hle/service/mnpp/mnpp_app.cpp
@@ -617,8 +627,8 @@ add_library(core STATIC
hle/service/nfp/nfp_interface.h hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h hle/service/nfp/nfp_types.h
hle/service/ngct/ngct.cpp hle/service/ngc/ngc.cpp
hle/service/ngct/ngct.h hle/service/ngc/ngc.h
hle/service/nifm/nifm.cpp hle/service/nifm/nifm.cpp
hle/service/nifm/nifm.h hle/service/nifm/nifm.h
hle/service/nim/nim.cpp hle/service/nim/nim.cpp

View File

@@ -406,6 +406,7 @@ struct System::Impl {
gpu_core->NotifyShutdown(); gpu_core->NotifyShutdown();
} }
Network::CancelPendingSocketOperations();
kernel.SuspendApplication(true); kernel.SuspendApplication(true);
if (services) { if (services) {
services->KillNVNFlinger(); services->KillNVNFlinger();
@@ -427,6 +428,7 @@ struct System::Impl {
debugger.reset(); debugger.reset();
kernel.Shutdown(); kernel.Shutdown();
memory.Reset(); memory.Reset();
Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock()) { if (auto room_member = room_network.GetRoomMember().lock()) {
Network::GameInfo game_info{}; Network::GameInfo game_info{};
@@ -562,6 +564,8 @@ struct System::Impl {
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_write_manager{}; gpu_dirty_memory_write_manager{};
std::deque<std::vector<u8>> user_channel;
}; };
System::System() : impl{std::make_unique<Impl>(*this)} {} System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -1036,6 +1040,10 @@ void System::ExecuteProgram(std::size_t program_index) {
} }
} }
std::deque<std::vector<u8>>& System::GetUserChannel() {
return impl->user_channel;
}
void System::RegisterExitCallback(ExitCallback&& callback) { void System::RegisterExitCallback(ExitCallback&& callback) {
impl->exit_callback = std::move(callback); impl->exit_callback = std::move(callback);
} }

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <deque>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@@ -459,6 +460,12 @@ public:
*/ */
void ExecuteProgram(std::size_t program_index); void ExecuteProgram(std::size_t program_index);
/**
* Gets a reference to the user channel stack.
* It is used to transfer data between programs.
*/
[[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
/// Type used for the frontend to designate a callback for System to exit the application. /// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>; using ExitCallback = std::function<void()>;

View File

@@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
CNMT::~CNMT() = default; CNMT::~CNMT() = default;
const CNMTHeader& CNMT::GetHeader() const {
return header;
}
u64 CNMT::GetTitleID() const { u64 CNMT::GetTitleID() const {
return header.title_id; return header.title_id;
} }

View File

@@ -89,6 +89,7 @@ public:
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
~CNMT(); ~CNMT();
const CNMTHeader& GetHeader() const;
u64 GetTitleID() const; u64 GetTitleID() const;
u32 GetTitleVersion() const; u32 GetTitleVersion() const;
TitleType GetType() const; TitleType GetType() const;

View File

@@ -9,6 +9,7 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h" #include "core/file_sys/common_funcs.h"
@@ -625,7 +626,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
nca->GetTitleId() != title_id) { nca->GetTitleId() != title_id) {
// Create fake cnmt for patch to multiprogram application // Create fake cnmt for patch to multiprogram application
const auto sub_nca_result = const auto sub_nca_result =
InstallEntry(*nca, TitleType::Update, overwrite_if_exists, copy); InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
if (sub_nca_result != InstallResult::Success) { if (sub_nca_result != InstallResult::Success) {
return sub_nca_result; return sub_nca_result;
} }
@@ -672,6 +673,31 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
} }
InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
const ContentRecord& base_record,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
const CNMTHeader header{
.title_id = nca.GetTitleId(),
.title_version = base_header.title_version,
.type = base_header.type,
.reserved = {},
.table_offset = 0x10,
.number_content_entries = 1,
.number_meta_entries = 0,
.attributes = 0,
.reserved2 = {},
.is_committed = 0,
.required_download_system_version = 0,
.reserved3 = {},
};
const OptionalHeader opt_header{0, 0};
const CNMT new_cnmt(header, opt_header, {base_record}, {});
if (!RawInstallYuzuMeta(new_cnmt)) {
return InstallResult::ErrorMetaFailed;
}
return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
}
bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
bool removed_data = false; bool removed_data = false;

View File

@@ -24,6 +24,7 @@ enum class NCAContentType : u8;
enum class TitleType : u8; enum class TitleType : u8;
struct ContentRecord; struct ContentRecord;
struct CNMTHeader;
struct MetaRecord; struct MetaRecord;
class RegisteredCache; class RegisteredCache;
@@ -169,6 +170,10 @@ public:
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy); const VfsCopyFunction& copy = &VfsRawCopy);
InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
const ContentRecord& base_record, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
// Removes an existing entry based on title id // Removes an existing entry based on title id
bool RemoveExistingEntry(u64 title_id) const; bool RemoveExistingEntry(u64 title_id) const;

View File

@@ -46,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
enum class LaunchParameterKind : u32 { enum class LaunchParameterKind : u32 {
ApplicationSpecific = 1, UserChannel = 1,
AccountPreselectedUser = 2, AccountPreselectedUser = 2,
}; };
@@ -1518,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto kind = rp.PopEnum<LaunchParameterKind>(); const auto kind = rp.PopEnum<LaunchParameterKind>();
LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); LOG_INFO(Service_AM, "called, kind={:08X}", kind);
if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { if (kind == LaunchParameterKind::UserChannel) {
const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { auto channel = system.GetUserChannel();
return system.GetFileSystemController().GetBCATDirectory(tid); if (channel.empty()) {
}); LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
const auto build_id_full = system.GetApplicationProcessBuildID(); IPC::ResponseBuilder rb{ctx, 2};
u64 build_id{}; rb.Push(AM::ResultNoDataInChannel);
std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
auto data =
backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
if (data.has_value()) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IStorage>(system, std::move(*data));
launch_popped_application_specific = true;
return; return;
} }
auto data = channel.back();
channel.pop_back();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IStorage>(system, std::move(data));
} else if (kind == LaunchParameterKind::AccountPreselectedUser && } else if (kind == LaunchParameterKind::AccountPreselectedUser &&
!launch_popped_account_preselect) { !launch_popped_account_preselect) {
// TODO: Verify this is hw-accurate
LaunchParameterAccountPreselectedUser params{}; LaunchParameterAccountPreselectedUser params{};
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
@@ -1550,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
params.current_user = *uuid; params.current_user = *uuid;
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
@@ -1558,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
rb.PushIpcInterface<IStorage>(system, std::move(buffer)); rb.PushIpcInterface<IStorage>(system, std::move(buffer));
launch_popped_account_preselect = true; launch_popped_account_preselect = true;
return; } else {
LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(AM::ResultNoDataInChannel);
} }
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(AM::ResultNoDataInChannel);
} }
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
@@ -1855,14 +1852,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
} }
void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_DEBUG(Service_AM, "called");
system.GetUserChannel().clear();
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_DEBUG(Service_AM, "called");
IPC::RequestParser rp{ctx};
const auto storage = rp.PopIpcInterface<IStorage>().lock();
if (storage) {
system.GetUserChannel().push_back(storage->GetData());
}
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);

View File

@@ -339,7 +339,6 @@ private:
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;
bool launch_popped_application_specific = false;
bool launch_popped_account_preselect = false; bool launch_popped_account_preselect = false;
s32 previous_program_index{-1}; s32 previous_program_index{-1};
Kernel::KEvent* gpu_error_detected_event; Kernel::KEvent* gpu_error_detected_event;

View File

@@ -85,15 +85,18 @@ void MiiEdit::Execute() {
break; break;
case MiiEditAppletMode::CreateMii: case MiiEditAppletMode::CreateMii:
case MiiEditAppletMode::EditMii: { case MiiEditAppletMode::EditMii: {
Service::Mii::MiiManager mii_manager; Mii::CharInfo char_info{};
Mii::StoreData store_data{};
store_data.BuildBase(Mii::Gender::Male);
char_info.SetFromStoreData(store_data);
const MiiEditCharInfo char_info{ const MiiEditCharInfo edit_char_info{
.mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
? applet_input_v4.char_info.mii_info ? applet_input_v4.char_info.mii_info
: mii_manager.BuildBase(Mii::Gender::Male)}, : char_info},
}; };
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
break; break;
} }
default: default:

View File

@@ -7,7 +7,8 @@
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/mii/types.h" #include "common/uuid.h"
#include "core/hle/service/mii/types/char_info.h"
namespace Service::AM::Applets { namespace Service::AM::Applets {

View File

@@ -7,17 +7,16 @@
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/server_manager.h" #include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
namespace Service::Mii { namespace Service::Mii {
constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
class IDatabaseService final : public ServiceFramework<IDatabaseService> { class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public: public:
explicit IDatabaseService(Core::System& system_) explicit IDatabaseService(Core::System& system_, bool is_system_)
: ServiceFramework{system_, "IDatabaseService"} { : ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"}, {0, &IDatabaseService::IsUpdated, "IsUpdated"},
@@ -54,34 +53,27 @@ public:
} }
private: private:
template <typename T>
std::vector<u8> SerializeArray(const std::vector<T>& values) {
std::vector<u8> out(values.size() * sizeof(T));
std::size_t offset{};
for (const auto& value : values) {
std::memcpy(out.data() + offset, &value, sizeof(T));
offset += sizeof(T);
}
return out;
}
void IsUpdated(HLERequestContext& ctx) { void IsUpdated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const bool is_updated = manager.IsUpdated(metadata, source_flag);
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); rb.Push<u8>(is_updated);
} }
void IsFullDatabase(HLERequestContext& ctx) { void IsFullDatabase(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called"); LOG_DEBUG(Service_Mii, "called");
const bool is_full_database = manager.IsFullDatabase();
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(manager.IsFullDatabase()); rb.Push<u8>(is_full_database);
} }
void GetCount(HLERequestContext& ctx) { void GetCount(HLERequestContext& ctx) {
@@ -90,57 +82,63 @@ private:
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const u32 mii_count = manager.GetCount(metadata, source_flag);
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u32>(manager.GetCount(source_flag)); rb.Push(mii_count);
} }
void Get(HLERequestContext& ctx) { void Get(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
const auto default_miis{manager.GetDefault(source_flag)}; u32 mii_count{};
if (default_miis.size() > 0) { std::vector<CharInfoElement> char_info_elements(output_size);
ctx.WriteBuffer(SerializeArray(default_miis)); Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(char_info_elements);
} }
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(result);
rb.Push<u32>(static_cast<u32>(default_miis.size())); rb.Push(mii_count);
} }
void Get1(HLERequestContext& ctx) { void Get1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
const auto default_miis{manager.GetDefault(source_flag)}; u32 mii_count{};
std::vector<CharInfo> char_info(output_size);
Result result = manager.Get(metadata, char_info, mii_count, source_flag);
std::vector<CharInfo> values; if (mii_count != 0) {
for (const auto& element : default_miis) { ctx.WriteBuffer(char_info);
values.emplace_back(element.info);
} }
ctx.WriteBuffer(SerializeArray(values));
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(result);
rb.Push<u32>(static_cast<u32>(default_miis.size())); rb.Push(mii_count);
} }
void UpdateLatest(HLERequestContext& ctx) { void UpdateLatest(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto info{rp.PopRaw<CharInfo>()}; const auto char_info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
CharInfo new_char_info{}; CharInfo new_char_info{};
const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)}; const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
if (result != ResultSuccess) { if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result); rb.Push(result);
return; return;
@@ -153,7 +151,6 @@ private:
void BuildRandom(HLERequestContext& ctx) { void BuildRandom(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto age{rp.PopRaw<Age>()}; const auto age{rp.PopRaw<Age>()};
const auto gender{rp.PopRaw<Gender>()}; const auto gender{rp.PopRaw<Gender>()};
const auto race{rp.PopRaw<Race>()}; const auto race{rp.PopRaw<Race>()};
@@ -162,47 +159,48 @@ private:
if (age > Age::All) { if (age > Age::All) {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT); rb.Push(ResultInvalidArgument);
LOG_ERROR(Service_Mii, "invalid age={}", age);
return; return;
} }
if (gender > Gender::All) { if (gender > Gender::All) {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT); rb.Push(ResultInvalidArgument);
LOG_ERROR(Service_Mii, "invalid gender={}", gender);
return; return;
} }
if (race > Race::All) { if (race > Race::All) {
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT); rb.Push(ResultInvalidArgument);
LOG_ERROR(Service_Mii, "invalid race={}", race);
return; return;
} }
CharInfo char_info{};
manager.BuildRandom(char_info, age, gender, race);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); rb.PushRaw<CharInfo>(char_info);
} }
void BuildDefault(HLERequestContext& ctx) { void BuildDefault(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto index{rp.Pop<u32>()}; const auto index{rp.Pop<u32>()};
LOG_DEBUG(Service_Mii, "called with index={}", index); LOG_INFO(Service_Mii, "called with index={}", index);
if (index > 5) { if (index > 5) {
LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
index);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERROR_INVALID_ARGUMENT); rb.Push(ResultInvalidArgument);
return; return;
} }
CharInfo char_info{};
manager.BuildDefault(char_info, index);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(manager.BuildDefault(index)); rb.PushRaw<CharInfo>(char_info);
} }
void GetIndex(HLERequestContext& ctx) { void GetIndex(HLERequestContext& ctx) {
@@ -211,19 +209,21 @@ private:
LOG_DEBUG(Service_Mii, "called"); LOG_DEBUG(Service_Mii, "called");
u32 index{}; s32 index{};
const auto result = manager.GetIndex(metadata, info, index);
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(manager.GetIndex(info, index)); rb.Push(result);
rb.Push(index); rb.Push(index);
} }
void SetInterfaceVersion(HLERequestContext& ctx) { void SetInterfaceVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
current_interface_version = rp.PopRaw<u32>(); const auto interface_version{rp.PopRaw<u32>()};
LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
UNIMPLEMENTED_IF(current_interface_version != 1); manager.SetInterfaceVersion(metadata, interface_version);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@@ -231,30 +231,27 @@ private:
void Convert(HLERequestContext& ctx) { void Convert(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
LOG_INFO(Service_Mii, "called"); LOG_INFO(Service_Mii, "called");
CharInfo char_info{};
manager.ConvertV3ToCharInfo(char_info, mii_v3);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); rb.PushRaw<CharInfo>(char_info);
} }
constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { MiiManager manager{};
return current_interface_version >= interface_version; DatabaseSessionMetadata metadata{};
} bool is_system{};
MiiManager manager;
u32 current_interface_version{};
u64 current_update_counter{};
}; };
class MiiDBModule final : public ServiceFramework<MiiDBModule> { class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public: public:
explicit MiiDBModule(Core::System& system_, const char* name_) explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
: ServiceFramework{system_, name_} { : ServiceFramework{system_, name_}, is_system{is_system_} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
@@ -268,10 +265,12 @@ private:
void GetDatabaseService(HLERequestContext& ctx) { void GetDatabaseService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1}; IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushIpcInterface<IDatabaseService>(system); rb.PushIpcInterface<IDatabaseService>(system, is_system);
LOG_DEBUG(Service_Mii, "called"); LOG_DEBUG(Service_Mii, "called");
} }
bool is_system{};
}; };
class MiiImg final : public ServiceFramework<MiiImg> { class MiiImg final : public ServiceFramework<MiiImg> {
@@ -303,8 +302,10 @@ public:
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); server_manager->RegisterNamedService("mii:e",
server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); std::make_shared<MiiDBModule>(system, "mii:e", true));
server_manager->RegisterNamedService("mii:u",
std::make_shared<MiiDBModule>(system, "mii:u", false));
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }

View File

@@ -10,385 +10,24 @@
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/raw_data.h" #include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/core_data.h"
#include "core/hle/service/mii/types/raw_data.h"
namespace Service::Mii { namespace Service::Mii {
namespace {
constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
constexpr MiiStoreData::Name DefaultMiiName{u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; MiiManager::MiiManager() {}
constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
constexpr std::array<u8, 62> EyeRotateLookup{
{0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
std::array<T, DestArraySize> out{};
std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
return out;
}
CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
MiiStoreBitFields bf;
std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
return {
.uuid = data.data.uuid,
.name = ResizeArray<char16_t, 10, 11>(data.data.name),
.font_region = static_cast<u8>(bf.font_region.Value()),
.favorite_color = static_cast<u8>(bf.favorite_color.Value()),
.gender = static_cast<u8>(bf.gender.Value()),
.height = static_cast<u8>(bf.height.Value()),
.build = static_cast<u8>(bf.build.Value()),
.type = static_cast<u8>(bf.type.Value()),
.region_move = static_cast<u8>(bf.region_move.Value()),
.faceline_type = static_cast<u8>(bf.faceline_type.Value()),
.faceline_color = static_cast<u8>(bf.faceline_color.Value()),
.faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
.faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
.hair_type = static_cast<u8>(bf.hair_type.Value()),
.hair_color = static_cast<u8>(bf.hair_color.Value()),
.hair_flip = static_cast<u8>(bf.hair_flip.Value()),
.eye_type = static_cast<u8>(bf.eye_type.Value()),
.eye_color = static_cast<u8>(bf.eye_color.Value()),
.eye_scale = static_cast<u8>(bf.eye_scale.Value()),
.eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
.eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
.eye_x = static_cast<u8>(bf.eye_x.Value()),
.eye_y = static_cast<u8>(bf.eye_y.Value()),
.eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
.eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
.eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
.eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
.eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
.eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
.eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
.nose_type = static_cast<u8>(bf.nose_type.Value()),
.nose_scale = static_cast<u8>(bf.nose_scale.Value()),
.nose_y = static_cast<u8>(bf.nose_y.Value()),
.mouth_type = static_cast<u8>(bf.mouth_type.Value()),
.mouth_color = static_cast<u8>(bf.mouth_color.Value()),
.mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
.mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
.mouth_y = static_cast<u8>(bf.mouth_y.Value()),
.beard_color = static_cast<u8>(bf.beard_color.Value()),
.beard_type = static_cast<u8>(bf.beard_type.Value()),
.mustache_type = static_cast<u8>(bf.mustache_type.Value()),
.mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
.mustache_y = static_cast<u8>(bf.mustache_y.Value()),
.glasses_type = static_cast<u8>(bf.glasses_type.Value()),
.glasses_color = static_cast<u8>(bf.glasses_color.Value()),
.glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
.glasses_y = static_cast<u8>(bf.glasses_y.Value()),
.mole_type = static_cast<u8>(bf.mole_type.Value()),
.mole_scale = static_cast<u8>(bf.mole_scale.Value()),
.mole_x = static_cast<u8>(bf.mole_x.Value()),
.mole_y = static_cast<u8>(bf.mole_y.Value()),
.padding = 0,
};
}
u16 GenerateCrc16(const void* data, std::size_t size) {
s32 crc{};
for (std::size_t i = 0; i < size; i++) {
crc ^= static_cast<const u8*>(data)[i] << 8;
for (std::size_t j = 0; j < 8; j++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = (crc ^ 0x1021) & 0xFFFF;
}
}
}
return Common::swap16(static_cast<u16>(crc));
}
template <typename T>
T GetRandomValue(T min, T max) {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
return static_cast<T>(distribution(gen));
}
template <typename T>
T GetRandomValue(T max) {
return GetRandomValue<T>({}, max);
}
MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
MiiStoreBitFields bf{};
if (gender == Gender::All) {
gender = GetRandomValue<Gender>(Gender::Maximum);
}
bf.gender.Assign(gender);
bf.favorite_color.Assign(GetRandomValue<u8>(11));
bf.region_move.Assign(0);
bf.font_region.Assign(FontRegion::Standard);
bf.type.Assign(0);
bf.height.Assign(64);
bf.build.Assign(64);
if (age == Age::All) {
const auto temp{GetRandomValue<int>(10)};
if (temp >= 8) {
age = Age::Old;
} else if (temp >= 4) {
age = Age::Normal;
} else {
age = Age::Young;
}
}
if (race == Race::All) {
const auto temp{GetRandomValue<int>(10)};
if (temp >= 8) {
race = Race::Black;
} else if (temp >= 4) {
race = Race::White;
} else {
race = Race::Asian;
}
}
u32 axis_y{};
if (gender == Gender::Female && age == Age::Young) {
axis_y = GetRandomValue<u32>(3);
}
const std::size_t index{3 * static_cast<std::size_t>(age) +
9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
static_cast<std::size_t>(age))};
const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
bf.faceline_type.Assign(
faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
bf.faceline_color.Assign(
faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
bf.faceline_wrinkle.Assign(
faceline_wrinkle_info
.values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
bf.faceline_makeup.Assign(
faceline_makeup_info
.values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
bf.hair_type.Assign(
hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
bf.hair_color.Assign(
HairColorLookup[hair_color_info
.values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
bf.eye_type.Assign(
eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
bf.eye_color.Assign(
EyeColorLookup[eye_color_info
.values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
bf.eye_scale.Assign(4);
bf.eye_aspect.Assign(3);
bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
bf.eye_x.Assign(2);
bf.eye_y.Assign(axis_y + 12);
bf.eyebrow_type.Assign(
eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
const auto eyebrow_y{race == Race::Asian ? 9 : 10};
const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
const auto eyebrow_rotate{
32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
bf.eyebrow_color.Assign(bf.hair_color);
bf.eyebrow_scale.Assign(4);
bf.eyebrow_aspect.Assign(3);
bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
bf.eyebrow_x.Assign(2);
bf.eyebrow_y.Assign(axis_y + eyebrow_y);
const auto nose_scale{gender == Gender::Female ? 3 : 4};
bf.nose_type.Assign(
nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
bf.nose_scale.Assign(nose_scale);
bf.nose_y.Assign(axis_y + 9);
const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
bf.mouth_type.Assign(
mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
bf.mouth_scale.Assign(4);
bf.mouth_aspect.Assign(3);
bf.mouth_y.Assign(axis_y + 13);
bf.beard_color.Assign(bf.hair_color);
bf.mustache_scale.Assign(4);
if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
const auto mustache_and_beard_flag{
GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
auto beard_type{BeardType::None};
auto mustache_type{MustacheType::None};
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
BeardAndMustacheFlag::Beard) {
beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
}
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
BeardAndMustacheFlag::Mustache) {
mustache_type =
GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
}
bf.mustache_type.Assign(mustache_type);
bf.beard_type.Assign(beard_type);
bf.mustache_y.Assign(10);
} else {
bf.mustache_type.Assign(MustacheType::None);
bf.beard_type.Assign(BeardType::None);
bf.mustache_y.Assign(axis_y + 10);
}
const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
u8 glasses_type{};
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
if (++glasses_type >= glasses_type_info.values_count) {
ASSERT(false);
break;
}
}
bf.glasses_type.Assign(glasses_type);
bf.glasses_color.Assign(GlassesColorLookup[0]);
bf.glasses_scale.Assign(4);
bf.glasses_y.Assign(axis_y + 10);
bf.mole_type.Assign(0);
bf.mole_scale.Assign(4);
bf.mole_x.Assign(2);
bf.mole_y.Assign(20);
return {DefaultMiiName, bf, user_id};
}
MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
MiiStoreBitFields bf{};
bf.font_region.Assign(info.font_region);
bf.favorite_color.Assign(info.favorite_color);
bf.gender.Assign(info.gender);
bf.height.Assign(info.height);
bf.build.Assign(info.weight);
bf.type.Assign(info.type);
bf.region_move.Assign(info.region);
bf.faceline_type.Assign(info.face_type);
bf.faceline_color.Assign(info.face_color);
bf.faceline_wrinkle.Assign(info.face_wrinkle);
bf.faceline_makeup.Assign(info.face_makeup);
bf.hair_type.Assign(info.hair_type);
bf.hair_color.Assign(HairColorLookup[info.hair_color]);
bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
bf.eye_type.Assign(info.eye_type);
bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
bf.eye_scale.Assign(info.eye_scale);
bf.eye_aspect.Assign(info.eye_aspect);
bf.eye_rotate.Assign(info.eye_rotate);
bf.eye_x.Assign(info.eye_x);
bf.eye_y.Assign(info.eye_y);
bf.eyebrow_type.Assign(info.eyebrow_type);
bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
bf.eyebrow_scale.Assign(info.eyebrow_scale);
bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
bf.eyebrow_x.Assign(info.eyebrow_x);
bf.eyebrow_y.Assign(info.eyebrow_y - 3);
bf.nose_type.Assign(info.nose_type);
bf.nose_scale.Assign(info.nose_scale);
bf.nose_y.Assign(info.nose_y);
bf.mouth_type.Assign(info.mouth_type);
bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
bf.mouth_scale.Assign(info.mouth_scale);
bf.mouth_aspect.Assign(info.mouth_aspect);
bf.mouth_y.Assign(info.mouth_y);
bf.beard_color.Assign(HairColorLookup[info.beard_color]);
bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
bf.mustache_scale.Assign(info.mustache_scale);
bf.mustache_y.Assign(info.mustache_y);
bf.glasses_type.Assign(info.glasses_type);
bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
bf.glasses_scale.Assign(info.glasses_scale);
bf.glasses_y.Assign(info.glasses_y);
bf.mole_type.Assign(info.mole_type);
bf.mole_scale.Assign(info.mole_scale);
bf.mole_x.Assign(info.mole_x);
bf.mole_y.Assign(info.mole_y);
return {DefaultMiiName, bf, user_id};
}
} // namespace
MiiStoreData::MiiStoreData() = default;
MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
const Common::UUID& user_id) {
data.name = name;
data.uuid = Common::UUID::MakeRandomRFC4122V4();
std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
data_crc = GenerateCrc16(data.data.data(), sizeof(data));
device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
}
MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {}
bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) { if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return false; return false;
} }
const bool result{current_update_counter != update_counter}; const auto metadata_update_counter = metadata.update_counter;
metadata.update_counter = update_counter;
current_update_counter = update_counter; return metadata_update_counter != update_counter;
return result;
} }
bool MiiManager::IsFullDatabase() const { bool MiiManager::IsFullDatabase() const {
@@ -396,306 +35,138 @@ bool MiiManager::IsFullDatabase() const {
return false; return false;
} }
u32 MiiManager::GetCount(SourceFlag source_flag) const { u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
std::size_t count{}; u32 mii_count{};
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
mii_count += DefaultMiiCount;
}
if ((source_flag & SourceFlag::Database) != SourceFlag::None) { if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
// TODO(bunnei): We don't implement the Mii database, but when we do, update this // TODO(bunnei): We don't implement the Mii database, but when we do, update this
count += 0;
} }
if ((source_flag & SourceFlag::Default) != SourceFlag::None) { return mii_count;
count += DefaultMiiCount;
}
return static_cast<u32>(count);
} }
Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) { Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) { if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ERROR_CANNOT_FIND_ENTRY; return ResultNotFound;
} }
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
return ERROR_CANNOT_FIND_ENTRY; return ResultNotFound;
} }
CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); StoreData store_data{};
store_data.BuildDefault(index);
out_char_info.SetFromStoreData(store_data);
} }
CharInfo MiiManager::BuildBase(Gender gender) { void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
const std::size_t index = gender == Gender::Female ? 1 : 0; StoreData store_data{};
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::BaseMii.at(index), user_id)); store_data.BuildBase(gender);
out_char_info.SetFromStoreData(store_data);
} }
CharInfo MiiManager::BuildDefault(std::size_t index) { void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); StoreData store_data{};
store_data.BuildRandom(age, gender, race);
out_char_info.SetFromStoreData(store_data);
} }
CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
Service::Mii::MiiManager manager; StoreData store_data{};
auto mii = manager.BuildBase(Mii::Gender::Male); mii_v3.BuildToStoreData(store_data);
out_char_info.SetFromStoreData(store_data);
}
if (!ValidateV3Info(mii_v3)) { Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
return mii; std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_elements, out_count, source_flag);
} }
// TODO: We are ignoring a bunch of data from the mii_v3 // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
mii.gender = static_cast<u8>(mii_v3.mii_information.gender); // Include default Mii at the end of the list
mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); return BuildDefault(out_elements, out_count, source_flag);
mii.height = mii_v3.height; }
mii.build = mii_v3.build;
// Copy name until string terminator Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
mii.name = {}; u32& out_count, SourceFlag source_flag) {
for (std::size_t index = 0; index < mii.name.size() - 1; index++) { if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
mii.name[index] = mii_v3.mii_name[index]; return BuildDefault(out_char_info, out_count, source_flag);
if (mii.name[index] == 0) {
break;
}
} }
mii.font_region = mii_v3.region_information.character_set; // TODO(bunnei): We don't implement the Mii database, so we can't have an entry
mii.faceline_type = mii_v3.appearance_bits1.face_shape; // Include default Mii at the end of the list
mii.faceline_color = mii_v3.appearance_bits1.skin_color; return BuildDefault(out_char_info, out_count, source_flag);
mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
mii.faceline_make = mii_v3.appearance_bits2.makeup;
mii.hair_type = mii_v3.hair_style;
mii.hair_color = mii_v3.appearance_bits3.hair_color;
mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
// TODO: Validate mii data
return mii;
} }
Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
Service::Mii::MiiManager manager; SourceFlag source_flag) {
Ver3StoreData mii_v3{};
// TODO: We are ignoring a bunch of data from the mii_v3
mii_v3.version = 1;
mii_v3.mii_information.gender.Assign(mii.gender);
mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
mii_v3.height = mii.height;
mii_v3.build = mii.build;
// Copy name until string terminator
mii_v3.mii_name = {};
for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
mii_v3.mii_name[index] = mii.name[index];
if (mii_v3.mii_name[index] == 0) {
break;
}
}
mii_v3.region_information.character_set.Assign(mii.font_region);
mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
mii_v3.hair_style = mii.hair_type;
mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
// These types are converted to V3 from a table
mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]);
mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]);
mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]);
mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]);
mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]);
mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]);
mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]);
mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]);
mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16));
// TODO: Validate mii_v3 data
return mii_v3;
}
NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const {
return {
.faceline_color = static_cast<u8>(mii.faceline_color & 0xf),
.hair_color = static_cast<u8>(mii.hair_color & 0x7f),
.eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
.eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
.mouth_color = static_cast<u8>(mii.mouth_color & 0x7f),
.beard_color = static_cast<u8>(mii.beard_color & 0x7f),
.glass_color = static_cast<u8>(mii.glasses_color & 0x7f),
.glass_type = static_cast<u8>(mii.glasses_type & 0x1f),
};
}
bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
is_valid = is_valid && (mii_v3.mii_name[0] != 0);
is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
is_valid = is_valid && (mii_v3.height < 128);
is_valid = is_valid && (mii_v3.build < 128);
is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
is_valid = is_valid && (mii_v3.hair_style < 132);
is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
return is_valid;
}
std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;
if ((source_flag & SourceFlag::Default) == SourceFlag::None) { if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return result; return ResultSuccess;
} }
for (std::size_t index = 0; index < DefaultMiiCount; index++) { StoreData store_data{};
result.emplace_back(BuildDefault(index), Source::Default);
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
store_data.BuildDefault(static_cast<u32>(index));
out_elements[out_count].source = Source::Default;
out_elements[out_count].char_info.SetFromStoreData(store_data);
out_count++;
} }
return result; return ResultSuccess;
} }
Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
SourceFlag source_flag) {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
StoreData store_data{};
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
store_data.BuildDefault(static_cast<u32>(index));
out_char_info[out_count].SetFromStoreData(store_data);
out_count++;
}
return ResultSuccess;
}
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
constexpr u32 INVALID_INDEX{0xFFFFFFFF}; constexpr u32 INVALID_INDEX{0xFFFFFFFF};
index = INVALID_INDEX; out_index = INVALID_INDEX;
// TODO(bunnei): We don't implement the Mii database, so we can't have an index // TODO(bunnei): We don't implement the Mii database, so we can't have an index
return ERROR_CANNOT_FIND_ENTRY; return ResultNotFound;
}
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) {
metadata.interface_version = version;
} }
} // namespace Service::Mii } // namespace Service::Mii

View File

@@ -6,7 +6,10 @@
#include <vector> #include <vector>
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/mii/types.h" #include "core/hle/service/mii/mii_types.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii { namespace Service::Mii {
@@ -16,26 +19,30 @@ class MiiManager {
public: public:
MiiManager(); MiiManager();
bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
bool IsFullDatabase() const; bool IsFullDatabase() const;
u32 GetCount(SourceFlag source_flag) const; u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag); Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
CharInfo BuildRandom(Age age, Gender gender, Race race); const CharInfo& char_info, SourceFlag source_flag);
CharInfo BuildBase(Gender gender); Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
CharInfo BuildDefault(std::size_t index); u32& out_count, SourceFlag source_flag);
CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
bool ValidateV3Info(const Ver3StoreData& mii_v3) const; u32& out_count, SourceFlag source_flag);
std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag); void BuildDefault(CharInfo& out_char_info, u32 index) const;
Result GetIndex(const CharInfo& info, u32& index); void BuildBase(CharInfo& out_char_info, Gender gender) const;
void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
// This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
// This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData s32& out_index);
NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
private: private:
const Common::UUID user_id{}; Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag);
Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag);
u64 update_counter{}; u64 update_counter{};
}; };

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
namespace Service::Mii {
constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
constexpr Result ResultNotFound{ErrorModule::Mii, 4};
constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
}; // namespace Service::Mii

View File

@@ -0,0 +1,694 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/uuid.h"
namespace Service::Mii {
constexpr u8 MaxHeight = 127;
constexpr u8 MaxBuild = 127;
constexpr u8 MaxType = 1;
constexpr u8 MaxRegionMove = 3;
constexpr u8 MaxEyeScale = 7;
constexpr u8 MaxEyeAspect = 6;
constexpr u8 MaxEyeRotate = 7;
constexpr u8 MaxEyeX = 12;
constexpr u8 MaxEyeY = 18;
constexpr u8 MaxEyebrowScale = 8;
constexpr u8 MaxEyebrowAspect = 6;
constexpr u8 MaxEyebrowRotate = 11;
constexpr u8 MaxEyebrowX = 12;
constexpr u8 MaxEyebrowY = 18;
constexpr u8 MaxNoseScale = 8;
constexpr u8 MaxNoseY = 18;
constexpr u8 MaxMouthScale = 8;
constexpr u8 MaxMoutAspect = 6;
constexpr u8 MaxMouthY = 18;
constexpr u8 MaxMustacheScale = 8;
constexpr u8 MasMustacheY = 16;
constexpr u8 MaxGlassScale = 7;
constexpr u8 MaxGlassY = 20;
constexpr u8 MaxMoleScale = 8;
constexpr u8 MaxMoleX = 16;
constexpr u8 MaxMoleY = 30;
constexpr u8 MaxVer3CommonColor = 7;
constexpr u8 MaxVer3GlassType = 8;
enum class Age : u8 {
Young,
Normal,
Old,
All, // Default
Max = All,
};
enum class Gender : u8 {
Male,
Female,
All, // Default
Max = Female,
};
enum class Race : u8 {
Black,
White,
Asian,
All, // Default
Max = All,
};
enum class HairType : u8 {
NormalLong, // Default
NormalShort,
NormalMedium,
NormalExtraLong,
NormalLongBottom,
NormalTwoPeaks,
PartingLong,
FrontLock,
PartingShort,
PartingExtraLongCurved,
PartingExtraLong,
PartingMiddleLong,
PartingSquared,
PartingLongBottom,
PeaksTop,
PeaksSquared,
PartingPeaks,
PeaksLongBottom,
Peaks,
PeaksRounded,
PeaksSide,
PeaksMedium,
PeaksLong,
PeaksRoundedLong,
PartingFrontPeaks,
PartingLongFront,
PartingLongRounded,
PartingFrontPeaksLong,
PartingExtraLongRounded,
LongRounded,
NormalUnknown1,
NormalUnknown2,
NormalUnknown3,
NormalUnknown4,
NormalUnknown5,
NormalUnknown6,
DreadLocks,
PlatedMats,
Caps,
Afro,
PlatedMatsLong,
Beanie,
Short,
ShortTopLongSide,
ShortUnknown1,
ShortUnknown2,
MilitaryParting,
Military,
ShortUnknown3,
ShortUnknown4,
ShortUnknown5,
ShortUnknown6,
NoneTop,
None,
LongUnknown1,
LongUnknown2,
LongUnknown3,
LongUnknown4,
LongUnknown5,
LongUnknown6,
LongUnknown7,
LongUnknown8,
LongUnknown9,
LongUnknown10,
LongUnknown11,
LongUnknown12,
LongUnknown13,
LongUnknown14,
LongUnknown15,
LongUnknown16,
LongUnknown17,
LongUnknown18,
LongUnknown19,
LongUnknown20,
LongUnknown21,
LongUnknown22,
LongUnknown23,
LongUnknown24,
LongUnknown25,
LongUnknown26,
LongUnknown27,
LongUnknown28,
LongUnknown29,
LongUnknown30,
LongUnknown31,
LongUnknown32,
LongUnknown33,
LongUnknown34,
LongUnknown35,
LongUnknown36,
LongUnknown37,
LongUnknown38,
LongUnknown39,
LongUnknown40,
LongUnknown41,
LongUnknown42,
LongUnknown43,
LongUnknown44,
LongUnknown45,
LongUnknown46,
LongUnknown47,
LongUnknown48,
LongUnknown49,
LongUnknown50,
LongUnknown51,
LongUnknown52,
LongUnknown53,
LongUnknown54,
LongUnknown55,
LongUnknown56,
LongUnknown57,
LongUnknown58,
LongUnknown59,
LongUnknown60,
LongUnknown61,
LongUnknown62,
LongUnknown63,
LongUnknown64,
LongUnknown65,
LongUnknown66,
TwoMediumFrontStrandsOneLongBackPonyTail,
TwoFrontStrandsLongBackPonyTail,
PartingFrontTwoLongBackPonyTails,
TwoFrontStrandsOneLongBackPonyTail,
LongBackPonyTail,
LongFrontTwoLongBackPonyTails,
StrandsTwoShortSidedPonyTails,
TwoMediumSidedPonyTails,
ShortFrontTwoBackPonyTails,
TwoShortSidedPonyTails,
TwoLongSidedPonyTails,
LongFrontTwoBackPonyTails,
Max = LongFrontTwoBackPonyTails,
};
enum class MoleType : u8 {
None, // Default
OneDot,
Max = OneDot,
};
enum class HairFlip : u8 {
Left, // Default
Right,
Max = Right,
};
enum class CommonColor : u8 {
// For simplicity common colors aren't listed
Max = 99,
Count = 100,
};
enum class FavoriteColor : u8 {
Red, // Default
Orange,
Yellow,
LimeGreen,
Green,
Blue,
LightBlue,
Pink,
Purple,
Brown,
White,
Black,
Max = Black,
};
enum class EyeType : u8 {
Normal, // Default
NormalLash,
WhiteLash,
WhiteNoBottom,
OvalAngledWhite,
AngryWhite,
DotLashType1,
Line,
DotLine,
OvalWhite,
RoundedWhite,
NormalShadow,
CircleWhite,
Circle,
CircleWhiteStroke,
NormalOvalNoBottom,
NormalOvalLarge,
NormalRoundedNoBottom,
SmallLash,
Small,
TwoSmall,
NormalLongLash,
WhiteTwoLashes,
WhiteThreeLashes,
DotAngry,
DotAngled,
Oval,
SmallWhite,
WhiteAngledNoBottom,
WhiteAngledNoLeft,
SmallWhiteTwoLashes,
LeafWhiteLash,
WhiteLargeNoBottom,
Dot,
DotLashType2,
DotThreeLashes,
WhiteOvalTop,
WhiteOvalBottom,
WhiteOvalBottomFlat,
WhiteOvalTwoLashes,
WhiteOvalThreeLashes,
WhiteOvalNoBottomTwoLashes,
DotWhite,
WhiteOvalTopFlat,
WhiteThinLeaf,
StarThreeLashes,
LineTwoLashes,
CrowsFeet,
WhiteNoBottomFlat,
WhiteNoBottomRounded,
WhiteSmallBottomLine,
WhiteNoBottomLash,
WhiteNoPartialBottomLash,
WhiteOvalBottomLine,
WhiteNoBottomLashTopLine,
WhiteNoPartialBottomTwoLashes,
NormalTopLine,
WhiteOvalLash,
RoundTired,
WhiteLarge,
Max = WhiteLarge,
};
enum class MouthType : u8 {
Neutral, // Default
NeutralLips,
Smile,
SmileStroke,
SmileTeeth,
LipsSmall,
LipsLarge,
Wave,
WaveAngrySmall,
NeutralStrokeLarge,
TeethSurprised,
LipsExtraLarge,
LipsUp,
NeutralDown,
Surprised,
TeethMiddle,
NeutralStroke,
LipsExtraSmall,
Malicious,
LipsDual,
NeutralComma,
NeutralUp,
TeethLarge,
WaveAngry,
LipsSexy,
SmileInverted,
LipsSexyOutline,
SmileRounded,
LipsTeeth,
NeutralOpen,
TeethRounded,
WaveAngrySmallInverted,
NeutralCommaInverted,
TeethFull,
SmileDownLine,
Kiss,
Max = Kiss,
};
enum class FontRegion : u8 {
Standard, // Default
China,
Korea,
Taiwan,
Max = Taiwan,
};
enum class FacelineType : u8 {
Sharp, // Default
Rounded,
SharpRounded,
SharpRoundedSmall,
Large,
LargeRounded,
SharpSmall,
Flat,
Bump,
Angular,
FlatRounded,
AngularSmall,
Max = AngularSmall,
};
enum class FacelineColor : u8 {
Beige, // Default
WarmBeige,
Natural,
Honey,
Chestnut,
Porcelain,
Ivory,
WarmIvory,
Almond,
Espresso,
Max = Espresso,
Count = Max + 1,
};
enum class FacelineWrinkle : u8 {
None, // Default
TearTroughs,
FacialPain,
Cheeks,
Folds,
UnderTheEyes,
SplitChin,
Chin,
BrowDroop,
MouthFrown,
CrowsFeet,
FoldsCrowsFrown,
Max = FoldsCrowsFrown,
};
enum class FacelineMake : u8 {
None, // Default
CheekPorcelain,
CheekNatural,
EyeShadowBlue,
CheekBlushPorcelain,
CheekBlushNatural,
CheekPorcelainEyeShadowBlue,
CheekPorcelainEyeShadowNatural,
CheekBlushPorcelainEyeShadowEspresso,
Freckles,
LionsManeBeard,
StubbleBeard,
Max = StubbleBeard,
};
enum class EyebrowType : u8 {
FlatAngledLarge, // Default
LowArchRoundedThin,
SoftAngledLarge,
MediumArchRoundedThin,
RoundedMedium,
LowArchMedium,
RoundedThin,
UpThin,
MediumArchRoundedMedium,
RoundedLarge,
UpLarge,
FlatAngledLargeInverted,
MediumArchFlat,
AngledThin,
HorizontalLarge,
HighArchFlat,
Flat,
MediumArchLarge,
LowArchThin,
RoundedThinInverted,
HighArchLarge,
Hairy,
Dotted,
None,
Max = None,
};
enum class NoseType : u8 {
Normal, // Default
Rounded,
Dot,
Arrow,
Roman,
Triangle,
Button,
RoundedInverted,
Potato,
Grecian,
Snub,
Aquiline,
ArrowLeft,
RoundedLarge,
Hooked,
Fat,
Droopy,
ArrowLarge,
Max = ArrowLarge,
};
enum class BeardType : u8 {
None,
Goatee,
GoateeLong,
LionsManeLong,
LionsMane,
Full,
Min = Goatee,
Max = Full,
};
enum class MustacheType : u8 {
None,
Walrus,
Pencil,
Horseshoe,
Normal,
Toothbrush,
Min = Walrus,
Max = Toothbrush,
};
enum class GlassType : u8 {
None,
Oval,
Wayfarer,
Rectangle,
TopRimless,
Rounded,
Oversized,
CatEye,
Square,
BottomRimless,
SemiOpaqueRounded,
SemiOpaqueCatEye,
SemiOpaqueOval,
SemiOpaqueRectangle,
SemiOpaqueAviator,
OpaqueRounded,
OpaqueCatEye,
OpaqueOval,
OpaqueRectangle,
OpaqueAviator,
Max = OpaqueAviator,
Count = Max + 1,
};
enum class BeardAndMustacheFlag : u32 {
Beard = 1,
Mustache,
All = Beard | Mustache,
};
DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
enum class Source : u32 {
Database = 0,
Default = 1,
Account = 2,
Friend = 3,
};
enum class SourceFlag : u32 {
None = 0,
Database = 1 << 0,
Default = 1 << 1,
};
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
enum class ValidationResult : u32 {
NoErrors = 0x0,
InvalidBeardColor = 0x1,
InvalidBeardType = 0x2,
InvalidBuild = 0x3,
InvalidEyeAspect = 0x4,
InvalidEyeColor = 0x5,
InvalidEyeRotate = 0x6,
InvalidEyeScale = 0x7,
InvalidEyeType = 0x8,
InvalidEyeX = 0x9,
InvalidEyeY = 0xa,
InvalidEyebrowAspect = 0xb,
InvalidEyebrowColor = 0xc,
InvalidEyebrowRotate = 0xd,
InvalidEyebrowScale = 0xe,
InvalidEyebrowType = 0xf,
InvalidEyebrowX = 0x10,
InvalidEyebrowY = 0x11,
InvalidFacelineColor = 0x12,
InvalidFacelineMake = 0x13,
InvalidFacelineWrinkle = 0x14,
InvalidFacelineType = 0x15,
InvalidColor = 0x16,
InvalidFont = 0x17,
InvalidGender = 0x18,
InvalidGlassColor = 0x19,
InvalidGlassScale = 0x1a,
InvalidGlassType = 0x1b,
InvalidGlassY = 0x1c,
InvalidHairColor = 0x1d,
InvalidHairFlip = 0x1e,
InvalidHairType = 0x1f,
InvalidHeight = 0x20,
InvalidMoleScale = 0x21,
InvalidMoleType = 0x22,
InvalidMoleX = 0x23,
InvalidMoleY = 0x24,
InvalidMouthAspect = 0x25,
InvalidMouthColor = 0x26,
InvalidMouthScale = 0x27,
InvalidMouthType = 0x28,
InvalidMouthY = 0x29,
InvalidMustacheScale = 0x2a,
InvalidMustacheType = 0x2b,
InvalidMustacheY = 0x2c,
InvalidNoseScale = 0x2e,
InvalidNoseType = 0x2f,
InvalidNoseY = 0x30,
InvalidRegionMove = 0x31,
InvalidCreateId = 0x32,
InvalidName = 0x33,
InvalidType = 0x35,
};
struct Nickname {
static constexpr std::size_t MaxNameSize = 10;
std::array<char16_t, MaxNameSize> data;
// Checks for null, non-zero terminated or dirty strings
bool IsValid() const {
if (data[0] == 0) {
return false;
}
if (data[MaxNameSize] != 0) {
return false;
}
std::size_t index = 1;
while (data[index] != 0) {
index++;
}
while (index < MaxNameSize && data[index] == 0) {
index++;
}
return index == MaxNameSize;
}
};
static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
struct DefaultMii {
u32 face_type{};
u32 face_color{};
u32 face_wrinkle{};
u32 face_makeup{};
u32 hair_type{};
u32 hair_color{};
u32 hair_flip{};
u32 eye_type{};
u32 eye_color{};
u32 eye_scale{};
u32 eye_aspect{};
u32 eye_rotate{};
u32 eye_x{};
u32 eye_y{};
u32 eyebrow_type{};
u32 eyebrow_color{};
u32 eyebrow_scale{};
u32 eyebrow_aspect{};
u32 eyebrow_rotate{};
u32 eyebrow_x{};
u32 eyebrow_y{};
u32 nose_type{};
u32 nose_scale{};
u32 nose_y{};
u32 mouth_type{};
u32 mouth_color{};
u32 mouth_scale{};
u32 mouth_aspect{};
u32 mouth_y{};
u32 mustache_type{};
u32 beard_type{};
u32 beard_color{};
u32 mustache_scale{};
u32 mustache_y{};
u32 glasses_type{};
u32 glasses_color{};
u32 glasses_scale{};
u32 glasses_y{};
u32 mole_type{};
u32 mole_scale{};
u32 mole_x{};
u32 mole_y{};
u32 height{};
u32 weight{};
u32 gender{};
u32 favorite_color{};
u32 region_move{};
u32 font_region{};
u32 type{};
Nickname nickname;
};
static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
struct DatabaseSessionMetadata {
u32 interface_version;
u32 magic;
u64 update_counter;
bool IsInterfaceVersionSupported(u32 version) const {
return version <= interface_version;
}
};
} // namespace Service::Mii

View File

@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <random>
#include <span>
#include "common/common_types.h"
#include "common/swap.h"
#include "common/uuid.h"
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
class MiiUtil {
public:
static u16 CalculateCrc16(const void* data, std::size_t size) {
s32 crc{};
for (std::size_t i = 0; i < size; i++) {
crc ^= static_cast<const u8*>(data)[i] << 8;
for (std::size_t j = 0; j < 8; j++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = (crc ^ 0x1021) & 0xFFFF;
}
}
}
return Common::swap16(static_cast<u16>(crc));
}
static Common::UUID MakeCreateId() {
return Common::UUID::MakeRandomRFC4122V4();
}
static Common::UUID GetDeviceId() {
// This should be nn::settings::detail::GetMiiAuthorId()
return Common::UUID::MakeDefault();
}
template <typename T>
static T GetRandomValue(T min, T max) {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<u64> distribution(static_cast<u64>(min),
static_cast<u64>(max));
return static_cast<T>(distribution(gen));
}
template <typename T>
static T GetRandomValue(T max) {
return GetRandomValue<T>({}, max);
}
static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) {
// TODO: This function needs to check against the font tables
return true;
}
};
} // namespace Service::Mii

View File

@@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "core/hle/service/mii/types.h"
namespace Service::Mii::RawData {
extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
} // namespace Service::Mii::RawData

View File

@@ -1,553 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/uuid.h"
namespace Service::Mii {
enum class Age : u32 {
Young,
Normal,
Old,
All,
};
enum class BeardType : u32 {
None,
Beard1,
Beard2,
Beard3,
Beard4,
Beard5,
};
enum class BeardAndMustacheFlag : u32 {
Beard = 1,
Mustache,
All = Beard | Mustache,
};
DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
enum class FontRegion : u32 {
Standard,
China,
Korea,
Taiwan,
};
enum class Gender : u32 {
Male,
Female,
All,
Maximum = Female,
};
enum class HairFlip : u32 {
Left,
Right,
Maximum = Right,
};
enum class MustacheType : u32 {
None,
Mustache1,
Mustache2,
Mustache3,
Mustache4,
Mustache5,
};
enum class Race : u32 {
Black,
White,
Asian,
All,
};
enum class Source : u32 {
Database = 0,
Default = 1,
Account = 2,
Friend = 3,
};
enum class SourceFlag : u32 {
None = 0,
Database = 1 << 0,
Default = 1 << 1,
};
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
// nn::mii::CharInfo
struct CharInfo {
Common::UUID uuid;
std::array<char16_t, 11> name;
u8 font_region;
u8 favorite_color;
u8 gender;
u8 height;
u8 build;
u8 type;
u8 region_move;
u8 faceline_type;
u8 faceline_color;
u8 faceline_wrinkle;
u8 faceline_make;
u8 hair_type;
u8 hair_color;
u8 hair_flip;
u8 eye_type;
u8 eye_color;
u8 eye_scale;
u8 eye_aspect;
u8 eye_rotate;
u8 eye_x;
u8 eye_y;
u8 eyebrow_type;
u8 eyebrow_color;
u8 eyebrow_scale;
u8 eyebrow_aspect;
u8 eyebrow_rotate;
u8 eyebrow_x;
u8 eyebrow_y;
u8 nose_type;
u8 nose_scale;
u8 nose_y;
u8 mouth_type;
u8 mouth_color;
u8 mouth_scale;
u8 mouth_aspect;
u8 mouth_y;
u8 beard_color;
u8 beard_type;
u8 mustache_type;
u8 mustache_scale;
u8 mustache_y;
u8 glasses_type;
u8 glasses_color;
u8 glasses_scale;
u8 glasses_y;
u8 mole_type;
u8 mole_scale;
u8 mole_x;
u8 mole_y;
u8 padding;
};
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<CharInfo>,
"All bits of CharInfo must contribute to its value.");
#pragma pack(push, 4)
struct MiiInfoElement {
MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
CharInfo info{};
Source source{};
};
static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
struct MiiStoreBitFields {
union {
u32 word_0{};
BitField<0, 8, u32> hair_type;
BitField<8, 7, u32> height;
BitField<15, 1, u32> mole_type;
BitField<16, 7, u32> build;
BitField<23, 1, HairFlip> hair_flip;
BitField<24, 7, u32> hair_color;
BitField<31, 1, u32> type;
};
union {
u32 word_1{};
BitField<0, 7, u32> eye_color;
BitField<7, 1, Gender> gender;
BitField<8, 7, u32> eyebrow_color;
BitField<16, 7, u32> mouth_color;
BitField<24, 7, u32> beard_color;
};
union {
u32 word_2{};
BitField<0, 7, u32> glasses_color;
BitField<8, 6, u32> eye_type;
BitField<14, 2, u32> region_move;
BitField<16, 6, u32> mouth_type;
BitField<22, 2, FontRegion> font_region;
BitField<24, 5, u32> eye_y;
BitField<29, 3, u32> glasses_scale;
};
union {
u32 word_3{};
BitField<0, 5, u32> eyebrow_type;
BitField<5, 3, MustacheType> mustache_type;
BitField<8, 5, u32> nose_type;
BitField<13, 3, BeardType> beard_type;
BitField<16, 5, u32> nose_y;
BitField<21, 3, u32> mouth_aspect;
BitField<24, 5, u32> mouth_y;
BitField<29, 3, u32> eyebrow_aspect;
};
union {
u32 word_4{};
BitField<0, 5, u32> mustache_y;
BitField<5, 3, u32> eye_rotate;
BitField<8, 5, u32> glasses_y;
BitField<13, 3, u32> eye_aspect;
BitField<16, 5, u32> mole_x;
BitField<21, 3, u32> eye_scale;
BitField<24, 5, u32> mole_y;
};
union {
u32 word_5{};
BitField<0, 5, u32> glasses_type;
BitField<8, 4, u32> favorite_color;
BitField<12, 4, u32> faceline_type;
BitField<16, 4, u32> faceline_color;
BitField<20, 4, u32> faceline_wrinkle;
BitField<24, 4, u32> faceline_makeup;
BitField<28, 4, u32> eye_x;
};
union {
u32 word_6{};
BitField<0, 4, u32> eyebrow_scale;
BitField<4, 4, u32> eyebrow_rotate;
BitField<8, 4, u32> eyebrow_x;
BitField<12, 4, u32> eyebrow_y;
BitField<16, 4, u32> nose_scale;
BitField<20, 4, u32> mouth_scale;
BitField<24, 4, u32> mustache_scale;
BitField<28, 4, u32> mole_scale;
};
};
static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
"MiiStoreBitFields is not trivially copyable.");
// This is nn::mii::Ver3StoreData
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
struct Ver3StoreData {
u8 version;
union {
u8 raw;
BitField<0, 1, u8> allow_copying;
BitField<1, 1, u8> profanity_flag;
BitField<2, 2, u8> region_lock;
BitField<4, 2, u8> character_set;
} region_information;
u16_be mii_id;
u64_be system_id;
u32_be specialness_and_creation_date;
std::array<u8, 0x6> creator_mac;
u16_be padding;
union {
u16 raw;
BitField<0, 1, u16> gender;
BitField<1, 4, u16> birth_month;
BitField<5, 5, u16> birth_day;
BitField<10, 4, u16> favorite_color;
BitField<14, 1, u16> favorite;
} mii_information;
std::array<char16_t, 0xA> mii_name;
u8 height;
u8 build;
union {
u8 raw;
BitField<0, 1, u8> disable_sharing;
BitField<1, 4, u8> face_shape;
BitField<5, 3, u8> skin_color;
} appearance_bits1;
union {
u8 raw;
BitField<0, 4, u8> wrinkles;
BitField<4, 4, u8> makeup;
} appearance_bits2;
u8 hair_style;
union {
u8 raw;
BitField<0, 3, u8> hair_color;
BitField<3, 1, u8> flip_hair;
} appearance_bits3;
union {
u32 raw;
BitField<0, 6, u32> eye_type;
BitField<6, 3, u32> eye_color;
BitField<9, 4, u32> eye_scale;
BitField<13, 3, u32> eye_vertical_stretch;
BitField<16, 5, u32> eye_rotation;
BitField<21, 4, u32> eye_spacing;
BitField<25, 5, u32> eye_y_position;
} appearance_bits4;
union {
u32 raw;
BitField<0, 5, u32> eyebrow_style;
BitField<5, 3, u32> eyebrow_color;
BitField<8, 4, u32> eyebrow_scale;
BitField<12, 3, u32> eyebrow_yscale;
BitField<16, 4, u32> eyebrow_rotation;
BitField<21, 4, u32> eyebrow_spacing;
BitField<25, 5, u32> eyebrow_y_position;
} appearance_bits5;
union {
u16 raw;
BitField<0, 5, u16> nose_type;
BitField<5, 4, u16> nose_scale;
BitField<9, 5, u16> nose_y_position;
} appearance_bits6;
union {
u16 raw;
BitField<0, 6, u16> mouth_type;
BitField<6, 3, u16> mouth_color;
BitField<9, 4, u16> mouth_scale;
BitField<13, 3, u16> mouth_horizontal_stretch;
} appearance_bits7;
union {
u8 raw;
BitField<0, 5, u8> mouth_y_position;
BitField<5, 3, u8> mustache_type;
} appearance_bits8;
u8 allow_copying;
union {
u16 raw;
BitField<0, 3, u16> bear_type;
BitField<3, 3, u16> facial_hair_color;
BitField<6, 4, u16> mustache_scale;
BitField<10, 5, u16> mustache_y_position;
} appearance_bits9;
union {
u16 raw;
BitField<0, 4, u16> glasses_type;
BitField<4, 3, u16> glasses_color;
BitField<7, 4, u16> glasses_scale;
BitField<11, 5, u16> glasses_y_position;
} appearance_bits10;
union {
u16 raw;
BitField<0, 1, u16> mole_enabled;
BitField<1, 4, u16> mole_scale;
BitField<5, 5, u16> mole_x_position;
BitField<10, 5, u16> mole_y_position;
} appearance_bits11;
std::array<u16_le, 0xA> author_name;
INSERT_PADDING_BYTES(0x2);
u16_be crc;
};
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
struct NfpStoreDataExtension {
u8 faceline_color;
u8 hair_color;
u8 eye_color;
u8 eyebrow_color;
u8 mouth_color;
u8 beard_color;
u8 glass_color;
u8 glass_type;
};
static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
};
constexpr std::array<u8, 100> Ver3HairColorTable{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
};
constexpr std::array<u8, 100> Ver3EyeColorTable{
0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
};
constexpr std::array<u8, 100> Ver3MouthlineColorTable{
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
};
constexpr std::array<u8, 100> Ver3GlassColorTable{
0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
};
constexpr std::array<u8, 20> Ver3GlassTypeTable{
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
};
struct MiiStoreData {
using Name = std::array<char16_t, 10>;
MiiStoreData();
MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
const Common::UUID& user_id);
// This corresponds to the above structure MiiStoreBitFields. I did it like this because the
// BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
// not suitable for our uses.
struct {
std::array<u8, 0x1C> data{};
static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
Name name{};
Common::UUID uuid{};
} data;
u16 data_crc{};
u16 device_crc{};
};
static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
struct MiiStoreDataElement {
MiiStoreData data{};
Source source{};
};
static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
struct MiiDatabase {
u32 magic{}; // 'NFDB'
std::array<MiiStoreData, 0x64> miis{};
INSERT_PADDING_BYTES(1);
u8 count{};
u16 crc{};
};
static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
struct RandomMiiValues {
std::array<u8, 0xbc> values{};
};
static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
struct RandomMiiData4 {
Gender gender{};
Age age{};
Race race{};
u32 values_count{};
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
struct RandomMiiData3 {
u32 arg_1;
u32 arg_2;
u32 values_count;
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
struct RandomMiiData2 {
u32 arg_1;
u32 values_count;
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
struct DefaultMii {
u32 face_type{};
u32 face_color{};
u32 face_wrinkle{};
u32 face_makeup{};
u32 hair_type{};
u32 hair_color{};
u32 hair_flip{};
u32 eye_type{};
u32 eye_color{};
u32 eye_scale{};
u32 eye_aspect{};
u32 eye_rotate{};
u32 eye_x{};
u32 eye_y{};
u32 eyebrow_type{};
u32 eyebrow_color{};
u32 eyebrow_scale{};
u32 eyebrow_aspect{};
u32 eyebrow_rotate{};
u32 eyebrow_x{};
u32 eyebrow_y{};
u32 nose_type{};
u32 nose_scale{};
u32 nose_y{};
u32 mouth_type{};
u32 mouth_color{};
u32 mouth_scale{};
u32 mouth_aspect{};
u32 mouth_y{};
u32 mustache_type{};
u32 beard_type{};
u32 beard_color{};
u32 mustache_scale{};
u32 mustache_y{};
u32 glasses_type{};
u32 glasses_color{};
u32 glasses_scale{};
u32 glasses_y{};
u32 mole_type{};
u32 mole_scale{};
u32 mole_x{};
u32 mole_y{};
u32 height{};
u32 weight{};
Gender gender{};
u32 favorite_color{};
u32 region{};
FontRegion font_region{};
u32 type{};
INSERT_PADDING_WORDS(5);
};
static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
#pragma pack(pop)
} // namespace Service::Mii

View File

@@ -0,0 +1,482 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
void CharInfo::SetFromStoreData(const StoreData& store_data) {
name = store_data.GetNickname();
null_terminator = '\0';
create_id = store_data.GetCreateId();
font_region = store_data.GetFontRegion();
favorite_color = store_data.GetFavoriteColor();
gender = store_data.GetGender();
height = store_data.GetHeight();
build = store_data.GetBuild();
type = store_data.GetType();
region_move = store_data.GetRegionMove();
faceline_type = store_data.GetFacelineType();
faceline_color = store_data.GetFacelineColor();
faceline_wrinkle = store_data.GetFacelineWrinkle();
faceline_make = store_data.GetFacelineMake();
hair_type = store_data.GetHairType();
hair_color = store_data.GetHairColor();
hair_flip = store_data.GetHairFlip();
eye_type = store_data.GetEyeType();
eye_color = store_data.GetEyeColor();
eye_scale = store_data.GetEyeScale();
eye_aspect = store_data.GetEyeAspect();
eye_rotate = store_data.GetEyeRotate();
eye_x = store_data.GetEyeX();
eye_y = store_data.GetEyeY();
eyebrow_type = store_data.GetEyebrowType();
eyebrow_color = store_data.GetEyebrowColor();
eyebrow_scale = store_data.GetEyebrowScale();
eyebrow_aspect = store_data.GetEyebrowAspect();
eyebrow_rotate = store_data.GetEyebrowRotate();
eyebrow_x = store_data.GetEyebrowX();
eyebrow_y = store_data.GetEyebrowY();
nose_type = store_data.GetNoseType();
nose_scale = store_data.GetNoseScale();
nose_y = store_data.GetNoseY();
mouth_type = store_data.GetMouthType();
mouth_color = store_data.GetMouthColor();
mouth_scale = store_data.GetMouthScale();
mouth_aspect = store_data.GetMouthAspect();
mouth_y = store_data.GetMouthY();
beard_color = store_data.GetBeardColor();
beard_type = store_data.GetBeardType();
mustache_type = store_data.GetMustacheType();
mustache_scale = store_data.GetMustacheScale();
mustache_y = store_data.GetMustacheY();
glass_type = store_data.GetGlassType();
glass_color = store_data.GetGlassColor();
glass_scale = store_data.GetGlassScale();
glass_y = store_data.GetGlassY();
mole_type = store_data.GetMoleType();
mole_scale = store_data.GetMoleScale();
mole_x = store_data.GetMoleX();
mole_y = store_data.GetMoleY();
padding = '\0';
}
ValidationResult CharInfo::Verify() const {
if (!create_id.IsValid()) {
return ValidationResult::InvalidCreateId;
}
if (!name.IsValid()) {
return ValidationResult::InvalidName;
}
if (font_region > FontRegion::Max) {
return ValidationResult::InvalidFont;
}
if (favorite_color > FavoriteColor::Max) {
return ValidationResult::InvalidColor;
}
if (gender > Gender::Max) {
return ValidationResult::InvalidGender;
}
if (height > MaxHeight) {
return ValidationResult::InvalidHeight;
}
if (build > MaxBuild) {
return ValidationResult::InvalidBuild;
}
if (type > MaxType) {
return ValidationResult::InvalidType;
}
if (region_move > MaxRegionMove) {
return ValidationResult::InvalidRegionMove;
}
if (faceline_type > FacelineType::Max) {
return ValidationResult::InvalidFacelineType;
}
if (faceline_color > FacelineColor::Max) {
return ValidationResult::InvalidFacelineColor;
}
if (faceline_wrinkle > FacelineWrinkle::Max) {
return ValidationResult::InvalidFacelineWrinkle;
}
if (faceline_make > FacelineMake::Max) {
return ValidationResult::InvalidFacelineMake;
}
if (hair_type > HairType::Max) {
return ValidationResult::InvalidHairType;
}
if (hair_color > CommonColor::Max) {
return ValidationResult::InvalidHairColor;
}
if (hair_flip > HairFlip::Max) {
return ValidationResult::InvalidHairFlip;
}
if (eye_type > EyeType::Max) {
return ValidationResult::InvalidEyeType;
}
if (eye_color > CommonColor::Max) {
return ValidationResult::InvalidEyeColor;
}
if (eye_scale > MaxEyeScale) {
return ValidationResult::InvalidEyeScale;
}
if (eye_aspect > MaxEyeAspect) {
return ValidationResult::InvalidEyeAspect;
}
if (eye_rotate > MaxEyeX) {
return ValidationResult::InvalidEyeRotate;
}
if (eye_x > MaxEyeX) {
return ValidationResult::InvalidEyeX;
}
if (eye_y > MaxEyeY) {
return ValidationResult::InvalidEyeY;
}
if (eyebrow_type > EyebrowType::Max) {
return ValidationResult::InvalidEyebrowType;
}
if (eyebrow_color > CommonColor::Max) {
return ValidationResult::InvalidEyebrowColor;
}
if (eyebrow_scale > MaxEyebrowScale) {
return ValidationResult::InvalidEyebrowScale;
}
if (eyebrow_aspect > MaxEyebrowAspect) {
return ValidationResult::InvalidEyebrowAspect;
}
if (eyebrow_rotate > MaxEyebrowRotate) {
return ValidationResult::InvalidEyebrowRotate;
}
if (eyebrow_x > MaxEyebrowX) {
return ValidationResult::InvalidEyebrowX;
}
if (eyebrow_y > MaxEyebrowY) {
return ValidationResult::InvalidEyebrowY;
}
if (nose_type > NoseType::Max) {
return ValidationResult::InvalidNoseType;
}
if (nose_scale > MaxNoseScale) {
return ValidationResult::InvalidNoseScale;
}
if (nose_y > MaxNoseY) {
return ValidationResult::InvalidNoseY;
}
if (mouth_type > MouthType::Max) {
return ValidationResult::InvalidMouthType;
}
if (mouth_color > CommonColor::Max) {
return ValidationResult::InvalidMouthColor;
}
if (mouth_scale > MaxMouthScale) {
return ValidationResult::InvalidMouthScale;
}
if (mouth_aspect > MaxMoutAspect) {
return ValidationResult::InvalidMouthAspect;
}
if (mouth_y > MaxMouthY) {
return ValidationResult::InvalidMoleY;
}
if (beard_color > CommonColor::Max) {
return ValidationResult::InvalidBeardColor;
}
if (beard_type > BeardType::Max) {
return ValidationResult::InvalidBeardType;
}
if (mustache_type > MustacheType::Max) {
return ValidationResult::InvalidMustacheType;
}
if (mustache_scale > MaxMustacheScale) {
return ValidationResult::InvalidMustacheScale;
}
if (mustache_y > MasMustacheY) {
return ValidationResult::InvalidMustacheY;
}
if (glass_type > GlassType::Max) {
return ValidationResult::InvalidGlassType;
}
if (glass_color > CommonColor::Max) {
return ValidationResult::InvalidGlassColor;
}
if (glass_scale > MaxGlassScale) {
return ValidationResult::InvalidGlassScale;
}
if (glass_y > MaxGlassY) {
return ValidationResult::InvalidGlassY;
}
if (mole_type > MoleType::Max) {
return ValidationResult::InvalidMoleType;
}
if (mole_scale > MaxMoleScale) {
return ValidationResult::InvalidMoleScale;
}
if (mole_x > MaxMoleX) {
return ValidationResult::InvalidMoleX;
}
if (mole_y > MaxMoleY) {
return ValidationResult::InvalidMoleY;
}
return ValidationResult::NoErrors;
}
Common::UUID CharInfo::GetCreateId() const {
return create_id;
}
Nickname CharInfo::GetNickname() const {
return name;
}
FontRegion CharInfo::GetFontRegion() const {
return font_region;
}
FavoriteColor CharInfo::GetFavoriteColor() const {
return favorite_color;
}
Gender CharInfo::GetGender() const {
return gender;
}
u8 CharInfo::GetHeight() const {
return height;
}
u8 CharInfo::GetBuild() const {
return build;
}
u8 CharInfo::GetType() const {
return type;
}
u8 CharInfo::GetRegionMove() const {
return region_move;
}
FacelineType CharInfo::GetFacelineType() const {
return faceline_type;
}
FacelineColor CharInfo::GetFacelineColor() const {
return faceline_color;
}
FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
return faceline_wrinkle;
}
FacelineMake CharInfo::GetFacelineMake() const {
return faceline_make;
}
HairType CharInfo::GetHairType() const {
return hair_type;
}
CommonColor CharInfo::GetHairColor() const {
return hair_color;
}
HairFlip CharInfo::GetHairFlip() const {
return hair_flip;
}
EyeType CharInfo::GetEyeType() const {
return eye_type;
}
CommonColor CharInfo::GetEyeColor() const {
return eye_color;
}
u8 CharInfo::GetEyeScale() const {
return eye_scale;
}
u8 CharInfo::GetEyeAspect() const {
return eye_aspect;
}
u8 CharInfo::GetEyeRotate() const {
return eye_rotate;
}
u8 CharInfo::GetEyeX() const {
return eye_x;
}
u8 CharInfo::GetEyeY() const {
return eye_y;
}
EyebrowType CharInfo::GetEyebrowType() const {
return eyebrow_type;
}
CommonColor CharInfo::GetEyebrowColor() const {
return eyebrow_color;
}
u8 CharInfo::GetEyebrowScale() const {
return eyebrow_scale;
}
u8 CharInfo::GetEyebrowAspect() const {
return eyebrow_aspect;
}
u8 CharInfo::GetEyebrowRotate() const {
return eyebrow_rotate;
}
u8 CharInfo::GetEyebrowX() const {
return eyebrow_x;
}
u8 CharInfo::GetEyebrowY() const {
return eyebrow_y;
}
NoseType CharInfo::GetNoseType() const {
return nose_type;
}
u8 CharInfo::GetNoseScale() const {
return nose_scale;
}
u8 CharInfo::GetNoseY() const {
return nose_y;
}
MouthType CharInfo::GetMouthType() const {
return mouth_type;
}
CommonColor CharInfo::GetMouthColor() const {
return mouth_color;
}
u8 CharInfo::GetMouthScale() const {
return mouth_scale;
}
u8 CharInfo::GetMouthAspect() const {
return mouth_aspect;
}
u8 CharInfo::GetMouthY() const {
return mouth_y;
}
CommonColor CharInfo::GetBeardColor() const {
return beard_color;
}
BeardType CharInfo::GetBeardType() const {
return beard_type;
}
MustacheType CharInfo::GetMustacheType() const {
return mustache_type;
}
u8 CharInfo::GetMustacheScale() const {
return mustache_scale;
}
u8 CharInfo::GetMustacheY() const {
return mustache_y;
}
GlassType CharInfo::GetGlassType() const {
return glass_type;
}
CommonColor CharInfo::GetGlassColor() const {
return glass_color;
}
u8 CharInfo::GetGlassScale() const {
return glass_scale;
}
u8 CharInfo::GetGlassY() const {
return glass_y;
}
MoleType CharInfo::GetMoleType() const {
return mole_type;
}
u8 CharInfo::GetMoleScale() const {
return mole_scale;
}
u8 CharInfo::GetMoleX() const {
return mole_x;
}
u8 CharInfo::GetMoleY() const {
return mole_y;
}
bool CharInfo::operator==(const CharInfo& info) {
bool is_identical = info.Verify() == ValidationResult::NoErrors;
is_identical &= name.data == info.GetNickname().data;
is_identical &= create_id == info.GetCreateId();
is_identical &= font_region == info.GetFontRegion();
is_identical &= favorite_color == info.GetFavoriteColor();
is_identical &= gender == info.GetGender();
is_identical &= height == info.GetHeight();
is_identical &= build == info.GetBuild();
is_identical &= type == info.GetType();
is_identical &= region_move == info.GetRegionMove();
is_identical &= faceline_type == info.GetFacelineType();
is_identical &= faceline_color == info.GetFacelineColor();
is_identical &= faceline_wrinkle == info.GetFacelineWrinkle();
is_identical &= faceline_make == info.GetFacelineMake();
is_identical &= hair_type == info.GetHairType();
is_identical &= hair_color == info.GetHairColor();
is_identical &= hair_flip == info.GetHairFlip();
is_identical &= eye_type == info.GetEyeType();
is_identical &= eye_color == info.GetEyeColor();
is_identical &= eye_scale == info.GetEyeScale();
is_identical &= eye_aspect == info.GetEyeAspect();
is_identical &= eye_rotate == info.GetEyeRotate();
is_identical &= eye_x == info.GetEyeX();
is_identical &= eye_y == info.GetEyeY();
is_identical &= eyebrow_type == info.GetEyebrowType();
is_identical &= eyebrow_color == info.GetEyebrowColor();
is_identical &= eyebrow_scale == info.GetEyebrowScale();
is_identical &= eyebrow_aspect == info.GetEyebrowAspect();
is_identical &= eyebrow_rotate == info.GetEyebrowRotate();
is_identical &= eyebrow_x == info.GetEyebrowX();
is_identical &= eyebrow_y == info.GetEyebrowY();
is_identical &= nose_type == info.GetNoseType();
is_identical &= nose_scale == info.GetNoseScale();
is_identical &= nose_y == info.GetNoseY();
is_identical &= mouth_type == info.GetMouthType();
is_identical &= mouth_color == info.GetMouthColor();
is_identical &= mouth_scale == info.GetMouthScale();
is_identical &= mouth_aspect == info.GetMouthAspect();
is_identical &= mouth_y == info.GetMouthY();
is_identical &= beard_color == info.GetBeardColor();
is_identical &= beard_type == info.GetBeardType();
is_identical &= mustache_type == info.GetMustacheType();
is_identical &= mustache_scale == info.GetMustacheScale();
is_identical &= mustache_y == info.GetMustacheY();
is_identical &= glass_type == info.GetGlassType();
is_identical &= glass_color == info.GetGlassColor();
is_identical &= glass_scale == info.GetGlassScale();
is_identical &= glass_y == info.GetGlassY();
is_identical &= mole_type == info.GetMoleType();
is_identical &= mole_scale == info.GetMoleScale();
is_identical &= mole_x == info.GetMoleX();
is_identical &= mole_y == info.GetMoleY();
return is_identical;
}
} // namespace Service::Mii

View File

@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
class StoreData;
// This is nn::mii::detail::CharInfoRaw
class CharInfo {
public:
void SetFromStoreData(const StoreData& store_data_raw);
ValidationResult Verify() const;
Common::UUID GetCreateId() const;
Nickname GetNickname() const;
FontRegion GetFontRegion() const;
FavoriteColor GetFavoriteColor() const;
Gender GetGender() const;
u8 GetHeight() const;
u8 GetBuild() const;
u8 GetType() const;
u8 GetRegionMove() const;
FacelineType GetFacelineType() const;
FacelineColor GetFacelineColor() const;
FacelineWrinkle GetFacelineWrinkle() const;
FacelineMake GetFacelineMake() const;
HairType GetHairType() const;
CommonColor GetHairColor() const;
HairFlip GetHairFlip() const;
EyeType GetEyeType() const;
CommonColor GetEyeColor() const;
u8 GetEyeScale() const;
u8 GetEyeAspect() const;
u8 GetEyeRotate() const;
u8 GetEyeX() const;
u8 GetEyeY() const;
EyebrowType GetEyebrowType() const;
CommonColor GetEyebrowColor() const;
u8 GetEyebrowScale() const;
u8 GetEyebrowAspect() const;
u8 GetEyebrowRotate() const;
u8 GetEyebrowX() const;
u8 GetEyebrowY() const;
NoseType GetNoseType() const;
u8 GetNoseScale() const;
u8 GetNoseY() const;
MouthType GetMouthType() const;
CommonColor GetMouthColor() const;
u8 GetMouthScale() const;
u8 GetMouthAspect() const;
u8 GetMouthY() const;
CommonColor GetBeardColor() const;
BeardType GetBeardType() const;
MustacheType GetMustacheType() const;
u8 GetMustacheScale() const;
u8 GetMustacheY() const;
GlassType GetGlassType() const;
CommonColor GetGlassColor() const;
u8 GetGlassScale() const;
u8 GetGlassY() const;
MoleType GetMoleType() const;
u8 GetMoleScale() const;
u8 GetMoleX() const;
u8 GetMoleY() const;
bool operator==(const CharInfo& info);
private:
Common::UUID create_id;
Nickname name;
u16 null_terminator;
FontRegion font_region;
FavoriteColor favorite_color;
Gender gender;
u8 height;
u8 build;
u8 type;
u8 region_move;
FacelineType faceline_type;
FacelineColor faceline_color;
FacelineWrinkle faceline_wrinkle;
FacelineMake faceline_make;
HairType hair_type;
CommonColor hair_color;
HairFlip hair_flip;
EyeType eye_type;
CommonColor eye_color;
u8 eye_scale;
u8 eye_aspect;
u8 eye_rotate;
u8 eye_x;
u8 eye_y;
EyebrowType eyebrow_type;
CommonColor eyebrow_color;
u8 eyebrow_scale;
u8 eyebrow_aspect;
u8 eyebrow_rotate;
u8 eyebrow_x;
u8 eyebrow_y;
NoseType nose_type;
u8 nose_scale;
u8 nose_y;
MouthType mouth_type;
CommonColor mouth_color;
u8 mouth_scale;
u8 mouth_aspect;
u8 mouth_y;
CommonColor beard_color;
BeardType beard_type;
MustacheType mustache_type;
u8 mustache_scale;
u8 mustache_y;
GlassType glass_type;
CommonColor glass_color;
u8 glass_scale;
u8 glass_y;
MoleType mole_type;
u8 mole_scale;
u8 mole_x;
u8 mole_y;
u8 padding;
};
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
static_assert(std::has_unique_object_representations_v<CharInfo>,
"All bits of CharInfo must contribute to its value.");
struct CharInfoElement {
CharInfo char_info{};
Source source{};
};
static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size.");
}; // namespace Service::Mii

View File

@@ -0,0 +1,601 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/core_data.h"
#include "core/hle/service/mii/types/raw_data.h"
namespace Service::Mii {
void CoreData::SetDefault() {
data = {};
name = GetDefaultNickname();
}
void CoreData::BuildRandom(Age age, Gender gender, Race race) {
if (gender == Gender::All) {
gender = MiiUtil::GetRandomValue(Gender::Max);
}
if (age == Age::All) {
const auto random{MiiUtil::GetRandomValue<int>(10)};
if (random >= 8) {
age = Age::Old;
} else if (random >= 4) {
age = Age::Normal;
} else {
age = Age::Young;
}
}
if (race == Race::All) {
const auto random{MiiUtil::GetRandomValue<int>(10)};
if (random >= 8) {
race = Race::Black;
} else if (random >= 4) {
race = Race::White;
} else {
race = Race::Asian;
}
}
SetGender(gender);
SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
SetRegionMove(0);
SetFontRegion(FontRegion::Standard);
SetType(0);
SetHeight(64);
SetBuild(64);
u32 axis_y{};
if (gender == Gender::Female && age == Age::Young) {
axis_y = MiiUtil::GetRandomValue<u32>(3);
}
const std::size_t index{3 * static_cast<std::size_t>(age) +
9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)};
const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at(
3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
const auto& hair_type_info{RawData::RandomMiiHairType.at(index)};
const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
static_cast<std::size_t>(age))};
const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)};
const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)};
const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)};
const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
data.faceline_type.Assign(
faceline_type_info
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
data.faceline_color.Assign(
faceline_color_info
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
data.faceline_wrinkle.Assign(
faceline_wrinkle_info
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
data.faceline_makeup.Assign(
faceline_makeup_info
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
data.hair_type.Assign(
hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]);
SetHairColor(RawData::GetHairColorFromVer3(
hair_color_info
.values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)]));
SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max));
data.eye_type.Assign(
eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]);
const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]};
SetEyeColor(RawData::GetEyeColorFromVer3(
eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)]));
SetEyeScale(4);
SetEyeAspect(3);
SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate));
SetEyeX(2);
SetEyeY(static_cast<u8>(axis_y + 12));
data.eyebrow_type.Assign(
eyebrow_type_info
.values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
const auto eyebrow_y{race == Race::Asian ? 9 : 10};
const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
const auto eyebrow_rotate{
32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
SetEyebrowColor(GetHairColor());
SetEyebrowScale(4);
SetEyebrowAspect(3);
SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate));
SetEyebrowX(2);
SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y));
data.nose_type.Assign(
nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]);
SetNoseScale(gender == Gender::Female ? 3 : 4);
SetNoseY(static_cast<u8>(axis_y + 9));
const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0};
data.mouth_type.Assign(
mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color));
SetMouthScale(4);
SetMouthAspect(3);
SetMouthY(static_cast<u8>(axis_y + 13));
SetBeardColor(GetHairColor());
SetMustacheScale(4);
if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) {
const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)};
auto beard_type{BeardType::None};
auto mustache_type{MustacheType::None};
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
BeardAndMustacheFlag::Beard) {
beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max);
}
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
BeardAndMustacheFlag::Mustache) {
mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max);
}
SetMustacheType(mustache_type);
SetBeardType(beard_type);
SetMustacheY(10);
} else {
SetMustacheType(MustacheType::None);
SetBeardType(BeardType::None);
SetMustacheY(static_cast<u8>(axis_y + 10));
}
const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)};
u8 glasses_type{};
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
if (++glasses_type >= glasses_type_info.values_count) {
ASSERT(false);
break;
}
}
SetGlassType(static_cast<GlassType>(glasses_type));
SetGlassColor(RawData::GetGlassColorFromVer3(0));
SetGlassScale(4);
SetMoleType(MoleType::None);
SetMoleScale(4);
SetMoleX(2);
SetMoleY(20);
}
u32 CoreData::IsValid() const {
// TODO: Complete this
return 0;
}
void CoreData::SetFontRegion(FontRegion value) {
data.font_region.Assign(static_cast<u32>(value));
}
void CoreData::SetFavoriteColor(FavoriteColor value) {
data.favorite_color.Assign(static_cast<u32>(value));
}
void CoreData::SetGender(Gender value) {
data.gender.Assign(static_cast<u32>(value));
}
void CoreData::SetHeight(u8 value) {
data.height.Assign(value);
}
void CoreData::SetBuild(u8 value) {
data.build.Assign(value);
}
void CoreData::SetType(u8 value) {
data.type.Assign(value);
}
void CoreData::SetRegionMove(u8 value) {
data.region_move.Assign(value);
}
void CoreData::SetFacelineType(FacelineType value) {
data.faceline_type.Assign(static_cast<u32>(value));
}
void CoreData::SetFacelineColor(FacelineColor value) {
data.faceline_color.Assign(static_cast<u32>(value));
}
void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
data.faceline_wrinkle.Assign(static_cast<u32>(value));
}
void CoreData::SetFacelineMake(FacelineMake value) {
data.faceline_makeup.Assign(static_cast<u32>(value));
}
void CoreData::SetHairType(HairType value) {
data.hair_type.Assign(static_cast<u32>(value));
}
void CoreData::SetHairColor(CommonColor value) {
data.hair_color.Assign(static_cast<u32>(value));
}
void CoreData::SetHairFlip(HairFlip value) {
data.hair_flip.Assign(static_cast<u32>(value));
}
void CoreData::SetEyeType(EyeType value) {
data.eye_type.Assign(static_cast<u32>(value));
}
void CoreData::SetEyeColor(CommonColor value) {
data.eye_color.Assign(static_cast<u32>(value));
}
void CoreData::SetEyeScale(u8 value) {
data.eye_scale.Assign(value);
}
void CoreData::SetEyeAspect(u8 value) {
data.eye_aspect.Assign(value);
}
void CoreData::SetEyeRotate(u8 value) {
data.eye_rotate.Assign(value);
}
void CoreData::SetEyeX(u8 value) {
data.eye_x.Assign(value);
}
void CoreData::SetEyeY(u8 value) {
data.eye_y.Assign(value);
}
void CoreData::SetEyebrowType(EyebrowType value) {
data.eyebrow_type.Assign(static_cast<u32>(value));
}
void CoreData::SetEyebrowColor(CommonColor value) {
data.eyebrow_color.Assign(static_cast<u32>(value));
}
void CoreData::SetEyebrowScale(u8 value) {
data.eyebrow_scale.Assign(value);
}
void CoreData::SetEyebrowAspect(u8 value) {
data.eyebrow_aspect.Assign(value);
}
void CoreData::SetEyebrowRotate(u8 value) {
data.eyebrow_rotate.Assign(value);
}
void CoreData::SetEyebrowX(u8 value) {
data.eyebrow_x.Assign(value);
}
void CoreData::SetEyebrowY(u8 value) {
data.eyebrow_y.Assign(value);
}
void CoreData::SetNoseType(NoseType value) {
data.nose_type.Assign(static_cast<u32>(value));
}
void CoreData::SetNoseScale(u8 value) {
data.nose_scale.Assign(value);
}
void CoreData::SetNoseY(u8 value) {
data.nose_y.Assign(value);
}
void CoreData::SetMouthType(u8 value) {
data.mouth_type.Assign(value);
}
void CoreData::SetMouthColor(CommonColor value) {
data.mouth_color.Assign(static_cast<u32>(value));
}
void CoreData::SetMouthScale(u8 value) {
data.mouth_scale.Assign(value);
}
void CoreData::SetMouthAspect(u8 value) {
data.mouth_aspect.Assign(value);
}
void CoreData::SetMouthY(u8 value) {
data.mouth_y.Assign(value);
}
void CoreData::SetBeardColor(CommonColor value) {
data.beard_color.Assign(static_cast<u32>(value));
}
void CoreData::SetBeardType(BeardType value) {
data.beard_type.Assign(static_cast<u32>(value));
}
void CoreData::SetMustacheType(MustacheType value) {
data.mustache_type.Assign(static_cast<u32>(value));
}
void CoreData::SetMustacheScale(u8 value) {
data.mustache_scale.Assign(value);
}
void CoreData::SetMustacheY(u8 value) {
data.mustache_y.Assign(value);
}
void CoreData::SetGlassType(GlassType value) {
data.glasses_type.Assign(static_cast<u32>(value));
}
void CoreData::SetGlassColor(CommonColor value) {
data.glasses_color.Assign(static_cast<u32>(value));
}
void CoreData::SetGlassScale(u8 value) {
data.glasses_scale.Assign(value);
}
void CoreData::SetGlassY(u8 value) {
data.glasses_y.Assign(value);
}
void CoreData::SetMoleType(MoleType value) {
data.mole_type.Assign(static_cast<u32>(value));
}
void CoreData::SetMoleScale(u8 value) {
data.mole_scale.Assign(value);
}
void CoreData::SetMoleX(u8 value) {
data.mole_x.Assign(value);
}
void CoreData::SetMoleY(u8 value) {
data.mole_y.Assign(value);
}
void CoreData::SetNickname(Nickname nickname) {
name = nickname;
}
FontRegion CoreData::GetFontRegion() const {
return static_cast<FontRegion>(data.font_region.Value());
}
FavoriteColor CoreData::GetFavoriteColor() const {
return static_cast<FavoriteColor>(data.favorite_color.Value());
}
Gender CoreData::GetGender() const {
return static_cast<Gender>(data.gender.Value());
}
u8 CoreData::GetHeight() const {
return static_cast<u8>(data.height.Value());
}
u8 CoreData::GetBuild() const {
return static_cast<u8>(data.build.Value());
}
u8 CoreData::GetType() const {
return static_cast<u8>(data.type.Value());
}
u8 CoreData::GetRegionMove() const {
return static_cast<u8>(data.region_move.Value());
}
FacelineType CoreData::GetFacelineType() const {
return static_cast<FacelineType>(data.faceline_type.Value());
}
FacelineColor CoreData::GetFacelineColor() const {
return static_cast<FacelineColor>(data.faceline_color.Value());
}
FacelineWrinkle CoreData::GetFacelineWrinkle() const {
return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
}
FacelineMake CoreData::GetFacelineMake() const {
return static_cast<FacelineMake>(data.faceline_makeup.Value());
}
HairType CoreData::GetHairType() const {
return static_cast<HairType>(data.hair_type.Value());
}
CommonColor CoreData::GetHairColor() const {
return static_cast<CommonColor>(data.hair_color.Value());
}
HairFlip CoreData::GetHairFlip() const {
return static_cast<HairFlip>(data.hair_flip.Value());
}
EyeType CoreData::GetEyeType() const {
return static_cast<EyeType>(data.eye_type.Value());
}
CommonColor CoreData::GetEyeColor() const {
return static_cast<CommonColor>(data.eye_color.Value());
}
u8 CoreData::GetEyeScale() const {
return static_cast<u8>(data.eye_scale.Value());
}
u8 CoreData::GetEyeAspect() const {
return static_cast<u8>(data.eye_aspect.Value());
}
u8 CoreData::GetEyeRotate() const {
return static_cast<u8>(data.eye_rotate.Value());
}
u8 CoreData::GetEyeX() const {
return static_cast<u8>(data.eye_x.Value());
}
u8 CoreData::GetEyeY() const {
return static_cast<u8>(data.eye_y.Value());
}
EyebrowType CoreData::GetEyebrowType() const {
return static_cast<EyebrowType>(data.eyebrow_type.Value());
}
CommonColor CoreData::GetEyebrowColor() const {
return static_cast<CommonColor>(data.eyebrow_color.Value());
}
u8 CoreData::GetEyebrowScale() const {
return static_cast<u8>(data.eyebrow_scale.Value());
}
u8 CoreData::GetEyebrowAspect() const {
return static_cast<u8>(data.eyebrow_aspect.Value());
}
u8 CoreData::GetEyebrowRotate() const {
return static_cast<u8>(data.eyebrow_rotate.Value());
}
u8 CoreData::GetEyebrowX() const {
return static_cast<u8>(data.eyebrow_x.Value());
}
u8 CoreData::GetEyebrowY() const {
return static_cast<u8>(data.eyebrow_y.Value());
}
NoseType CoreData::GetNoseType() const {
return static_cast<NoseType>(data.nose_type.Value());
}
u8 CoreData::GetNoseScale() const {
return static_cast<u8>(data.nose_scale.Value());
}
u8 CoreData::GetNoseY() const {
return static_cast<u8>(data.nose_y.Value());
}
MouthType CoreData::GetMouthType() const {
return static_cast<MouthType>(data.mouth_type.Value());
}
CommonColor CoreData::GetMouthColor() const {
return static_cast<CommonColor>(data.mouth_color.Value());
}
u8 CoreData::GetMouthScale() const {
return static_cast<u8>(data.mouth_scale.Value());
}
u8 CoreData::GetMouthAspect() const {
return static_cast<u8>(data.mouth_aspect.Value());
}
u8 CoreData::GetMouthY() const {
return static_cast<u8>(data.mouth_y.Value());
}
CommonColor CoreData::GetBeardColor() const {
return static_cast<CommonColor>(data.beard_color.Value());
}
BeardType CoreData::GetBeardType() const {
return static_cast<BeardType>(data.beard_type.Value());
}
MustacheType CoreData::GetMustacheType() const {
return static_cast<MustacheType>(data.mustache_type.Value());
}
u8 CoreData::GetMustacheScale() const {
return static_cast<u8>(data.mustache_scale.Value());
}
u8 CoreData::GetMustacheY() const {
return static_cast<u8>(data.mustache_y.Value());
}
GlassType CoreData::GetGlassType() const {
return static_cast<GlassType>(data.glasses_type.Value());
}
CommonColor CoreData::GetGlassColor() const {
return static_cast<CommonColor>(data.glasses_color.Value());
}
u8 CoreData::GetGlassScale() const {
return static_cast<u8>(data.glasses_scale.Value());
}
u8 CoreData::GetGlassY() const {
return static_cast<u8>(data.glasses_y.Value());
}
MoleType CoreData::GetMoleType() const {
return static_cast<MoleType>(data.mole_type.Value());
}
u8 CoreData::GetMoleScale() const {
return static_cast<u8>(data.mole_scale.Value());
}
u8 CoreData::GetMoleX() const {
return static_cast<u8>(data.mole_x.Value());
}
u8 CoreData::GetMoleY() const {
return static_cast<u8>(data.mole_y.Value());
}
Nickname CoreData::GetNickname() const {
return name;
}
Nickname CoreData::GetDefaultNickname() const {
return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
}
Nickname CoreData::GetInvalidNickname() const {
return {u'?', u'?', u'?'};
}
} // namespace Service::Mii

View File

@@ -0,0 +1,216 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
struct StoreDataBitFields {
union {
u32 word_0{};
BitField<0, 8, u32> hair_type;
BitField<8, 7, u32> height;
BitField<15, 1, u32> mole_type;
BitField<16, 7, u32> build;
BitField<23, 1, u32> hair_flip;
BitField<24, 7, u32> hair_color;
BitField<31, 1, u32> type;
};
union {
u32 word_1{};
BitField<0, 7, u32> eye_color;
BitField<7, 1, u32> gender;
BitField<8, 7, u32> eyebrow_color;
BitField<16, 7, u32> mouth_color;
BitField<24, 7, u32> beard_color;
};
union {
u32 word_2{};
BitField<0, 7, u32> glasses_color;
BitField<8, 6, u32> eye_type;
BitField<14, 2, u32> region_move;
BitField<16, 6, u32> mouth_type;
BitField<22, 2, u32> font_region;
BitField<24, 5, u32> eye_y;
BitField<29, 3, u32> glasses_scale;
};
union {
u32 word_3{};
BitField<0, 5, u32> eyebrow_type;
BitField<5, 3, u32> mustache_type;
BitField<8, 5, u32> nose_type;
BitField<13, 3, u32> beard_type;
BitField<16, 5, u32> nose_y;
BitField<21, 3, u32> mouth_aspect;
BitField<24, 5, u32> mouth_y;
BitField<29, 3, u32> eyebrow_aspect;
};
union {
u32 word_4{};
BitField<0, 5, u32> mustache_y;
BitField<5, 3, u32> eye_rotate;
BitField<8, 5, u32> glasses_y;
BitField<13, 3, u32> eye_aspect;
BitField<16, 5, u32> mole_x;
BitField<21, 3, u32> eye_scale;
BitField<24, 5, u32> mole_y;
};
union {
u32 word_5{};
BitField<0, 5, u32> glasses_type;
BitField<8, 4, u32> favorite_color;
BitField<12, 4, u32> faceline_type;
BitField<16, 4, u32> faceline_color;
BitField<20, 4, u32> faceline_wrinkle;
BitField<24, 4, u32> faceline_makeup;
BitField<28, 4, u32> eye_x;
};
union {
u32 word_6{};
BitField<0, 4, u32> eyebrow_scale;
BitField<4, 4, u32> eyebrow_rotate;
BitField<8, 4, u32> eyebrow_x;
BitField<12, 4, u32> eyebrow_y;
BitField<16, 4, u32> nose_scale;
BitField<20, 4, u32> mouth_scale;
BitField<24, 4, u32> mustache_scale;
BitField<28, 4, u32> mole_scale;
};
};
static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
"StoreDataBitFields is not trivially copyable.");
class CoreData {
public:
void SetDefault();
void BuildRandom(Age age, Gender gender, Race race);
u32 IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
void SetGender(Gender value);
void SetHeight(u8 value);
void SetBuild(u8 value);
void SetType(u8 value);
void SetRegionMove(u8 value);
void SetFacelineType(FacelineType value);
void SetFacelineColor(FacelineColor value);
void SetFacelineWrinkle(FacelineWrinkle value);
void SetFacelineMake(FacelineMake value);
void SetHairType(HairType value);
void SetHairColor(CommonColor value);
void SetHairFlip(HairFlip value);
void SetEyeType(EyeType value);
void SetEyeColor(CommonColor value);
void SetEyeScale(u8 value);
void SetEyeAspect(u8 value);
void SetEyeRotate(u8 value);
void SetEyeX(u8 value);
void SetEyeY(u8 value);
void SetEyebrowType(EyebrowType value);
void SetEyebrowColor(CommonColor value);
void SetEyebrowScale(u8 value);
void SetEyebrowAspect(u8 value);
void SetEyebrowRotate(u8 value);
void SetEyebrowX(u8 value);
void SetEyebrowY(u8 value);
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
void SetMouthY(u8 value);
void SetBeardColor(CommonColor value);
void SetBeardType(BeardType value);
void SetMustacheType(MustacheType value);
void SetMustacheScale(u8 value);
void SetMustacheY(u8 value);
void SetGlassType(GlassType value);
void SetGlassColor(CommonColor value);
void SetGlassScale(u8 value);
void SetGlassY(u8 value);
void SetMoleType(MoleType value);
void SetMoleScale(u8 value);
void SetMoleX(u8 value);
void SetMoleY(u8 value);
void SetNickname(Nickname nickname);
FontRegion GetFontRegion() const;
FavoriteColor GetFavoriteColor() const;
Gender GetGender() const;
u8 GetHeight() const;
u8 GetBuild() const;
u8 GetType() const;
u8 GetRegionMove() const;
FacelineType GetFacelineType() const;
FacelineColor GetFacelineColor() const;
FacelineWrinkle GetFacelineWrinkle() const;
FacelineMake GetFacelineMake() const;
HairType GetHairType() const;
CommonColor GetHairColor() const;
HairFlip GetHairFlip() const;
EyeType GetEyeType() const;
CommonColor GetEyeColor() const;
u8 GetEyeScale() const;
u8 GetEyeAspect() const;
u8 GetEyeRotate() const;
u8 GetEyeX() const;
u8 GetEyeY() const;
EyebrowType GetEyebrowType() const;
CommonColor GetEyebrowColor() const;
u8 GetEyebrowScale() const;
u8 GetEyebrowAspect() const;
u8 GetEyebrowRotate() const;
u8 GetEyebrowX() const;
u8 GetEyebrowY() const;
NoseType GetNoseType() const;
u8 GetNoseScale() const;
u8 GetNoseY() const;
MouthType GetMouthType() const;
CommonColor GetMouthColor() const;
u8 GetMouthScale() const;
u8 GetMouthAspect() const;
u8 GetMouthY() const;
CommonColor GetBeardColor() const;
BeardType GetBeardType() const;
MustacheType GetMustacheType() const;
u8 GetMustacheScale() const;
u8 GetMustacheY() const;
GlassType GetGlassType() const;
CommonColor GetGlassColor() const;
u8 GetGlassScale() const;
u8 GetGlassY() const;
MoleType GetMoleType() const;
u8 GetMoleScale() const;
u8 GetMoleX() const;
u8 GetMoleY() const;
Nickname GetNickname() const;
Nickname GetDefaultNickname() const;
Nickname GetInvalidNickname() const;
private:
StoreDataBitFields data{};
Nickname name{};
};
static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
}; // namespace Service::Mii

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii::RawData {
struct RandomMiiValues {
std::array<u8, 188> values{};
};
static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
struct RandomMiiData4 {
u32 gender{};
u32 age{};
u32 race{};
u32 values_count{};
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
struct RandomMiiData3 {
u32 arg_1;
u32 arg_2;
u32 values_count;
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
struct RandomMiiData2 {
u32 arg_1;
u32 values_count;
std::array<u32, 47> values{};
};
static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
extern const std::array<u8, 62> EyeRotateLookup;
extern const std::array<u8, 24> EyebrowRotateLookup;
extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
u8 FromVer3GetFacelineColor(u8 color);
u8 FromVer3GetHairColor(u8 color);
u8 FromVer3GetEyeColor(u8 color);
u8 FromVer3GetMouthlineColor(u8 color);
u8 FromVer3GetGlassColor(u8 color);
u8 FromVer3GetGlassType(u8 type);
FacelineColor GetFacelineColorFromVer3(u32 color);
CommonColor GetHairColorFromVer3(u32 color);
CommonColor GetEyeColorFromVer3(u32 color);
CommonColor GetMouthColorFromVer3(u32 color);
CommonColor GetGlassColorFromVer3(u32 color);
} // namespace Service::Mii::RawData

View File

@@ -0,0 +1,643 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/raw_data.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
void StoreData::BuildDefault(u32 mii_index) {
const auto& default_mii = RawData::DefaultMii[mii_index];
core_data.SetDefault();
core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
core_data.SetFacelineColor(
RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
core_data.SetEyebrowColor(
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
core_data.SetMouthColor(
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
core_data.SetBeardColor(
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
core_data.SetGlassColor(
RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
core_data.SetHeight(static_cast<u8>(default_mii.height));
core_data.SetBuild(static_cast<u8>(default_mii.weight));
core_data.SetGender(static_cast<Gender>(default_mii.gender));
core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
core_data.SetType(static_cast<u8>(default_mii.type));
core_data.SetNickname(default_mii.nickname);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
}
void StoreData::BuildBase(Gender gender) {
const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0];
core_data.SetDefault();
core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
core_data.SetFacelineColor(
RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
core_data.SetEyebrowColor(
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
core_data.SetMouthColor(
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
core_data.SetBeardColor(
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
core_data.SetGlassColor(
RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
core_data.SetHeight(static_cast<u8>(default_mii.height));
core_data.SetBuild(static_cast<u8>(default_mii.weight));
core_data.SetGender(static_cast<Gender>(default_mii.gender));
core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
core_data.SetType(static_cast<u8>(default_mii.type));
core_data.SetNickname(default_mii.nickname);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
}
void StoreData::BuildRandom(Age age, Gender gender, Race race) {
core_data.BuildRandom(age, gender, race);
const auto device_id = MiiUtil::GetDeviceId();
create_id = MiiUtil::MakeCreateId();
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
}
void StoreData::SetInvalidName() {
const auto& invalid_name = core_data.GetInvalidNickname();
const auto device_id = MiiUtil::GetDeviceId();
core_data.SetNickname(invalid_name);
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
}
bool StoreData::IsSpecial() const {
return GetType() == 1;
}
u32 StoreData::IsValid() const {
// TODO: complete this
return 0;
}
void StoreData::SetFontRegion(FontRegion value) {
core_data.SetFontRegion(value);
}
void StoreData::SetFavoriteColor(FavoriteColor value) {
core_data.SetFavoriteColor(value);
}
void StoreData::SetGender(Gender value) {
core_data.SetGender(value);
}
void StoreData::SetHeight(u8 value) {
core_data.SetHeight(value);
}
void StoreData::SetBuild(u8 value) {
core_data.SetBuild(value);
}
void StoreData::SetType(u8 value) {
core_data.SetType(value);
}
void StoreData::SetRegionMove(u8 value) {
core_data.SetRegionMove(value);
}
void StoreData::SetFacelineType(FacelineType value) {
core_data.SetFacelineType(value);
}
void StoreData::SetFacelineColor(FacelineColor value) {
core_data.SetFacelineColor(value);
}
void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
core_data.SetFacelineWrinkle(value);
}
void StoreData::SetFacelineMake(FacelineMake value) {
core_data.SetFacelineMake(value);
}
void StoreData::SetHairType(HairType value) {
core_data.SetHairType(value);
}
void StoreData::SetHairColor(CommonColor value) {
core_data.SetHairColor(value);
}
void StoreData::SetHairFlip(HairFlip value) {
core_data.SetHairFlip(value);
}
void StoreData::SetEyeType(EyeType value) {
core_data.SetEyeType(value);
}
void StoreData::SetEyeColor(CommonColor value) {
core_data.SetEyeColor(value);
}
void StoreData::SetEyeScale(u8 value) {
core_data.SetEyeScale(value);
}
void StoreData::SetEyeAspect(u8 value) {
core_data.SetEyeAspect(value);
}
void StoreData::SetEyeRotate(u8 value) {
core_data.SetEyeRotate(value);
}
void StoreData::SetEyeX(u8 value) {
core_data.SetEyeX(value);
}
void StoreData::SetEyeY(u8 value) {
core_data.SetEyeY(value);
}
void StoreData::SetEyebrowType(EyebrowType value) {
core_data.SetEyebrowType(value);
}
void StoreData::SetEyebrowColor(CommonColor value) {
core_data.SetEyebrowColor(value);
}
void StoreData::SetEyebrowScale(u8 value) {
core_data.SetEyebrowScale(value);
}
void StoreData::SetEyebrowAspect(u8 value) {
core_data.SetEyebrowAspect(value);
}
void StoreData::SetEyebrowRotate(u8 value) {
core_data.SetEyebrowRotate(value);
}
void StoreData::SetEyebrowX(u8 value) {
core_data.SetEyebrowX(value);
}
void StoreData::SetEyebrowY(u8 value) {
core_data.SetEyebrowY(value);
}
void StoreData::SetNoseType(NoseType value) {
core_data.SetNoseType(value);
}
void StoreData::SetNoseScale(u8 value) {
core_data.SetNoseScale(value);
}
void StoreData::SetNoseY(u8 value) {
core_data.SetNoseY(value);
}
void StoreData::SetMouthType(u8 value) {
core_data.SetMouthType(value);
}
void StoreData::SetMouthColor(CommonColor value) {
core_data.SetMouthColor(value);
}
void StoreData::SetMouthScale(u8 value) {
core_data.SetMouthScale(value);
}
void StoreData::SetMouthAspect(u8 value) {
core_data.SetMouthAspect(value);
}
void StoreData::SetMouthY(u8 value) {
core_data.SetMouthY(value);
}
void StoreData::SetBeardColor(CommonColor value) {
core_data.SetBeardColor(value);
}
void StoreData::SetBeardType(BeardType value) {
core_data.SetBeardType(value);
}
void StoreData::SetMustacheType(MustacheType value) {
core_data.SetMustacheType(value);
}
void StoreData::SetMustacheScale(u8 value) {
core_data.SetMustacheScale(value);
}
void StoreData::SetMustacheY(u8 value) {
core_data.SetMustacheY(value);
}
void StoreData::SetGlassType(GlassType value) {
core_data.SetGlassType(value);
}
void StoreData::SetGlassColor(CommonColor value) {
core_data.SetGlassColor(value);
}
void StoreData::SetGlassScale(u8 value) {
core_data.SetGlassScale(value);
}
void StoreData::SetGlassY(u8 value) {
core_data.SetGlassY(value);
}
void StoreData::SetMoleType(MoleType value) {
core_data.SetMoleType(value);
}
void StoreData::SetMoleScale(u8 value) {
core_data.SetMoleScale(value);
}
void StoreData::SetMoleX(u8 value) {
core_data.SetMoleX(value);
}
void StoreData::SetMoleY(u8 value) {
core_data.SetMoleY(value);
}
void StoreData::SetNickname(Nickname value) {
core_data.SetNickname(value);
}
Common::UUID StoreData::GetCreateId() const {
return create_id;
}
FontRegion StoreData::GetFontRegion() const {
return static_cast<FontRegion>(core_data.GetFontRegion());
}
FavoriteColor StoreData::GetFavoriteColor() const {
return core_data.GetFavoriteColor();
}
Gender StoreData::GetGender() const {
return core_data.GetGender();
}
u8 StoreData::GetHeight() const {
return core_data.GetHeight();
}
u8 StoreData::GetBuild() const {
return core_data.GetBuild();
}
u8 StoreData::GetType() const {
return core_data.GetType();
}
u8 StoreData::GetRegionMove() const {
return core_data.GetRegionMove();
}
FacelineType StoreData::GetFacelineType() const {
return core_data.GetFacelineType();
}
FacelineColor StoreData::GetFacelineColor() const {
return core_data.GetFacelineColor();
}
FacelineWrinkle StoreData::GetFacelineWrinkle() const {
return core_data.GetFacelineWrinkle();
}
FacelineMake StoreData::GetFacelineMake() const {
return core_data.GetFacelineMake();
}
HairType StoreData::GetHairType() const {
return core_data.GetHairType();
}
CommonColor StoreData::GetHairColor() const {
return core_data.GetHairColor();
}
HairFlip StoreData::GetHairFlip() const {
return core_data.GetHairFlip();
}
EyeType StoreData::GetEyeType() const {
return core_data.GetEyeType();
}
CommonColor StoreData::GetEyeColor() const {
return core_data.GetEyeColor();
}
u8 StoreData::GetEyeScale() const {
return core_data.GetEyeScale();
}
u8 StoreData::GetEyeAspect() const {
return core_data.GetEyeAspect();
}
u8 StoreData::GetEyeRotate() const {
return core_data.GetEyeRotate();
}
u8 StoreData::GetEyeX() const {
return core_data.GetEyeX();
}
u8 StoreData::GetEyeY() const {
return core_data.GetEyeY();
}
EyebrowType StoreData::GetEyebrowType() const {
return core_data.GetEyebrowType();
}
CommonColor StoreData::GetEyebrowColor() const {
return core_data.GetEyebrowColor();
}
u8 StoreData::GetEyebrowScale() const {
return core_data.GetEyebrowScale();
}
u8 StoreData::GetEyebrowAspect() const {
return core_data.GetEyebrowAspect();
}
u8 StoreData::GetEyebrowRotate() const {
return core_data.GetEyebrowRotate();
}
u8 StoreData::GetEyebrowX() const {
return core_data.GetEyebrowX();
}
u8 StoreData::GetEyebrowY() const {
return core_data.GetEyebrowY();
}
NoseType StoreData::GetNoseType() const {
return core_data.GetNoseType();
}
u8 StoreData::GetNoseScale() const {
return core_data.GetNoseScale();
}
u8 StoreData::GetNoseY() const {
return core_data.GetNoseY();
}
MouthType StoreData::GetMouthType() const {
return core_data.GetMouthType();
}
CommonColor StoreData::GetMouthColor() const {
return core_data.GetMouthColor();
}
u8 StoreData::GetMouthScale() const {
return core_data.GetMouthScale();
}
u8 StoreData::GetMouthAspect() const {
return core_data.GetMouthAspect();
}
u8 StoreData::GetMouthY() const {
return core_data.GetMouthY();
}
CommonColor StoreData::GetBeardColor() const {
return core_data.GetBeardColor();
}
BeardType StoreData::GetBeardType() const {
return core_data.GetBeardType();
}
MustacheType StoreData::GetMustacheType() const {
return core_data.GetMustacheType();
}
u8 StoreData::GetMustacheScale() const {
return core_data.GetMustacheScale();
}
u8 StoreData::GetMustacheY() const {
return core_data.GetMustacheY();
}
GlassType StoreData::GetGlassType() const {
return core_data.GetGlassType();
}
CommonColor StoreData::GetGlassColor() const {
return core_data.GetGlassColor();
}
u8 StoreData::GetGlassScale() const {
return core_data.GetGlassScale();
}
u8 StoreData::GetGlassY() const {
return core_data.GetGlassY();
}
MoleType StoreData::GetMoleType() const {
return core_data.GetMoleType();
}
u8 StoreData::GetMoleScale() const {
return core_data.GetMoleScale();
}
u8 StoreData::GetMoleX() const {
return core_data.GetMoleX();
}
u8 StoreData::GetMoleY() const {
return core_data.GetMoleY();
}
Nickname StoreData::GetNickname() const {
return core_data.GetNickname();
}
bool StoreData::operator==(const StoreData& data) {
bool is_identical = data.core_data.IsValid() == 0;
is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
is_identical &= GetCreateId() == data.GetCreateId();
is_identical &= GetFontRegion() == data.GetFontRegion();
is_identical &= GetFavoriteColor() == data.GetFavoriteColor();
is_identical &= GetGender() == data.GetGender();
is_identical &= GetHeight() == data.GetHeight();
is_identical &= GetBuild() == data.GetBuild();
is_identical &= GetType() == data.GetType();
is_identical &= GetRegionMove() == data.GetRegionMove();
is_identical &= GetFacelineType() == data.GetFacelineType();
is_identical &= GetFacelineColor() == data.GetFacelineColor();
is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle();
is_identical &= GetFacelineMake() == data.GetFacelineMake();
is_identical &= GetHairType() == data.GetHairType();
is_identical &= GetHairColor() == data.GetHairColor();
is_identical &= GetHairFlip() == data.GetHairFlip();
is_identical &= GetEyeType() == data.GetEyeType();
is_identical &= GetEyeColor() == data.GetEyeColor();
is_identical &= GetEyeScale() == data.GetEyeScale();
is_identical &= GetEyeAspect() == data.GetEyeAspect();
is_identical &= GetEyeRotate() == data.GetEyeRotate();
is_identical &= GetEyeX() == data.GetEyeX();
is_identical &= GetEyeY() == data.GetEyeY();
is_identical &= GetEyebrowType() == data.GetEyebrowType();
is_identical &= GetEyebrowColor() == data.GetEyebrowColor();
is_identical &= GetEyebrowScale() == data.GetEyebrowScale();
is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect();
is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate();
is_identical &= GetEyebrowX() == data.GetEyebrowX();
is_identical &= GetEyebrowY() == data.GetEyebrowY();
is_identical &= GetNoseType() == data.GetNoseType();
is_identical &= GetNoseScale() == data.GetNoseScale();
is_identical &= GetNoseY() == data.GetNoseY();
is_identical &= GetMouthType() == data.GetMouthType();
is_identical &= GetMouthColor() == data.GetMouthColor();
is_identical &= GetMouthScale() == data.GetMouthScale();
is_identical &= GetMouthAspect() == data.GetMouthAspect();
is_identical &= GetMouthY() == data.GetMouthY();
is_identical &= GetBeardColor() == data.GetBeardColor();
is_identical &= GetBeardType() == data.GetBeardType();
is_identical &= GetMustacheType() == data.GetMustacheType();
is_identical &= GetMustacheScale() == data.GetMustacheScale();
is_identical &= GetMustacheY() == data.GetMustacheY();
is_identical &= GetGlassType() == data.GetGlassType();
is_identical &= GetGlassColor() == data.GetGlassColor();
is_identical &= GetGlassScale() == data.GetGlassScale();
is_identical &= GetGlassY() == data.GetGlassY();
is_identical &= GetMoleType() == data.GetMoleType();
is_identical &= GetMoleScale() == data.GetMoleScale();
is_identical &= GetMoleX() == data.GetMoleX();
is_identical &= data.GetMoleY() == data.GetMoleY();
return is_identical;
}
} // namespace Service::Mii

View File

@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/mii/mii_types.h"
#include "core/hle/service/mii/types/core_data.h"
namespace Service::Mii {
class StoreData {
public:
// nn::mii::detail::StoreDataRaw::BuildDefault
void BuildDefault(u32 mii_index);
// nn::mii::detail::StoreDataRaw::BuildDefault
void BuildBase(Gender gender);
// nn::mii::detail::StoreDataRaw::BuildRandom
void BuildRandom(Age age, Gender gender, Race race);
bool IsSpecial() const;
u32 IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
void SetGender(Gender value);
void SetHeight(u8 value);
void SetBuild(u8 value);
void SetType(u8 value);
void SetRegionMove(u8 value);
void SetFacelineType(FacelineType value);
void SetFacelineColor(FacelineColor value);
void SetFacelineWrinkle(FacelineWrinkle value);
void SetFacelineMake(FacelineMake value);
void SetHairType(HairType value);
void SetHairColor(CommonColor value);
void SetHairFlip(HairFlip value);
void SetEyeType(EyeType value);
void SetEyeColor(CommonColor value);
void SetEyeScale(u8 value);
void SetEyeAspect(u8 value);
void SetEyeRotate(u8 value);
void SetEyeX(u8 value);
void SetEyeY(u8 value);
void SetEyebrowType(EyebrowType value);
void SetEyebrowColor(CommonColor value);
void SetEyebrowScale(u8 value);
void SetEyebrowAspect(u8 value);
void SetEyebrowRotate(u8 value);
void SetEyebrowX(u8 value);
void SetEyebrowY(u8 value);
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
void SetMouthY(u8 value);
void SetBeardColor(CommonColor value);
void SetBeardType(BeardType value);
void SetMustacheType(MustacheType value);
void SetMustacheScale(u8 value);
void SetMustacheY(u8 value);
void SetGlassType(GlassType value);
void SetGlassColor(CommonColor value);
void SetGlassScale(u8 value);
void SetGlassY(u8 value);
void SetMoleType(MoleType value);
void SetMoleScale(u8 value);
void SetMoleX(u8 value);
void SetMoleY(u8 value);
void SetNickname(Nickname nickname);
void SetInvalidName();
Common::UUID GetCreateId() const;
FontRegion GetFontRegion() const;
FavoriteColor GetFavoriteColor() const;
Gender GetGender() const;
u8 GetHeight() const;
u8 GetBuild() const;
u8 GetType() const;
u8 GetRegionMove() const;
FacelineType GetFacelineType() const;
FacelineColor GetFacelineColor() const;
FacelineWrinkle GetFacelineWrinkle() const;
FacelineMake GetFacelineMake() const;
HairType GetHairType() const;
CommonColor GetHairColor() const;
HairFlip GetHairFlip() const;
EyeType GetEyeType() const;
CommonColor GetEyeColor() const;
u8 GetEyeScale() const;
u8 GetEyeAspect() const;
u8 GetEyeRotate() const;
u8 GetEyeX() const;
u8 GetEyeY() const;
EyebrowType GetEyebrowType() const;
CommonColor GetEyebrowColor() const;
u8 GetEyebrowScale() const;
u8 GetEyebrowAspect() const;
u8 GetEyebrowRotate() const;
u8 GetEyebrowX() const;
u8 GetEyebrowY() const;
NoseType GetNoseType() const;
u8 GetNoseScale() const;
u8 GetNoseY() const;
MouthType GetMouthType() const;
CommonColor GetMouthColor() const;
u8 GetMouthScale() const;
u8 GetMouthAspect() const;
u8 GetMouthY() const;
CommonColor GetBeardColor() const;
BeardType GetBeardType() const;
MustacheType GetMustacheType() const;
u8 GetMustacheScale() const;
u8 GetMustacheY() const;
GlassType GetGlassType() const;
CommonColor GetGlassColor() const;
u8 GetGlassScale() const;
u8 GetGlassY() const;
MoleType GetMoleType() const;
u8 GetMoleScale() const;
u8 GetMoleX() const;
u8 GetMoleY() const;
Nickname GetNickname() const;
bool operator==(const StoreData& data);
private:
CoreData core_data{};
Common::UUID create_id{};
u16 data_crc{};
u16 device_crc{};
};
static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
struct StoreDataElement {
StoreData store_data{};
Source source{};
};
static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size.");
}; // namespace Service::Mii

View File

@@ -0,0 +1,241 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/raw_data.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
namespace Service::Mii {
void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf;
hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f;
eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f;
eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f;
mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f;
beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f;
glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f;
glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f;
}
void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
out_store_data.BuildBase(Gender::Male);
if (!IsValid()) {
return;
}
// TODO: We are ignoring a bunch of data from the mii_v3
out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
out_store_data.SetFavoriteColor(
static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
out_store_data.SetHeight(height);
out_store_data.SetBuild(build);
out_store_data.SetNickname(mii_name);
out_store_data.SetFontRegion(
static_cast<FontRegion>(static_cast<u8>(region_information.font_region)));
out_store_data.SetFacelineType(
static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
out_store_data.SetFacelineColor(
static_cast<FacelineColor>(appearance_bits1.faceline_color.Value()));
out_store_data.SetFacelineWrinkle(
static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
out_store_data.SetFacelineMake(
static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
out_store_data.SetHairType(static_cast<HairType>(hair_type));
out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value()));
out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value()));
out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale));
out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect));
out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate));
out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x));
out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y));
out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
out_store_data.SetEyebrowColor(
static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value()));
out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale));
out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect));
out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate));
out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x));
out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y));
out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale));
out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y));
out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type));
out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value()));
out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale));
out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect));
out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y));
out_store_data.SetMustacheType(
static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale));
out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y));
out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value()));
out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value()));
out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale));
out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y));
out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale));
out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x));
out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y));
}
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
version = 1;
mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
height = store_data.GetHeight();
build = store_data.GetBuild();
mii_name = store_data.GetNickname();
region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion()));
appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType()));
appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle()));
appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake()));
hair_type = static_cast<u8>(store_data.GetHairType());
appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip()));
appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType()));
appearance_bits4.eye_scale.Assign(store_data.GetEyeScale());
appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect());
appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate());
appearance_bits4.eye_x.Assign(store_data.GetEyeX());
appearance_bits4.eye_y.Assign(store_data.GetEyeY());
appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType()));
appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale());
appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect());
appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate());
appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX());
appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY());
appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType()));
appearance_bits6.nose_scale.Assign(store_data.GetNoseScale());
appearance_bits6.nose_y.Assign(store_data.GetNoseY());
appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType()));
appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale());
appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect());
appearance_bits8.mouth_y.Assign(store_data.GetMouthY());
appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType()));
appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale());
appearance_bits9.mustache_y.Assign(store_data.GetMustacheY());
appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType()));
appearance_bits10.glass_scale.Assign(store_data.GetGlassScale());
appearance_bits10.glass_y.Assign(store_data.GetGlassY());
appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType()));
appearance_bits11.mole_scale.Assign(store_data.GetMoleScale());
appearance_bits11.mole_x.Assign(store_data.GetMoleX());
appearance_bits11.mole_y.Assign(store_data.GetMoleY());
// These types are converted to V3 from a table
appearance_bits1.faceline_color.Assign(
RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor())));
appearance_bits3.hair_color.Assign(
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor())));
appearance_bits4.eye_color.Assign(
RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor())));
appearance_bits5.eyebrow_color.Assign(
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor())));
appearance_bits7.mouth_color.Assign(
RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor())));
appearance_bits9.beard_color.Assign(
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor())));
appearance_bits10.glass_color.Assign(
RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor())));
appearance_bits10.glass_type.Assign(
RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType())));
crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16));
}
u32 Ver3StoreData::IsValid() const {
bool is_valid = version == 0 || version == 3;
is_valid = is_valid && (mii_name.data[0] != '\0');
is_valid = is_valid && (mii_information.birth_month < 13);
is_valid = is_valid && (mii_information.birth_day < 32);
is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max));
is_valid = is_valid && (height <= MaxHeight);
is_valid = is_valid && (build <= MaxBuild);
is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max));
is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2);
is_valid =
is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max));
is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max));
is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max));
is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor);
is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max));
is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2);
is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale);
is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect);
is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate);
is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX);
is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY);
is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max));
is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor);
is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale);
is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect);
is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate);
is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX);
is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY);
is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max));
is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale);
is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY);
is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max));
is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3);
is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale);
is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect);
is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY);
is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY);
is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale);
is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX);
is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY);
return is_valid;
}
} // namespace Service::Mii

View File

@@ -0,0 +1,160 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
class StoreData;
// This is nn::mii::Ver3StoreData
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
struct NfpStoreDataExtension {
void SetFromStoreData(const StoreData& store_data);
u8 faceline_color;
u8 hair_color;
u8 eye_color;
u8 eyebrow_color;
u8 mouth_color;
u8 beard_color;
u8 glass_color;
u8 glass_type;
};
static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
#pragma pack(push, 4)
class Ver3StoreData {
public:
void BuildToStoreData(StoreData& out_store_data) const;
void BuildFromStoreData(const StoreData& store_data);
u32 IsValid() const;
u8 version;
union {
u8 raw;
BitField<0, 1, u8> allow_copying;
BitField<1, 1, u8> profanity_flag;
BitField<2, 2, u8> region_lock;
BitField<4, 2, u8> font_region;
} region_information;
u16_be mii_id;
u64_be system_id;
u32_be specialness_and_creation_date;
std::array<u8, 6> creator_mac;
u16_be padding;
union {
u16 raw;
BitField<0, 1, u16> gender;
BitField<1, 4, u16> birth_month;
BitField<5, 5, u16> birth_day;
BitField<10, 4, u16> favorite_color;
BitField<14, 1, u16> favorite;
} mii_information;
Nickname mii_name;
u8 height;
u8 build;
union {
u8 raw;
BitField<0, 1, u8> disable_sharing;
BitField<1, 4, u8> faceline_type;
BitField<5, 3, u8> faceline_color;
} appearance_bits1;
union {
u8 raw;
BitField<0, 4, u8> faceline_wrinkle;
BitField<4, 4, u8> faceline_make;
} appearance_bits2;
u8 hair_type;
union {
u8 raw;
BitField<0, 3, u8> hair_color;
BitField<3, 1, u8> hair_flip;
} appearance_bits3;
union {
u32 raw;
BitField<0, 6, u32> eye_type;
BitField<6, 3, u32> eye_color;
BitField<9, 4, u32> eye_scale;
BitField<13, 3, u32> eye_aspect;
BitField<16, 5, u32> eye_rotate;
BitField<21, 4, u32> eye_x;
BitField<25, 5, u32> eye_y;
} appearance_bits4;
union {
u32 raw;
BitField<0, 5, u32> eyebrow_type;
BitField<5, 3, u32> eyebrow_color;
BitField<8, 4, u32> eyebrow_scale;
BitField<12, 3, u32> eyebrow_aspect;
BitField<16, 4, u32> eyebrow_rotate;
BitField<21, 4, u32> eyebrow_x;
BitField<25, 5, u32> eyebrow_y;
} appearance_bits5;
union {
u16 raw;
BitField<0, 5, u16> nose_type;
BitField<5, 4, u16> nose_scale;
BitField<9, 5, u16> nose_y;
} appearance_bits6;
union {
u16 raw;
BitField<0, 6, u16> mouth_type;
BitField<6, 3, u16> mouth_color;
BitField<9, 4, u16> mouth_scale;
BitField<13, 3, u16> mouth_aspect;
} appearance_bits7;
union {
u8 raw;
BitField<0, 5, u8> mouth_y;
BitField<5, 3, u8> mustache_type;
} appearance_bits8;
u8 allow_copying;
union {
u16 raw;
BitField<0, 3, u16> beard_type;
BitField<3, 3, u16> beard_color;
BitField<6, 4, u16> mustache_scale;
BitField<10, 5, u16> mustache_y;
} appearance_bits9;
union {
u16 raw;
BitField<0, 4, u16> glass_type;
BitField<4, 3, u16> glass_color;
BitField<7, 4, u16> glass_scale;
BitField<11, 5, u16> glass_y;
} appearance_bits10;
union {
u16 raw;
BitField<0, 1, u16> mole_type;
BitField<1, 4, u16> mole_scale;
BitField<5, 5, u16> mole_x;
BitField<10, 5, u16> mole_y;
} appearance_bits11;
Nickname author_name;
INSERT_PADDING_BYTES(0x2);
u16_be crc;
};
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
#pragma pack(pop)
}; // namespace Service::Mii

View File

@@ -28,7 +28,6 @@
#include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfc/common/amiibo_crypto.h" #include "core/hle/service/nfc/common/amiibo_crypto.h"
#include "core/hle/service/nfc/common/device.h" #include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/mifare_result.h" #include "core/hle/service/nfc/mifare_result.h"
@@ -681,12 +680,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
return ResultRegistrationIsNotInitialized; return ResultRegistrationIsNotInitialized;
} }
Service::Mii::MiiManager manager; Mii::CharInfo char_info{};
Mii::StoreData store_data{};
tag_data.owner_mii.BuildToStoreData(store_data);
char_info.SetFromStoreData(store_data);
const auto& settings = tag_data.settings; const auto& settings = tag_data.settings;
// TODO: Validate this data // TODO: Validate this data
register_info = { register_info = {
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), .mii_char_info = char_info,
.creation_date = settings.init_date.GetWriteDate(), .creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings), .amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region, .font_region = settings.settings.font_region,
@@ -825,8 +828,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return ResultWrongDeviceState; return ResultWrongDeviceState;
} }
Service::Mii::MiiManager manager; Service::Mii::StoreData store_data{};
const auto mii = manager.BuildBase(Mii::Gender::Male); Service::Mii::NfpStoreDataExtension extension{};
store_data.BuildBase(Mii::Gender::Male);
extension.SetFromStoreData(store_data);
auto& settings = tag_data.settings; auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) { if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -835,8 +841,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
} }
SetAmiiboName(settings, register_info.amiibo_name); SetAmiiboName(settings, register_info.amiibo_name);
tag_data.owner_mii = manager.BuildFromStoreData(mii); tag_data.owner_mii.BuildFromStoreData(store_data);
tag_data.mii_extension = manager.SetFromStoreData(mii); tag_data.mii_extension = extension;
tag_data.unknown = 0; tag_data.unknown = 0;
tag_data.unknown2 = {}; tag_data.unknown2 = {};
settings.country_code_id = 0; settings.country_code_id = 0;
@@ -1453,7 +1459,7 @@ void NfcDevice::UpdateRegisterInfoCrc() {
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
const NFP::EncryptedNTAG215File& encrypted_file) const { const NFP::EncryptedNTAG215File& encrypted_file) const {
Service::Mii::MiiManager manager; Service::Mii::StoreData store_data{};
auto& settings = stubbed_tag_data.settings; auto& settings = stubbed_tag_data.settings;
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
@@ -1467,7 +1473,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0); settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildBase(Mii::Gender::Male)); store_data.BuildBase(Mii::Gender::Male);
stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);
// Admin info // Admin info
settings.settings.amiibo_initialized.Assign(1); settings.settings.amiibo_initialized.Assign(1);

View File

@@ -6,7 +6,9 @@
#include <array> #include <array>
#include "common/swap.h" #include "common/swap.h"
#include "core/hle/service/mii/types.h" #include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
#include "core/hle/service/mii/types/ver3_store_data.h"
#include "core/hle/service/nfc/nfc_types.h" #include "core/hle/service/nfc/nfc_types.h"
namespace Service::NFP { namespace Service::NFP {
@@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
// This is nn::nfp::RegisterInfoPrivate // This is nn::nfp::RegisterInfoPrivate
struct RegisterInfoPrivate { struct RegisterInfoPrivate {
Service::Mii::MiiStoreData mii_store_data; Service::Mii::StoreData mii_store_data;
WriteDate creation_date; WriteDate creation_date;
AmiiboName amiibo_name; AmiiboName amiibo_name;
u8 font_region; u8 font_region;

View File

@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGC {
class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
public:
explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgctServiceImpl::Match, "Match"},
{1, &NgctServiceImpl::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
public:
explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
{1, &NgcServiceImpl::Check, "Check"},
{2, &NgcServiceImpl::Mask, "Mask"},
{3, &NgcServiceImpl::Reload, "Reload"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
static constexpr u32 NgcContentVersion = 1;
// This is nn::ngc::detail::ProfanityFilterOption
struct ProfanityFilterOption {
INSERT_PADDING_BYTES_NOINIT(0x20);
};
static_assert(sizeof(ProfanityFilterOption) == 0x20,
"ProfanityFilterOption has incorrect size");
void GetContentVersion(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This calls nn::ngc::ProfanityFilter::GetContentVersion
const u32 version = NgcContentVersion;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(version);
}
void Check(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
[[maybe_unused]] const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::CheckProfanityWords
const u32 out_flags = 0;
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Mask(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
struct InputParameters {
u32 flags;
ProfanityFilterOption option;
};
IPC::RequestParser rp{ctx};
[[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
const auto input = ctx.ReadBuffer(0);
// This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
const u32 out_flags = 0;
ctx.WriteBuffer(input);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(out_flags);
}
void Reload(HLERequestContext& ctx) {
LOG_INFO(Service_NGC, "(STUBBED) called");
// This reloads the database.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGC

View File

@@ -7,8 +7,8 @@ namespace Core {
class System; class System;
} }
namespace Service::NGCT { namespace Service::NGC {
void LoopProcess(Core::System& system); void LoopProcess(Core::System& system);
} // namespace Service::NGCT } // namespace Service::NGC

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngct/ngct.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGCT {
class IService final : public ServiceFramework<IService> {
public:
explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IService::Match, "Match"},
{1, &IService::Filter, "Filter"},
};
// clang-format on
RegisterHandlers(functions);
}
private:
void Match(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
// Return false since we don't censor anything
rb.Push(false);
}
void Filter(HLERequestContext& ctx) {
const auto buffer = ctx.ReadBuffer();
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(buffer.data()), buffer.size());
LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
// Return the same string since we don't censor anything
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::NGCT

View File

@@ -43,7 +43,7 @@
#include "core/hle/service/ncm/ncm.h" #include "core/hle/service/ncm/ncm.h"
#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/ngct/ngct.h" #include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h" #include "core/hle/service/nim/nim.h"
#include "core/hle/service/npns/npns.h" #include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });

View File

@@ -48,15 +48,32 @@ enum class CallType {
using socklen_t = int; using socklen_t = int;
SOCKET interrupt_socket = static_cast<SOCKET>(-1);
void InterruptSocketOperations() {
closesocket(interrupt_socket);
}
void AcknowledgeInterrupt() {
interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
}
void Initialize() { void Initialize() {
WSADATA wsa_data; WSADATA wsa_data;
(void)WSAStartup(MAKEWORD(2, 2), &wsa_data); (void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
AcknowledgeInterrupt();
} }
void Finalize() { void Finalize() {
InterruptSocketOperations();
WSACleanup(); WSACleanup();
} }
SOCKET GetInterruptSocket() {
return interrupt_socket;
}
sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result; sockaddr_in result;
@@ -157,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD;
constexpr int SD_SEND = SHUT_WR; constexpr int SD_SEND = SHUT_WR;
constexpr int SD_BOTH = SHUT_RDWR; constexpr int SD_BOTH = SHUT_RDWR;
void Initialize() {} int interrupt_pipe_fd[2] = {-1, -1};
void Finalize() {} void Initialize() {
if (pipe(interrupt_pipe_fd) != 0) {
LOG_ERROR(Network, "Failed to create interrupt pipe!");
}
int flags = fcntl(interrupt_pipe_fd[0], F_GETFL);
ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0,
"Failed to set nonblocking state for interrupt pipe");
}
void Finalize() {
if (interrupt_pipe_fd[0] >= 0) {
close(interrupt_pipe_fd[0]);
}
if (interrupt_pipe_fd[1] >= 0) {
close(interrupt_pipe_fd[1]);
}
}
void InterruptSocketOperations() {
u8 value = 0;
ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
}
void AcknowledgeInterrupt() {
u8 value = 0;
ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value));
if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) {
LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown");
}
}
SOCKET GetInterruptSocket() {
return interrupt_pipe_fd[0];
}
sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
sockaddr_in result; sockaddr_in result;
@@ -490,6 +540,14 @@ NetworkInstance::~NetworkInstance() {
Finalize(); Finalize();
} }
void CancelPendingSocketOperations() {
InterruptSocketOperations();
}
void RestartSocketOperations() {
AcknowledgeInterrupt();
}
std::optional<IPv4Address> GetHostIPv4Address() { std::optional<IPv4Address> GetHostIPv4Address() {
const auto network_interface = Network::GetSelectedNetworkInterface(); const auto network_interface = Network::GetSelectedNetworkInterface();
if (!network_interface.has_value()) { if (!network_interface.has_value()) {
@@ -560,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
return result; return result;
}); });
const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); host_pollfds.push_back(WSAPOLLFD{
.fd = GetInterruptSocket(),
.events = POLLIN,
.revents = 0,
});
const int result =
WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout);
if (result == 0) { if (result == 0) {
ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
[](WSAPOLLFD fd) { return fd.revents == 0; })); [](WSAPOLLFD fd) { return fd.revents == 0; }));
@@ -627,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
sockaddr_in addr; sockaddr_in addr;
socklen_t addrlen = sizeof(addr); socklen_t addrlen = sizeof(addr);
std::vector<WSAPOLLFD> host_pollfds{
WSAPOLLFD{fd, POLLIN, 0},
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
};
while (true) {
const int pollres =
WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1);
if (host_pollfds[1].revents != 0) {
// Interrupt signaled before a client could be accepted, break
return {AcceptResult{}, Errno::AGAIN};
}
if (pollres > 0) {
break;
}
}
const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
if (new_socket == INVALID_SOCKET) { if (new_socket == INVALID_SOCKET) {

View File

@@ -96,6 +96,9 @@ public:
~NetworkInstance(); ~NetworkInstance();
}; };
void CancelPendingSocketOperations();
void RestartSocketOperations();
#ifdef _WIN32 #ifdef _WIN32
constexpr IPv4Address TranslateIPv4(in_addr addr) { constexpr IPv4Address TranslateIPv4(in_addr addr) {
auto& bytes = addr.S_un.S_un_b; auto& bytes = addr.S_un.S_un_b;

View File

@@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown"; return "unknown";
} }
constexpr std::array<const char*, 66> RESULT_MESSAGES{ constexpr std::array<const char*, 68> RESULT_MESSAGES{
"The operation completed successfully.", "The operation completed successfully.",
"The loader requested to load is already loaded.", "The loader requested to load is already loaded.",
"The operation is not implemented.", "The operation is not implemented.",
@@ -175,6 +175,8 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{
"The KIP BLZ decompression of the section failed unexpectedly.", "The KIP BLZ decompression of the section failed unexpectedly.",
"The INI file has a bad header.", "The INI file has a bad header.",
"The INI file contains more than the maximum allowable number of KIP files.", "The INI file contains more than the maximum allowable number of KIP files.",
"Integrity verification could not be performed for this file.",
"Integrity verification failed.",
}; };
std::string GetResultStatusString(ResultStatus status) { std::string GetResultStatusString(ResultStatus status) {

View File

@@ -3,6 +3,7 @@
#pragma once #pragma once
#include <functional>
#include <iosfwd> #include <iosfwd>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -132,6 +133,8 @@ enum class ResultStatus : u16 {
ErrorBLZDecompressionFailed, ErrorBLZDecompressionFailed,
ErrorBadINIHeader, ErrorBadINIHeader,
ErrorINITooManyKIPs, ErrorINITooManyKIPs,
ErrorIntegrityVerificationNotImplemented,
ErrorIntegrityVerificationFailed,
}; };
std::string GetResultStatusString(ResultStatus status); std::string GetResultStatusString(ResultStatus status);
@@ -169,6 +172,13 @@ public:
*/ */
virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0;
/**
* Try to verify the integrity of the file.
*/
virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
return ResultStatus::ErrorIntegrityVerificationNotImplemented;
}
/** /**
* Get the code (typically .code section) of the application * Get the code (typically .code section) of the application
* *

View File

@@ -3,6 +3,8 @@
#include <utility> #include <utility>
#include "common/hex_util.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
@@ -12,6 +14,7 @@
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h" #include "core/loader/nca.h"
#include "mbedtls/sha256.h"
namespace Loader { namespace Loader {
@@ -80,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result; return load_result;
} }
ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
using namespace Common::Literals;
constexpr size_t NcaFileNameWithHashLength = 36;
constexpr size_t NcaFileNameHashLength = 32;
constexpr size_t NcaSha256HashLength = 32;
constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2;
// Get the file name.
const auto name = file->GetName();
// We won't try to verify meta NCAs.
if (name.ends_with(".cnmt.nca")) {
return ResultStatus::Success;
}
// Check if we can verify this file. NCAs should be named after their hashes.
if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) {
LOG_WARNING(Loader, "Unable to validate NCA with name {}", name);
return ResultStatus::ErrorIntegrityVerificationNotImplemented;
}
// Get the expected truncated hash of the NCA.
const auto input_hash =
Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false);
// Declare buffer to read into.
std::vector<u8> buffer(4_MiB);
// Initialize sha256 verification context.
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts_ret(&ctx, 0);
// Ensure we maintain a clean state on exit.
SCOPE_EXIT({ mbedtls_sha256_free(&ctx); });
// Declare counters.
const size_t total_size = file->GetSize();
size_t processed_size = 0;
// Begin iterating the file.
while (processed_size < total_size) {
// Refill the buffer.
const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size);
const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size);
// Update the hash function with the buffer contents.
mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size);
// Update counters.
processed_size += read_size;
// Call the progress function.
if (!progress_callback(processed_size, total_size)) {
return ResultStatus::ErrorIntegrityVerificationFailed;
}
}
// Finalize context and compute the output hash.
std::array<u8, NcaSha256HashLength> output_hash;
mbedtls_sha256_finish_ret(&ctx, output_hash.data());
// Compare to expected.
if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) {
LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name);
return ResultStatus::ErrorIntegrityVerificationFailed;
}
// File verified.
return ResultStatus::Success;
}
ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
if (nca == nullptr) { if (nca == nullptr) {
return ResultStatus::ErrorNotInitialized; return ResultStatus::ErrorNotInitialized;

View File

@@ -39,6 +39,8 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@@ -117,6 +117,42 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S
return result; return result;
} }
ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
// Extracted-type NSPs can't be verified.
if (nsp->IsExtractedType()) {
return ResultStatus::ErrorIntegrityVerificationNotImplemented;
}
// Get list of all NCAs.
const auto ncas = nsp->GetNCAsCollapsed();
size_t total_size = 0;
size_t processed_size = 0;
// Loop over NCAs, collecting the total size to verify.
for (const auto& nca : ncas) {
total_size += nca->GetBaseFile()->GetSize();
}
// Loop over NCAs again, verifying each.
for (const auto& nca : ncas) {
AppLoader_NCA loader_nca(nca->GetBaseFile());
const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
return progress_callback(processed_size + nca_processed_size, total_size);
};
const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
if (verification_result != ResultStatus::Success) {
return verification_result;
}
processed_size += nca->GetBaseFile()->GetSize();
}
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) {
return secondary_loader->ReadRomFS(out_file); return secondary_loader->ReadRomFS(out_file);
} }

View File

@@ -45,6 +45,8 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@@ -85,6 +85,40 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S
return result; return result;
} }
ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) {
// Verify secure partition, as it is the only thing we can process.
auto secure_partition = xci->GetSecurePartitionNSP();
// Get list of all NCAs.
const auto ncas = secure_partition->GetNCAsCollapsed();
size_t total_size = 0;
size_t processed_size = 0;
// Loop over NCAs, collecting the total size to verify.
for (const auto& nca : ncas) {
total_size += nca->GetBaseFile()->GetSize();
}
// Loop over NCAs again, verifying each.
for (const auto& nca : ncas) {
AppLoader_NCA loader_nca(nca->GetBaseFile());
const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) {
return progress_callback(processed_size + nca_processed_size, total_size);
};
const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback);
if (verification_result != ResultStatus::Success) {
return verification_result;
}
processed_size += nca->GetBaseFile()->GetSize();
}
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) {
return nca_loader->ReadRomFS(out_file); return nca_loader->ReadRomFS(out_file);
} }

View File

@@ -45,6 +45,8 @@ public:
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override;
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override;
ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramId(u64& out_program_id) override;

View File

@@ -204,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
if (def.count > 1) { if (def.count > 1) {
throw NotImplementedException("Indirect texture sample"); throw NotImplementedException("Indirect texture sample");
} }
const Id sampler_id{def.id}; return ctx.OpLoad(ctx.image_buffer_type, def.id);
const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
return ctx.OpImage(ctx.image_buffer_type, id);
} else { } else {
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
if (def.count > 1) { if (def.count > 1) {

View File

@@ -74,6 +74,11 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
throw InvalidArgument("Invalid image format {}", format); throw InvalidArgument("Invalid image format {}", format);
} }
spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
const auto spv_format = GetImageFormat(format);
return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
}
Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) { Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
const spv::ImageFormat format{GetImageFormat(desc.format)}; const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id type{ctx.U32[1]}; const Id type{ctx.U32[1]};
@@ -1242,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
} }
const spv::ImageFormat format{spv::ImageFormat::Unknown}; const spv::ImageFormat format{spv::ImageFormat::Unknown};
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)}; const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
texture_buffers.reserve(info.texture_buffer_descriptors.size()); texture_buffers.reserve(info.texture_buffer_descriptors.size());
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
if (desc.count != 1) { if (desc.count != 1) {
@@ -1271,7 +1275,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
if (desc.count != 1) { if (desc.count != 1) {
throw NotImplementedException("Array of image buffers"); throw NotImplementedException("Array of image buffers");
} }
const spv::ImageFormat format{GetImageFormat(desc.format)}; const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)};
const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)}; const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};

View File

@@ -206,7 +206,6 @@ public:
Id output_u32{}; Id output_u32{};
Id image_buffer_type{}; Id image_buffer_type{};
Id sampled_texture_buffer_type{};
Id image_u32{}; Id image_u32{};
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};

View File

@@ -66,21 +66,6 @@ std::string BuildCommaSeparatedExtensions(
return fmt::format("{}", fmt::join(available_extensions, ",")); return fmt::format("{}", fmt::join(available_extensions, ","));
} }
DebugCallback MakeDebugCallback(const vk::Instance& instance, const vk::InstanceDispatch& dld) {
if (!Settings::values.renderer_debug) {
return DebugCallback{};
}
const std::optional properties = vk::EnumerateInstanceExtensionProperties(dld);
const auto it = std::ranges::find_if(*properties, [](const auto& prop) {
return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0;
});
if (it != properties->end()) {
return CreateDebugUtilsCallback(instance);
} else {
return CreateDebugReportCallback(instance);
}
}
} // Anonymous namespace } // Anonymous namespace
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
@@ -103,7 +88,8 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())), cpu_memory(cpu_memory_), gpu(gpu_), library(OpenLibrary(context.get())),
instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type, instance(CreateInstance(*library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
Settings::values.renderer_debug.GetValue())), Settings::values.renderer_debug.GetValue())),
debug_callback(MakeDebugCallback(instance, dld)), debug_messenger(Settings::values.renderer_debug ? CreateDebugUtilsCallback(instance)
: vk::DebugUtilsMessenger{}),
surface(CreateSurface(instance, render_window.GetWindowInfo())), surface(CreateSurface(instance, render_window.GetWindowInfo())),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(), device(CreateDevice(instance, dld, *surface)), memory_allocator(device), state_tracker(),
scheduler(device, state_tracker), scheduler(device, state_tracker),

View File

@@ -35,8 +35,6 @@ class GPU;
namespace Vulkan { namespace Vulkan {
using DebugCallback = std::variant<vk::DebugUtilsMessenger, vk::DebugReportCallback>;
Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld,
VkSurfaceKHR surface); VkSurfaceKHR surface);
@@ -75,7 +73,7 @@ private:
vk::InstanceDispatch dld; vk::InstanceDispatch dld;
vk::Instance instance; vk::Instance instance;
DebugCallback debug_callback; vk::DebugUtilsMessenger debug_messenger;
vk::SurfaceKHR surface; vk::SurfaceKHR surface;
ScreenInfo screen_info; ScreenInfo screen_info;

View File

@@ -529,17 +529,20 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
buffer_handles.push_back(handle); buffer_handles.push_back(handle);
} }
if (device.IsExtExtendedDynamicStateSupported()) { if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([bindings_ = std::move(bindings), scheduler.Record([this, bindings_ = std::move(bindings),
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, cmdbuf.BindVertexBuffers2EXT(bindings_.min_index,
bindings_.max_index - bindings_.min_index, std::min(bindings_.max_index - bindings_.min_index,
device.GetMaxVertexInputBindings()),
buffer_handles_.data(), bindings_.offsets.data(), buffer_handles_.data(), bindings_.offsets.data(),
bindings_.sizes.data(), bindings_.strides.data()); bindings_.sizes.data(), bindings_.strides.data());
}); });
} else { } else {
scheduler.Record([bindings_ = std::move(bindings), scheduler.Record([this, bindings_ = std::move(bindings),
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) { buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffers(bindings_.min_index, bindings_.max_index - bindings_.min_index, cmdbuf.BindVertexBuffers(bindings_.min_index,
std::min(bindings_.max_index - bindings_.min_index,
device.GetMaxVertexInputBindings()),
buffer_handles_.data(), bindings_.offsets.data()); buffer_handles_.data(), bindings_.offsets.data());
}); });
} }

View File

@@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
return nullptr; return nullptr;
} }
const auto& image_map_ids = it->second; const auto& image_map_ids = it->second;
boost::container::small_vector<const ImageBase*, 4> valid_images;
for (const ImageMapId map_id : image_map_ids) { for (const ImageMapId map_id : image_map_ids) {
const ImageMapView& map = slot_map_views[map_id]; const ImageMapView& map = slot_map_views[map_id];
const ImageBase& image = slot_images[map.image_id]; const ImageBase& image = slot_images[map.image_id];
@@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
if (image.image_view_ids.empty()) { if (image.image_view_ids.empty()) {
continue; continue;
} }
return &slot_image_views[image.image_view_ids.at(0)]; valid_images.push_back(&image);
} }
if (valid_images.size() == 1) [[likely]] {
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
if (valid_images.size() > 0) [[unlikely]] {
std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
return a->modification_tick > b->modification_tick;
});
return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
}
return nullptr; return nullptr;
} }

View File

@@ -63,22 +63,6 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
return VK_FALSE; return VK_FALSE;
} }
VkBool32 DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType,
uint64_t object, size_t location, int32_t messageCode,
const char* pLayerPrefix, const char* pMessage, void* pUserData) {
const VkDebugReportFlagBitsEXT severity = static_cast<VkDebugReportFlagBitsEXT>(flags);
const std::string_view message{pMessage};
if (severity & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
LOG_CRITICAL(Render_Vulkan, "{}", message);
} else if (severity & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
LOG_WARNING(Render_Vulkan, "{}", message);
} else if (severity & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
LOG_INFO(Render_Vulkan, "{}", message);
} else if (severity & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
LOG_DEBUG(Render_Vulkan, "{}", message);
}
return VK_FALSE;
}
} // Anonymous namespace } // Anonymous namespace
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) { vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
@@ -98,15 +82,4 @@ vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance) {
}); });
} }
vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance) {
return instance.CreateDebugReportCallback({
.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = VK_DEBUG_REPORT_DEBUG_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT,
.pfnCallback = DebugReportCallback,
.pUserData = nullptr,
});
}
} // namespace Vulkan } // namespace Vulkan

View File

@@ -9,6 +9,4 @@ namespace Vulkan {
vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance); vk::DebugUtilsMessenger CreateDebugUtilsCallback(const vk::Instance& instance);
vk::DebugReportCallback CreateDebugReportCallback(const vk::Instance& instance);
} // namespace Vulkan } // namespace Vulkan

View File

@@ -76,11 +76,9 @@ namespace {
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
} }
#endif #endif
if (enable_validation) { if (enable_validation &&
const bool debug_utils = AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) {
AreExtensionsSupported(dld, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME}); extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
extensions.push_back(debug_utils ? VK_EXT_DEBUG_UTILS_EXTENSION_NAME
: VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
} }
return extensions; return extensions;
} }

View File

@@ -160,7 +160,8 @@ void QtAmiiboSettingsDialog::LoadAmiiboData() {
} }
const auto amiibo_name = std::string(register_info.amiibo_name.data()); const auto amiibo_name = std::string(register_info.amiibo_name.data());
const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data()); const auto owner_name =
Common::UTF16ToUTF8(register_info.mii_char_info.GetNickname().data.data());
const auto creation_date = const auto creation_date =
QDate(register_info.creation_date.year, register_info.creation_date.month, QDate(register_info.creation_date.year, register_info.creation_date.month,
register_info.creation_date.day); register_info.creation_date.day);

View File

@@ -193,14 +193,10 @@ void ConfigureGraphics::PopulateVSyncModeSelection() {
: vsync_mode_combobox_enum_map[current_index]; : vsync_mode_combobox_enum_map[current_index];
int index{}; int index{};
const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
if (device == -1) {
// Invalid device
return;
}
const auto& present_modes = //< relevant vector of present modes for the selected device or API const auto& present_modes = //< relevant vector of present modes for the selected device or API
backend == Settings::RendererBackend::Vulkan ? device_present_modes[device] backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
: default_present_modes; : default_present_modes;
vsync_mode_combobox->clear(); vsync_mode_combobox->clear();
vsync_mode_combobox_enum_map.clear(); vsync_mode_combobox_enum_map.clear();
@@ -497,11 +493,19 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
} }
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) { const auto selected_backend = [&]() {
return Settings::values.renderer_backend.GetValue(true); if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
return Settings::values.renderer_backend.GetValue(true);
}
return static_cast<Settings::RendererBackend>(
combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
.at(api_combobox->currentIndex())
.first);
}();
if (selected_backend == Settings::RendererBackend::Vulkan &&
UISettings::values.has_broken_vulkan) {
return Settings::RendererBackend::OpenGL;
} }
return static_cast<Settings::RendererBackend>( return selected_backend;
combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
.at(api_combobox->currentIndex())
.first);
} }

View File

@@ -557,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS"));
QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS"));
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
#ifndef WIN32 #ifndef WIN32
@@ -588,10 +589,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path);
}); });
connect(start_game, &QAction::triggered, [this, path]() { connect(start_game, &QAction::triggered, [this, path]() {
emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal); emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Normal,
AmLaunchType::UserInitiated);
}); });
connect(start_game_global, &QAction::triggered, [this, path]() { connect(start_game_global, &QAction::triggered, [this, path]() {
emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global); emit BootGame(QString::fromStdString(path), 0, 0, StartGameType::Global,
AmLaunchType::UserInitiated);
}); });
connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { connect(open_mod_location, &QAction::triggered, [this, program_id, path]() {
emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path);
@@ -628,6 +631,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() {
emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC);
}); });
connect(verify_integrity, &QAction::triggered,
[this, path]() { emit VerifyIntegrityRequested(path); });
connect(copy_tid, &QAction::triggered, connect(copy_tid, &QAction::triggered,
[this, program_id]() { emit CopyTIDRequested(program_id); }); [this, program_id]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {

View File

@@ -28,6 +28,7 @@ class GameListWorker;
class GameListSearchField; class GameListSearchField;
class GameListDir; class GameListDir;
class GMainWindow; class GMainWindow;
enum class AmLaunchType;
enum class StartGameType; enum class StartGameType;
namespace FileSys { namespace FileSys {
@@ -103,7 +104,7 @@ public:
signals: signals:
void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, void BootGame(const QString& game_path, u64 program_id, std::size_t program_index,
StartGameType type); StartGameType type, AmLaunchType launch_type);
void GameChosen(const QString& game_path, const u64 title_id = 0); void GameChosen(const QString& game_path, const u64 title_id = 0);
void ShouldCancelWorker(); void ShouldCancelWorker();
void OpenFolderRequested(u64 program_id, GameListOpenTarget target, void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
@@ -113,6 +114,7 @@ signals:
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path); const std::string& game_path);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id); void CopyTIDRequested(u64 program_id);
void CreateShortcut(u64 program_id, const std::string& game_path, void CreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target); GameListShortcutTarget target);

View File

@@ -8,6 +8,7 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include "core/loader/nca.h"
#ifdef __APPLE__ #ifdef __APPLE__
#include <unistd.h> // for chdir #include <unistd.h> // for chdir
#endif #endif
@@ -442,8 +443,13 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
"#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>" "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
"here for instructions to fix the issue</a>.")); "here for instructions to fix the issue</a>."));
#ifdef HAS_OPENGL
Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
#else
Settings::values.renderer_backend = Settings::RendererBackend::Null;
#endif
UpdateAPIText();
renderer_status_button->setDisabled(true); renderer_status_button->setDisabled(true);
renderer_status_button->setChecked(false); renderer_status_button->setChecked(false);
} else { } else {
@@ -1447,6 +1453,8 @@ void GMainWindow::ConnectWidgetEvents() {
&GMainWindow::OnGameListRemoveInstalledEntry); &GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::VerifyIntegrityRequested, this,
&GMainWindow::OnGameListVerifyIntegrity);
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry); &GMainWindow::OnGameListNavigateToGamedbEntry);
@@ -1547,6 +1555,7 @@ void GMainWindow::ConnectMenuEvents() {
// Help // Help
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
connect_menu(ui->action_About, &GMainWindow::OnAbout); connect_menu(ui->action_About, &GMainWindow::OnAbout);
} }
@@ -1698,7 +1707,8 @@ void GMainWindow::AllowOSSleep() {
#endif #endif
} }
bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) { bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index,
AmLaunchType launch_type) {
// Shutdown previous session if the emu thread is still active... // Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr) { if (emu_thread != nullptr) {
ShutdownGame(); ShutdownGame();
@@ -1710,6 +1720,10 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetFilesystem(vfs); system->SetFilesystem(vfs);
if (launch_type == AmLaunchType::UserInitiated) {
system->GetUserChannel().clear();
}
system->SetAppletFrontendSet({ system->SetAppletFrontendSet({
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
(UISettings::values.controller_applet_disabled.GetValue() == true) (UISettings::values.controller_applet_disabled.GetValue() == true)
@@ -1849,7 +1863,7 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
} }
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index, void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
StartGameType type) { StartGameType type, AmLaunchType launch_type) {
LOG_INFO(Frontend, "yuzu starting..."); LOG_INFO(Frontend, "yuzu starting...");
StoreRecentFile(filename); // Put the filename on top of the list StoreRecentFile(filename); // Put the filename on top of the list
@@ -1893,7 +1907,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
} }
} }
if (!LoadROM(filename, program_id, program_index)) { if (!LoadROM(filename, program_id, program_index, launch_type)) {
return; return;
} }
@@ -2708,6 +2722,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
} }
} }
void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) {
const auto NotImplemented = [this] {
QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"),
tr("File contents were not checked for validity."));
};
const auto Failed = [this] {
QMessageBox::critical(this, tr("Integrity verification failed!"),
tr("File contents may be corrupt."));
};
const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
if (loader == nullptr) {
NotImplemented();
return;
}
QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
progress.setAutoClose(false);
progress.setAutoReset(false);
const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) {
if (progress.wasCanceled()) {
return false;
}
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
return true;
};
const auto status = loader->VerifyIntegrity(QtProgressCallback);
if (progress.wasCanceled() ||
status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) {
NotImplemented();
return;
}
if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) {
Failed();
return;
}
progress.close();
QMessageBox::information(this, tr("Integrity verification succeeded!"),
tr("The operation completed successfully."));
}
void GMainWindow::OnGameListCopyTID(u64 program_id) { void GMainWindow::OnGameListCopyTID(u64 program_id) {
QClipboard* clipboard = QGuiApplication::clipboard(); QClipboard* clipboard = QGuiApplication::clipboard();
clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
@@ -3314,7 +3376,8 @@ void GMainWindow::OnLoadComplete() {
void GMainWindow::OnExecuteProgram(std::size_t program_index) { void GMainWindow::OnExecuteProgram(std::size_t program_index) {
ShutdownGame(); ShutdownGame();
BootGame(last_filename_booted, 0, program_index); BootGame(last_filename_booted, 0, program_index, StartGameType::Normal,
AmLaunchType::ApplicationInitiated);
} }
void GMainWindow::OnExit() { void GMainWindow::OnExit() {
@@ -3794,10 +3857,14 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnToggleGraphicsAPI() { void GMainWindow::OnToggleGraphicsAPI() {
auto api = Settings::values.renderer_backend.GetValue(); auto api = Settings::values.renderer_backend.GetValue();
if (api == Settings::RendererBackend::OpenGL) { if (api != Settings::RendererBackend::Vulkan) {
api = Settings::RendererBackend::Vulkan; api = Settings::RendererBackend::Vulkan;
} else { } else {
#ifdef HAS_OPENGL
api = Settings::RendererBackend::OpenGL; api = Settings::RendererBackend::OpenGL;
#else
api = Settings::RendererBackend::Null;
#endif
} }
Settings::values.renderer_backend.SetValue(api); Settings::values.renderer_backend.SetValue(api);
renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan); renderer_status_button->setChecked(api == Settings::RendererBackend::Vulkan);
@@ -3941,6 +4008,108 @@ void GMainWindow::OnOpenYuzuFolder() {
QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
} }
void GMainWindow::OnVerifyInstalledContents() {
// Declare sizes.
size_t total_size = 0;
size_t processed_size = 0;
// Initialize a progress dialog.
QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
progress.setAutoClose(false);
progress.setAutoReset(false);
// Declare a list of file names which failed to verify.
std::vector<std::string> failed;
// Declare progress callback.
auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) {
if (progress.wasCanceled()) {
return false;
}
progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size));
return true;
};
// Get content registries.
auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
auto user_contents = system->GetFileSystemController().GetUserNANDContents();
std::vector<FileSys::RegisteredCache*> content_providers;
if (bis_contents) {
content_providers.push_back(bis_contents);
}
if (user_contents) {
content_providers.push_back(user_contents);
}
// Get associated NCA files.
std::vector<FileSys::VirtualFile> nca_files;
// Get all installed IDs.
for (auto nca_provider : content_providers) {
const auto entries = nca_provider->ListEntriesFilter();
for (const auto& entry : entries) {
auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
if (!nca_file) {
continue;
}
total_size += nca_file->GetSize();
nca_files.push_back(std::move(nca_file));
}
}
// Using the NCA loader, determine if all NCAs are valid.
for (auto& nca_file : nca_files) {
Loader::AppLoader_NCA nca_loader(nca_file);
auto status = nca_loader.VerifyIntegrity(QtProgressCallback);
if (progress.wasCanceled()) {
break;
}
if (status != Loader::ResultStatus::Success) {
FileSys::NCA nca(nca_file);
const auto title_id = nca.GetTitleId();
std::string title_name = "unknown";
const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
FileSys::ContentRecordType::Control);
if (control && control->GetStatus() == Loader::ResultStatus::Success) {
const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
*provider};
const auto [nacp, logo] = pm.ParseControlNCA(*control);
if (nacp) {
title_name = nacp->GetApplicationName();
}
}
if (title_id > 0) {
failed.push_back(
fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
} else {
failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
}
}
processed_size += nca_file->GetSize();
}
progress.close();
if (failed.size() > 0) {
auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n")));
QMessageBox::critical(
this, tr("Integrity verification failed!"),
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
} else {
QMessageBox::information(this, tr("Integrity verification succeeded!"),
tr("The operation completed successfully."));
}
}
void GMainWindow::OnAbout() { void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this); AboutDialog aboutDialog(this);
aboutDialog.exec(); aboutDialog.exec();

Some files were not shown because too many files have changed in this diff Show More