Compare commits

...

44 Commits

Author SHA1 Message Date
3d110c1c3b Android #80 2023-09-24 00:57:39 +00:00
ace91dd0c0 Merge pull request #11567 from liamwhite/fixing-my-error
emit_spirv: fix incorrect use of descriptor index in image atomics
2023-09-23 13:00:31 +02:00
2921a24268 Merge pull request #11573 from t895/import-fix
android: Use smaller read buffer size for exporting user data
2023-09-22 22:02:06 -04:00
5269a46399 android: Use smaller read buffer size for exporting user data
The File.readBytes() extension attempts to load an entire file into a byte array. This would cause crashes when loading huge files into memory.
2023-09-22 16:51:48 -04:00
33e2dce715 Merge pull request #11572 from t895/import-heuristic
android: Adjust valid user data check
2023-09-22 14:53:39 -04:00
f3bc7354b1 android: Adjust valid user data check 2023-09-22 12:05:44 -04:00
bd5ae33153 Merge pull request #11561 from german77/hle_applet
am: mii_edit: Implement DB operations
2023-09-22 09:56:14 -04:00
16f1592e50 Merge pull request #11557 from GPUCode/brr-format
renderer_vulkan: Correct component order for A4B4G4R4_UNORM
2023-09-22 09:56:04 -04:00
fda08cbbb0 Merge pull request #11563 from Kelebek1/dma_regs
Fix DMA engine register offsets
2023-09-22 09:55:54 -04:00
1e24d02434 emit_spirv: fix incorrect use of descriptor index in image atomics 2023-09-22 00:39:09 -04:00
a57ca3fb66 am: mii_edit: Implement DB operations 2023-09-21 18:21:39 -06:00
c619199bb4 Merge pull request #11564 from t895/overlay-inset-fix
android: Update androidx window library to 1.2.0-beta03
2023-09-21 19:15:36 -04:00
703bf7cfce android: Update androidx window library to 1.2.0-beta03
Fixes an issue with the input overlay on certain devices where the controls would appear offscreen.
2023-09-21 17:36:14 -04:00
4f69be8169 Fix DMA engine register offsets 2023-09-21 20:21:00 +01:00
9e9cb28471 Merge pull request #11555 from yuzu-emu/revert-11551-allow-save-imports-always
Revert "android: Allow save imports always"
2023-09-21 09:21:19 -04:00
2ffea42ec8 Merge pull request #11553 from rkfg/pfs-fix
pfs: Fix reading filenames past the buffer end
2023-09-21 09:21:08 -04:00
4a59dc2947 renderer_vulkan: Correct component order for A4B4G4R4_UNORM 2023-09-21 15:33:44 +03:00
c644c1a90a Revert "android: Allow save imports always" 2023-09-21 02:57:28 -04:00
753bc3a448 pfs: Fix reading filenames past the buffer end 2023-09-21 05:12:05 +03:00
c708643972 Merge pull request #11551 from t895/allow-save-imports-always
android: Allow save imports always
2023-09-20 18:32:31 -04:00
a85325f56a android: Remove unused strings related to the save manager 2023-09-20 15:01:03 -04:00
bdb4fd208f android: Allow importing saves even if no saves are found
Exporting still won't be allowed on an empty save directory.
2023-09-20 15:00:34 -04:00
1fae4a01a8 Merge pull request #11543 from t895/import-export-user-data
android: Add import/export buttons for user data
2023-09-20 10:17:24 -04:00
1e740df9b8 android: Add import/export buttons for user data 2023-09-19 15:54:47 -04:00
df56ecc318 Merge pull request #11542 from t895/touch-offset-fix
android: Screen orientation and aspect ratio fixes
2023-09-19 09:25:09 -04:00
49cb89e324 Merge pull request #11526 from german77/mii_service_v2
service: mii: Update implementation Part2 - Mii database support
2023-09-19 09:24:49 -04:00
da8cbbf958 Merge pull request #11536 from abouvier/renderdoc
cmake: prefer system renderdoc header
2023-09-19 09:24:36 -04:00
55087ab08a Merge pull request #11538 from cathyjf/renderdoc-check-correct-win32-symbol
renderdoc: Check for `_WIN32` symbol rather than `WIN32`
2023-09-19 09:24:23 -04:00
d7e8926ff6 Merge pull request #11540 from liamwhite/aoc-ec
aoc: stub purchase info calls
2023-09-19 09:24:08 -04:00
7dd3d1b8ad android: Ignore validation layers library in git 2023-09-19 00:31:43 -04:00
fd09784231 android: Don't pause emulation when entering PiP 2023-09-19 00:31:43 -04:00
3b612cff28 android: Fix showing input overlay in PiP 2023-09-19 00:31:43 -04:00
32d65fc8de android: Properly update emulation surface
Previously the emulation surface wasn't being updated during configuration changes and only during specific view events. This would break input and the screen dimensions after each orientation/aspect ratio change. Now a new surface is provided every time and the display dimensions are updated as needed.
2023-09-19 00:31:43 -04:00
3ff29de4a1 aoc: stub purchase info calls 2023-09-18 16:20:53 -04:00
9fef6560f0 renderdoc: Check for _WIN32 symbol rather than WIN32 2023-09-18 12:09:51 -07:00
dca36ebb87 service: mii: Address review comments 2023-09-18 11:08:04 -06:00
f93f31f4ae cmake: prefer system renderdoc header 2023-09-18 18:35:20 +02:00
2fb71aecb0 service: mii: Implement the rest of the service 2023-09-17 16:06:25 -06:00
9a878de33f service: mii: Implement database manager 2023-09-17 16:06:25 -06:00
a50b50f8b4 service: mii: Implement figurine database 2023-09-17 16:06:25 -06:00
bd409c3416 service: mii: Add device crc16 2023-09-17 16:06:25 -06:00
2f22b53732 service: nfc: Fully Implement GetRegisterInfoPrivate 2023-09-17 16:06:25 -06:00
1e8616bd01 service: mii: Complete structs and fix mistakes 2023-09-17 16:06:23 -06:00
e3c546a1ed android: Export PiP receiver on API 33 and later
Could cause crashes on API 33+ devices
2023-09-17 17:33:33 -04:00
56 changed files with 2452 additions and 423 deletions

View File

@ -333,6 +333,7 @@ find_package(LLVM 17 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
find_package(RenderDoc MODULE)
find_package(VulkanMemoryAllocator CONFIG)
find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED)

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_path(RenderDoc_INCLUDE_DIR renderdoc_app.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RenderDoc
REQUIRED_VARS RenderDoc_INCLUDE_DIR
)
if (RenderDoc_FOUND AND NOT TARGET RenderDoc::API)
add_library(RenderDoc::API INTERFACE IMPORTED)
set_target_properties(RenderDoc::API PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${RenderDoc_INCLUDE_DIR}"
)
endif()
mark_as_advanced(RenderDoc_INCLUDE_DIR)

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-License-Identifier: GPL-2.0-or-later

View File

@ -174,8 +174,11 @@ target_include_directories(stb PUBLIC ./stb)
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
add_library(renderdoc INTERFACE)
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
if (NOT TARGET RenderDoc::API)
add_library(renderdoc INTERFACE)
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
add_library(RenderDoc::API ALIAS renderdoc)
endif()
if (ANDROID)
if (ARCHITECTURE_arm64)

View File

@ -63,3 +63,6 @@ fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Autogenerated library for vulkan validation layers
libVkLayer_khronos_validation.so

View File

@ -214,7 +214,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.1.0")
implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
@ -397,6 +398,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
@ -409,8 +411,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
addAction(actionMute)
addAction(actionUnmute)
}.also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(pictureInPictureReceiver, it, RECEIVER_EXPORTED)
} else {
registerReceiver(pictureInPictureReceiver, it)
}
}
} else {
try {
unregisterReceiver(pictureInPictureReceiver)

View File

@ -49,6 +49,7 @@ class HomeSettingAdapter(
holder.option.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
activity,
titleId = holder.option.disabledTitleId,
descriptionId = holder.option.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)

View File

@ -26,6 +26,7 @@ import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class AboutFragment : Fragment() {
private var _binding: FragmentAboutBinding? = null
@ -92,6 +93,12 @@ class AboutFragment : Fragment() {
}
}
val mainActivity = requireActivity() as MainActivity
binding.buttonExport.setOnClickListener { mainActivity.exportUserData.launch("export.zip") }
binding.buttonImport.setOnClickListener {
mainActivity.importUserData.launch(arrayOf("application/zip"))
}
binding.buttonDiscord.setOnClickListener { openLink(getString(R.string.support_link)) }
binding.buttonWebsite.setOnClickListener { openLink(getString(R.string.website_link)) }
binding.buttonGithub.setOnClickListener { openLink(getString(R.string.github_link)) }

View File

@ -15,7 +15,6 @@ import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Rational
import android.view.*
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
@ -287,13 +286,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateScreenLayout()
if (emulationActivity?.isInPictureInPictureMode == true) {
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE
binding.surfaceInputOverlay.visibility = View.INVISIBLE
}
}
} else {
@ -328,7 +328,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
override fun onPause() {
if (emulationState.isRunning) {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
}
super.onPause()
@ -394,16 +394,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun updateScreenLayout() {
binding.surfaceEmulation.setAspectRatio(
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
3 -> Rational(16, 10)
4 -> null // Stretch
else -> Rational(16, 9)
}
)
binding.surfaceEmulation.setAspectRatio(null)
emulationActivity?.buildPictureInPictureParams()
updateOrientation()
}
@ -693,7 +684,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private class EmulationState(private val gamePath: String) {
private var state: State
private var surface: Surface? = null
private var runWhenSurfaceIsValid = false
init {
// Starting state is stopped.
@ -751,8 +741,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// If the surface is set, run now. Otherwise, wait for it to get set.
if (surface != null) {
runWithValidSurface()
} else {
runWhenSurfaceIsValid = true
}
}
@ -760,7 +748,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun newSurface(surface: Surface?) {
this.surface = surface
if (runWhenSurfaceIsValid) {
if (this.surface != null) {
runWithValidSurface()
}
}
@ -788,10 +776,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun runWithValidSurface() {
runWhenSurfaceIsValid = false
NativeLibrary.surfaceChanged(surface)
when (state) {
State.STOPPED -> {
NativeLibrary.surfaceChanged(surface)
val emulationThread = Thread({
Log.debug("[EmulationFragment] Starting emulation thread.")
NativeLibrary.run(gamePath)
@ -801,7 +788,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.surfaceChanged(surface)
NativeLibrary.unpauseEmulation()
}

View File

@ -187,6 +187,7 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) {
if (!validZip) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.save_file_invalid_zip_structure,
descriptionId = R.string.save_file_invalid_zip_structure_description
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)

View File

@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -18,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
@ -28,19 +30,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE)
binding = DialogProgressBarBinding.inflate(layoutInflater)
binding.progressBar.isIndeterminate = true
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(titleId)
.setView(binding.root)
.create()
dialog.setCanceledOnTouchOutside(false)
if (cancellable) {
dialog.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
taskViewModel.setCancelled(true)
}
}
val alertDialog = dialog.create()
alertDialog.setCanceledOnTouchOutside(false)
if (!taskViewModel.isRunning.value) {
taskViewModel.runTask()
}
return dialog
return alertDialog
}
override fun onCreateView(
@ -53,14 +63,18 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.isComplete.collect {
if (it) {
dismiss()
when (val result = taskViewModel.result.value) {
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
.show()
is String -> Toast.makeText(
requireContext(),
result,
Toast.LENGTH_LONG
).show()
is MessageDialogFragment -> result.show(
requireActivity().supportFragmentManager,
@ -72,22 +86,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.cancelled.collect {
if (it) {
dialog?.setTitle(R.string.cancelling)
}
}
}
}
}
}
companion object {
const val TAG = "IndeterminateProgressDialogFragment"
private const val TITLE = "Title"
private const val CANCELLABLE = "Cancellable"
fun newInstance(
activity: AppCompatActivity,
titleId: Int,
cancellable: Boolean = false,
task: () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)
args.putBoolean(CANCELLABLE, cancellable)
dialog.arguments = args
return dialog
}

View File

@ -4,14 +4,21 @@
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.MessageDialogViewModel
class MessageDialogFragment : DialogFragment() {
private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE_ID)
val titleString = requireArguments().getString(TITLE_STRING)!!
@ -37,6 +44,12 @@ class MessageDialogFragment : DialogFragment() {
return dialog.show()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
messageDialogViewModel.dismissAction.invoke()
messageDialogViewModel.clear()
}
private fun openLink(link: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(intent)
@ -52,11 +65,13 @@ class MessageDialogFragment : DialogFragment() {
private const val HELP_LINK = "Link"
fun newInstance(
activity: FragmentActivity,
titleId: Int = 0,
titleString: String = "",
descriptionId: Int = 0,
descriptionString: String = "",
helpLinkId: Int = 0
helpLinkId: Int = 0,
dismissAction: () -> Unit = {}
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
@ -67,6 +82,8 @@ class MessageDialogFragment : DialogFragment() {
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
dismissAction
dialog.arguments = bundle
return dialog
}

View File

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.lifecycle.ViewModel
class MessageDialogViewModel : ViewModel() {
var dismissAction: () -> Unit = {}
fun clear() {
dismissAction = {}
}
}

View File

@ -20,12 +20,20 @@ class TaskViewModel : ViewModel() {
val isRunning: StateFlow<Boolean> get() = _isRunning
private val _isRunning = MutableStateFlow(false)
val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false)
lateinit var task: () -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
_cancelled.value = false
}
fun setCancelled(value: Boolean) {
_cancelled.value = value
}
fun runTask() {

View File

@ -46,13 +46,22 @@ import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
private val homeViewModel: HomeViewModel by viewModels()
private val gamesViewModel: GamesViewModel by viewModels()
private val taskViewModel: TaskViewModel by viewModels()
override var themeId: Int = 0
@ -307,6 +316,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
this,
titleId = R.string.reading_keys_failure,
descriptionId = R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
@ -336,6 +346,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return true
} else {
MessageDialogFragment.newInstance(
this,
titleId = R.string.invalid_keys_error,
descriptionId = R.string.install_keys_failure_description,
helpLinkId = R.string.dumping_keys_quickstart_link
@ -376,6 +387,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
MessageDialogFragment.newInstance(
this,
titleId = R.string.firmware_installed_failure,
descriptionId = R.string.firmware_installed_failure_description
)
@ -395,7 +407,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task
task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
@ -407,6 +419,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (FileUtil.getExtension(result) != "bin") {
MessageDialogFragment.newInstance(
this,
titleId = R.string.reading_keys_failure,
descriptionId = R.string.install_amiibo_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
@ -434,6 +447,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
).show()
} else {
MessageDialogFragment.newInstance(
this,
titleId = R.string.invalid_keys_error,
descriptionId = R.string.install_keys_failure_description,
helpLinkId = R.string.dumping_keys_quickstart_link
@ -583,12 +597,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
installResult.append(separator)
}
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_failure,
descriptionString = installResult.toString().trim(),
helpLinkId = R.string.install_game_content_help_link
)
} else {
return@newInstance MessageDialogFragment.newInstance(
this,
titleId = R.string.install_game_content_success,
descriptionString = installResult.toString().trim()
)
@ -596,4 +612,119 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}
val exportUserData = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
) {
val zos = ZipOutputStream(
BufferedOutputStream(contentResolver.openOutputStream(result))
)
zos.use { stream ->
File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
if (taskViewModel.cancelled.value) {
return@newInstance R.string.user_data_export_cancelled
}
if (!file.isDirectory) {
val newPath = file.path.substring(
DirectoryInitialization.userDirectory!!.length,
file.path.length
)
stream.putNextEntry(ZipEntry(newPath))
val buffer = ByteArray(8096)
var read: Int
FileInputStream(file).use { fis ->
while (fis.read(buffer).also { read = it } != -1) {
stream.write(buffer, 0, read)
}
}
stream.closeEntry()
}
}
}
return@newInstance getString(R.string.user_data_export_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
val importUserData =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
) {
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
checkStream.use { stream ->
var ze: ZipEntry? = null
while (stream.nextEntry?.also { ze = it } != null) {
val itemName = ze!!.name.trim()
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
isYuzuBackup = true
return@use
}
}
}
if (!isYuzuBackup) {
return@newInstance getString(R.string.invalid_yuzu_backup)
}
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
val zis =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
val userDirectory = File(DirectoryInitialization.userDirectory!!)
val canonicalPath = userDirectory.canonicalPath + '/'
zis.use { stream ->
var ze: ZipEntry? = stream.nextEntry
while (ze != null) {
val newFile = File(userDirectory, ze!!.name)
val destinationDirectory =
if (ze!!.isDirectory) newFile else newFile.parentFile
if (!newFile.canonicalPath.startsWith(canonicalPath)) {
throw SecurityException(
"Zip file attempted path traversal! ${ze!!.name}"
)
}
if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
throw IOException("Failed to create directory $destinationDirectory")
}
if (!ze!!.isDirectory) {
val buffer = ByteArray(8096)
var read: Int
BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
while (zis.read(buffer).also { read = it } != -1) {
bos.write(buffer, 0, read)
}
}
}
ze = stream.nextEntry
}
}
// Reinitialize relevant data
NativeLibrary.initializeEmulation()
gamesViewModel.reloadGames(false)
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}

View File

@ -11,6 +11,12 @@
#include "jni/emu_window/emu_window.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
// Ensures that we emulate with the correct aspect ratio.
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
window_info.render_surface = reinterpret_cast<void*>(surface);
}
@ -62,14 +68,8 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste
return;
}
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
// Ensures that we emulate with the correct aspect ratio.
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
OnSurfaceChanged(surface);
window_info.type = Core::Frontend::WindowSystemType::Android;
window_info.render_surface = reinterpret_cast<void*>(surface);
m_input_subsystem->Initialize();
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
</vector>

View File

@ -1,24 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:padding="24dp"
app:trackCornerRadius="4dp" />
<TextView
android:id="@+id/progress_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="24dp"
android:gravity="end" />
</LinearLayout>

View File

@ -176,6 +176,67 @@
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="16dp"
android:paddingHorizontal="16dp"
android:orientation="vertical"
android:layout_weight="1">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart"
android:text="@string/user_data" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
android:text="@string/user_data_description" />
</LinearLayout>
<Button
android:id="@+id/button_import"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/string_import"
android:tooltipText="@string/string_import"
app:icon="@drawable/ic_import" />
<Button
android:id="@+id/button_export"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="24dp"
android:layout_gravity="center_vertical"
android:contentDescription="@string/export"
android:tooltipText="@string/export"
app:icon="@drawable/ic_export" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -128,6 +128,15 @@
<string name="contributors_link">https://github.com/yuzu-emu/yuzu/graphs/contributors</string>
<string name="licenses_description">Projects that make yuzu for Android possible</string>
<string name="build">Build</string>
<string name="user_data">User data</string>
<string name="user_data_description">Import/export all app data.\n\nWhen importing user data, all existing user data will be deleted!</string>
<string name="exporting_user_data">Exporting user data…</string>
<string name="importing_user_data">Importing user data…</string>
<string name="import_user_data">Import user data</string>
<string name="invalid_yuzu_backup">Invalid yuzu backup</string>
<string name="user_data_export_success">User data exported successfully</string>
<string name="user_data_import_success">User data imported successfully</string>
<string name="user_data_export_cancelled">Export cancelled</string>
<string name="support_link">https://discord.gg/u77vRWY</string>
<string name="website_link">https://yuzu-emu.org/</string>
<string name="github_link">https://github.com/yuzu-emu</string>
@ -215,6 +224,9 @@
<string name="auto">Auto</string>
<string name="submit">Submit</string>
<string name="string_null">Null</string>
<string name="string_import">Import</string>
<string name="export">Export</string>
<string name="cancelling">Cancelling</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>

View File

@ -596,6 +596,10 @@ add_library(core STATIC
hle/service/mii/types/ver3_store_data.h
hle/service/mii/mii.cpp
hle/service/mii/mii.h
hle/service/mii/mii_database.cpp
hle/service/mii/mii_database.h
hle/service/mii/mii_database_manager.cpp
hle/service/mii/mii_database_manager.h
hle/service/mii/mii_manager.cpp
hle/service/mii/mii_manager.h
hle/service/mii/mii_result.h
@ -890,7 +894,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
endif()

View File

@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
// Actually read in now...
std::vector<u8> file_data = file->ReadBytes(metadata_size);
const std::size_t total_size = file_data.size();
file_data.push_back(0);
if (total_size != metadata_size) {
status = Loader::ResultStatus::ErrorIncorrectPFSFileSize;

View File

@ -7,7 +7,9 @@
#include "core/frontend/applets/mii_edit.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_mii_edit.h"
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/sm/sm.h"
namespace Service::AM::Applets {
@ -56,6 +58,12 @@ void MiiEdit::Initialize() {
sizeof(MiiEditAppletInputV4));
break;
}
manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager();
if (manager == nullptr) {
manager = std::make_shared<Mii::MiiManager>();
}
manager->Initialize(metadata);
}
bool MiiEdit::TransactionComplete() const {
@ -78,22 +86,46 @@ void MiiEdit::Execute() {
// This is a default stub for each of the MiiEdit applet modes.
switch (applet_input_common.applet_mode) {
case MiiEditAppletMode::ShowMiiEdit:
case MiiEditAppletMode::AppendMii:
case MiiEditAppletMode::AppendMiiImage:
case MiiEditAppletMode::UpdateMiiImage:
MiiEditOutput(MiiEditResult::Success, 0);
break;
case MiiEditAppletMode::CreateMii:
case MiiEditAppletMode::EditMii: {
Mii::CharInfo char_info{};
case MiiEditAppletMode::AppendMii: {
Mii::StoreData store_data{};
store_data.BuildBase(Mii::Gender::Male);
char_info.SetFromStoreData(store_data);
store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
store_data.SetNickname({u'y', u'u', u'z', u'u'});
store_data.SetChecksum();
const auto result = manager->AddOrReplace(metadata, store_data);
if (result.IsError()) {
MiiEditOutput(MiiEditResult::Cancel, 0);
break;
}
s32 index = manager->FindIndex(store_data.GetCreateId(), false);
if (index == -1) {
MiiEditOutput(MiiEditResult::Cancel, 0);
break;
}
MiiEditOutput(MiiEditResult::Success, index);
break;
}
case MiiEditAppletMode::CreateMii: {
Mii::CharInfo char_info{};
manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All);
const MiiEditCharInfo edit_char_info{
.mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
? applet_input_v4.char_info.mii_info
: char_info},
.mii_info{char_info},
};
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
break;
}
case MiiEditAppletMode::EditMii: {
const MiiEditCharInfo edit_char_info{
.mii_info{applet_input_v4.char_info.mii_info},
};
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);

View File

@ -11,6 +11,11 @@ namespace Core {
class System;
} // namespace Core
namespace Service::Mii {
struct DatabaseSessionMetadata;
class MiiManager;
} // namespace Service::Mii
namespace Service::AM::Applets {
class MiiEdit final : public Applet {
@ -40,6 +45,8 @@ private:
MiiEditAppletInputV4 applet_input_v4{};
bool is_complete{false};
std::shared_ptr<Mii::MiiManager> manager = nullptr;
Mii::DatabaseSessionMetadata metadata{};
};
} // namespace Service::AM::Applets

View File

@ -22,6 +22,8 @@
namespace Service::AOC {
constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400};
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
@ -54,8 +56,8 @@ public:
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
{3, nullptr, "PopPurchasedProductInfo"},
{4, nullptr, "PopPurchasedProductInfoWithUid"},
{3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"},
{4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"},
};
// clang-format on
@ -101,6 +103,20 @@ private:
rb.PushCopyObjects(purchased_event->GetReadableEvent());
}
void PopPurchasedProductInfo(HLERequestContext& ctx) {
LOG_DEBUG(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultNoPurchasedProductInfoAvailable);
}
void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) {
LOG_DEBUG(Service_AOC, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultNoPurchasedProductInfoAvailable);
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* purchased_event;

View File

@ -8,6 +8,9 @@
#include "core/hle/service/mii/mii.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/mii_result.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/server_manager.h"
#include "core/hle/service/service.h"
@ -15,8 +18,10 @@ namespace Service::Mii {
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
public:
explicit IDatabaseService(Core::System& system_, bool is_system_)
: ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} {
explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager,
bool is_system_)
: ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{
is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
@ -27,29 +32,31 @@ public:
{5, &IDatabaseService::UpdateLatest, "UpdateLatest"},
{6, &IDatabaseService::BuildRandom, "BuildRandom"},
{7, &IDatabaseService::BuildDefault, "BuildDefault"},
{8, nullptr, "Get2"},
{9, nullptr, "Get3"},
{10, nullptr, "UpdateLatest1"},
{11, nullptr, "FindIndex"},
{12, nullptr, "Move"},
{13, nullptr, "AddOrReplace"},
{14, nullptr, "Delete"},
{15, nullptr, "DestroyFile"},
{16, nullptr, "DeleteFile"},
{17, nullptr, "Format"},
{8, &IDatabaseService::Get2, "Get2"},
{9, &IDatabaseService::Get3, "Get3"},
{10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"},
{11, &IDatabaseService::FindIndex, "FindIndex"},
{12, &IDatabaseService::Move, "Move"},
{13, &IDatabaseService::AddOrReplace, "AddOrReplace"},
{14, &IDatabaseService::Delete, "Delete"},
{15, &IDatabaseService::DestroyFile, "DestroyFile"},
{16, &IDatabaseService::DeleteFile, "DeleteFile"},
{17, &IDatabaseService::Format, "Format"},
{18, nullptr, "Import"},
{19, nullptr, "Export"},
{20, nullptr, "IsBrokenDatabaseWithClearFlag"},
{20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"},
{21, &IDatabaseService::GetIndex, "GetIndex"},
{22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
{23, &IDatabaseService::Convert, "Convert"},
{24, nullptr, "ConvertCoreDataToCharInfo"},
{25, nullptr, "ConvertCharInfoToCoreData"},
{26, nullptr, "Append"},
{24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"},
{25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"},
{26, &IDatabaseService::Append, "Append"},
};
// clang-format on
RegisterHandlers(functions);
manager->Initialize(metadata);
}
private:
@ -59,7 +66,7 @@ private:
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const bool is_updated = manager.IsUpdated(metadata, source_flag);
const bool is_updated = manager->IsUpdated(metadata, source_flag);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -69,7 +76,7 @@ private:
void IsFullDatabase(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
const bool is_full_database = manager.IsFullDatabase();
const bool is_full_database = manager->IsFullDatabase();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -80,9 +87,9 @@ private:
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
const u32 mii_count = manager->GetCount(metadata, source_flag);
const u32 mii_count = manager.GetCount(metadata, source_flag);
LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@ -94,16 +101,17 @@ private:
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
u32 mii_count{};
std::vector<CharInfoElement> char_info_elements(output_size);
Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(char_info_elements);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
@ -114,16 +122,17 @@ private:
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
u32 mii_count{};
std::vector<CharInfo> char_info(output_size);
Result result = manager.Get(metadata, char_info, mii_count, source_flag);
const auto result = manager->Get(metadata, char_info, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(char_info);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
@ -134,10 +143,10 @@ private:
const auto char_info{rp.PopRaw<CharInfo>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
CharInfo new_char_info{};
const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag);
if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
@ -146,7 +155,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(new_char_info);
rb.PushRaw(new_char_info);
}
void BuildRandom(HLERequestContext& ctx) {
@ -176,18 +185,18 @@ private:
}
CharInfo char_info{};
manager.BuildRandom(char_info, age, gender, race);
manager->BuildRandom(char_info, age, gender, race);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(char_info);
rb.PushRaw(char_info);
}
void BuildDefault(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto index{rp.Pop<u32>()};
LOG_INFO(Service_Mii, "called with index={}", index);
LOG_DEBUG(Service_Mii, "called with index={}", index);
if (index > 5) {
IPC::ResponseBuilder rb{ctx, 2};
@ -196,11 +205,243 @@ private:
}
CharInfo char_info{};
manager.BuildDefault(char_info, index);
manager->BuildDefault(char_info, index);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<CharInfo>(char_info);
rb.PushRaw(char_info);
}
void Get2(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()};
u32 mii_count{};
std::vector<StoreDataElement> store_data_elements(output_size);
const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(store_data_elements);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
}
void Get3(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto source_flag{rp.PopRaw<SourceFlag>()};
const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()};
u32 mii_count{};
std::vector<StoreData> store_data(output_size);
const auto result = manager->Get(metadata, store_data, mii_count, source_flag);
if (mii_count != 0) {
ctx.WriteBuffer(store_data);
}
LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag,
output_size, mii_count);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(mii_count);
}
void UpdateLatest1(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto store_data{rp.PopRaw<StoreData>()};
const auto source_flag{rp.PopRaw<SourceFlag>()};
LOG_INFO(Service_Mii, "called with source_flag={}", source_flag);
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
StoreData new_store_data{};
if (result.IsSuccess()) {
result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag);
}
if (result.IsFailure()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.PushRaw<StoreData>(new_store_data);
}
void FindIndex(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
const auto is_special{rp.PopRaw<bool>()};
LOG_INFO(Service_Mii, "called with create_id={}, is_special={}",
create_id.FormattedString(), is_special);
const s32 index = manager->FindIndex(create_id, is_special);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(index);
}
void Move(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
const auto new_index{rp.PopRaw<s32>()};
LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(),
new_index);
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
const u32 count = manager->GetCount(metadata, SourceFlag::Database);
if (new_index < 0 || new_index >= static_cast<s32>(count)) {
result = ResultInvalidArgument;
}
}
if (result.IsSuccess()) {
result = manager->Move(metadata, new_index, create_id);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void AddOrReplace(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto store_data{rp.PopRaw<StoreData>()};
LOG_INFO(Service_Mii, "called");
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
result = manager->AddOrReplace(metadata, store_data);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Delete(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto create_id{rp.PopRaw<Common::UUID>()};
LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString());
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
result = manager->Delete(metadata, create_id);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void DestroyFile(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->DestroyFile(metadata);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void DeleteFile(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->DeleteFile();
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void Format(HLERequestContext& ctx) {
// This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
const bool is_db_test_mode_enabled = false;
LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled);
Result result = ResultSuccess;
if (!is_db_test_mode_enabled) {
result = ResultTestModeOnly;
}
if (result.IsSuccess()) {
result = manager->Format(metadata);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
bool is_broken_with_clear_flag = false;
Result result = ResultSuccess;
if (!is_system) {
result = ResultPermissionDenied;
}
if (result.IsSuccess()) {
is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata);
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push<u8>(is_broken_with_clear_flag);
}
void GetIndex(HLERequestContext& ctx) {
@ -210,7 +451,7 @@ private:
LOG_DEBUG(Service_Mii, "called");
s32 index{};
const auto result = manager.GetIndex(metadata, info, index);
const auto result = manager->GetIndex(metadata, info, index);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
@ -223,7 +464,7 @@ private:
LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
manager.SetInterfaceVersion(metadata, interface_version);
manager->SetInterfaceVersion(metadata, interface_version);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@ -236,22 +477,61 @@ private:
LOG_INFO(Service_Mii, "called");
CharInfo char_info{};
manager.ConvertV3ToCharInfo(char_info, mii_v3);
const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(ResultSuccess);
rb.Push(result);
rb.PushRaw<CharInfo>(char_info);
}
MiiManager manager{};
void ConvertCoreDataToCharInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto core_data{rp.PopRaw<CoreData>()};
LOG_INFO(Service_Mii, "called");
CharInfo char_info{};
const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
rb.Push(result);
rb.PushRaw<CharInfo>(char_info);
}
void ConvertCharInfoToCoreData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto char_info{rp.PopRaw<CharInfo>()};
LOG_INFO(Service_Mii, "called");
CoreData core_data{};
const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info);
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)};
rb.Push(result);
rb.PushRaw<CoreData>(core_data);
}
void Append(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto char_info{rp.PopRaw<CharInfo>()};
LOG_INFO(Service_Mii, "called");
const auto result = manager->Append(metadata, char_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
std::shared_ptr<MiiManager> manager = nullptr;
DatabaseSessionMetadata metadata{};
bool is_system{};
};
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public:
explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
: ServiceFramework{system_, name_}, is_system{is_system_} {
MiiDBModule::MiiDBModule(Core::System& system_, const char* name_,
std::shared_ptr<MiiManager> mii_manager, bool is_system_)
: ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
@ -259,28 +539,34 @@ public:
// clang-format on
RegisterHandlers(functions);
}
private:
void GetDatabaseService(HLERequestContext& ctx) {
if (manager == nullptr) {
manager = std::make_shared<MiiManager>();
}
}
MiiDBModule::~MiiDBModule() = default;
void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<IDatabaseService>(system, is_system);
rb.PushIpcInterface<IDatabaseService>(system, manager, is_system);
LOG_DEBUG(Service_Mii, "called");
}
}
bool is_system{};
};
std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() {
return manager;
}
class MiiImg final : public ServiceFramework<MiiImg> {
public:
explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
{0, &MiiImg::Initialize, "Initialize"},
{10, nullptr, "Reload"},
{11, nullptr, "GetCount"},
{11, &MiiImg::GetCount, "GetCount"},
{12, nullptr, "IsEmpty"},
{13, nullptr, "IsFull"},
{14, nullptr, "GetAttribute"},
@ -297,15 +583,32 @@ public:
RegisterHandlers(functions);
}
private:
void Initialize(HLERequestContext& ctx) {
LOG_INFO(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetCount(HLERequestContext& ctx) {
LOG_DEBUG(Service_Mii, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push(0);
}
};
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
std::shared_ptr<MiiManager> manager = nullptr;
server_manager->RegisterNamedService("mii:e",
std::make_shared<MiiDBModule>(system, "mii:e", true));
server_manager->RegisterNamedService("mii:u",
std::make_shared<MiiDBModule>(system, "mii:u", false));
server_manager->RegisterNamedService(
"mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true));
server_manager->RegisterNamedService(
"mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false));
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
ServerManager::RunServer(std::move(server_manager));
}

View File

@ -3,11 +3,29 @@
#pragma once
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Service::Mii {
class MiiManager;
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
public:
explicit MiiDBModule(Core::System& system_, const char* name_,
std::shared_ptr<MiiManager> mii_manager, bool is_system_);
~MiiDBModule() override;
std::shared_ptr<MiiManager> GetMiiManager();
private:
void GetDatabaseService(HLERequestContext& ctx);
std::shared_ptr<MiiManager> manager = nullptr;
bool is_system{};
};
void LoopProcess(Core::System& system);

View File

@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_database.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
namespace Service::Mii {
u8 NintendoFigurineDatabase::GetDatabaseLength() const {
return database_length;
}
bool NintendoFigurineDatabase::IsFull() const {
return database_length >= MaxDatabaseLength;
}
StoreData NintendoFigurineDatabase::Get(std::size_t index) const {
StoreData store_data = miis.at(index);
// This hack is to make external database dumps compatible
store_data.SetDeviceChecksum();
return store_data;
}
u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const {
if (magic == MiiMagic) {
return GetDatabaseLength();
}
u32 mii_count{};
for (std::size_t index = 0; index < mii_count; ++index) {
const auto& store_data = Get(index);
if (!store_data.IsSpecial()) {
mii_count++;
}
}
return mii_count;
}
bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index,
const Common::UUID& create_id) const {
for (std::size_t index = 0; index < database_length; ++index) {
if (miis[index].GetCreateId() == create_id) {
out_index = static_cast<u32>(index);
return true;
}
}
return false;
}
Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) {
if (current_index == new_index) {
return ResultNotUpdated;
}
const StoreData store_data = miis[current_index];
if (new_index > current_index) {
// Shift left
const u32 index_diff = new_index - current_index;
for (std::size_t i = 0; i < index_diff; i++) {
miis[current_index + i] = miis[current_index + i + 1];
}
} else {
// Shift right
const u32 index_diff = current_index - new_index;
for (std::size_t i = 0; i < index_diff; i++) {
miis[current_index - i] = miis[current_index - i - 1];
}
}
miis[new_index] = store_data;
crc = GenerateDatabaseCrc();
return ResultSuccess;
}
void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) {
miis[index] = store_data;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::Add(const StoreData& store_data) {
miis[database_length] = store_data;
database_length++;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::Delete(u32 index) {
// Shift left
const s32 new_database_size = database_length - 1;
if (static_cast<s32>(index) < new_database_size) {
for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) {
miis[i] = miis[i + 1];
}
}
database_length = static_cast<u8>(new_database_size);
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::CleanDatabase() {
miis = {};
version = 1;
magic = DatabaseMagic;
database_length = 0;
crc = GenerateDatabaseCrc();
}
void NintendoFigurineDatabase::CorruptCrc() {
crc = GenerateDatabaseCrc();
crc = ~crc;
}
Result NintendoFigurineDatabase::CheckIntegrity() {
if (magic != DatabaseMagic) {
return ResultInvalidDatabaseSignature;
}
if (version != 1) {
return ResultInvalidDatabaseVersion;
}
if (crc != GenerateDatabaseCrc()) {
return ResultInvalidDatabaseChecksum;
}
if (database_length >= MaxDatabaseLength) {
return ResultInvalidDatabaseLength;
}
return ResultSuccess;
}
u16 NintendoFigurineDatabase::GenerateDatabaseCrc() {
return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc));
}
} // namespace Service::Mii

View File

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/result.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
constexpr std::size_t MaxDatabaseLength{100};
constexpr u32 MiiMagic{0xa523b78f};
constexpr u32 DatabaseMagic{0x4244464e}; // NFDB
class NintendoFigurineDatabase {
public:
/// Returns the total mii count.
u8 GetDatabaseLength() const;
/// Returns true if database is full.
bool IsFull() const;
/// Returns the mii of the specified index.
StoreData Get(std::size_t index) const;
/// Returns the total mii count. Ignoring special mii.
u32 GetCount(const DatabaseSessionMetadata& metadata) const;
/// Returns the index of a mii. If the mii isn't found returns false.
bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const;
/// Moves the location of a specific mii.
Result Move(u32 current_index, u32 new_index);
/// Replaces mii with new data.
void Replace(u32 index, const StoreData& store_data);
/// Adds a new mii to the end of the database.
void Add(const StoreData& store_data);
/// Removes mii from database and shifts left the remainding data.
void Delete(u32 index);
/// Deletes all contents with a fresh database
void CleanDatabase();
/// Intentionally sets a bad checksum
void CorruptCrc();
/// Returns success if database is valid otherwise returns the corresponding error code.
Result CheckIntegrity();
private:
/// Returns the checksum of the database
u16 GenerateDatabaseCrc();
u32 magic{}; // 'NFDB'
std::array<StoreData, MaxDatabaseLength> miis{};
u8 version{};
u8 database_length{};
u16 crc{};
};
static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98,
"NintendoFigurineDatabase has incorrect size.");
}; // namespace Service::Mii

View File

@ -0,0 +1,420 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/store_data.h"
namespace Service::Mii {
const char* DbFileName = "MiiDatabase.dat";
DatabaseManager::DatabaseManager() {}
Result DatabaseManager::MountSaveData() {
if (!is_save_data_mounted) {
system_save_dir =
Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030";
if (is_test_db) {
system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) /
"system/save/8000000000000031";
}
// mount point should be "mii:"
if (!Common::FS::CreateDirs(system_save_dir)) {
return ResultUnknown;
}
}
is_save_data_mounted = true;
return ResultSuccess;
}
Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) {
is_database_broken = false;
if (!is_save_data_mounted) {
return ResultInvalidArgument;
}
database.CleanDatabase();
update_counter++;
metadata.update_counter = update_counter;
const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read,
Common::FS::FileType::BinaryFile};
if (!db_file.IsOpen()) {
return SaveDatabase();
}
if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) {
is_database_broken = true;
}
if (db_file.Read(database) != 1) {
is_database_broken = true;
}
if (is_database_broken) {
// Dragons happen here for simplicity just clean the database
LOG_ERROR(Service_Mii, "Mii database is corrupted");
database.CleanDatabase();
return ResultUnknown;
}
const auto result = database.CheckIntegrity();
if (result.IsError()) {
LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw);
database.CleanDatabase();
return ResultSuccess;
}
LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}",
database.GetDatabaseLength());
return ResultSuccess;
}
bool DatabaseManager::IsFullDatabase() const {
return database.GetDatabaseLength() == MaxDatabaseLength;
}
bool DatabaseManager::IsModified() const {
return is_moddified;
}
u64 DatabaseManager::GetUpdateCounter() const {
return update_counter;
}
u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const {
const u32 database_size = database.GetDatabaseLength();
if (metadata.magic == MiiMagic) {
return database_size;
}
// Special mii can't be used. Skip those.
u32 mii_count{};
for (std::size_t index = 0; index < database_size; ++index) {
const auto& store_data = database.Get(index);
if (store_data.IsSpecial()) {
continue;
}
mii_count++;
}
return mii_count;
}
void DatabaseManager::Get(StoreData& out_store_data, std::size_t index,
const DatabaseSessionMetadata& metadata) const {
if (metadata.magic == MiiMagic) {
out_store_data = database.Get(index);
return;
}
// The index refeers to the mii index without special mii.
// Search on the database until we find it
u32 virtual_index = 0;
const u32 database_size = database.GetDatabaseLength();
for (std::size_t i = 0; i < database_size; ++i) {
const auto& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
if (virtual_index == index) {
out_store_data = store_data;
return;
}
virtual_index++;
}
// This function doesn't fail. It returns the first mii instead
out_store_data = database.Get(0);
}
Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id,
bool is_special) const {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (is_special) {
out_index = index;
return ResultSuccess;
}
if (database.Get(index).IsSpecial()) {
return ResultNotFound;
}
out_index = 0;
if (index < 1) {
return ResultSuccess;
}
for (std::size_t i = 0; i <= index; ++i) {
if (database.Get(i).IsSpecial()) {
continue;
}
out_index++;
}
return ResultSuccess;
}
Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
const Common::UUID& create_id) const {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (metadata.magic == MiiMagic) {
out_index = index;
return ResultSuccess;
}
if (database.Get(index).IsSpecial()) {
return ResultNotFound;
}
out_index = 0;
if (index < 1) {
return ResultSuccess;
}
// The index refeers to the mii index without special mii.
// Search on the database until we find it
for (std::size_t i = 0; i <= index; ++i) {
const auto& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
out_index++;
}
return ResultSuccess;
}
Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index,
const Common::UUID& create_id) const {
const auto database_size = database.GetDatabaseLength();
if (database_size >= 1) {
u32 virtual_index{};
for (std::size_t i = 0; i < database_size; ++i) {
const StoreData& store_data = database.Get(i);
if (store_data.IsSpecial()) {
continue;
}
if (virtual_index == new_index) {
const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
return ResultSuccess;
}
virtual_index++;
}
}
const bool is_found = database.GetIndexByCreatorId(out_index, create_id);
if (!is_found) {
return ResultNotFound;
}
const StoreData& store_data = database.Get(out_index);
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
return ResultSuccess;
}
Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index,
const Common::UUID& create_id) {
u32 current_index{};
if (metadata.magic == MiiMagic) {
const bool is_found = database.GetIndexByCreatorId(current_index, create_id);
if (!is_found) {
return ResultNotFound;
}
} else {
const auto result = FindMoveIndex(current_index, new_index, create_id);
if (result.IsError()) {
return result;
}
}
const auto result = database.Move(current_index, new_index);
if (result.IsFailure()) {
return result;
}
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata,
const StoreData& store_data) {
if (store_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidStoreData;
}
if (metadata.magic != MiiMagic && store_data.IsSpecial()) {
return ResultInvalidOperation;
}
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId());
if (is_found) {
const StoreData& old_store_data = database.Get(index);
if (store_data.IsSpecial() != old_store_data.IsSpecial()) {
return ResultInvalidOperation;
}
database.Replace(index, store_data);
} else {
if (database.IsFull()) {
return ResultDatabaseFull;
}
database.Add(store_data);
}
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
u32 index{};
const bool is_found = database.GetIndexByCreatorId(index, create_id);
if (!is_found) {
return ResultNotFound;
}
if (metadata.magic != MiiMagic) {
const auto& store_data = database.Get(index);
if (store_data.IsSpecial()) {
return ResultInvalidOperation;
}
}
database.Delete(index);
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
return ResultSuccess;
}
Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo2;
}
if (char_info.GetType() == 1) {
return ResultInvalidCharInfoType;
}
u32 index{};
StoreData store_data{};
// Loop until the mii we created is not on the database
do {
store_data.BuildWithCharInfo(char_info);
} while (database.GetIndexByCreatorId(index, store_data.GetCreateId()));
const Result result = store_data.Restore();
if (result.IsSuccess() || result == ResultNotUpdated) {
return AddOrReplace(metadata, store_data);
}
return result;
}
Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) {
database.CorruptCrc();
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
const auto result = SaveDatabase();
database.CleanDatabase();
return result;
}
Result DatabaseManager::DeleteFile() {
const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName);
// TODO: Return proper FS error here
return result ? ResultSuccess : ResultUnknown;
}
void DatabaseManager::Format(DatabaseSessionMetadata& metadata) {
database.CleanDatabase();
is_moddified = true;
update_counter++;
metadata.update_counter = update_counter;
}
Result DatabaseManager::SaveDatabase() {
// TODO: Replace unknown error codes with proper FS error codes when available
if (!Common::FS::Exists(system_save_dir / DbFileName)) {
if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to create mii database");
return ResultUnknown;
}
}
const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName);
if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) {
if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to delete mii database");
return ResultUnknown;
}
if (!Common::FS::NewFile(system_save_dir / DbFileName)) {
LOG_ERROR(Service_Mii, "Failed to create mii database");
return ResultUnknown;
}
}
const Common::FS::IOFile db_file{system_save_dir / DbFileName,
Common::FS::FileAccessMode::ReadWrite,
Common::FS::FileType::BinaryFile};
if (db_file.Write(database) != 1) {
LOG_ERROR(Service_Mii, "Failed to save mii database");
return ResultUnknown;
}
is_moddified = false;
return ResultSuccess;
}
} // namespace Service::Mii

View File

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/fs/fs.h"
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_database.h"
namespace Service::Mii {
class CharInfo;
class StoreData;
class DatabaseManager {
public:
DatabaseManager();
Result MountSaveData();
Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken);
bool IsFullDatabase() const;
bool IsModified() const;
u64 GetUpdateCounter() const;
void Get(StoreData& out_store_data, std::size_t index,
const DatabaseSessionMetadata& metadata) const;
u32 GetCount(const DatabaseSessionMetadata& metadata) const;
Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const;
Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index,
const Common::UUID& create_id) const;
Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const;
Result Move(DatabaseSessionMetadata& metadata, u32 current_index,
const Common::UUID& create_id);
Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data);
Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
Result DestroyFile(DatabaseSessionMetadata& metadata);
Result DeleteFile();
void Format(DatabaseSessionMetadata& metadata);
Result SaveDatabase();
private:
// This is the global value of
// nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled");
bool is_test_db{};
bool is_moddified{};
bool is_save_data_mounted{};
u64 update_counter{};
NintendoFigurineDatabase database{};
std::filesystem::path system_save_dir{};
};
}; // namespace Service::Mii

View File

@ -1,59 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <random>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/mii/mii_database_manager.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/mii_result.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/core_data.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 {
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
MiiManager::MiiManager() {}
bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return false;
}
const auto metadata_update_counter = metadata.update_counter;
metadata.update_counter = update_counter;
return metadata_update_counter != update_counter;
}
bool MiiManager::IsFullDatabase() const {
// TODO(bunnei): We don't implement the Mii database, so it cannot be full
return false;
}
u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
u32 mii_count{};
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
mii_count += DefaultMiiCount;
}
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
// TODO(bunnei): We don't implement the Mii database, but when we do, update this
}
return mii_count;
}
Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
return ResultNotFound;
Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) {
database_manager.MountSaveData();
database_manager.Initialize(metadata, is_broken_with_clear_flag);
return ResultSuccess;
}
void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
@ -74,39 +41,368 @@ void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Ra
out_char_info.SetFromStoreData(store_data);
}
void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
bool MiiManager::IsFullDatabase() const {
return database_manager.IsFullDatabase();
}
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const {
metadata.interface_version = version;
}
bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return false;
}
const u64 metadata_update_counter = metadata.update_counter;
const u64 database_update_counter = database_manager.GetUpdateCounter();
metadata.update_counter = database_update_counter;
return metadata_update_counter != database_update_counter;
}
u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
u32 mii_count{};
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
mii_count += DefaultMiiCount;
}
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
mii_count += database_manager.GetCount(metadata);
}
return mii_count;
}
Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index,
const Common::UUID& create_id) {
const auto result = database_manager.Move(metadata, index, create_id);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) {
const auto result = database_manager.AddOrReplace(metadata, store_data);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) {
const auto result = database_manager.Delete(metadata, create_id);
if (result.IsFailure()) {
return result;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const {
s32 index{};
const auto result = database_manager.FindIndex(index, create_id, is_special);
if (result.IsError()) {
index = -1;
}
return index;
}
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index) const {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
s32 index{};
const bool is_special = metadata.magic == MiiMagic;
const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special);
if (result.IsError()) {
index = -1;
}
if (index == -1) {
return ResultNotFound;
}
out_index = index;
return ResultSuccess;
}
Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) {
const auto result = database_manager.Append(metadata, char_info);
if (result.IsError()) {
return ResultNotFound;
}
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) {
const bool is_broken = is_broken_with_clear_flag;
if (is_broken_with_clear_flag) {
is_broken_with_clear_flag = false;
database_manager.Format(metadata);
database_manager.SaveDatabase();
}
return is_broken;
}
Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) {
is_broken_with_clear_flag = true;
return database_manager.DestroyFile(metadata);
}
Result MiiManager::DeleteFile() {
return database_manager.DeleteFile();
}
Result MiiManager::Format(DatabaseSessionMetadata& metadata) {
database_manager.Format(metadata);
if (!database_manager.IsModified()) {
return ResultNotUpdated;
}
return database_manager.SaveDatabase();
}
Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
if (!mii_v3.IsValid()) {
return ResultInvalidCharInfo;
}
StoreData store_data{};
mii_v3.BuildToStoreData(store_data);
const auto name = store_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
store_data.SetInvalidName();
}
out_char_info.SetFromStoreData(store_data);
return ResultSuccess;
}
Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info,
const CoreData& core_data) const {
if (core_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
StoreData store_data{};
store_data.BuildWithCoreData(core_data);
const auto name = store_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) {
store_data.SetInvalidName();
}
out_char_info.SetFromStoreData(store_data);
return ResultSuccess;
}
Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data,
const CharInfo& char_info) const {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
out_core_data.BuildFromCharInfo(char_info);
const auto name = out_core_data.GetNickname();
if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) {
out_core_data.SetNickname(out_core_data.GetInvalidNickname());
}
return ResultSuccess;
}
Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
if (metadata.IsInterfaceVersionSupported(1)) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
}
u32 index{};
Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId());
if (result.IsError()) {
return result;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
if (store_data.GetType() != char_info.GetType()) {
return ResultNotFound;
}
out_char_info.SetFromStoreData(store_data);
if (char_info == out_char_info) {
return ResultNotUpdated;
}
return ResultSuccess;
}
Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
const StoreData& store_data, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return ResultNotFound;
}
if (metadata.IsInterfaceVersionSupported(1)) {
if (store_data.IsValid() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
}
}
u32 index{};
Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId());
if (result.IsError()) {
return result;
}
database_manager.Get(out_store_data, index, metadata);
if (out_store_data.GetType() != store_data.GetType()) {
return ResultNotFound;
}
if (store_data == out_store_data) {
return ResultNotUpdated;
}
return ResultSuccess;
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_elements, out_count, source_flag);
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_elements[out_count].source = Source::Database;
out_elements[out_count].char_info.SetFromStoreData(store_data);
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_elements, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag) {
u32& out_count, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_char_info, out_count, source_flag);
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_char_info[out_count].SetFromStoreData(store_data);
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_char_info, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
std::span<StoreDataElement> out_elements, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_elements, out_count, source_flag);
}
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_elements[out_count].store_data = store_data;
out_elements[out_count].source = Source::Database;
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_elements, out_count, source_flag);
}
Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
u32& out_count, SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
return BuildDefault(out_store_data, out_count, source_flag);
}
const auto mii_count = database_manager.GetCount(metadata);
for (std::size_t index = 0; index < mii_count; ++index) {
if (out_store_data.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
StoreData store_data{};
database_manager.Get(store_data, index, metadata);
out_store_data[out_count] = store_data;
out_count++;
}
// Include default Mii at the end of the list
return BuildDefault(out_store_data, out_count, source_flag);
}
Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou
}
Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
SourceFlag source_flag) {
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun
return ResultSuccess;
}
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index) {
if (char_info.Verify() != ValidationResult::NoErrors) {
return ResultInvalidCharInfo;
Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
out_index = INVALID_INDEX;
out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index));
out_elements[out_count].source = Source::Default;
out_count++;
}
// TODO(bunnei): We don't implement the Mii database, so we can't have an index
return ResultNotFound;
return ResultSuccess;
}
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) {
metadata.interface_version = version;
Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
SourceFlag source_flag) const {
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
return ResultSuccess;
}
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
return ResultInvalidArgumentSize;
}
out_char_info[out_count].BuildDefault(static_cast<u32>(index));
out_count++;
}
return ResultSuccess;
}
} // namespace Service::Mii

View File

@ -3,47 +3,85 @@
#pragma once
#include <vector>
#include <span>
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_database_manager.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 {
class CharInfo;
class CoreData;
class StoreData;
class Ver3StoreData;
// The Mii manager is responsible for loading and storing the Miis to the database in NAND along
// with providing an easy interface for HLE emulation of the mii service.
struct CharInfoElement;
struct StoreDataElement;
// The Mii manager is responsible for handling mii operations along with providing an easy interface
// for HLE emulation of the mii service.
class MiiManager {
public:
MiiManager();
Result Initialize(DatabaseSessionMetadata& metadata);
bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
bool IsFullDatabase() const;
u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag);
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
u32& out_count, SourceFlag source_flag);
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag);
// Auto generated mii
void BuildDefault(CharInfo& out_char_info, u32 index) const;
void BuildBase(CharInfo& out_char_info, Gender gender) const;
void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
// Database operations
bool IsFullDatabase() const;
void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const;
bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id);
Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data);
Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id);
s32 FindIndex(const Common::UUID& create_id, bool is_special) const;
Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
s32& out_index);
void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
s32& out_index) const;
Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info);
// Test database operations
bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata);
Result DestroyFile(DatabaseSessionMetadata& metadata);
Result DeleteFile();
Result Format(DatabaseSessionMetadata& metadata);
// Mii conversions
Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const;
Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const;
Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
const CharInfo& char_info, SourceFlag source_flag) const;
Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data,
const StoreData& store_data, SourceFlag source_flag) const;
// Overloaded getters
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements,
u32& out_count, SourceFlag source_flag) const;
Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data,
u32& out_count, SourceFlag source_flag) const;
private:
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);
SourceFlag source_flag) const;
Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
SourceFlag source_flag) const;
Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count,
SourceFlag source_flag) const;
Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count,
SourceFlag source_flag) const;
u64 update_counter{};
DatabaseManager database_manager{};
// This should be a global value
bool is_broken_with_clear_flag{};
};
}; // namespace Service::Mii

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -13,8 +13,15 @@ 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 ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101};
constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103};
constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104};
constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105};
constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107};
constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204};
constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205};
}; // namespace Service::Mii

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@ -13,6 +13,7 @@
namespace Service::Mii {
constexpr std::size_t MaxNameSize = 10;
constexpr u8 MaxHeight = 127;
constexpr u8 MaxBuild = 127;
constexpr u8 MaxType = 1;
@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8;
constexpr u8 MaxEyebrowAspect = 6;
constexpr u8 MaxEyebrowRotate = 11;
constexpr u8 MaxEyebrowX = 12;
constexpr u8 MaxEyebrowY = 18;
constexpr u8 MaxEyebrowY = 15;
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 MaxMustacheY = 16;
constexpr u8 MaxGlassScale = 7;
constexpr u8 MaxGlassY = 20;
constexpr u8 MaxMoleScale = 8;
@ -599,12 +600,12 @@ enum class ValidationResult : u32 {
InvalidRegionMove = 0x31,
InvalidCreateId = 0x32,
InvalidName = 0x33,
InvalidChecksum = 0x34,
InvalidType = 0x35,
};
struct Nickname {
static constexpr std::size_t MaxNameSize = 10;
std::array<char16_t, MaxNameSize> data;
std::array<char16_t, MaxNameSize> data{};
// Checks for null or dirty strings
bool IsValid() const {

View File

@ -28,6 +28,32 @@ public:
return Common::swap16(static_cast<u16>(crc));
}
static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) {
constexpr u16 magic{0x1021};
s32 crc{};
for (std::size_t i = 0; i < uuid.uuid.size(); i++) {
for (std::size_t j = 0; j < 8; j++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = crc ^ magic;
}
}
crc ^= uuid.uuid[i];
}
// As much as this looks wrong this is what N's does
for (std::size_t i = 0; i < data_size * 8; i++) {
crc <<= 1;
if ((crc & 0x10000) != 0) {
crc = crc ^ magic;
}
}
return Common::swap16(static_cast<u16>(crc));
}
static Common::UUID MakeCreateId() {
return Common::UUID::MakeRandomRFC4122V4();
}

View File

@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const {
if (eyebrow_x > MaxEyebrowX) {
return ValidationResult::InvalidEyebrowX;
}
if (eyebrow_y > MaxEyebrowY) {
if (eyebrow_y - 3 > MaxEyebrowY) {
return ValidationResult::InvalidEyebrowY;
}
if (nose_type > NoseType::Max) {
@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const {
if (mustache_scale > MaxMustacheScale) {
return ValidationResult::InvalidMustacheScale;
}
if (mustache_y > MasMustacheY) {
if (mustache_y > MaxMustacheY) {
return ValidationResult::InvalidMustacheY;
}
if (glass_type > GlassType::Max) {

View File

@ -70,59 +70,59 @@ public:
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;
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>,

View File

@ -3,6 +3,7 @@
#include "common/assert.h"
#include "core/hle/service/mii/mii_util.h"
#include "core/hle/service/mii/types/char_info.h"
#include "core/hle/service/mii/types/core_data.h"
#include "core/hle/service/mii/types/raw_data.h"
@ -170,7 +171,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
u8 glasses_type{};
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
if (++glasses_type >= glasses_type_info.values_count) {
ASSERT(false);
glasses_type = 0;
break;
}
}
@ -178,6 +179,7 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
SetGlassType(static_cast<GlassType>(glasses_type));
SetGlassColor(RawData::GetGlassColorFromVer3(0));
SetGlassScale(4);
SetGlassY(static_cast<u8>(axis_y + 10));
SetMoleType(MoleType::None);
SetMoleScale(4);
@ -185,9 +187,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) {
SetMoleY(20);
}
u32 CoreData::IsValid() const {
// TODO: Complete this
return 0;
void CoreData::BuildFromCharInfo(const CharInfo& char_info) {
name = char_info.GetNickname();
SetFontRegion(char_info.GetFontRegion());
SetFavoriteColor(char_info.GetFavoriteColor());
SetGender(char_info.GetGender());
SetHeight(char_info.GetHeight());
SetBuild(char_info.GetBuild());
SetType(char_info.GetType());
SetRegionMove(char_info.GetRegionMove());
SetFacelineType(char_info.GetFacelineType());
SetFacelineColor(char_info.GetFacelineColor());
SetFacelineWrinkle(char_info.GetFacelineWrinkle());
SetFacelineMake(char_info.GetFacelineMake());
SetHairType(char_info.GetHairType());
SetHairColor(char_info.GetHairColor());
SetHairFlip(char_info.GetHairFlip());
SetEyeType(char_info.GetEyeType());
SetEyeColor(char_info.GetEyeColor());
SetEyeScale(char_info.GetEyeScale());
SetEyeAspect(char_info.GetEyeAspect());
SetEyeRotate(char_info.GetEyeRotate());
SetEyeX(char_info.GetEyeX());
SetEyeY(char_info.GetEyeY());
SetEyebrowType(char_info.GetEyebrowType());
SetEyebrowColor(char_info.GetEyebrowColor());
SetEyebrowScale(char_info.GetEyebrowScale());
SetEyebrowAspect(char_info.GetEyebrowAspect());
SetEyebrowRotate(char_info.GetEyebrowRotate());
SetEyebrowX(char_info.GetEyebrowX());
SetEyebrowY(char_info.GetEyebrowY() - 3);
SetNoseType(char_info.GetNoseType());
SetNoseScale(char_info.GetNoseScale());
SetNoseY(char_info.GetNoseY());
SetMouthType(char_info.GetMouthType());
SetMouthColor(char_info.GetMouthColor());
SetMouthScale(char_info.GetMouthScale());
SetMouthAspect(char_info.GetMouthAspect());
SetMouthY(char_info.GetMouthY());
SetBeardColor(char_info.GetBeardColor());
SetBeardType(char_info.GetBeardType());
SetMustacheType(char_info.GetMustacheType());
SetMustacheScale(char_info.GetMustacheScale());
SetMustacheY(char_info.GetMustacheY());
SetGlassType(char_info.GetGlassType());
SetGlassColor(char_info.GetGlassColor());
SetGlassScale(char_info.GetGlassScale());
SetGlassY(char_info.GetGlassY());
SetMoleType(char_info.GetMoleType());
SetMoleScale(char_info.GetMoleScale());
SetMoleX(char_info.GetMoleX());
SetMoleY(char_info.GetMoleY());
}
ValidationResult CoreData::IsValid() const {
if (!name.IsValid()) {
return ValidationResult::InvalidName;
}
if (GetFontRegion() > FontRegion::Max) {
return ValidationResult::InvalidFont;
}
if (GetFavoriteColor() > FavoriteColor::Max) {
return ValidationResult::InvalidColor;
}
if (GetGender() > Gender::Max) {
return ValidationResult::InvalidGender;
}
if (GetHeight() > MaxHeight) {
return ValidationResult::InvalidHeight;
}
if (GetBuild() > MaxBuild) {
return ValidationResult::InvalidBuild;
}
if (GetType() > MaxType) {
return ValidationResult::InvalidType;
}
if (GetRegionMove() > MaxRegionMove) {
return ValidationResult::InvalidRegionMove;
}
if (GetFacelineType() > FacelineType::Max) {
return ValidationResult::InvalidFacelineType;
}
if (GetFacelineColor() > FacelineColor::Max) {
return ValidationResult::InvalidFacelineColor;
}
if (GetFacelineWrinkle() > FacelineWrinkle::Max) {
return ValidationResult::InvalidFacelineWrinkle;
}
if (GetFacelineMake() > FacelineMake::Max) {
return ValidationResult::InvalidFacelineMake;
}
if (GetHairType() > HairType::Max) {
return ValidationResult::InvalidHairType;
}
if (GetHairColor() > CommonColor::Max) {
return ValidationResult::InvalidHairColor;
}
if (GetHairFlip() > HairFlip::Max) {
return ValidationResult::InvalidHairFlip;
}
if (GetEyeType() > EyeType::Max) {
return ValidationResult::InvalidEyeType;
}
if (GetEyeColor() > CommonColor::Max) {
return ValidationResult::InvalidEyeColor;
}
if (GetEyeScale() > MaxEyeScale) {
return ValidationResult::InvalidEyeScale;
}
if (GetEyeAspect() > MaxEyeAspect) {
return ValidationResult::InvalidEyeAspect;
}
if (GetEyeRotate() > MaxEyeRotate) {
return ValidationResult::InvalidEyeRotate;
}
if (GetEyeX() > MaxEyeX) {
return ValidationResult::InvalidEyeX;
}
if (GetEyeY() > MaxEyeY) {
return ValidationResult::InvalidEyeY;
}
if (GetEyebrowType() > EyebrowType::Max) {
return ValidationResult::InvalidEyebrowType;
}
if (GetEyebrowColor() > CommonColor::Max) {
return ValidationResult::InvalidEyebrowColor;
}
if (GetEyebrowScale() > MaxEyebrowScale) {
return ValidationResult::InvalidEyebrowScale;
}
if (GetEyebrowAspect() > MaxEyebrowAspect) {
return ValidationResult::InvalidEyebrowAspect;
}
if (GetEyebrowRotate() > MaxEyebrowRotate) {
return ValidationResult::InvalidEyebrowRotate;
}
if (GetEyebrowX() > MaxEyebrowX) {
return ValidationResult::InvalidEyebrowX;
}
if (GetEyebrowY() > MaxEyebrowY) {
return ValidationResult::InvalidEyebrowY;
}
if (GetNoseType() > NoseType::Max) {
return ValidationResult::InvalidNoseType;
}
if (GetNoseScale() > MaxNoseScale) {
return ValidationResult::InvalidNoseScale;
}
if (GetNoseY() > MaxNoseY) {
return ValidationResult::InvalidNoseY;
}
if (GetMouthType() > MouthType::Max) {
return ValidationResult::InvalidMouthType;
}
if (GetMouthColor() > CommonColor::Max) {
return ValidationResult::InvalidMouthColor;
}
if (GetMouthScale() > MaxMouthScale) {
return ValidationResult::InvalidMouthScale;
}
if (GetMouthAspect() > MaxMoutAspect) {
return ValidationResult::InvalidMouthAspect;
}
if (GetMouthY() > MaxMouthY) {
return ValidationResult::InvalidMouthY;
}
if (GetBeardColor() > CommonColor::Max) {
return ValidationResult::InvalidBeardColor;
}
if (GetBeardType() > BeardType::Max) {
return ValidationResult::InvalidBeardType;
}
if (GetMustacheType() > MustacheType::Max) {
return ValidationResult::InvalidMustacheType;
}
if (GetMustacheScale() > MaxMustacheScale) {
return ValidationResult::InvalidMustacheScale;
}
if (GetMustacheY() > MaxMustacheY) {
return ValidationResult::InvalidMustacheY;
}
if (GetGlassType() > GlassType::Max) {
return ValidationResult::InvalidGlassType;
}
if (GetGlassColor() > CommonColor::Max) {
return ValidationResult::InvalidGlassColor;
}
if (GetGlassScale() > MaxGlassScale) {
return ValidationResult::InvalidGlassScale;
}
if (GetGlassY() > MaxGlassY) {
return ValidationResult::InvalidGlassY;
}
if (GetMoleType() > MoleType::Max) {
return ValidationResult::InvalidMoleType;
}
if (GetMoleScale() > MaxMoleScale) {
return ValidationResult::InvalidMoleScale;
}
if (GetMoleX() > MaxMoleX) {
return ValidationResult::InvalidMoleX;
}
if (GetMoleY() > MaxMoleY) {
return ValidationResult::InvalidMoleY;
}
return ValidationResult::NoErrors;
}
void CoreData::SetFontRegion(FontRegion value) {
@ -314,8 +518,8 @@ void CoreData::SetNoseY(u8 value) {
data.nose_y.Assign(value);
}
void CoreData::SetMouthType(u8 value) {
data.mouth_type.Assign(value);
void CoreData::SetMouthType(MouthType value) {
data.mouth_type.Assign(static_cast<u32>(value));
}
void CoreData::SetMouthColor(CommonColor value) {

View File

@ -6,6 +6,7 @@
#include "core/hle/service/mii/mii_types.h"
namespace Service::Mii {
class CharInfo;
struct StoreDataBitFields {
union {
@ -100,8 +101,9 @@ class CoreData {
public:
void SetDefault();
void BuildRandom(Age age, Gender gender, Race race);
void BuildFromCharInfo(const CharInfo& char_info);
u32 IsValid() const;
ValidationResult IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
@ -134,7 +136,7 @@ public:
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthType(MouthType value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
@ -212,5 +214,6 @@ private:
Nickname name{};
};
static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable.");
}; // namespace Service::Mii

View File

@ -1716,18 +1716,18 @@ const std::array<RandomMiiData4, 18> RandomMiiMouthType{
const std::array<RandomMiiData2, 3> RandomMiiGlassType{
RandomMiiData2{
.arg_1 = 0,
.values_count = 9,
.values = {90, 94, 96, 100, 0, 0, 0, 0, 0},
.values_count = 4,
.values = {90, 94, 96, 100},
},
RandomMiiData2{
.arg_1 = 1,
.values_count = 9,
.values = {83, 86, 90, 93, 94, 96, 98, 100, 0},
.values_count = 8,
.values = {83, 86, 90, 93, 94, 96, 98, 100},
},
RandomMiiData2{
.arg_1 = 2,
.values_count = 9,
.values = {78, 83, 0, 93, 0, 0, 98, 100, 0},
.values_count = 8,
.values = {78, 83, 0, 93, 0, 0, 98, 100},
},
};

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/mii/mii_result.h"
#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"
@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) {
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.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
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.SetMouthType(static_cast<MouthType>(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));
@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) {
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));
SetChecksum();
}
void StoreData::BuildBase(Gender gender) {
@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) {
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.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3));
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.SetMouthType(static_cast<MouthType>(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));
@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) {
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));
SetChecksum();
}
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));
SetChecksum();
}
void StoreData::SetInvalidName() {
const auto& invalid_name = core_data.GetInvalidNickname();
void StoreData::BuildWithCharInfo(const CharInfo& char_info) {
core_data.BuildFromCharInfo(char_info);
create_id = MiiUtil::MakeCreateId();
SetChecksum();
}
void StoreData::BuildWithCoreData(const CoreData& in_core_data) {
core_data = in_core_data;
create_id = MiiUtil::MakeCreateId();
SetChecksum();
}
Result StoreData::Restore() {
// TODO: Implement this
return ResultNotUpdated;
}
ValidationResult StoreData::IsValid() const {
if (core_data.IsValid() != ValidationResult::NoErrors) {
return core_data.IsValid();
}
if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) {
return ValidationResult::InvalidChecksum;
}
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));
if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) {
return ValidationResult::InvalidChecksum;
}
return ValidationResult::NoErrors;
}
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);
}
@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) {
core_data.SetNoseY(value);
}
void StoreData::SetMouthType(u8 value) {
void StoreData::SetMouthType(MouthType value) {
core_data.SetMouthType(value);
}
@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) {
core_data.SetNickname(value);
}
void StoreData::SetInvalidName() {
const auto& invalid_name = core_data.GetInvalidNickname();
core_data.SetNickname(invalid_name);
SetChecksum();
}
void StoreData::SetChecksum() {
SetDataChecksum();
SetDeviceChecksum();
}
void StoreData::SetDataChecksum() {
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID));
}
void StoreData::SetDeviceChecksum() {
const auto device_id = MiiUtil::GetDeviceId();
device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData));
}
Common::UUID StoreData::GetCreateId() const {
return create_id;
}
@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const {
}
bool StoreData::operator==(const StoreData& data) {
bool is_identical = data.core_data.IsValid() == 0;
bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors;
is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
is_identical &= GetCreateId() == data.GetCreateId();
is_identical &= GetFontRegion() == data.GetFontRegion();

View File

@ -3,6 +3,7 @@
#pragma once
#include "core/hle/result.h"
#include "core/hle/service/mii/mii_types.h"
#include "core/hle/service/mii/types/core_data.h"
@ -10,18 +11,17 @@ 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);
void BuildWithCharInfo(const CharInfo& char_info);
void BuildWithCoreData(const CoreData& in_core_data);
Result Restore();
ValidationResult IsValid() const;
bool IsSpecial() const;
u32 IsValid() const;
void SetFontRegion(FontRegion value);
void SetFavoriteColor(FavoriteColor value);
void SetGender(Gender value);
@ -53,7 +53,7 @@ public:
void SetNoseType(NoseType value);
void SetNoseScale(u8 value);
void SetNoseY(u8 value);
void SetMouthType(u8 value);
void SetMouthType(MouthType value);
void SetMouthColor(CommonColor value);
void SetMouthScale(u8 value);
void SetMouthAspect(u8 value);
@ -73,6 +73,9 @@ public:
void SetMoleY(u8 value);
void SetNickname(Nickname nickname);
void SetInvalidName();
void SetChecksum();
void SetDataChecksum();
void SetDeviceChecksum();
Common::UUID GetCreateId() const;
FontRegion GetFontRegion() const;
@ -135,6 +138,8 @@ private:
u16 device_crc{};
};
static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
static_assert(std::is_trivially_copyable_v<StoreData>,
"StoreData type must be trivially copyable.");
struct StoreDataElement {
StoreData store_data{};

View File

@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
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()));
@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
out_store_data.SetNickname(mii_name);
out_store_data.SetFontRegion(
static_cast<FontRegion>(static_cast<u8>(region_information.font_region)));
static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value())));
out_store_data.SetFacelineType(
static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
out_store_data.SetFacelineColor(
static_cast<FacelineColor>(appearance_bits1.faceline_color.Value()));
RawData::GetFacelineColorFromVer3(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.SetHairColor(RawData::GetHairColorFromVer3(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.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value()));
out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value()));
out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value()));
out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value()));
out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value()));
out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value()));
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));
RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value()));
out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value()));
out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value()));
out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value()));
out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value()));
out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3));
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.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value()));
out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value()));
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.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value()));
out_store_data.SetMouthColor(
RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value()));
out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value()));
out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value()));
out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value()));
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.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value()));
out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value()));
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.SetBeardColor(
RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value()));
// Glass type is compatible as it is. It doesn't need a table
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.SetGlassColor(
RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value()));
out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value()));
out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value()));
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));
out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value()));
out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value()));
out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value()));
out_store_data.SetChecksum();
}
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const {
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.mustache_y <= MaxMustacheY);
is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const {
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_bits10.glass_y <= MaxGlassY);
is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);

View File

@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
}
@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info
return ResultRegistrationIsNotInitialized;
}
Service::Mii::MiiManager manager;
Mii::StoreData store_data{};
const auto& settings = tag_data.settings;
tag_data.owner_mii.BuildToStoreData(store_data);
// TODO: Validate and complete this data
register_info = {
.mii_store_data = {},
.mii_store_data = store_data,
.creation_date = settings.init_date.GetWriteDate(),
.amiibo_name = GetAmiiboName(settings),
.font_region = settings.settings.font_region,
@ -1372,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
// Convert from utf16 to utf8
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1);
return amiibo_name;
}

View File

@ -7,7 +7,7 @@
#include "common/dynamic_library.h"
#include "core/tools/renderdoc.h"
#ifdef WIN32
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>

View File

@ -7,15 +7,12 @@
namespace Shader::Backend::SPIRV {
namespace {
Id Image(EmitContext& ctx, const IR::Value& index, IR::TextureInstInfo info) {
if (!index.IsImmediate()) {
throw NotImplementedException("Indirect image indexing");
}
Id Image(EmitContext& ctx, IR::TextureInstInfo info) {
if (info.type == TextureType::Buffer) {
const ImageBufferDefinition def{ctx.image_buffers.at(index.U32())};
const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)};
return def.id;
} else {
const ImageDefinition def{ctx.images.at(index.U32())};
const ImageDefinition def{ctx.images.at(info.descriptor_index)};
return def.id;
}
}
@ -28,8 +25,12 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
if (!index.IsImmediate() || index.U32() != 0) {
// TODO: handle layers
throw NotImplementedException("Image indexing");
}
const auto info{inst->Flags<IR::TextureInstInfo>()};
const Id image{Image(ctx, index, info)};
const Id image{Image(ctx, info)};
const Id pointer{ctx.OpImageTexelPointer(ctx.image_u32, image, coords, ctx.Const(0U))};
const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);

View File

@ -74,11 +74,6 @@ spv::ImageFormat GetImageFormat(ImageFormat 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) {
const spv::ImageFormat format{GetImageFormat(desc.format)};
const Id type{ctx.U32[1]};
@ -1275,7 +1270,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
if (desc.count != 1) {
throw NotImplementedException("Array of image buffers");
}
const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)};
const spv::ImageFormat format{GetImageFormat(desc.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 id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};

View File

@ -109,10 +109,11 @@ void MaxwellDMA::Launch() {
const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
ASSERT(regs.remap_const.component_size_minus_one == 3);
accelerate.BufferClear(regs.offset_out, regs.line_length_in, regs.remap_consta_value);
accelerate.BufferClear(regs.offset_out, regs.line_length_in,
regs.remap_const.remap_consta_value);
read_buffer.resize_destructive(regs.line_length_in * sizeof(u32));
std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in);
std::ranges::fill(span, regs.remap_consta_value);
std::ranges::fill(span, regs.remap_const.remap_consta_value);
memory_manager.WriteBlockUnsafe(regs.offset_out,
reinterpret_cast<u8*>(read_buffer.data()),
regs.line_length_in * sizeof(u32));

View File

@ -214,14 +214,15 @@ public:
NO_WRITE = 6,
};
PackedGPUVAddr address;
u32 remap_consta_value;
u32 remap_constb_value;
union {
BitField<0, 12, u32> dst_components_raw;
BitField<0, 3, Swizzle> dst_x;
BitField<4, 3, Swizzle> dst_y;
BitField<8, 3, Swizzle> dst_z;
BitField<12, 3, Swizzle> dst_w;
BitField<0, 12, u32> dst_components_raw;
BitField<16, 2, u32> component_size_minus_one;
BitField<20, 2, u32> num_src_components_minus_one;
BitField<24, 2, u32> num_dst_components_minus_one;
@ -274,55 +275,57 @@ private:
struct Regs {
union {
struct {
u32 reserved[0x40];
INSERT_PADDING_BYTES_NOINIT(0x100);
u32 nop;
u32 reserved01[0xf];
INSERT_PADDING_BYTES_NOINIT(0x3C);
u32 pm_trigger;
u32 reserved02[0x3f];
INSERT_PADDING_BYTES_NOINIT(0xFC);
Semaphore semaphore;
u32 reserved03[0x2];
INSERT_PADDING_BYTES_NOINIT(0x8);
RenderEnable render_enable;
PhysMode src_phys_mode;
PhysMode dst_phys_mode;
u32 reserved04[0x26];
INSERT_PADDING_BYTES_NOINIT(0x98);
LaunchDMA launch_dma;
u32 reserved05[0x3f];
INSERT_PADDING_BYTES_NOINIT(0xFC);
PackedGPUVAddr offset_in;
PackedGPUVAddr offset_out;
s32 pitch_in;
s32 pitch_out;
u32 line_length_in;
u32 line_count;
u32 reserved06[0xb6];
u32 remap_consta_value;
u32 remap_constb_value;
INSERT_PADDING_BYTES_NOINIT(0x2E0);
RemapConst remap_const;
DMA::Parameters dst_params;
u32 reserved07[0x1];
INSERT_PADDING_BYTES_NOINIT(0x4);
DMA::Parameters src_params;
u32 reserved08[0x275];
INSERT_PADDING_BYTES_NOINIT(0x9D4);
u32 pm_trigger_end;
u32 reserved09[0x3ba];
INSERT_PADDING_BYTES_NOINIT(0xEE8);
};
std::array<u32, NUM_REGS> reg_array;
};
} regs{};
static_assert(sizeof(Regs) == NUM_REGS * 4);
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MaxwellDMA::Regs, field_name) == position * 4, \
static_assert(offsetof(MaxwellDMA::Regs, field_name) == position, \
"Field " #field_name " has invalid position")
ASSERT_REG_POSITION(launch_dma, 0xC0);
ASSERT_REG_POSITION(offset_in, 0x100);
ASSERT_REG_POSITION(offset_out, 0x102);
ASSERT_REG_POSITION(pitch_in, 0x104);
ASSERT_REG_POSITION(pitch_out, 0x105);
ASSERT_REG_POSITION(line_length_in, 0x106);
ASSERT_REG_POSITION(line_count, 0x107);
ASSERT_REG_POSITION(remap_const, 0x1C0);
ASSERT_REG_POSITION(dst_params, 0x1C3);
ASSERT_REG_POSITION(src_params, 0x1CA);
ASSERT_REG_POSITION(semaphore, 0x240);
ASSERT_REG_POSITION(render_enable, 0x254);
ASSERT_REG_POSITION(src_phys_mode, 0x260);
ASSERT_REG_POSITION(launch_dma, 0x300);
ASSERT_REG_POSITION(offset_in, 0x400);
ASSERT_REG_POSITION(offset_out, 0x408);
ASSERT_REG_POSITION(pitch_in, 0x410);
ASSERT_REG_POSITION(pitch_out, 0x414);
ASSERT_REG_POSITION(line_length_in, 0x418);
ASSERT_REG_POSITION(line_count, 0x41C);
ASSERT_REG_POSITION(remap_const, 0x700);
ASSERT_REG_POSITION(dst_params, 0x70C);
ASSERT_REG_POSITION(src_params, 0x728);
ASSERT_REG_POSITION(pm_trigger_end, 0x1114);
#undef ASSERT_REG_POSITION
};

View File

@ -185,7 +185,7 @@ struct FormatTuple {
{VK_FORMAT_BC2_SRGB_BLOCK}, // BC2_SRGB
{VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB
{VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB
{VK_FORMAT_R4G4B4A4_UNORM_PACK16}, // A4B4G4R4_UNORM
{VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT}, // A4B4G4R4_UNORM
{VK_FORMAT_R4G4_UNORM_PACK8}, // G4R4_UNORM
{VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB
{VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB

View File

@ -600,7 +600,7 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
}
void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle,
bool emulate_bgr565) {
bool emulate_bgr565, bool emulate_a4b4g4r4) {
switch (format) {
case PixelFormat::A1B5G5R5_UNORM:
std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed);
@ -616,6 +616,11 @@ void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4
case PixelFormat::G4R4_UNORM:
std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed);
break;
case PixelFormat::A4B4G4R4_UNORM:
if (emulate_a4b4g4r4) {
std::ranges::reverse(swizzle);
}
break;
default:
break;
}
@ -1649,7 +1654,8 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
};
if (!info.IsRenderTarget()) {
swizzle = info.Swizzle();
TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565());
TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565(),
!device->IsExt4444FormatsSupported());
if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) {
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
}

View File

@ -76,6 +76,11 @@ constexpr std::array VK_FORMAT_R32G32B32_SFLOAT{
VK_FORMAT_UNDEFINED,
};
constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
VK_FORMAT_UNDEFINED,
};
} // namespace Alternatives
enum class NvidiaArchitecture {
@ -110,6 +115,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) {
return Alternatives::R8G8B8_SSCALED.data();
case VK_FORMAT_R32G32B32_SFLOAT:
return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data();
case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT:
return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data();
default:
return nullptr;
}
@ -238,6 +245,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_R32_SINT,
VK_FORMAT_R32_UINT,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,
VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R5G5B5A1_UNORM_PACK16,
VK_FORMAT_R5G6B5_UNORM_PACK16,

View File

@ -45,6 +45,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \
FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \
FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \
FEATURE(EXT, 4444Formats, 4444_FORMATS, format_a4b4g4r4) \
FEATURE(EXT, IndexTypeUint8, INDEX_TYPE_UINT8, index_type_uint8) \
FEATURE(EXT, LineRasterization, LINE_RASTERIZATION, line_rasterization) \
FEATURE(EXT, PrimitiveTopologyListRestart, PRIMITIVE_TOPOLOGY_LIST_RESTART, \
@ -97,6 +98,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_4444_FORMATS_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME) \
@ -144,6 +146,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
#define FOR_EACH_VK_RECOMMENDED_FEATURE(FEATURE_NAME) \
FEATURE_NAME(custom_border_color, customBorderColors) \
FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \
FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \
FEATURE_NAME(index_type_uint8, indexTypeUint8) \
FEATURE_NAME(primitive_topology_list_restart, primitiveTopologyListRestart) \
FEATURE_NAME(provoking_vertex, provokingVertexLast) \
@ -488,6 +491,11 @@ public:
return extensions.extended_dynamic_state3;
}
/// Returns true if the device supports VK_EXT_4444_formats.
bool IsExt4444FormatsSupported() const {
return features.format_a4b4g4r4.formatA4B4G4R4;
}
/// Returns true if the device supports VK_EXT_extended_dynamic_state3.
bool IsExtExtendedDynamicState3BlendingSupported() const {
return dynamic_state3_blending;