Compare commits

...

74 Commits

Author SHA1 Message Date
f2c5e67978 Android 200 2024-01-21 01:01:01 +00:00
dd07e2c8fe Merge yuzu-emu#12579 2024-01-21 01:01:00 +00:00
d4cfd07049 Merge yuzu-emu#12499 2024-01-21 01:01:00 +00:00
93a3342841 Merge pull request #12720 from t895/return-to-global
android: Change "Clear" to "Use global setting" for per-game settings
2024-01-20 13:56:31 -05:00
7b3e26acc9 android: Change "Clear" to "Use global setting" for per-game settings 2024-01-20 13:37:47 -05:00
444e86d191 Merge pull request #12688 from liamwhite/wl-present-fix
renderer_vulkan: recreate swapchain when frame size changes
2024-01-20 13:36:18 -05:00
61ce0088ae Merge pull request #12724 from merryhime/fs-u8str-overloads
fs/file: Explicitly convert std::u8string to std::filesystem::path
2024-01-20 13:35:41 -05:00
b3aa3633c7 Merge pull request #12721 from t895/card-elevation
android: Use elevated card style for home setting card
2024-01-20 13:35:30 -05:00
627ba271ad Merge pull request #12719 from t895/sort-search
android: Sort recently added/played games by time
2024-01-20 13:35:14 -05:00
2faa631676 Merge pull request #12715 from t895/remove-addons
android: Add uninstall addon button
2024-01-20 13:35:03 -05:00
5838779162 Merge pull request #12660 from german77/better-vibration
service: hid: Fully implement abstract vibration
2024-01-20 13:34:54 -05:00
23fd1041c1 Merge pull request #12701 from liamwhite/flinger-layer-issues
vi: check layer state before opening or closing
2024-01-20 13:34:32 -05:00
5c398ede6f fs/file: Explicitly convert std::u8string to std::filesystem::path 2024-01-20 17:46:30 +00:00
378e4752a6 android: Use elevated card style for home setting card 2024-01-20 03:55:48 -05:00
dad48f16b7 android: Sort recently added/played games by time 2024-01-20 03:18:48 -05:00
a363fa78ef frontend_common: Add documentation for content_mananger 2024-01-19 20:54:50 -05:00
03fa91ba3c android: Add addon delete button
Required some refactoring of retrieving patches in order for the frontend to pass the right information to ContentManager for deletion.
2024-01-19 20:54:50 -05:00
d79d4d5986 android: Use callback to update progress bar dialogs 2024-01-19 17:09:36 -05:00
ccd3dd842f frontend_common: Add content manager utility functions
Creates utility functions to remove/install DLC, updates, and base game content
2024-01-19 17:09:35 -05:00
b4a8e1ef8a Merge pull request #12713 from shinra-electric/mvk-127
macOS: Bump MoltenVK to v1.2.7
2024-01-19 13:07:14 -05:00
5ea8f05ec6 Bump MoltenVK to v1.2.7 2024-01-19 17:28:53 +01:00
10535e0016 Merge pull request #12687 from german77/amiibo-lock
core: hid: Disable special features before disconnecting the controllers
2024-01-19 09:33:31 -05:00
a8c552e261 Merge pull request #12695 from anpilley/user-arguments-v2
Allow -u to accept a username string in addition to index
2024-01-19 09:33:25 -05:00
932bd98824 Merge pull request #12709 from german77/npad-disc
service: hid: Clear controller status when aruid is no longer used
2024-01-19 09:33:16 -05:00
9f376cd901 service: hid: Clear controller status when aruid is no longer used 2024-01-19 00:09:49 -06:00
a560b9f5a2 Merge pull request #12678 from german77/settings_impl
service: set: Implement stubbed functions
2024-01-18 21:18:37 -05:00
4f04bd3697 Merge pull request #12683 from german77/amiibo-dump
service: nfc: Create backup when none exist
2024-01-18 21:18:27 -05:00
97c8b49444 Merge pull request #12644 from liamwhite/vkspec-image-offset
shader_recompiler: fix Offset operand usage for non-OpImage*Gather
2024-01-18 21:18:19 -05:00
3092855d5a Merge pull request #12702 from german77/android-input
input_common: Add android input engine
2024-01-18 09:16:58 -05:00
72f803c366 input_common: Add android input engine 2024-01-17 22:47:56 -06:00
c87b96435d Merge pull request #12699 from t895/overlay-saving
android: Save overlay data while using emulation fragment
2024-01-17 22:56:40 -05:00
e4bbb24dcf vi: check layer state before opening or closing 2024-01-17 22:03:40 -05:00
6536d29c61 Update based on feedback 2024-01-17 18:14:05 -08:00
116f76e4b6 android: Save overlay data while using emulation fragment
This should have been fully embraced before but the items within the popup menu and the adjust controls dialog fell through. This ensures that everything related to the overlay is saved during emulation and can't be lost during a crash.
2024-01-17 20:14:25 -05:00
ce89580749 nvnflinger: ensure display abandonment considers all layers and future layers 2024-01-17 18:45:39 -05:00
dff0a7c52a Allow -u to accept a username string in addition to index, and suppress the User selector even if settings requires it to be shown for one instance only. 2024-01-17 10:31:00 -08:00
915efa4236 Merge pull request #12689 from liamwhite/remove-format
ci: remove format dep from mainline step2
2024-01-17 00:36:07 -05:00
4548e5ae1d ci: remove format dep from mainline step2 2024-01-16 22:59:20 -05:00
46c2435235 Merge pull request #12380 from flodavid/save-profile
Save configuration profile name used by players
2024-01-16 21:27:25 -06:00
e9eb017aac renderer_vulkan: recreate swapchain when frame size changes 2024-01-16 16:09:39 -05:00
0b0e9ef18d core: hid: Disable special features before disconnecting the controllers 2024-01-16 14:44:54 -06:00
7f5adf8982 service: set: Implement stubbed functions 2024-01-15 23:17:03 -06:00
89d6856090 service: set: Refractor setting service 2024-01-15 23:16:36 -06:00
2cacb9d48c service: hid: Fully implement abstract vibration 2024-01-15 23:15:40 -06:00
2c29c2b8dd Merge pull request #12686 from szepeviktor/typos3
Fix more typos
2024-01-15 23:26:08 -05:00
16abda59be Fix typos in master 2024-01-16 00:09:25 +00:00
90ab89a0b0 Merge remote-tracking branch 'origin/master' into typos3 2024-01-16 00:09:00 +00:00
6531ad56a6 Fix typos in arrays.xml 2024-01-15 23:39:45 +00:00
e8671ed04e Fix one more typo 2024-01-15 23:34:11 +00:00
2044ae6b3a Fix more typos 2024-01-15 23:26:53 +00:00
c661b95864 service: nfc: Create backup when none exist 2024-01-15 14:07:54 -06:00
c683ec2bcb Merge pull request #12681 from t895/stick-toggles
android: Fix overlay toggle ordering
2024-01-15 13:52:53 -05:00
2e4e33156e Merge pull request #12680 from t895/format-mainline
ci: Remove format step from mainline builds
2024-01-15 13:52:48 -05:00
04f4eeaca2 Merge pull request #12677 from GPUCode/whyy-modders
core: Support multiple modules per patcher
2024-01-15 13:52:38 -05:00
2e4b32204c Merge pull request #12665 from german77/proof
service: acc: Only save profiles when profiles have changed
2024-01-15 13:52:33 -05:00
34db13486a Merge pull request #12659 from liamwhite/audio-memory
audio: fetch process object from handle table
2024-01-15 13:52:01 -05:00
c6c6bb4041 Merge pull request #12652 from liamwhite/huge-pile-of-spirv-spaghetti
shader_recompiler: emulate 8-bit and 16-bit storage writes with cas loop
2024-01-15 13:51:36 -05:00
a2ffb419c9 Merge pull request #12612 from liamwhite/fs-pid
fsp-srv: use program registry for SetCurrentProcess
2024-01-15 13:51:14 -05:00
0127cec371 Merge pull request #12611 from liamwhite/resource-management-is-hard
kernel: fix resource management issues
2024-01-15 13:50:58 -05:00
db3a6075f5 Merge pull request #12610 from liamwhite/reply-and-dont-receive
server_manager: respond to session close correctly
2024-01-15 13:50:43 -05:00
8876a15227 android: Fix overlay toggle ordering 2024-01-15 12:41:49 -05:00
954eb40237 ci: Remove format step from mainline builds 2024-01-15 10:30:57 -05:00
d4acdac168 core: Support multiple modules per patcher 2024-01-15 00:46:05 +02:00
bee22540a1 service: acc: Only save profiles when profiles have changed 2024-01-13 14:28:29 -06:00
76880b84f9 loader: fix homebrew nro registration 2024-01-13 13:48:56 -05:00
2f0b57ca13 kernel: optimize page free on shutdown 2024-01-12 19:19:07 -05:00
f90a022d3a kernel: fix debugger and process list lifetime 2024-01-12 18:31:33 -05:00
f2fed21c11 kernel: fix page leak on process termination 2024-01-12 18:31:33 -05:00
d940974789 audio: fetch process object from handle table 2024-01-12 10:03:16 -05:00
2a0d707ce1 shader_recompiler: emulate 8-bit and 16-bit storage writes with cas loop 2024-01-11 16:50:59 -05:00
aae9eea532 fsp-srv: use program registry for SetCurrentProcess 2024-01-11 11:28:52 -05:00
2044a289f8 shader_recompiler: fix Offset operand usage for non-OpImage*Gather 2024-01-11 00:56:37 -05:00
63b835f822 Save profile name used
- Save the profile name in global config
- Read the profile name when reading the global config
2024-01-08 18:43:56 +01:00
200b371d13 server_manager: respond to session close correctly 2024-01-07 21:33:24 -05:00
439 changed files with 15392 additions and 7547 deletions

View File

@ -8,17 +8,7 @@ variables:
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
stages:
- stage: format
displayName: 'format'
jobs:
- job: format
displayName: 'clang'
pool:
vmImage: ubuntu-latest
steps:
- template: ./templates/format-check.yml
- stage: build
dependsOn: format
displayName: 'build'
jobs:
- job: build
@ -43,7 +33,6 @@ stages:
cache: 'true'
version: $(DisplayVersion)
- stage: build_win
dependsOn: format
displayName: 'build-windows'
jobs:
- job: build

View File

@ -1,3 +1,13 @@
| Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----|
| [12499](https://github.com/yuzu-emu/yuzu-android//pull/12499) | [`fab260fc2`](https://github.com/yuzu-emu/yuzu-android//pull/12499/files) | Rework time services | [Kelebek1](https://github.com/Kelebek1/) | Yes |
| [12579](https://github.com/yuzu-emu/yuzu-android//pull/12579) | [`748465f5a`](https://github.com/yuzu-emu/yuzu-android//pull/12579/files) | Core: Implement Device Mapping & GPU SMMU | [FernandoS27](https://github.com/FernandoS27/) | Yes |
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

@ -178,6 +178,9 @@ if (NOT TARGET stb::headers)
add_library(stb::headers ALIAS stb)
endif()
add_library(tz tz/tz/tz.cpp)
target_include_directories(tz PUBLIC ./tz)
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
target_include_directories(bc_decoder PUBLIC ./bc_decoder)

1636
externals/tz/tz/tz.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

81
externals/tz/tz/tz.h vendored Normal file
View File

@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-FileCopyrightText: 1996 Arthur David Olson
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include <cstdint>
#include <limits>
#include <span>
#include <array>
#include <time.h>
namespace Tz {
using u8 = uint8_t;
using s8 = int8_t;
using u16 = uint16_t;
using s16 = int16_t;
using u32 = uint32_t;
using s32 = int32_t;
using u64 = uint64_t;
using s64 = int64_t;
constexpr size_t TZ_MAX_TIMES = 1000;
constexpr size_t TZ_MAX_TYPES = 128;
constexpr size_t TZ_MAX_CHARS = 50;
constexpr size_t MY_TZNAME_MAX = 255;
constexpr size_t TZNAME_MAXIMUM = 255;
constexpr size_t TZ_MAX_LEAPS = 50;
constexpr s64 TIME_T_MAX = std::numeric_limits<s64>::max();
constexpr s64 TIME_T_MIN = std::numeric_limits<s64>::min();
constexpr size_t CHARS_EXTRA = 3;
constexpr size_t MAX_ZONE_CHARS = std::max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof("UTC"));
constexpr size_t MAX_TZNAME_CHARS = 2 * (MY_TZNAME_MAX + 1);
struct ttinfo {
s32 tt_utoff;
bool tt_isdst;
s32 tt_desigidx;
bool tt_ttisstd;
bool tt_ttisut;
};
static_assert(sizeof(ttinfo) == 0x10, "ttinfo has the wrong size!");
struct Rule {
s32 timecnt;
s32 typecnt;
s32 charcnt;
bool goback;
bool goahead;
std::array <u8, 0x2> padding0;
std::array<s64, TZ_MAX_TIMES> ats;
std::array<u8, TZ_MAX_TIMES> types;
std::array<ttinfo, TZ_MAX_TYPES> ttis;
std::array<char, std::max(MAX_ZONE_CHARS, MAX_TZNAME_CHARS)> chars;
s32 defaulttype;
std::array <u8, 0x12C4> padding1;
};
static_assert(sizeof(Rule) == 0x4000, "Rule has the wrong size!");
struct CalendarTimeInternal {
s32 tm_sec;
s32 tm_min;
s32 tm_hour;
s32 tm_mday;
s32 tm_mon;
s32 tm_year;
s32 tm_wday;
s32 tm_yday;
s32 tm_isdst;
std::array<char, 16> tm_zone;
s32 tm_utoff;
s32 time_index;
};
static_assert(sizeof(CalendarTimeInternal) == 0x3C, "CalendarTimeInternal has the wrong size!");
s32 ParseTimeZoneBinary(Rule& out_rule, std::span<const u8> binary);
bool localtime_rz(CalendarTimeInternal* tmp, Rule* sp, time_t* timep);
u32 mktime_tzname(time_t* out_time, Rule* sp, CalendarTimeInternal* tmp);
} // namespace Tz

View File

@ -21,6 +21,8 @@ import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.Patch
/**
* Class which contains methods that interact
@ -235,9 +237,12 @@ object NativeLibrary {
/**
* Installs a nsp or xci file to nand
* @param filename String representation of file uri
* @param extension Lowercase string representation of file extension without "."
* @return int representation of [InstallResult]
*/
external fun installFileToNand(filename: String, extension: String): Int
external fun installFileToNand(
filename: String,
callback: (max: Long, progress: Long) -> Boolean
): Int
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
@ -535,9 +540,29 @@ object NativeLibrary {
*
* @param path Path to game file. Can be a [Uri].
* @param programId String representation of a game's program ID
* @return Array of pairs where the first value is the name of an addon and the second is the version
* @return Array of available patches
*/
external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>?
external fun getPatchesForFile(path: String, programId: String): Array<Patch>?
/**
* Removes an update for a given [programId]
* @param programId String representation of a game's program ID
*/
external fun removeUpdate(programId: String)
/**
* Removes all DLC for a [programId]
* @param programId String representation of a game's program ID
*/
external fun removeDLC(programId: String)
/**
* Removes a mod installed for a given [programId]
* @param programId String representation of a game's program ID
* @param name The name of a mod as given by [getPatchesForFile]. This corresponds with the name
* of the mod's directory in a game's load folder.
*/
external fun removeMod(programId: String, name: String)
/**
* Gets the save location for a specific game
@ -609,15 +634,4 @@ object NativeLibrary {
const val RELEASED = 0
const val PRESSED = 1
}
/**
* Result from installFileToNand
*/
object InstallFileToNandResult {
const val Success = 0
const val SuccessFileOverwritten = 1
const val Error = 2
const val ErrorBaseGame = 3
const val ErrorFilenameExtension = 4
}
}

View File

@ -49,7 +49,6 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.MemoryUtil
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat
@ -171,11 +170,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener()
}
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {

View File

@ -6,27 +6,32 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Addon
import org.yuzu.yuzu_emu.model.Patch
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() {
class AddonAdapter(val addonViewModel: AddonViewModel) :
AbstractDiffAdapter<Patch, AddonAdapter.AddonViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) }
}
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
AbstractViewHolder<Addon>(binding) {
override fun bind(model: Addon) {
AbstractViewHolder<Patch>(binding) {
override fun bind(model: Patch) {
binding.root.setOnClickListener {
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
binding.addonCheckbox.isChecked = !binding.addonCheckbox.isChecked
}
binding.title.text = model.title
binding.title.text = model.name
binding.version.text = model.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
binding.addonCheckbox.setOnCheckedChangeListener { _, checked ->
model.enabled = checked
}
binding.addonSwitch.isChecked = model.enabled
binding.addonCheckbox.isChecked = model.enabled
binding.buttonDelete.setOnClickListener {
addonViewModel.setAddonToDelete(model)
}
}
}
}

View File

@ -74,7 +74,7 @@ class AddonsFragment : Fragment() {
binding.listAddons.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = AddonAdapter()
adapter = AddonAdapter(addonViewModel)
}
viewLifecycleOwner.lifecycleScope.apply {
@ -110,6 +110,21 @@ class AddonsFragment : Fragment() {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.addonToDelete.collect {
if (it != null) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.confirm_uninstall,
descriptionId = R.string.confirm_uninstall_description,
positiveAction = { addonViewModel.onDeleteAddon(it) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
addonViewModel.setAddonToDelete(null)
}
}
}
}
}
binding.buttonInstall.setOnClickListener {
@ -156,22 +171,22 @@ class AddonsFragment : Fragment() {
descriptionId = R.string.invalid_directory_description
)
if (isValid) {
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_game_content,
false
) {
) { progressCallback, _ ->
val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName)
try {
externalAddonDirectory.copyFilesTo(internalAddonDirectory)
externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback)
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
}

View File

@ -75,7 +75,7 @@ class DriverManagerFragment : Fragment() {
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
binding.toolbarDrivers.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_driver_clear -> {
R.id.menu_driver_use_global -> {
StringSetting.DRIVER_PATH.global = true
driverViewModel.updateDriverList()
(binding.listDrivers.adapter as DriverAdapter)
@ -93,7 +93,7 @@ class DriverManagerFragment : Fragment() {
repeatOnLifecycle(Lifecycle.State.STARTED) {
driverViewModel.showClearButton.collect {
binding.toolbarDrivers.menu
.findItem(R.id.menu_driver_clear).isVisible = it
.findItem(R.id.menu_driver_use_global).isVisible = it
}
}
}
@ -173,11 +173,11 @@ class DriverManagerFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_driver,
false
) {
) { _, _ ->
val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
val driverFile = File(driverPath)
@ -213,6 +213,6 @@ class DriverManagerFragment : Fragment() {
}
}
return@newInstance Any()
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(childFragmentManager, ProgressDialogFragment.TAG)
}
}

View File

@ -554,6 +554,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
}
popup.setOnDismissListener { NativeConfig.saveGlobalConfig() }
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_toggle_fps -> {
@ -720,7 +721,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.emulation_control_adjust)
.setView(adjustBinding.root)
.setPositiveButton(android.R.string.ok, null)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
NativeConfig.saveGlobalConfig()
}
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
setControlScale(50)
setControlOpacity(100)

View File

@ -44,7 +44,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
@ -357,27 +356,17 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
) { _, _ ->
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
FileUtil.unzipToInternalStorage(result.toString(), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null
if (files != null) {
@ -422,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
/**
@ -436,11 +425,11 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
) { _, _ ->
val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation),
@ -452,6 +441,6 @@ class GamePropertiesFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}

View File

@ -34,7 +34,6 @@ import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.math.BigInteger
@ -195,26 +194,20 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
) { progressCallback, _ ->
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
FileUtil.unzipToInternalStorage(
result.toString(),
cacheSaveDir,
progressCallback
)
val files = cacheSaveDir.listFiles()
var successfulImports = 0
var failedImports = 0
@ -287,7 +280,7 @@ class InstallableFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
private val exportSaves = registerForActivityResult(
@ -297,11 +290,11 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
) { _, _ ->
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
@ -338,6 +331,6 @@ class InstallableFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}

View File

@ -23,11 +23,13 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() {
class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
private lateinit var binding: DialogProgressBarBinding
private val PROGRESS_BAR_RESOLUTION = 1000
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE)
@ -61,6 +63,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message.isSelected = true
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@ -97,6 +100,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.progress.collect {
if (it != 0.0) {
binding.progressBar.apply {
isIndeterminate = false
progress = (
(it / taskViewModel.maxProgress.value) *
PROGRESS_BAR_RESOLUTION
).toInt()
min = 0
max = PROGRESS_BAR_RESOLUTION
}
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.message.collect {
if (it.isEmpty()) {
binding.message.visibility = View.GONE
} else {
binding.message.visibility = View.VISIBLE
binding.message.text = it
}
}
}
}
}
}
@ -108,6 +140,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
alertDialog.setTitle(getString(R.string.cancelling))
binding.progressBar.isIndeterminate = true
taskViewModel.setCancelled(true)
}
}
@ -122,9 +155,12 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
task: suspend () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
task: suspend (
progressCallback: (max: Long, progress: Long) -> Boolean,
messageCallback: (message: String) -> Unit
) -> Any
): ProgressDialogFragment {
val dialog = ProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)

View File

@ -136,14 +136,14 @@ class SearchFragment : Fragment() {
baseList.filter {
val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
lastPlayedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
}
}.sortedByDescending { preferences.getLong(it.keyLastPlayedTime, 0L) }
}
R.id.chip_recently_added -> {
baseList.filter {
val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
addedTime > (System.currentTimeMillis() - 24 * 60 * 60 * 1000)
}
}.sortedByDescending { preferences.getLong(it.keyAddedToLibraryTime, 0L) }
}
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }

View File

@ -1,10 +0,0 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
data class Addon(
var enabled: Boolean,
val title: String,
val version: String
)

View File

@ -15,8 +15,8 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() {
private val _addonList = MutableStateFlow(mutableListOf<Addon>())
val addonList get() = _addonList.asStateFlow()
private val _patchList = MutableStateFlow(mutableListOf<Patch>())
val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false)
val showModInstallPicker get() = _showModInstallPicker.asStateFlow()
@ -24,6 +24,9 @@ class AddonViewModel : ViewModel() {
private val _showModNoticeDialog = MutableStateFlow(false)
val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow()
private val _addonToDelete = MutableStateFlow<Patch?>(null)
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null
private val isRefreshing = AtomicBoolean(false)
@ -40,36 +43,47 @@ class AddonViewModel : ViewModel() {
isRefreshing.set(true)
viewModelScope.launch {
withContext(Dispatchers.IO) {
val addonList = mutableListOf<Addon>()
val disabledAddons = NativeConfig.getDisabledAddons(game!!.programId)
NativeLibrary.getAddonsForFile(game!!.path, game!!.programId)?.forEach {
val name = it.first.replace("[D] ", "")
addonList.add(Addon(!disabledAddons.contains(name), name, it.second))
}
addonList.sortBy { it.title }
_addonList.value = addonList
val patchList = (
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
?: emptyArray()
).toMutableList()
patchList.sortBy { it.name }
_patchList.value = patchList
isRefreshing.set(false)
}
}
}
fun setAddonToDelete(patch: Patch?) {
_addonToDelete.value = patch
}
fun onDeleteAddon(patch: Patch) {
when (PatchType.from(patch.type)) {
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
}
refreshAddons()
}
fun onCloseAddons() {
if (_addonList.value.isEmpty()) {
if (_patchList.value.isEmpty()) {
return
}
NativeConfig.setDisabledAddons(
game!!.programId,
_addonList.value.mapNotNull {
_patchList.value.mapNotNull {
if (it.enabled) {
null
} else {
it.title
it.name
}
}.toTypedArray()
)
NativeConfig.saveGlobalConfig()
_addonList.value.clear()
_patchList.value.clear()
game = null
}

View File

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
enum class InstallResult(val int: Int) {
Success(0),
Overwrite(1),
Failure(2),
BaseInstallAttempted(3);
companion object {
fun from(int: Int): InstallResult = entries.firstOrNull { it.int == int } ?: Success
}
}

View File

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import androidx.annotation.Keep
@Keep
data class Patch(
var enabled: Boolean,
val name: String,
val version: String,
val type: Int,
val programId: String,
val titleId: String
)

View File

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
enum class PatchType(val int: Int) {
Update(0),
DLC(1),
Mod(2);
companion object {
fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update
}
}

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
@ -23,13 +24,28 @@ class TaskViewModel : ViewModel() {
val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false)
lateinit var task: suspend () -> Any
private val _progress = MutableStateFlow(0.0)
val progress = _progress.asStateFlow()
private val _maxProgress = MutableStateFlow(0.0)
val maxProgress = _maxProgress.asStateFlow()
private val _message = MutableStateFlow("")
val message = _message.asStateFlow()
lateinit var task: suspend (
progressCallback: (max: Long, progress: Long) -> Boolean,
messageCallback: (message: String) -> Unit
) -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
_cancelled.value = false
_progress.value = 0.0
_maxProgress.value = 0.0
_message.value = ""
}
fun setCancelled(value: Boolean) {
@ -43,7 +59,16 @@ class TaskViewModel : ViewModel() {
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
val res = task()
val res = task(
{ max, progress ->
_maxProgress.value = max.toDouble()
_progress.value = progress.toDouble()
return@task cancelled.value
},
{ message ->
_message.value = message
}
)
_result.value = res
_isComplete.value = true
_isRunning.value = false

View File

@ -38,12 +38,13 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
@ -369,26 +370,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
val inputZip = contentResolver.openInputStream(result)
if (inputZip == null) {
Toast.makeText(
applicationContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
val task: () -> Any = {
ProgressDialogFragment.newInstance(
this,
R.string.firmware_installing
) { progressCallback, _ ->
var messageToShow: Any
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
FileUtil.unzipToInternalStorage(
result.toString(),
cacheFirmwareDir,
progressCallback
)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
@ -404,18 +402,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
Log.error("[MainActivity] Firmware install failed - ${e.message}")
messageToShow = getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val getAmiiboKey =
@ -474,11 +467,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.verifying_content,
false
) {
) { _, _ ->
var updatesMatchProgram = true
for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram(
@ -501,44 +494,42 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
positiveAction = { homeViewModel.setContentToInstall(documents) }
)
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
private fun installContent(documents: List<Uri>) {
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.installing_game_content
) {
) { progressCallback, messageCallback ->
var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
var errorExtension = 0
var errorOther = 0
var error = 0
documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it))
when (
NativeLibrary.installFileToNand(
it.toString(),
FileUtil.getExtension(it)
InstallResult.from(
NativeLibrary.installFileToNand(
it.toString(),
progressCallback
)
)
) {
NativeLibrary.InstallFileToNandResult.Success -> {
InstallResult.Success -> {
installSuccess += 1
}
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
InstallResult.Overwrite -> {
installOverwrite += 1
}
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
InstallResult.BaseInstallAttempted -> {
errorBaseGame += 1
}
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
errorExtension += 1
}
else -> {
errorOther += 1
InstallResult.Failure -> {
error += 1
}
}
}
@ -565,7 +556,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
val errorTotal: Int = errorBaseGame + errorExtension + errorOther
val errorTotal: Int = errorBaseGame + error
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
@ -582,14 +573,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
if (errorExtension > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_file_extension)
)
installResult.append(separator)
}
if (errorOther > 0) {
if (error > 0) {
installResult.append(
getString(R.string.install_game_content_failure_description)
)
@ -608,7 +592,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
descriptionString = installResult.toString().trim()
)
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val exportUserData = registerForActivityResult(
@ -618,16 +602,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
) {
) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!),
DirectoryInitialization.userDirectory!!,
BufferedOutputStream(contentResolver.openOutputStream(result)),
taskViewModel.cancelled,
progressCallback,
compression = false
)
return@newInstance when (zipResult) {
@ -635,7 +619,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val importUserData =
@ -644,10 +628,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
) {
) { progressCallback, _ ->
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
@ -676,8 +660,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Copy archive to internal storage
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(contentResolver.openInputStream(result)),
File(DirectoryInitialization.userDirectory!!)
result.toString(),
File(DirectoryInitialization.userDirectory!!),
progressCallback
)
} catch (e: Exception) {
return@newInstance MessageDialogFragment.newInstance(
@ -694,6 +679,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
}

View File

@ -7,7 +7,6 @@ import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
@ -19,6 +18,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream
import java.io.OutputStream
import java.lang.NullPointerException
import java.nio.charset.StandardCharsets
import java.util.zip.Deflater
@ -104,7 +104,7 @@ object FileUtil {
/**
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
* This function will be faster than DoucmentFile.listFiles
* This function will be faster than DocumentFile.listFiles
* @param uri Directory uri.
* @return CheapDocument lists.
*/
@ -283,12 +283,34 @@ object FileUtil {
/**
* Extracts the given zip file into the given directory.
* @param path String representation of a [Uri] or a typical path delimited by '/'
* @param destDir Location to unzip the contents of [path] into
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
*/
@Throws(SecurityException::class)
fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
ZipInputStream(zipStream).use { zis ->
fun unzipToInternalStorage(
path: String,
destDir: File,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
) {
var totalEntries = 0L
ZipInputStream(getInputStream(path)).use { zis ->
var tempEntry = zis.nextEntry
while (tempEntry != null) {
tempEntry = zis.nextEntry
totalEntries++
}
}
var progress = 0L
ZipInputStream(getInputStream(path)).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
if (progressCallback.invoke(totalEntries, progress)) {
return@use
}
val newFile = File(destDir, entry.name)
val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
@ -304,6 +326,7 @@ object FileUtil {
newFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
progress++
}
}
}
@ -313,14 +336,15 @@ object FileUtil {
* @param inputFile File representation of the item that will be zipped
* @param rootDir Directory containing the inputFile
* @param outputStream Stream where the zip file will be output
* @param cancelled [StateFlow] that reports whether this process has been cancelled
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
* @param compression Disables compression if true
*/
fun zipFromInternalStorage(
inputFile: File,
rootDir: String,
outputStream: BufferedOutputStream,
cancelled: StateFlow<Boolean>? = null,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false },
compression: Boolean = true
): TaskState {
try {
@ -330,8 +354,10 @@ object FileUtil {
zos.setLevel(Deflater.NO_COMPRESSION)
}
var count = 0L
val totalFiles = inputFile.walkTopDown().count().toLong()
inputFile.walkTopDown().forEach { file ->
if (cancelled?.value == true) {
if (progressCallback.invoke(totalFiles, count)) {
return TaskState.Cancelled
}
@ -343,6 +369,7 @@ object FileUtil {
if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) }
}
count++
}
}
}
@ -356,9 +383,14 @@ object FileUtil {
/**
* Helper function that copies the contents of a DocumentFile folder into a [File]
* @param file [File] representation of the folder to copy into
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
* @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa
*/
fun DocumentFile.copyFilesTo(file: File) {
fun DocumentFile.copyFilesTo(
file: File,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
) {
file.mkdirs()
if (!this.isDirectory || !file.isDirectory) {
throw IllegalStateException(
@ -366,7 +398,13 @@ object FileUtil {
)
}
var count = 0L
val totalFiles = this.listFiles().size.toLong()
this.listFiles().forEach {
if (progressCallback.invoke(totalFiles, count)) {
return
}
val newFile = File(file, it.name!!)
if (it.isDirectory) {
newFile.mkdirs()
@ -381,6 +419,7 @@ object FileUtil {
newFile.outputStream().use { os -> bos.copyTo(os) }
}
}
count++
}
}
@ -427,6 +466,18 @@ object FileUtil {
}
}
fun getInputStream(path: String) = if (path.contains("content://")) {
Uri.parse(path).inputStream()
} else {
File(path).inputStream()
}
fun getOutputStream(path: String) = if (path.contains("content://")) {
Uri.parse(path).outputStream()
} else {
File(path).outputStream()
}
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8)
@ -434,4 +485,19 @@ object FileUtil {
@Throws(IOException::class)
fun getStringFromInputStream(stream: InputStream): String =
String(stream.readBytes(), StandardCharsets.UTF_8)
fun DocumentFile.inputStream(): InputStream =
YuzuApplication.appContext.contentResolver.openInputStream(uri)!!
fun DocumentFile.outputStream(): OutputStream =
YuzuApplication.appContext.contentResolver.openOutputStream(uri)!!
fun Uri.inputStream(): InputStream =
YuzuApplication.appContext.contentResolver.openInputStream(this)!!
fun Uri.outputStream(): OutputStream =
YuzuApplication.appContext.contentResolver.openOutputStream(this)!!
fun Uri.asDocumentFile(): DocumentFile? =
DocumentFile.fromSingleUri(YuzuApplication.appContext, this)
}

View File

@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.utils
import android.net.Uri
import android.os.Build
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
@ -123,7 +122,7 @@ object GpuDriverHelper {
// Unzip the driver.
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(copiedFile.inputStream()),
copiedFile.path,
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {
@ -156,7 +155,7 @@ object GpuDriverHelper {
// Unzip the driver to the private installation directory
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(driver.inputStream()),
driver.path,
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {

View File

@ -42,3 +42,19 @@ double GetJDouble(JNIEnv* env, jobject jdouble) {
jobject ToJDouble(JNIEnv* env, double value) {
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
}
s32 GetJInteger(JNIEnv* env, jobject jinteger) {
return env->GetIntField(jinteger, IDCache::GetIntegerValueField());
}
jobject ToJInteger(JNIEnv* env, s32 value) {
return env->NewObject(IDCache::GetIntegerClass(), IDCache::GetIntegerConstructor(), value);
}
bool GetJBoolean(JNIEnv* env, jobject jboolean) {
return env->GetBooleanField(jboolean, IDCache::GetBooleanValueField());
}
jobject ToJBoolean(JNIEnv* env, bool value) {
return env->NewObject(IDCache::GetBooleanClass(), IDCache::GetBooleanConstructor(), value);
}

View File

@ -6,6 +6,7 @@
#include <string>
#include <jni.h>
#include "common/common_types.h"
std::string GetJString(JNIEnv* env, jstring jstr);
jstring ToJString(JNIEnv* env, std::string_view str);
@ -13,3 +14,9 @@ jstring ToJString(JNIEnv* env, std::u16string_view str);
double GetJDouble(JNIEnv* env, jobject jdouble);
jobject ToJDouble(JNIEnv* env, double value);
s32 GetJInteger(JNIEnv* env, jobject jinteger);
jobject ToJInteger(JNIEnv* env, s32 value);
bool GetJBoolean(JNIEnv* env, jobject jboolean);
jobject ToJBoolean(JNIEnv* env, bool value);

View File

@ -21,7 +21,7 @@ void AndroidConfig::ReloadAllValues() {
}
void AndroidConfig::SaveAllValues() {
Save();
SaveValues();
SaveAndroidValues();
}

View File

@ -43,10 +43,27 @@ static jfieldID s_overlay_control_data_landscape_position_field;
static jfieldID s_overlay_control_data_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_position_field;
static jclass s_patch_class;
static jmethodID s_patch_constructor;
static jfieldID s_patch_enabled_field;
static jfieldID s_patch_name_field;
static jfieldID s_patch_version_field;
static jfieldID s_patch_type_field;
static jfieldID s_patch_program_id_field;
static jfieldID s_patch_title_id_field;
static jclass s_double_class;
static jmethodID s_double_constructor;
static jfieldID s_double_value_field;
static jclass s_integer_class;
static jmethodID s_integer_constructor;
static jfieldID s_integer_value_field;
static jclass s_boolean_class;
static jmethodID s_boolean_constructor;
static jfieldID s_boolean_value_field;
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace IDCache {
@ -186,6 +203,38 @@ jfieldID GetOverlayControlDataFoldablePositionField() {
return s_overlay_control_data_foldable_position_field;
}
jclass GetPatchClass() {
return s_patch_class;
}
jmethodID GetPatchConstructor() {
return s_patch_constructor;
}
jfieldID GetPatchEnabledField() {
return s_patch_enabled_field;
}
jfieldID GetPatchNameField() {
return s_patch_name_field;
}
jfieldID GetPatchVersionField() {
return s_patch_version_field;
}
jfieldID GetPatchTypeField() {
return s_patch_type_field;
}
jfieldID GetPatchProgramIdField() {
return s_patch_program_id_field;
}
jfieldID GetPatchTitleIdField() {
return s_patch_title_id_field;
}
jclass GetDoubleClass() {
return s_double_class;
}
@ -198,6 +247,30 @@ jfieldID GetDoubleValueField() {
return s_double_value_field;
}
jclass GetIntegerClass() {
return s_integer_class;
}
jmethodID GetIntegerConstructor() {
return s_integer_constructor;
}
jfieldID GetIntegerValueField() {
return s_integer_value_field;
}
jclass GetBooleanClass() {
return s_boolean_class;
}
jmethodID GetBooleanConstructor() {
return s_boolean_constructor;
}
jfieldID GetBooleanValueField() {
return s_boolean_value_field;
}
} // namespace IDCache
#ifdef __cplusplus
@ -278,12 +351,37 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
env->DeleteLocalRef(overlay_control_data_class);
const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch");
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
s_patch_constructor = env->GetMethodID(
patch_class, "<init>",
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
s_patch_type_field = env->GetFieldID(patch_class, "type", "I");
s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;");
s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;");
env->DeleteLocalRef(patch_class);
const jclass double_class = env->FindClass("java/lang/Double");
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
s_double_value_field = env->GetFieldID(double_class, "value", "D");
env->DeleteLocalRef(double_class);
const jclass int_class = env->FindClass("java/lang/Integer");
s_integer_class = reinterpret_cast<jclass>(env->NewGlobalRef(int_class));
s_integer_constructor = env->GetMethodID(int_class, "<init>", "(I)V");
s_integer_value_field = env->GetFieldID(int_class, "value", "I");
env->DeleteLocalRef(int_class);
const jclass boolean_class = env->FindClass("java/lang/Boolean");
s_boolean_class = reinterpret_cast<jclass>(env->NewGlobalRef(boolean_class));
s_boolean_constructor = env->GetMethodID(boolean_class, "<init>", "(Z)V");
s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z");
env->DeleteLocalRef(boolean_class);
// Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
@ -309,7 +407,10 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_string_class);
env->DeleteGlobalRef(s_pair_class);
env->DeleteGlobalRef(s_overlay_control_data_class);
env->DeleteGlobalRef(s_patch_class);
env->DeleteGlobalRef(s_double_class);
env->DeleteGlobalRef(s_integer_class);
env->DeleteGlobalRef(s_boolean_class);
// UnInitialize applets
SoftwareKeyboard::CleanupJNI(env);

View File

@ -43,8 +43,25 @@ jfieldID GetOverlayControlDataLandscapePositionField();
jfieldID GetOverlayControlDataPortraitPositionField();
jfieldID GetOverlayControlDataFoldablePositionField();
jclass GetPatchClass();
jmethodID GetPatchConstructor();
jfieldID GetPatchEnabledField();
jfieldID GetPatchNameField();
jfieldID GetPatchVersionField();
jfieldID GetPatchTypeField();
jfieldID GetPatchProgramIdField();
jfieldID GetPatchTitleIdField();
jclass GetDoubleClass();
jmethodID GetDoubleConstructor();
jfieldID GetDoubleValueField();
jclass GetIntegerClass();
jmethodID GetIntegerConstructor();
jfieldID GetIntegerValueField();
jclass GetBooleanClass();
jmethodID GetBooleanConstructor();
jfieldID GetBooleanValueField();
} // namespace IDCache

View File

@ -17,6 +17,7 @@
#include <core/file_sys/patch_manager.h>
#include <core/file_sys/savedata_factory.h>
#include <core/loader/nro.h>
#include <frontend_common/content_manager.h>
#include <jni.h>
#include "common/detached_tasks.h"
@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
m_native_window = native_window;
}
int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
std::size_t block_size) {
if (src == nullptr || dest == nullptr) {
return false;
}
if (!dest->Resize(src->GetSize())) {
return false;
}
using namespace Common::Literals;
[[maybe_unused]] std::vector<u8> buffer(1_MiB);
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
jconst read = src->Read(buffer.data(), buffer.size(), i);
dest->Write(buffer.data(), read, i);
}
return true;
};
enum InstallResult {
Success = 0,
SuccessFileOverwritten = 1,
InstallError = 2,
ErrorBaseGame = 3,
ErrorFilenameExtension = 4,
};
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
if (file_extension == "nsp") {
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
if (nsp->IsExtractedType()) {
return InstallError;
}
} else {
return ErrorFilenameExtension;
}
if (!nsp) {
return InstallError;
}
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
return InstallError;
}
jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
copy_func);
switch (res) {
case FileSys::InstallResult::Success:
return Success;
case FileSys::InstallResult::OverwriteExisting:
return SuccessFileOverwritten;
case FileSys::InstallResult::ErrorBaseInstall:
return ErrorBaseGame;
default:
return InstallError;
}
}
void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
const std::string& custom_driver_dir,
const std::string& custom_driver_name,
@ -512,10 +452,20 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject
}
int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
jstring j_file,
jstring j_file_extension) {
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file),
GetJString(env, j_file_extension));
jstring j_file, jobject jcallback) {
auto jlambdaClass = env->GetObjectClass(jcallback);
auto jlambdaInvokeMethod = env->GetMethodID(
jlambdaClass, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
const auto callback = [env, jcallback, jlambdaInvokeMethod](size_t max, size_t progress) {
auto jwasCancelled = env->CallObjectMethod(jcallback, jlambdaInvokeMethod,
ToJDouble(env, max), ToJDouble(env, progress));
return GetJBoolean(env, jwasCancelled);
};
return static_cast<int>(
ContentManager::InstallNSP(&EmulationSession::GetInstance().System(),
EmulationSession::GetInstance().System().GetFilesystem().get(),
GetJString(env, j_file), callback));
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
@ -770,8 +720,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
ASSERT(user_id);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
{}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, 1,
user_id->AsU128(), 0);
const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
if (!Common::FS::CreateParentDirs(full_path)) {
@ -824,9 +774,9 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env,
return true;
}
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, jobject jobj,
jstring jpath,
jstring jprogramId) {
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
jstring jpath,
jstring jprogramId) {
const auto path = GetJString(env, jpath);
const auto vFile =
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
@ -843,20 +793,40 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
auto addons = pm.GetPatchVersionNames(update_raw);
auto jemptyString = ToJString(env, "");
auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
jemptyString, jemptyString);
jobjectArray jaddonsArray =
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair);
auto patches = pm.GetPatches(update_raw);
jobjectArray jpatchArray =
env->NewObjectArray(patches.size(), IDCache::GetPatchClass(), nullptr);
int i = 0;
for (const auto& addon : addons) {
jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
ToJString(env, addon.first), ToJString(env, addon.second));
env->SetObjectArrayElement(jaddonsArray, i, jaddon);
for (const auto& patch : patches) {
jobject jpatch = env->NewObject(
IDCache::GetPatchClass(), IDCache::GetPatchConstructor(), patch.enabled,
ToJString(env, patch.name), ToJString(env, patch.version),
static_cast<jint>(patch.type), ToJString(env, std::to_string(patch.program_id)),
ToJString(env, std::to_string(patch.title_id)));
env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i;
}
return jaddonsArray;
return jpatchArray;
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveAllDLC(&EmulationSession::GetInstance().System(), program_id);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
jstring jname) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id, GetJString(env, jname));
}
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
@ -878,7 +848,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
FileSys::Mode::Read);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
{}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
program_id, user_id->AsU128(), 0);
return ToJString(env, user_save_data_path);
}

View File

@ -7,6 +7,7 @@
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/perf_stats.h"
#include "frontend_common/content_manager.h"
#include "jni/applets/software_keyboard.h"
#include "jni/emu_window/emu_window.h"
#include "video_core/rasterizer_interface.h"
@ -29,7 +30,6 @@ public:
void SetNativeWindow(ANativeWindow* native_window);
void SurfaceChanged();
int InstallFileToNand(std::string filename, std::string file_extension);
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name,
const std::string& file_redirect_dir);

View File

@ -205,7 +205,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEn
jstring jkey) {
auto setting = getSetting<std::string>(env, jkey);
if (setting != nullptr) {
return setting->RuntimeModfiable();
return setting->RuntimeModifiable();
}
return true;
}

View File

@ -2,16 +2,16 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewFilledStyle"
style="?attr/materialCardViewElevatedStyle"
android:id="@+id/option_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginHorizontal="12dp"
android:background="?attr/selectableItemBackground"
android:backgroundTint="?attr/colorSurfaceVariant"
android:clickable="true"
android:focusable="true">
android:focusable="true"
app:cardElevation="4dp">
<LinearLayout
android:id="@+id/option_layout"

View File

@ -1,8 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
app:trackCornerRadius="4dp" />
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/message"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="6dp"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:visibility="gone" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
app:trackCornerRadius="4dp" />
</LinearLayout>

View File

@ -14,12 +14,11 @@
android:id="@+id/text_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/addon_switch"
app:layout_constraintEnd_toStartOf="@+id/addon_switch"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toStartOf="@+id/addon_checkbox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/addon_switch">
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
@ -42,16 +41,29 @@
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/addon_switch"
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/addon_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:gravity="center"
android:nextFocusLeft="@id/addon_container"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="8dp"
app:layout_constraintTop_toTopOf="@+id/text_container"
app:layout_constraintBottom_toBottomOf="@+id/text_container"
app:layout_constraintEnd_toStartOf="@+id/button_delete" />
<Button
android:id="@+id/button_delete"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/delete"
android:tooltipText="@string/delete"
app:icon="@drawable/ic_delete"
app:iconTint="?attr/colorControlNormal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/text_container"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="@+id/addon_checkbox"
app:layout_constraintBottom_toBottomOf="@+id/addon_checkbox" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -69,7 +69,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
android:text="@string/clear"
android:text="@string/use_global_setting"
tools:visibility="visible" />
</LinearLayout>

View File

@ -63,7 +63,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/clear"
android:text="@string/use_global_setting"
android:visibility="gone"
tools:visibility="visible" />

View File

@ -1,11 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_driver_clear"
android:icon="@drawable/ic_clear"
android:title="@string/clear"
app:showAsAction="always" />
android:id="@+id/menu_driver_use_global"
android:title="@string/use_global_setting" />
</menu>

View File

@ -29,7 +29,7 @@
<item>@string/language_dutch</item>
<item>@string/language_english</item>
<item>@string/language_french</item>
<item>@string/langauge_german</item>
<item>@string/language_german</item>
<item>@string/language_italian</item>
<item>@string/language_japanese</item>
<item>@string/language_korean</item>
@ -228,10 +228,10 @@
<item>R</item>
<item>ZL</item>
<item>ZR</item>
<item>@string/gamepad_left_stick</item>
<item>@string/gamepad_right_stick</item>
<item>L3</item>
<item>R3</item>
<item>@string/gamepad_left_stick</item>
<item>@string/gamepad_right_stick</item>
<item>@string/gamepad_d_pad</item>
</string-array>

View File

@ -286,6 +286,8 @@
<string name="custom">Custom</string>
<string name="notice">Notice</string>
<string name="import_complete">Import complete</string>
<string name="more_options">More options</string>
<string name="use_global_setting">Use global setting</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
@ -348,6 +350,8 @@
<string name="verifying_content">Verifying content…</string>
<string name="content_install_notice">Content install notice</string>
<string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string>
<string name="confirm_uninstall">Confirm uninstall</string>
<string name="confirm_uninstall_description">Are you sure you want to uninstall this addon?</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is encrypted</string>
@ -410,7 +414,7 @@
<string name="language_japanese" translatable="false">日本語</string>
<string name="language_english" translatable="false">English</string>
<string name="language_french" translatable="false">Français</string>
<string name="langauge_german" translatable="false">Deutsch</string>
<string name="language_german" translatable="false">Deutsch</string>
<string name="language_italian" translatable="false">Italiano</string>
<string name="language_spanish" translatable="false">Español</string>
<string name="language_chinese" translatable="false">简体中文</string>

View File

@ -11,7 +11,7 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system);
opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) {
LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
LOG_ERROR(Service_Audio, "OpusDecoder failed to initialize.");
return;
}
}

View File

@ -8,8 +8,11 @@
#include "audio_core/sink/sink_stream.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/guest_memory.h"
#include "core/memory.h"
#include "core/hle/kernel/k_process.h"
namespace AudioCore {
using namespace std::literals;
@ -25,7 +28,7 @@ DeviceSession::~DeviceSession() {
}
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
u16 channel_count_, size_t session_id_, u32 handle_,
u16 channel_count_, size_t session_id_, Kernel::KProcess* handle_,
u64 applet_resource_user_id_, Sink::StreamType type_) {
if (stream) {
Finalize();
@ -36,6 +39,7 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for
channel_count = channel_count_;
session_id = session_id_;
handle = handle_;
handle->Open();
applet_resource_user_id = applet_resource_user_id_;
if (type == Sink::StreamType::In) {
@ -54,6 +58,11 @@ void DeviceSession::Finalize() {
sink->CloseStream(stream);
stream = nullptr;
}
if (handle) {
handle->Close();
handle = nullptr;
}
}
void DeviceSession::Start() {
@ -91,7 +100,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
stream->AppendBuffer(new_buffer, tmp_samples);
} else {
Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
system.ApplicationMemory(), buffer.samples, buffer.size / sizeof(s16));
handle->GetMemory(), buffer.samples, buffer.size / sizeof(s16));
stream->AppendBuffer(new_buffer, samples);
}
}
@ -100,7 +109,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) {
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
system.ApplicationMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
handle->GetMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
}
}

View File

@ -20,6 +20,10 @@ struct EventType;
} // namespace Timing
} // namespace Core
namespace Kernel {
class KProcess;
} // namespace Kernel
namespace AudioCore {
namespace Sink {
@ -44,13 +48,13 @@ public:
* @param sample_format - Sample format for this device's output.
* @param channel_count - Number of channels for this device (2 or 6).
* @param session_id - This session's id.
* @param handle - Handle for this device session (unused).
* @param handle - Process handle for this device session.
* @param applet_resource_user_id - Applet resource user id for this device session (unused).
* @param type - Type of this stream (Render, In, Out).
* @return Result code for this call.
*/
Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
size_t session_id, u32 handle, u64 applet_resource_user_id,
size_t session_id, Kernel::KProcess* handle, u64 applet_resource_user_id,
Sink::StreamType type);
/**
@ -137,8 +141,8 @@ private:
u16 channel_count{};
/// Session id of this device session
size_t session_id{};
/// Handle of this device session
u32 handle{};
/// Process handle of device memory owner
Kernel::KProcess* handle{};
/// Applet resource user id of this device session
u64 applet_resource_user_id{};
/// Total number of samples played by this device session

View File

@ -57,7 +57,7 @@ Result System::IsConfigValid(const std::string_view device_name,
}
Result System::Initialize(std::string device_name, const AudioInParameter& in_params,
const u32 handle_, const u64 applet_resource_user_id_) {
Kernel::KProcess* handle_, const u64 applet_resource_user_id_) {
auto result{IsConfigValid(device_name, in_params)};
if (result.IsError()) {
return result;

View File

@ -19,7 +19,8 @@ class System;
namespace Kernel {
class KEvent;
}
class KProcess;
} // namespace Kernel
namespace AudioCore::AudioIn {
@ -93,12 +94,12 @@ public:
*
* @param device_name - The name of the requested input device.
* @param in_params - Input parameters, see AudioInParameter.
* @param handle - Unused.
* @param handle - Process handle.
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle,
u64 applet_resource_user_id);
Result Initialize(std::string device_name, const AudioInParameter& in_params,
Kernel::KProcess* handle, u64 applet_resource_user_id);
/**
* Start this system.
@ -244,8 +245,8 @@ public:
private:
/// Core system
Core::System& system;
/// (Unused)
u32 handle{};
/// Process handle
Kernel::KProcess* handle{};
/// (Unused)
u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready

View File

@ -48,8 +48,8 @@ Result System::IsConfigValid(std::string_view device_name,
return Service::Audio::ResultInvalidChannelCount;
}
Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_,
u64 applet_resource_user_id_) {
Result System::Initialize(std::string device_name, const AudioOutParameter& in_params,
Kernel::KProcess* handle_, u64 applet_resource_user_id_) {
auto result = IsConfigValid(device_name, in_params);
if (result.IsError()) {
return result;

View File

@ -19,7 +19,8 @@ class System;
namespace Kernel {
class KEvent;
}
class KProcess;
} // namespace Kernel
namespace AudioCore::AudioOut {
@ -84,12 +85,12 @@ public:
*
* @param device_name - The name of the requested output device.
* @param in_params - Input parameters, see AudioOutParameter.
* @param handle - Unused.
* @param handle - Process handle.
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle,
u64 applet_resource_user_id);
Result Initialize(std::string device_name, const AudioOutParameter& in_params,
Kernel::KProcess* handle, u64 applet_resource_user_id);
/**
* Start this system.
@ -228,8 +229,8 @@ public:
private:
/// Core system
Core::System& system;
/// (Unused)
u32 handle{};
/// Process handle
Kernel::KProcess* handle{};
/// (Unused)
u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready

View File

@ -41,7 +41,7 @@ void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
const VoiceState& voice_state, const s8 channel) {
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
auto destination{splitter_context.GetDestinationData(voice_info.splitter_id, 0)};
u32 dest_id{0};
while (destination != nullptr) {
if (destination->IsConfigured()) {
@ -55,7 +55,7 @@ void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
}
}
dest_id++;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
destination = splitter_context.GetDestinationData(voice_info.splitter_id, dest_id);
}
}
} else {
@ -234,7 +234,7 @@ void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto i{channel};
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
auto destination{splitter_context.GetDestinationData(voice_info.splitter_id, i)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
const auto mix_id{destination->GetMixId()};
@ -249,7 +249,7 @@ void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
}
}
i += voice_info.channel_count;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
destination = splitter_context.GetDestinationData(voice_info.splitter_id, i);
}
}
} else {
@ -591,7 +591,7 @@ void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
if (mix_info.dst_splitter_id != UnusedSplitterId) {
s16 dest_id{0};
auto destination{
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
splitter_context.GetDestinationData(mix_info.dst_splitter_id, dest_id)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
auto splitter_mix_id{destination->GetMixId()};
@ -612,7 +612,7 @@ void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
}
dest_id++;
destination =
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
splitter_context.GetDestinationData(mix_info.dst_splitter_id, dest_id);
}
}
} else {

View File

@ -9,6 +9,7 @@
#include "common/fixed_point.h"
#include "common/logging/log.h"
#include "common/scratch_buffer.h"
#include "core/guest_memory.h"
#include "core/memory.h"
namespace AudioCore::Renderer {

View File

@ -93,7 +93,7 @@ bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_pa
for (u32 i = 0; i < destination_count; i++) {
auto destination{
splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
splitter_context.GetDestinationData(in_params.dest_splitter_id, i)};
if (destination) {
const auto destination_id{destination->GetMixId()};

View File

@ -9,7 +9,7 @@
namespace AudioCore::Renderer {
SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
SplitterDestinationData* SplitterContext::GetDestinationData(const s32 splitter_id,
const s32 destination_id) {
return splitter_infos[splitter_id].GetData(destination_id);
}

View File

@ -42,7 +42,7 @@ public:
* @param destination_id - Destination index within the splitter.
* @return Pointer to the found destination. May be nullptr.
*/
SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
SplitterDestinationData* GetDestinationData(s32 splitter_id, s32 destination_id);
/**
* Get a splitter from the given index.

View File

@ -29,28 +29,32 @@ NativeClock::NativeClock() {
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
}
void NativeClock::Reset() {
start_ticks = GetUptime();
}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)};
}
u64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime() - start_ticks, guest_cntfrq_factor);
}
u64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime() - start_ticks, gputick_cntfrq_factor);
}
u64 NativeClock::GetHostTicksNow() const {
u64 cntvct_el0 = 0;
s64 NativeClock::GetUptime() const {
s64 cntvct_el0 = 0;
asm volatile("dsb ish\n\t"
"mrs %[cntvct_el0], cntvct_el0\n\t"
"dsb ish\n\t"
@ -58,15 +62,11 @@ u64 NativeClock::GetHostTicksNow() const {
return cntvct_el0;
}
u64 NativeClock::GetHostTicksElapsed() const {
return GetHostTicksNow();
}
bool NativeClock::IsNative() const {
return true;
}
u64 NativeClock::GetHostCNTFRQ() {
s64 NativeClock::GetHostCNTFRQ() {
u64 cntfrq_el0 = 0;
std::string_view board{""};
#ifdef ANDROID

View File

@ -11,23 +11,23 @@ class NativeClock final : public WallClock {
public:
explicit NativeClock();
void Reset() override;
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
u64 GetCNTPCT() const override;
s64 GetCNTPCT() const override;
u64 GetGPUTick() const override;
s64 GetGPUTick() const override;
u64 GetHostTicksNow() const override;
u64 GetHostTicksElapsed() const override;
s64 GetUptime() const override;
bool IsNative() const override;
static u64 GetHostCNTFRQ();
static s64 GetHostCNTFRQ();
public:
using FactorType = unsigned __int128;
@ -42,6 +42,7 @@ private:
FactorType ms_cntfrq_factor;
FactorType guest_cntfrq_factor;
FactorType gputick_cntfrq_factor;
s64 start_ticks;
};
} // namespace Common::Arm64

View File

@ -45,6 +45,7 @@ using f32 = float; ///< 32-bit floating point
using f64 = double; ///< 64-bit floating point
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
using DAddr = u64; ///< Represents a pointer in the device specific virtual address space.
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.

View File

@ -37,7 +37,7 @@ void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
template <typename FileStream, typename Path>
void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) {
if constexpr (IsChar<typename Path::value_type>) {
file_stream.open(ToU8String(path), open_mode);
file_stream.open(std::filesystem::path{ToU8String(path)}, open_mode);
} else {
file_stream.open(std::filesystem::path{path}, open_mode);
}

View File

@ -18,4 +18,4 @@ struct MemoryInfo {
*/
[[nodiscard]] const MemoryInfo& GetMemInfo();
} // namespace Common
} // namespace Common

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/page_table.h"
#include "common/scope_exit.h"
namespace Common {
@ -11,29 +12,10 @@ PageTable::~PageTable() noexcept = default;
bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context,
Common::ProcessAddress address) const {
// Setup invalid defaults.
out_entry->phys_addr = 0;
out_entry->block_size = page_size;
out_context->next_page = 0;
out_context->next_offset = GetInteger(address);
out_context->next_page = address / page_size;
// Validate that we can read the actual entry.
const auto page = address / page_size;
if (page >= backing_addr.size()) {
return false;
}
// Validate that the entry is mapped.
const auto phys_addr = backing_addr[page];
if (phys_addr == 0) {
return false;
}
// Populate the results.
out_entry->phys_addr = phys_addr + GetInteger(address);
out_context->next_page = page + 1;
out_context->next_offset = GetInteger(address) + page_size;
return true;
return this->ContinueTraversal(out_entry, out_context);
}
bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
@ -41,6 +23,12 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
out_entry->phys_addr = 0;
out_entry->block_size = page_size;
// Regardless of whether the page was mapped, advance on exit.
SCOPE_EXIT({
context->next_page += 1;
context->next_offset += page_size;
});
// Validate that we can read the actual entry.
const auto page = context->next_page;
if (page >= backing_addr.size()) {
@ -55,8 +43,6 @@ bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* c
// Populate the results.
out_entry->phys_addr = phys_addr + context->next_offset;
context->next_page = page + 1;
context->next_offset += page_size;
return true;
}

View File

@ -419,7 +419,9 @@ struct Values {
linkage, false, "custom_rtc_enabled", Category::System, Specialization::Paired, true, true};
SwitchableSetting<s64> custom_rtc{
linkage, 0, "custom_rtc", Category::System, Specialization::Time,
true, true, &custom_rtc_enabled};
false, true, &custom_rtc_enabled};
SwitchableSetting<s64, false> custom_rtc_offset{
linkage, 0, "custom_rtc_offset", Category::System, Specialization::Countable, true, true};
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
s64 custom_rtc_differential;
SwitchableSetting<bool> rng_seed_enabled{

View File

@ -35,7 +35,7 @@ bool BasicSetting::Save() const {
return save;
}
bool BasicSetting::RuntimeModfiable() const {
bool BasicSetting::RuntimeModifiable() const {
return runtime_modifiable;
}

View File

@ -186,7 +186,7 @@ public:
/**
* @returns true if the current setting can be changed while the guest is running.
*/
[[nodiscard]] bool RuntimeModfiable() const;
[[nodiscard]] bool RuntimeModifiable() const;
/**
* @returns A unique number corresponding to the setting.

View File

@ -12,9 +12,8 @@
namespace Common {
struct UUID {
std::array<u8, 0x10> uuid{};
std::array<u8, 0x10> uuid;
/// Constructs an invalid UUID.
constexpr UUID() = default;
/// Constructs a UUID from a reference to a 128 bit array.
@ -34,14 +33,6 @@ struct UUID {
*/
explicit UUID(std::string_view uuid_string);
~UUID() = default;
constexpr UUID(const UUID&) noexcept = default;
constexpr UUID(UUID&&) noexcept = default;
constexpr UUID& operator=(const UUID&) noexcept = default;
constexpr UUID& operator=(UUID&&) noexcept = default;
/**
* Returns whether the stored UUID is valid or not.
*
@ -121,6 +112,7 @@ struct UUID {
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
};
static_assert(sizeof(UUID) == 0x10, "UUID has incorrect size.");
static_assert(std::is_trivial_v<UUID>);
/// An invalid UUID. This UUID has all its bytes set to 0.
constexpr UUID InvalidUUID = {};

View File

@ -18,34 +18,39 @@ namespace Common {
class StandardWallClock final : public WallClock {
public:
explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
explicit StandardWallClock() {}
void Reset() override {
start_time = std::chrono::system_clock::now();
}
std::chrono::nanoseconds GetTimeNS() const override {
return SteadyClock::Now() - start_time;
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::microseconds GetTimeUS() const override {
return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
std::chrono::milliseconds GetTimeMS() const override {
return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
}
u64 GetCNTPCT() const override {
return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
s64 GetCNTPCT() const override {
return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
}
u64 GetGPUTick() const override {
return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
s64 GetGPUTick() const override {
return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
}
u64 GetHostTicksNow() const override {
return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
}
u64 GetHostTicksElapsed() const override {
return static_cast<u64>(GetTimeNS().count());
s64 GetUptime() const override {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now() - start_time)
.count();
}
bool IsNative() const override {
@ -53,7 +58,7 @@ public:
}
private:
SteadyClock::time_point start_time;
std::chrono::system_clock::time_point start_time{};
};
std::unique_ptr<WallClock> CreateOptimalClock() {

View File

@ -19,6 +19,8 @@ public:
virtual ~WallClock() = default;
virtual void Reset() = 0;
/// @returns The time in nanoseconds since the construction of this clock.
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
@ -29,16 +31,13 @@ public:
virtual std::chrono::milliseconds GetTimeMS() const = 0;
/// @returns The guest CNTPCT ticks since the construction of this clock.
virtual u64 GetCNTPCT() const = 0;
virtual s64 GetCNTPCT() const = 0;
/// @returns The guest GPU ticks since the construction of this clock.
virtual u64 GetGPUTick() const = 0;
virtual s64 GetGPUTick() const = 0;
/// @returns The raw host timer ticks since an indeterminate epoch.
virtual u64 GetHostTicksNow() const = 0;
/// @returns The raw host timer ticks since the construction of this clock.
virtual u64 GetHostTicksElapsed() const = 0;
virtual s64 GetUptime() const = 0;
/// @returns Whether the clock directly uses the host's hardware clock.
virtual bool IsNative() const = 0;

View File

@ -8,39 +8,39 @@
namespace Common::X64 {
NativeClock::NativeClock(u64 rdtsc_frequency_)
: start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
: rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den,
rdtsc_frequency)},
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
void NativeClock::Reset() {
start_ticks = FencedRDTSC();
}
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)};
}
std::chrono::microseconds NativeClock::GetTimeUS() const {
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)};
}
std::chrono::milliseconds NativeClock::GetTimeMS() const {
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)};
}
u64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
s64 NativeClock::GetCNTPCT() const {
return MultiplyHigh(GetUptime() - start_ticks, cntpct_rdtsc_factor);
}
u64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
s64 NativeClock::GetGPUTick() const {
return MultiplyHigh(GetUptime() - start_ticks, gputick_rdtsc_factor);
}
u64 NativeClock::GetHostTicksNow() const {
return FencedRDTSC();
}
u64 NativeClock::GetHostTicksElapsed() const {
return FencedRDTSC() - start_ticks;
s64 NativeClock::GetUptime() const {
return static_cast<s64>(FencedRDTSC());
}
bool NativeClock::IsNative() const {

View File

@ -11,19 +11,19 @@ class NativeClock final : public WallClock {
public:
explicit NativeClock(u64 rdtsc_frequency_);
void Reset() override;
std::chrono::nanoseconds GetTimeNS() const override;
std::chrono::microseconds GetTimeUS() const override;
std::chrono::milliseconds GetTimeMS() const override;
u64 GetCNTPCT() const override;
s64 GetCNTPCT() const override;
u64 GetGPUTick() const override;
s64 GetGPUTick() const override;
u64 GetHostTicksNow() const override;
u64 GetHostTicksElapsed() const override;
s64 GetUptime() const override;
bool IsNative() const override;

View File

@ -37,6 +37,8 @@ add_library(core STATIC
debugger/gdbstub_arch.h
debugger/gdbstub.cpp
debugger/gdbstub.h
device_memory_manager.h
device_memory_manager.inc
device_memory.cpp
device_memory.h
file_sys/fssystem/fs_i_storage.h
@ -490,6 +492,10 @@ add_library(core STATIC
hle/service/filesystem/fsp_pr.h
hle/service/filesystem/fsp_srv.cpp
hle/service/filesystem/fsp_srv.h
hle/service/filesystem/romfs_controller.cpp
hle/service/filesystem/romfs_controller.h
hle/service/filesystem/save_data_controller.cpp
hle/service/filesystem/save_data_controller.h
hle/service/fgm/fgm.cpp
hle/service/fgm/fgm.h
hle/service/friend/friend.cpp
@ -509,6 +515,24 @@ add_library(core STATIC
hle/service/glue/glue_manager.h
hle/service/glue/notif.cpp
hle/service/glue/notif.h
hle/service/glue/time/alarm_worker.cpp
hle/service/glue/time/alarm_worker.h
hle/service/glue/time/file_timestamp_worker.cpp
hle/service/glue/time/file_timestamp_worker.h
hle/service/glue/time/manager.cpp
hle/service/glue/time/manager.h
hle/service/glue/time/pm_state_change_handler.cpp
hle/service/glue/time/pm_state_change_handler.h
hle/service/glue/time/standard_steady_clock_resource.cpp
hle/service/glue/time/standard_steady_clock_resource.h
hle/service/glue/time/static.cpp
hle/service/glue/time/static.h
hle/service/glue/time/time_zone.cpp
hle/service/glue/time/time_zone.h
hle/service/glue/time/time_zone_binary.cpp
hle/service/glue/time/time_zone_binary.h
hle/service/glue/time/worker.cpp
hle/service/glue/time/worker.h
hle/service/grc/grc.cpp
hle/service/grc/grc.h
hle/service/hid/hid.cpp
@ -605,6 +629,8 @@ add_library(core STATIC
hle/service/ns/pdm_qry.h
hle/service/nvdrv/core/container.cpp
hle/service/nvdrv/core/container.h
hle/service/nvdrv/core/heap_mapper.cpp
hle/service/nvdrv/core/heap_mapper.h
hle/service/nvdrv/core/nvmap.cpp
hle/service/nvdrv/core/nvmap.h
hle/service/nvdrv/core/syncpoint_manager.cpp
@ -685,6 +711,46 @@ add_library(core STATIC
hle/service/prepo/prepo.h
hle/service/psc/psc.cpp
hle/service/psc/psc.h
hle/service/psc/time/alarms.cpp
hle/service/psc/time/alarms.h
hle/service/psc/time/clocks/context_writers.cpp
hle/service/psc/time/clocks/context_writers.h
hle/service/psc/time/clocks/ephemeral_network_system_clock_core.h
hle/service/psc/time/clocks/standard_local_system_clock_core.cpp
hle/service/psc/time/clocks/standard_local_system_clock_core.h
hle/service/psc/time/clocks/standard_network_system_clock_core.cpp
hle/service/psc/time/clocks/standard_network_system_clock_core.h
hle/service/psc/time/clocks/standard_steady_clock_core.cpp
hle/service/psc/time/clocks/standard_steady_clock_core.h
hle/service/psc/time/clocks/standard_user_system_clock_core.cpp
hle/service/psc/time/clocks/standard_user_system_clock_core.h
hle/service/psc/time/clocks/steady_clock_core.h
hle/service/psc/time/clocks/system_clock_core.cpp
hle/service/psc/time/clocks/system_clock_core.h
hle/service/psc/time/clocks/tick_based_steady_clock_core.cpp
hle/service/psc/time/clocks/tick_based_steady_clock_core.h
hle/service/psc/time/common.cpp
hle/service/psc/time/common.h
hle/service/psc/time/errors.h
hle/service/psc/time/shared_memory.cpp
hle/service/psc/time/shared_memory.h
hle/service/psc/time/static.cpp
hle/service/psc/time/static.h
hle/service/psc/time/manager.h
hle/service/psc/time/power_state_service.cpp
hle/service/psc/time/power_state_service.h
hle/service/psc/time/service_manager.cpp
hle/service/psc/time/service_manager.h
hle/service/psc/time/steady_clock.cpp
hle/service/psc/time/steady_clock.h
hle/service/psc/time/system_clock.cpp
hle/service/psc/time/system_clock.h
hle/service/psc/time/time_zone.cpp
hle/service/psc/time/time_zone.h
hle/service/psc/time/time_zone_service.cpp
hle/service/psc/time/time_zone_service.h
hle/service/psc/time/power_state_request_manager.cpp
hle/service/psc/time/power_state_request_manager.h
hle/service/ptm/psm.cpp
hle/service/ptm/psm.h
hle/service/ptm/ptm.cpp
@ -708,22 +774,23 @@ add_library(core STATIC
hle/service/server_manager.h
hle/service/service.cpp
hle/service/service.h
hle/service/set/appln_settings.cpp
hle/service/set/appln_settings.h
hle/service/set/device_settings.cpp
hle/service/set/device_settings.h
hle/service/set/setting_formats/appln_settings.cpp
hle/service/set/setting_formats/appln_settings.h
hle/service/set/setting_formats/device_settings.cpp
hle/service/set/setting_formats/device_settings.h
hle/service/set/setting_formats/system_settings.cpp
hle/service/set/setting_formats/system_settings.h
hle/service/set/setting_formats/private_settings.cpp
hle/service/set/setting_formats/private_settings.h
hle/service/set/factory_settings_server.cpp
hle/service/set/factory_settings_server.h
hle/service/set/firmware_debug_settings_server.cpp
hle/service/set/firmware_debug_settings_server.h
hle/service/set/private_settings.cpp
hle/service/set/private_settings.h
hle/service/set/settings.cpp
hle/service/set/settings.h
hle/service/set/settings_server.cpp
hle/service/set/settings_server.h
hle/service/set/system_settings.cpp
hle/service/set/system_settings.h
hle/service/set/settings_types.h
hle/service/set/system_settings_server.cpp
hle/service/set/system_settings_server.h
hle/service/sm/sm.cpp
@ -751,40 +818,6 @@ add_library(core STATIC
hle/service/ssl/ssl.cpp
hle/service/ssl/ssl.h
hle/service/ssl/ssl_backend.h
hle/service/time/clock_types.h
hle/service/time/ephemeral_network_system_clock_context_writer.h
hle/service/time/ephemeral_network_system_clock_core.h
hle/service/time/errors.h
hle/service/time/local_system_clock_context_writer.h
hle/service/time/network_system_clock_context_writer.h
hle/service/time/standard_local_system_clock_core.h
hle/service/time/standard_network_system_clock_core.h
hle/service/time/standard_steady_clock_core.cpp
hle/service/time/standard_steady_clock_core.h
hle/service/time/standard_user_system_clock_core.cpp
hle/service/time/standard_user_system_clock_core.h
hle/service/time/steady_clock_core.h
hle/service/time/system_clock_context_update_callback.cpp
hle/service/time/system_clock_context_update_callback.h
hle/service/time/system_clock_core.cpp
hle/service/time/system_clock_core.h
hle/service/time/tick_based_steady_clock_core.cpp
hle/service/time/tick_based_steady_clock_core.h
hle/service/time/time.cpp
hle/service/time/time.h
hle/service/time/time_interface.cpp
hle/service/time/time_interface.h
hle/service/time/time_manager.cpp
hle/service/time/time_manager.h
hle/service/time/time_sharedmemory.cpp
hle/service/time/time_sharedmemory.h
hle/service/time/time_zone_content_manager.cpp
hle/service/time/time_zone_content_manager.h
hle/service/time/time_zone_manager.cpp
hle/service/time/time_zone_manager.h
hle/service/time/time_zone_service.cpp
hle/service/time/time_zone_service.h
hle/service/time/time_zone_types.h
hle/service/usb/usb.cpp
hle/service/usb/usb.h
hle/service/vi/display/vi_display.cpp
@ -865,7 +898,7 @@ endif()
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
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})

View File

@ -22,14 +22,10 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
constexpr size_t MaxRelativeBranch = 128_MiB;
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
Patcher::Patcher() : c(m_patch_instructions) {}
Patcher::~Patcher() = default;
void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// Branch to the first instruction of the module.
this->BranchToModule(0);
Patcher::Patcher() : c(m_patch_instructions) {
// The first word of the patch section is always a branch to the first instruction of the
// module.
c.dw(0);
// Write save context helper function.
c.l(m_save_context);
@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Write load context helper function.
c.l(m_load_context);
WriteLoadContext();
}
Patcher::~Patcher() = default;
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
const size_t image_size = program_image.size();
if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
return false;
}
// Add a new module patch to our list
modules.emplace_back();
curr_patch = &modules.back();
// The first word of the patch section is always a branch to the first instruction of the
// module.
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
// Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size);
@ -94,16 +109,17 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
}
if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
m_exclusives.push_back(i);
curr_patch->m_exclusives.push_back(i);
}
}
// Determine patching mode for the final relocation step
const size_t image_size = program_image.size();
total_program_size += image_size;
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
return true;
}
void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) {
@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) {
rc.B(rel.patch_offset - patch_size - rel.module_offset);
} else {
rc.B(image_size - rel.module_offset + rel.patch_offset);
rc.B(total_program_size - rel.module_offset + rel.patch_offset);
}
};
@ -129,7 +145,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) {
rc.B(patch_size - rel.patch_offset + rel.module_offset);
} else {
rc.B(rel.module_offset - image_size - rel.patch_offset);
rc.B(rel.module_offset - total_program_size - rel.patch_offset);
}
};
@ -137,7 +153,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_offset;
} else {
return GetInteger(load_base) + image_size + patch_offset;
return GetInteger(load_base) + total_program_size + patch_offset;
}
};
@ -150,39 +166,50 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
};
// We are now ready to relocate!
for (const Relocation& rel : m_branch_to_patch_relocations) {
auto& patch = modules[m_relocate_module_index++];
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
for (const Relocation& rel : m_branch_to_module_relocations) {
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel);
}
// Rewrite PC constants and record post trampolines
for (const Relocation& rel : m_write_module_pc_relocations) {
for (const Relocation& rel : patch.m_write_module_pc_relocations) {
oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset));
}
for (const Trampoline& rel : m_trampolines) {
for (const Trampoline& rel : patch.m_trampolines) {
out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
}
// Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
// Convert to ordered to preserve this assumption.
for (const ModuleTextAddress i : m_exclusives) {
for (const ModuleTextAddress i : patch.m_exclusives) {
auto exclusive = Exclusive{text_words[i]};
text_words[i] = exclusive.AsOrdered();
}
// Copy to program image
if (this->mode == PatchMode::PreText) {
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else {
program_image.resize(image_size + patch_size);
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
// Remove the patched module size from the total. This is done so total_program_size
// always represents the distance from the currently patched module to the patch section.
total_program_size -= image_size;
// Only copy to the program image of the last module
if (m_relocate_module_index == modules.size()) {
if (this->mode == PatchMode::PreText) {
ASSERT(image_size == total_program_size);
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else {
program_image.resize(image_size + patch_size);
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
}
return true;
}
return false;
}
size_t Patcher::GetSectionSize() const noexcept {
@ -322,7 +349,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
// Write the post-SVC trampoline address, which will jump back to the guest after restoring its
// state.
m_trampolines.push_back({c.offset(), module_dest});
curr_patch->m_trampolines.push_back({c.offset(), module_dest});
// Host called this location. Save the return address so we can
// unwind the stack properly when jumping back.

View File

@ -31,9 +31,9 @@ public:
explicit Patcher();
~Patcher();
void PatchText(const Kernel::PhysicalMemory& program_image,
bool PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code);
void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
@ -61,16 +61,16 @@ private:
private:
void BranchToPatch(uintptr_t module_dest) {
m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
}
void BranchToModule(uintptr_t module_dest) {
m_branch_to_module_relocations.push_back({c.offset(), module_dest});
curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
c.dw(0);
}
void WriteModulePc(uintptr_t module_dest) {
m_write_module_pc_relocations.push_back({c.offset(), module_dest});
curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
c.dx(0);
}
@ -84,15 +84,22 @@ private:
uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
};
struct ModulePatch {
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<ModuleTextAddress> m_exclusives{};
};
oaknut::VectorCodeGenerator c;
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<ModuleTextAddress> m_exclusives{};
oaknut::Label m_save_context{};
oaknut::Label m_load_context{};
PatchMode mode{PatchMode::None};
size_t total_program_size{};
size_t m_relocate_module_index{};
std::vector<ModulePatch> modules;
ModulePatch* curr_patch;
};
} // namespace Core::NCE

View File

@ -28,6 +28,7 @@
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h"
#include "core/gpu_dirty_memory_manager.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
@ -39,9 +40,14 @@
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/glue/time/static.h"
#include "core/hle/service/psc/time/static.h"
#include "core/hle/service/psc/time/steady_clock.h"
#include "core/hle/service/psc/time/system_clock.h"
#include "core/hle/service/psc/time/time_zone_service.h"
#include "core/hle/service/service.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/time/time_manager.h"
#include "core/internal_network/network.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@ -129,8 +135,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, hid_core{}, room_network{}, cpu_manager{system},
reporter{system}, applet_manager{system}, profile_manager{}, time_manager{system} {}
: kernel{system}, fs_controller{system}, hid_core{}, room_network{},
cpu_manager{system}, reporter{system}, applet_manager{system}, profile_manager{} {}
void Initialize(System& system) {
device_memory = std::make_unique<Core::DeviceMemory>();
@ -142,8 +148,6 @@ struct System::Impl {
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
RefreshTime();
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@ -181,14 +185,57 @@ struct System::Impl {
Initialize(system);
}
void RefreshTime() {
void RefreshTime(System& system) {
if (!system.IsPoweredOn()) {
return;
}
auto settings_service =
system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys",
true);
auto static_service_a =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:a", true);
auto static_service_s =
system.ServiceManager().GetService<Service::PSC::Time::StaticService>("time:s", true);
std::shared_ptr<Service::PSC::Time::SystemClock> user_clock;
static_service_a->GetStandardUserSystemClock(user_clock);
std::shared_ptr<Service::PSC::Time::SystemClock> local_clock;
static_service_a->GetStandardLocalSystemClock(local_clock);
std::shared_ptr<Service::PSC::Time::SystemClock> network_clock;
static_service_s->GetStandardNetworkSystemClock(network_clock);
std::shared_ptr<Service::Glue::Time::TimeZoneService> timezone_service;
static_service_a->GetTimeZoneService(timezone_service);
Service::PSC::Time::LocationName name{};
auto new_name = Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue());
std::memcpy(name.name.data(), new_name.data(), std::min(name.name.size(), new_name.size()));
timezone_service->SetDeviceLocation(name);
u64 time_offset = 0;
if (Settings::values.custom_rtc_enabled) {
time_offset = Settings::values.custom_rtc_offset.GetValue();
}
const auto posix_time = std::chrono::system_clock::now().time_since_epoch();
const auto current_time =
std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
Settings::values.custom_rtc_differential =
(Settings::values.custom_rtc_enabled ? Settings::values.custom_rtc.GetValue()
: current_time) -
current_time;
const u64 current_time =
+std::chrono::duration_cast<std::chrono::seconds>(posix_time).count();
const u64 new_time = current_time + time_offset;
Service::PSC::Time::SystemClockContext context{};
settings_service->SetUserSystemClockContext(context);
user_clock->SetCurrentTime(new_time);
local_clock->SetCurrentTime(new_time);
network_clock->GetSystemClockContext(context);
settings_service->SetNetworkSystemClockContext(context);
network_clock->SetCurrentTime(new_time);
}
void Run() {
@ -264,9 +311,6 @@ struct System::Impl {
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
services = std::make_unique<Service::Services>(service_manager, system);
// Initialize time manager, which must happen after kernel is created
time_manager.Initialize();
is_powered_on = true;
exit_locked = false;
exit_requested = false;
@ -413,9 +457,9 @@ struct System::Impl {
kernel.ShutdownCores();
services.reset();
service_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
telemetry_session.reset();
time_manager.Shutdown();
core_timing.ClearPendingEvents();
app_loader.reset();
audio_core.reset();
@ -531,7 +575,6 @@ struct System::Impl {
/// Service State
Service::Glue::ARPManager arp_manager;
Service::Account::ProfileManager profile_manager;
Service::Time::TimeManager time_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
@ -564,6 +607,9 @@ struct System::Impl {
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_managers;
std::deque<std::vector<u8>> user_channel;
};
@ -650,8 +696,14 @@ size_t System::GetCurrentHostThreadID() const {
return impl->kernel.GetCurrentHostThreadID();
}
void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
return this->ApplicationProcess()->GatherGPUDirtyMemory(callback);
std::span<GPUDirtyMemoryManager> System::GetGPUDirtyMemoryManager() {
return impl->gpu_dirty_memory_managers;
}
void System::GatherGPUDirtyMemory(std::function<void(PAddr, size_t)>& callback) {
for (auto& manager : impl->gpu_dirty_memory_managers) {
manager.Gather(callback);
}
}
PerfStatsResults System::GetAndResetPerfStats() {
@ -900,14 +952,6 @@ const Service::Account::ProfileManager& System::GetProfileManager() const {
return impl->profile_manager;
}
Service::Time::TimeManager& System::GetTimeManager() {
return impl->time_manager;
}
const Service::Time::TimeManager& System::GetTimeManager() const {
return impl->time_manager;
}
void System::SetExitLocked(bool locked) {
impl->exit_locked = locked;
}
@ -1019,13 +1063,9 @@ void System::Exit() {
}
void System::ApplySettings() {
impl->RefreshTime();
impl->RefreshTime(*this);
if (IsPoweredOn()) {
if (Settings::values.custom_rtc_enabled) {
const s64 posix_time{Settings::values.custom_rtc.GetValue()};
GetTimeManager().UpdateLocalSystemClockTime(posix_time);
}
Renderer().RefreshBaseSettings();
}
}

View File

@ -8,6 +8,7 @@
#include <functional>
#include <memory>
#include <mutex>
#include <span>
#include <string>
#include <vector>
@ -72,10 +73,6 @@ namespace SM {
class ServiceManager;
} // namespace SM
namespace Time {
class TimeManager;
} // namespace Time
} // namespace Service
namespace Tegra {
@ -116,6 +113,7 @@ class CpuManager;
class Debugger;
class DeviceMemory;
class ExclusiveMonitor;
class GPUDirtyMemoryManager;
class PerfStats;
class Reporter;
class SpeedLimiter;
@ -224,7 +222,9 @@ public:
/// Prepare the core emulation for a reschedule
void PrepareReschedule(u32 core_index);
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
std::span<GPUDirtyMemoryManager> GetGPUDirtyMemoryManager();
void GatherGPUDirtyMemory(std::function<void(PAddr, size_t)>& callback);
[[nodiscard]] size_t GetCurrentHostThreadID() const;
@ -377,9 +377,6 @@ public:
[[nodiscard]] Service::Account::ProfileManager& GetProfileManager();
[[nodiscard]] const Service::Account::ProfileManager& GetProfileManager() const;
[[nodiscard]] Service::Time::TimeManager& GetTimeManager();
[[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const;
[[nodiscard]] Core::Debugger& GetDebugger();
[[nodiscard]] const Core::Debugger& GetDebugger() const;

View File

@ -66,6 +66,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0;
shutting_down = false;
cpu_ticks = 0;
clock->Reset();
if (is_multicore) {
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
}
@ -157,7 +158,7 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
}
}
for (auto h : to_remove) {
for (auto& h : to_remove) {
event_queue.erase(h);
}

View File

@ -114,7 +114,7 @@ public:
}
Kernel::KThread* GetActiveThread() override {
return state->active_thread;
return state->active_thread.GetPointerUnsafe();
}
private:
@ -147,11 +147,14 @@ private:
std::scoped_lock lk{connection_lock};
// Find the process we are going to debug.
SetDebugProcess();
// Ensure everything is stopped.
PauseEmulation();
// Set up the new frontend.
frontend = std::make_unique<GDBStub>(*this, system);
frontend = std::make_unique<GDBStub>(*this, system, debug_process.GetPointerUnsafe());
// Set the new state. This will tear down any existing state.
state = ConnectionState{
@ -194,15 +197,20 @@ private:
UpdateActiveThread();
if (state->info.type == SignalType::Watchpoint) {
frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
frontend->Watchpoint(std::addressof(*state->active_thread),
*state->info.watchpoint);
} else {
frontend->Stopped(state->active_thread);
frontend->Stopped(std::addressof(*state->active_thread));
}
break;
case SignalType::ShuttingDown:
frontend->ShuttingDown();
// Release members.
state->active_thread.Reset(nullptr);
debug_process.Reset(nullptr);
// Wait for emulation to shut down gracefully now.
state->signal_pipe.close();
state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
@ -222,7 +230,7 @@ private:
stopped = true;
PauseEmulation();
UpdateActiveThread();
frontend->Stopped(state->active_thread);
frontend->Stopped(state->active_thread.GetPointerUnsafe());
break;
}
case DebuggerAction::Continue:
@ -232,7 +240,7 @@ private:
MarkResumed([&] {
state->active_thread->SetStepState(Kernel::StepState::StepPending);
state->active_thread->Resume(Kernel::SuspendType::Debug);
ResumeEmulation(state->active_thread);
ResumeEmulation(state->active_thread.GetPointerUnsafe());
});
break;
case DebuggerAction::StepThreadLocked: {
@ -255,6 +263,7 @@ private:
}
void PauseEmulation() {
Kernel::KScopedLightLock ll{debug_process->GetListLock()};
Kernel::KScopedSchedulerLock sl{system.Kernel()};
// Put all threads to sleep on next scheduler round.
@ -264,6 +273,9 @@ private:
}
void ResumeEmulation(Kernel::KThread* except = nullptr) {
Kernel::KScopedLightLock ll{debug_process->GetListLock()};
Kernel::KScopedSchedulerLock sl{system.Kernel()};
// Wake up all threads.
for (auto& thread : ThreadList()) {
if (std::addressof(thread) == except) {
@ -277,15 +289,16 @@ private:
template <typename Callback>
void MarkResumed(Callback&& cb) {
Kernel::KScopedSchedulerLock sl{system.Kernel()};
stopped = false;
cb();
}
void UpdateActiveThread() {
Kernel::KScopedLightLock ll{debug_process->GetListLock()};
auto& threads{ThreadList()};
for (auto& thread : threads) {
if (std::addressof(thread) == state->active_thread) {
if (std::addressof(thread) == state->active_thread.GetPointerUnsafe()) {
// Thread is still alive, no need to update.
return;
}
@ -293,12 +306,18 @@ private:
state->active_thread = std::addressof(threads.front());
}
private:
void SetDebugProcess() {
debug_process = std::move(system.Kernel().GetProcessList().back());
}
Kernel::KProcess::ThreadList& ThreadList() {
return system.ApplicationProcess()->GetThreadList();
return debug_process->GetThreadList();
}
private:
System& system;
Kernel::KScopedAutoObject<Kernel::KProcess> debug_process;
std::unique_ptr<DebuggerFrontend> frontend;
boost::asio::io_context io_context;
@ -310,7 +329,7 @@ private:
boost::process::async_pipe signal_pipe;
SignalInfo info;
Kernel::KThread* active_thread;
Kernel::KScopedAutoObject<Kernel::KThread> active_thread;
std::array<u8, 4096> client_data;
bool pipe_data;
};

View File

@ -108,9 +108,9 @@ static std::string EscapeXML(std::string_view data) {
return escaped;
}
GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
: DebuggerFrontend(backend_), system{system_} {
if (system.ApplicationProcess()->Is64Bit()) {
GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_, Kernel::KProcess* debug_process_)
: DebuggerFrontend(backend_), system{system_}, debug_process{debug_process_} {
if (GetProcess()->Is64Bit()) {
arch = std::make_unique<GDBStubA64>();
} else {
arch = std::make_unique<GDBStubA32>();
@ -276,7 +276,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
std::vector<u8> mem(size);
if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) {
if (GetMemory().ReadBlock(addr, mem.data(), size)) {
// Restore any bytes belonging to replaced instructions.
auto it = replaced_instructions.lower_bound(addr);
for (; it != replaced_instructions.end() && it->first < addr + size; it++) {
@ -310,8 +310,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const auto mem_substr{std::string_view(command).substr(mem_sep)};
const auto mem{Common::HexStringToVector(mem_substr, false)};
if (system.ApplicationMemory().WriteBlock(addr, mem.data(), size)) {
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, size);
if (GetMemory().WriteBlock(addr, mem.data(), size)) {
Core::InvalidateInstructionCacheRange(GetProcess(), addr, size);
SendReply(GDB_STUB_REPLY_OK);
} else {
SendReply(GDB_STUB_REPLY_ERR);
@ -353,7 +353,7 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
SendReply(GDB_STUB_REPLY_ERR);
return;
}
@ -362,22 +362,20 @@ void GDBStub::HandleBreakpointInsert(std::string_view command) {
switch (type) {
case BreakpointType::Software:
replaced_instructions[addr] = system.ApplicationMemory().Read32(addr);
system.ApplicationMemory().Write32(addr, arch->BreakpointInstruction());
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
replaced_instructions[addr] = GetMemory().Read32(addr);
GetMemory().Write32(addr, arch->BreakpointInstruction());
Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
success = true;
break;
case BreakpointType::WriteWatch:
success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
Kernel::DebugWatchpointType::Write);
success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
break;
case BreakpointType::ReadWatch:
success = system.ApplicationProcess()->InsertWatchpoint(addr, size,
Kernel::DebugWatchpointType::Read);
success = GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
break;
case BreakpointType::AccessWatch:
success = system.ApplicationProcess()->InsertWatchpoint(
addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
success =
GetProcess()->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
break;
case BreakpointType::Hardware:
default:
@ -400,7 +398,7 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
if (!system.ApplicationMemory().IsValidVirtualAddressRange(addr, size)) {
if (!GetMemory().IsValidVirtualAddressRange(addr, size)) {
SendReply(GDB_STUB_REPLY_ERR);
return;
}
@ -411,24 +409,22 @@ void GDBStub::HandleBreakpointRemove(std::string_view command) {
case BreakpointType::Software: {
const auto orig_insn{replaced_instructions.find(addr)};
if (orig_insn != replaced_instructions.end()) {
system.ApplicationMemory().Write32(addr, orig_insn->second);
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), addr, sizeof(u32));
GetMemory().Write32(addr, orig_insn->second);
Core::InvalidateInstructionCacheRange(GetProcess(), addr, sizeof(u32));
replaced_instructions.erase(addr);
success = true;
}
break;
}
case BreakpointType::WriteWatch:
success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
Kernel::DebugWatchpointType::Write);
success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Write);
break;
case BreakpointType::ReadWatch:
success = system.ApplicationProcess()->RemoveWatchpoint(addr, size,
Kernel::DebugWatchpointType::Read);
success = GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Read);
break;
case BreakpointType::AccessWatch:
success = system.ApplicationProcess()->RemoveWatchpoint(
addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
success =
GetProcess()->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite);
break;
case BreakpointType::Hardware:
default:
@ -466,10 +462,10 @@ void GDBStub::HandleQuery(std::string_view command) {
const auto target_xml{arch->GetTargetXML()};
SendReply(PaginateBuffer(target_xml, command.substr(30)));
} else if (command.starts_with("Offsets")) {
const auto main_offset = Core::FindMainModuleEntrypoint(system.ApplicationProcess());
const auto main_offset = Core::FindMainModuleEntrypoint(GetProcess());
SendReply(fmt::format("TextSeg={:x}", GetInteger(main_offset)));
} else if (command.starts_with("Xfer:libraries:read::")) {
auto modules = Core::FindModules(system.ApplicationProcess());
auto modules = Core::FindModules(GetProcess());
std::string buffer;
buffer += R"(<?xml version="1.0"?>)";
@ -483,7 +479,7 @@ void GDBStub::HandleQuery(std::string_view command) {
SendReply(PaginateBuffer(buffer, command.substr(21)));
} else if (command.starts_with("fThreadInfo")) {
// beginning of list
const auto& threads = system.ApplicationProcess()->GetThreadList();
const auto& threads = GetProcess()->GetThreadList();
std::vector<std::string> thread_ids;
for (const auto& thread : threads) {
thread_ids.push_back(fmt::format("{:x}", thread.GetThreadId()));
@ -497,7 +493,7 @@ void GDBStub::HandleQuery(std::string_view command) {
buffer += R"(<?xml version="1.0"?>)";
buffer += "<threads>";
const auto& threads = system.ApplicationProcess()->GetThreadList();
const auto& threads = GetProcess()->GetThreadList();
for (const auto& thread : threads) {
auto thread_name{Core::GetThreadName(&thread)};
if (!thread_name) {
@ -613,7 +609,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
std::string reply;
auto* process = system.ApplicationProcess();
auto* process = GetProcess();
auto& page_table = process->GetPageTable();
const char* commands = "Commands:\n"
@ -714,7 +710,7 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
}
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
auto& threads{system.ApplicationProcess()->GetThreadList()};
auto& threads{GetProcess()->GetThreadList()};
for (auto& thread : threads) {
if (thread.GetThreadId() == thread_id) {
return std::addressof(thread);
@ -783,4 +779,12 @@ void GDBStub::SendStatus(char status) {
backend.WriteToClient(buf);
}
Kernel::KProcess* GDBStub::GetProcess() {
return debug_process;
}
Core::Memory::Memory& GDBStub::GetMemory() {
return GetProcess()->GetMemory();
}
} // namespace Core

View File

@ -12,13 +12,22 @@
#include "core/debugger/debugger_interface.h"
#include "core/debugger/gdbstub_arch.h"
namespace Kernel {
class KProcess;
}
namespace Core::Memory {
class Memory;
}
namespace Core {
class System;
class GDBStub : public DebuggerFrontend {
public:
explicit GDBStub(DebuggerBackend& backend, Core::System& system);
explicit GDBStub(DebuggerBackend& backend, Core::System& system,
Kernel::KProcess* debug_process);
~GDBStub() override;
void Connected() override;
@ -42,8 +51,12 @@ private:
void SendReply(std::string_view data);
void SendStatus(char status);
Kernel::KProcess* GetProcess();
Core::Memory::Memory& GetMemory();
private:
Core::System& system;
Kernel::KProcess* debug_process;
std::unique_ptr<GDBStubArch> arch;
std::vector<char> current_command;
std::map<VAddr, u32> replaced_instructions;

View File

@ -31,6 +31,12 @@ public:
DramMemoryMap::Base;
}
template <typename T>
PAddr GetRawPhysicalAddr(const T* ptr) const {
return static_cast<PAddr>(reinterpret_cast<uintptr_t>(ptr) -
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer()));
}
template <typename T>
T* GetPointer(Common::PhysicalAddress addr) {
return reinterpret_cast<T*>(buffer.BackingBasePointer() +
@ -43,6 +49,16 @@ public:
(GetInteger(addr) - DramMemoryMap::Base));
}
template <typename T>
T* GetPointerFromRaw(PAddr addr) {
return reinterpret_cast<T*>(buffer.BackingBasePointer() + addr);
}
template <typename T>
const T* GetPointerFromRaw(PAddr addr) const {
return reinterpret_cast<T*>(buffer.BackingBasePointer() + addr);
}
Common::HostMemory buffer;
};

View File

@ -0,0 +1,211 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <atomic>
#include <deque>
#include <memory>
#include <mutex>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "common/virtual_buffer.h"
namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL;
constexpr size_t DEVICE_PAGESIZE = 1ULL << DEVICE_PAGEBITS;
constexpr size_t DEVICE_PAGEMASK = DEVICE_PAGESIZE - 1ULL;
class DeviceMemory;
namespace Memory {
class Memory;
}
template <typename DTraits>
struct DeviceMemoryManagerAllocator;
struct Asid {
size_t id;
};
template <typename Traits>
class DeviceMemoryManager {
using DeviceInterface = typename Traits::DeviceInterface;
using DeviceMethods = typename Traits::DeviceMethods;
public:
DeviceMemoryManager(const DeviceMemory& device_memory);
~DeviceMemoryManager();
void BindInterface(DeviceInterface* device_inter);
DAddr Allocate(size_t size);
void AllocateFixed(DAddr start, size_t size);
void Free(DAddr start, size_t size);
void Map(DAddr address, VAddr virtual_address, size_t size, Asid asid, bool track = false);
void Unmap(DAddr address, size_t size);
void TrackContinuityImpl(DAddr address, VAddr virtual_address, size_t size, Asid asid);
void TrackContinuity(DAddr address, VAddr virtual_address, size_t size, Asid asid) {
std::scoped_lock lk(mapping_guard);
TrackContinuityImpl(address, virtual_address, size, asid);
}
// Write / Read
template <typename T>
T* GetPointer(DAddr address);
template <typename T>
const T* GetPointer(DAddr address) const;
template <typename Func>
void ApplyOpOnPAddr(PAddr address, Common::ScratchBuffer<u32>& buffer, Func&& operation) {
DAddr subbits = static_cast<DAddr>(address & page_mask);
const u32 base = compressed_device_addr[(address >> page_bits)];
if ((base >> MULTI_FLAG_BITS) == 0) [[likely]] {
const DAddr d_address = (static_cast<DAddr>(base) << page_bits) + subbits;
operation(d_address);
return;
}
InnerGatherDeviceAddresses(buffer, address);
for (u32 value : buffer) {
operation((static_cast<DAddr>(value) << page_bits) + subbits);
}
}
template <typename Func>
void ApplyOpOnPointer(const u8* p, Common::ScratchBuffer<u32>& buffer, Func&& operation) {
PAddr address = GetRawPhysicalAddr<u8>(p);
ApplyOpOnPAddr(address, buffer, operation);
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = static_cast<PAddr>(address & page_mask);
auto paddr = compressed_physical_ptr[(address >> page_bits)];
if (paddr == 0) {
return 0;
}
return (static_cast<PAddr>(paddr - 1) << page_bits) + subbits;
}
template <typename T>
void Write(DAddr address, T value);
template <typename T>
T Read(DAddr address) const;
u8* GetSpan(const DAddr src_addr, const std::size_t size);
const u8* GetSpan(const DAddr src_addr, const std::size_t size) const;
void ReadBlock(DAddr address, void* dest_pointer, size_t size);
void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size);
void WriteBlock(DAddr address, const void* src_pointer, size_t size);
void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size);
Asid RegisterProcess(Memory::Memory* memory);
void UnregisterProcess(Asid id);
void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta);
static constexpr size_t AS_BITS = Traits::device_virtual_bits;
private:
static constexpr size_t device_virtual_bits = Traits::device_virtual_bits;
static constexpr size_t device_as_size = 1ULL << device_virtual_bits;
static constexpr size_t physical_min_bits = 32;
static constexpr size_t physical_max_bits = 33;
static constexpr size_t page_bits = 12;
static constexpr size_t page_size = 1ULL << page_bits;
static constexpr size_t page_mask = page_size - 1ULL;
static constexpr u32 physical_address_base = 1U << page_bits;
static constexpr u32 MULTI_FLAG_BITS = 31;
static constexpr u32 MULTI_FLAG = 1U << MULTI_FLAG_BITS;
static constexpr u32 MULTI_MASK = ~MULTI_FLAG;
template <typename T>
T* GetPointerFromRaw(PAddr addr) {
return reinterpret_cast<T*>(physical_base + addr);
}
template <typename T>
const T* GetPointerFromRaw(PAddr addr) const {
return reinterpret_cast<T*>(physical_base + addr);
}
template <typename T>
PAddr GetRawPhysicalAddr(const T* ptr) const {
return static_cast<PAddr>(reinterpret_cast<uintptr_t>(ptr) - physical_base);
}
void WalkBlock(const DAddr addr, const std::size_t size, auto on_unmapped, auto on_memory,
auto increment);
void InnerGatherDeviceAddresses(Common::ScratchBuffer<u32>& buffer, PAddr address);
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base;
DeviceInterface* device_inter;
Common::VirtualBuffer<u32> compressed_physical_ptr;
Common::VirtualBuffer<u32> compressed_device_addr;
Common::VirtualBuffer<u32> continuity_tracker;
// Process memory interfaces
std::deque<size_t> id_pool;
std::deque<Memory::Memory*> registered_processes;
// Memory protection management
static constexpr size_t guest_max_as_bits = 39;
static constexpr size_t guest_as_size = 1ULL << guest_max_as_bits;
static constexpr size_t guest_mask = guest_as_size - 1ULL;
static constexpr size_t asid_start_bit = guest_max_as_bits;
std::pair<Asid, VAddr> ExtractCPUBacking(size_t page_index) {
auto content = cpu_backing_address[page_index];
const VAddr address = content & guest_mask;
const Asid asid{static_cast<size_t>(content >> asid_start_bit)};
return std::make_pair(asid, address);
}
void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
cpu_backing_address[page_index] = address | (asid.id << asid_start_bit);
}
Common::VirtualBuffer<VAddr> cpu_backing_address;
static constexpr size_t subentries = 8 / sizeof(u8);
static constexpr size_t subentries_mask = subentries - 1;
class CounterEntry final {
public:
CounterEntry() = default;
std::atomic_uint8_t& Count(std::size_t page) {
return values[page & subentries_mask];
}
const std::atomic_uint8_t& Count(std::size_t page) const {
return values[page & subentries_mask];
}
private:
std::array<std::atomic_uint8_t, subentries> values{};
};
static_assert(sizeof(CounterEntry) == subentries * sizeof(u8),
"CounterEntry should be 8 bytes!");
static constexpr size_t num_counter_entries =
(1ULL << (device_virtual_bits - page_bits)) / subentries;
using CachedPages = std::array<CounterEntry, num_counter_entries>;
std::unique_ptr<CachedPages> cached_pages;
std::mutex counter_guard;
std::mutex mapping_guard;
};
} // namespace Core

View File

@ -0,0 +1,582 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
#include <limits>
#include <memory>
#include <type_traits>
#include "common/address_space.h"
#include "common/address_space.inc"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/div_ceil.h"
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/device_memory.h"
#include "core/device_memory_manager.h"
#include "core/memory.h"
namespace Core {
namespace {
class MultiAddressContainer {
public:
MultiAddressContainer() = default;
~MultiAddressContainer() = default;
void GatherValues(u32 start_entry, Common::ScratchBuffer<u32>& buffer) {
buffer.resize(8);
buffer.resize(0);
size_t index = 0;
const auto add_value = [&](u32 value) {
buffer[index] = value;
index++;
buffer.resize(index);
};
u32 iter_entry = start_entry;
Entry* current = &storage[iter_entry - 1];
add_value(current->value);
while (current->next_entry != 0) {
iter_entry = current->next_entry;
current = &storage[iter_entry - 1];
add_value(current->value);
}
}
u32 Register(u32 value) {
return RegisterImplementation(value);
}
void Register(u32 value, u32 start_entry) {
auto entry_id = RegisterImplementation(value);
u32 iter_entry = start_entry;
Entry* current = &storage[iter_entry - 1];
while (current->next_entry != 0) {
iter_entry = current->next_entry;
current = &storage[iter_entry - 1];
}
current->next_entry = entry_id;
}
std::pair<bool, u32> Unregister(u32 value, u32 start_entry) {
u32 iter_entry = start_entry;
Entry* previous{};
Entry* current = &storage[iter_entry - 1];
Entry* next{};
bool more_than_one_remaining = false;
u32 result_start{start_entry};
size_t count = 0;
while (current->value != value) {
count++;
previous = current;
iter_entry = current->next_entry;
current = &storage[iter_entry - 1];
}
// Find next
u32 next_entry = current->next_entry;
if (next_entry != 0) {
next = &storage[next_entry - 1];
more_than_one_remaining = next->next_entry != 0 || previous != nullptr;
}
if (previous) {
previous->next_entry = next_entry;
} else {
result_start = next_entry;
}
free_entries.emplace_back(iter_entry);
return std::make_pair(more_than_one_remaining || count > 1, result_start);
}
u32 ReleaseEntry(u32 start_entry) {
Entry* current = &storage[start_entry - 1];
free_entries.emplace_back(start_entry);
return current->value;
}
private:
u32 RegisterImplementation(u32 value) {
auto entry_id = GetNewEntry();
auto& entry = storage[entry_id - 1];
entry.next_entry = 0;
entry.value = value;
return entry_id;
}
u32 GetNewEntry() {
if (!free_entries.empty()) {
u32 result = free_entries.front();
free_entries.pop_front();
return result;
}
storage.emplace_back();
u32 new_entry = static_cast<u32>(storage.size());
return new_entry;
}
struct Entry {
u32 next_entry{};
u32 value{};
};
std::deque<Entry> storage;
std::deque<u32> free_entries;
};
struct EmptyAllocator {
EmptyAllocator([[maybe_unused]] DAddr address) {}
};
} // namespace
template <typename DTraits>
struct DeviceMemoryManagerAllocator {
static constexpr size_t device_virtual_bits = DTraits::device_virtual_bits;
static constexpr DAddr first_address = 1ULL << Memory::YUZU_PAGEBITS;
static constexpr DAddr max_device_area = 1ULL << device_virtual_bits;
DeviceMemoryManagerAllocator() : main_allocator(first_address) {}
Common::FlatAllocator<DAddr, 0, device_virtual_bits> main_allocator;
MultiAddressContainer multi_dev_address;
/// Returns true when vaddr -> vaddr+size is fully contained in the buffer
template <bool pin_area>
[[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept {
return addr >= 0 && addr + size <= max_device_area;
}
DAddr Allocate(size_t size) {
return main_allocator.Allocate(size);
}
void AllocateFixed(DAddr b_address, size_t b_size) {
main_allocator.AllocateFixed(b_address, b_size);
}
void Free(DAddr b_address, size_t b_size) {
main_allocator.Free(b_address, b_size);
}
};
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{reinterpret_cast<const uintptr_t>(device_memory_.buffer.BackingBasePointer())},
device_inter{nullptr}, compressed_physical_ptr(device_as_size >> Memory::YUZU_PAGEBITS),
compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() ==
Settings::MemoryLayout::Memory_4Gb
? physical_min_bits
: physical_max_bits) -
Memory::YUZU_PAGEBITS)),
continuity_tracker(device_as_size >> Memory::YUZU_PAGEBITS),
cpu_backing_address(device_as_size >> Memory::YUZU_PAGEBITS) {
impl = std::make_unique<DeviceMemoryManagerAllocator<Traits>>();
cached_pages = std::make_unique<CachedPages>();
const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS;
for (size_t i = 0; i < total_virtual; i++) {
compressed_physical_ptr[i] = 0;
continuity_tracker[i] = 1;
cpu_backing_address[i] = 0;
}
const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() ==
Settings::MemoryLayout::Memory_4Gb
? physical_min_bits
: physical_max_bits) -
Memory::YUZU_PAGEBITS);
for (size_t i = 0; i < total_phys; i++) {
compressed_device_addr[i] = 0;
}
}
template <typename Traits>
DeviceMemoryManager<Traits>::~DeviceMemoryManager() = default;
template <typename Traits>
void DeviceMemoryManager<Traits>::BindInterface(DeviceInterface* device_inter_) {
device_inter = device_inter_;
}
template <typename Traits>
DAddr DeviceMemoryManager<Traits>::Allocate(size_t size) {
return impl->Allocate(size);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::AllocateFixed(DAddr start, size_t size) {
return impl->AllocateFixed(start, size);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::Free(DAddr start, size_t size) {
impl->Free(start, size);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size_t size,
Asid asid, bool track) {
Core::Memory::Memory* process_memory = registered_processes[asid.id];
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
std::scoped_lock lk(mapping_guard);
for (size_t i = 0; i < num_pages; i++) {
const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE;
auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress));
if (ptr == nullptr) [[unlikely]] {
compressed_physical_ptr[start_page_d + i] = 0;
continue;
}
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
compressed_physical_ptr[start_page_d + i] = phys_addr;
InsertCPUBacking(start_page_d + i, new_vaddress, asid);
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
const u32 new_dev = static_cast<u32>(start_page_d + i);
if (base_dev == 0) [[likely]] {
compressed_device_addr[phys_addr - 1U] = new_dev;
continue;
}
u32 start_id = base_dev & MULTI_MASK;
if ((base_dev >> MULTI_FLAG_BITS) == 0) {
start_id = impl->multi_dev_address.Register(base_dev);
compressed_device_addr[phys_addr - 1U] = MULTI_FLAG | start_id;
}
impl->multi_dev_address.Register(new_dev, start_id);
}
if (track) {
TrackContinuityImpl(address, virtual_address, size, asid);
}
}
template <typename Traits>
void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
device_inter->InvalidateRegion(address, size);
std::scoped_lock lk(mapping_guard);
for (size_t i = 0; i < num_pages; i++) {
auto phys_addr = compressed_physical_ptr[start_page_d + i];
compressed_physical_ptr[start_page_d + i] = 0;
cpu_backing_address[start_page_d + i] = 0;
if (phys_addr != 0) [[likely]] {
const u32 base_dev = compressed_device_addr[phys_addr - 1U];
if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] {
compressed_device_addr[phys_addr - 1] = 0;
continue;
}
const auto [more_entries, new_start] = impl->multi_dev_address.Unregister(
static_cast<u32>(start_page_d + i), base_dev & MULTI_MASK);
if (!more_entries) {
compressed_device_addr[phys_addr - 1] =
impl->multi_dev_address.ReleaseEntry(new_start);
continue;
}
compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG;
}
}
}
template <typename Traits>
void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address,
size_t size, Asid asid) {
Core::Memory::Memory* process_memory = registered_processes[asid.id];
size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
uintptr_t last_ptr = 0;
size_t page_count = 1;
for (size_t i = num_pages; i > 0; i--) {
size_t index = i - 1;
const VAddr new_vaddress = virtual_address + index * Memory::YUZU_PAGESIZE;
const uintptr_t new_ptr = reinterpret_cast<uintptr_t>(
process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress)));
if (new_ptr + page_size == last_ptr) {
page_count++;
} else {
page_count = 1;
}
last_ptr = new_ptr;
continuity_tracker[start_page_d + index] = static_cast<u32>(page_count);
}
}
template <typename Traits>
u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(continuity_tracker[page_index]) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
}
template <typename Traits>
const u8* DeviceMemoryManager<Traits>::GetSpan(const DAddr src_addr, const std::size_t size) const {
size_t page_index = src_addr >> page_bits;
size_t subbits = src_addr & page_mask;
if ((static_cast<size_t>(continuity_tracker[page_index]) << page_bits) >= size + subbits) {
return GetPointer<u8>(src_addr);
}
return nullptr;
}
template <typename Traits>
void DeviceMemoryManager<Traits>::InnerGatherDeviceAddresses(Common::ScratchBuffer<u32>& buffer,
PAddr address) {
size_t phys_addr = address >> page_bits;
std::scoped_lock lk(mapping_guard);
u32 backing = compressed_device_addr[phys_addr];
if ((backing >> MULTI_FLAG_BITS) != 0) {
impl->multi_dev_address.GatherValues(backing & MULTI_MASK, buffer);
return;
}
buffer.resize(1);
buffer[0] = backing;
}
template <typename Traits>
template <typename T>
T* DeviceMemoryManager<Traits>::GetPointer(DAddr address) {
const size_t index = address >> Memory::YUZU_PAGEBITS;
const size_t offset = address & Memory::YUZU_PAGEMASK;
auto phys_addr = compressed_physical_ptr[index];
if (phys_addr == 0) [[unlikely]] {
return nullptr;
}
return GetPointerFromRaw<T>((static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) +
offset);
}
template <typename Traits>
template <typename T>
const T* DeviceMemoryManager<Traits>::GetPointer(DAddr address) const {
const size_t index = address >> Memory::YUZU_PAGEBITS;
const size_t offset = address & Memory::YUZU_PAGEMASK;
auto phys_addr = compressed_physical_ptr[index];
if (phys_addr == 0) [[unlikely]] {
return nullptr;
}
return GetPointerFromRaw<T>((static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) +
offset);
}
template <typename Traits>
template <typename T>
void DeviceMemoryManager<Traits>::Write(DAddr address, T value) {
T* ptr = GetPointer<T>(address);
if (!ptr) [[unlikely]] {
return;
}
std::memcpy(ptr, &value, sizeof(T));
}
template <typename Traits>
template <typename T>
T DeviceMemoryManager<Traits>::Read(DAddr address) const {
const T* ptr = GetPointer<T>(address);
T result{};
if (!ptr) [[unlikely]] {
return result;
}
std::memcpy(&result, ptr, sizeof(T));
return result;
}
template <typename Traits>
void DeviceMemoryManager<Traits>::WalkBlock(DAddr addr, std::size_t size, auto on_unmapped,
auto on_memory, auto increment) {
std::size_t remaining_size = size;
std::size_t page_index = addr >> Memory::YUZU_PAGEBITS;
std::size_t page_offset = addr & Memory::YUZU_PAGEMASK;
while (remaining_size) {
const size_t next_pages = static_cast<std::size_t>(continuity_tracker[page_index]);
const std::size_t copy_amount =
std::min((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size);
const auto current_vaddr =
static_cast<u64>((page_index << Memory::YUZU_PAGEBITS) + page_offset);
SCOPE_EXIT({
page_index += next_pages;
page_offset = 0;
increment(copy_amount);
remaining_size -= copy_amount;
});
auto phys_addr = compressed_physical_ptr[page_index];
if (phys_addr == 0) {
on_unmapped(copy_amount, current_vaddr);
continue;
}
auto* mem_ptr = GetPointerFromRaw<u8>(
(static_cast<PAddr>(phys_addr - 1) << Memory::YUZU_PAGEBITS) + page_offset);
on_memory(copy_amount, mem_ptr);
}
}
template <typename Traits>
void DeviceMemoryManager<Traits>::ReadBlock(DAddr address, void* dest_pointer, size_t size) {
device_inter->FlushRegion(address, size);
WalkBlock(
address, size,
[&](size_t copy_amount, DAddr current_vaddr) {
LOG_ERROR(
HW_Memory,
"Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, address, size);
std::memset(dest_pointer, 0, copy_amount);
},
[&](size_t copy_amount, const u8* const src_ptr) {
std::memcpy(dest_pointer, src_ptr, copy_amount);
},
[&](const std::size_t copy_amount) {
dest_pointer = static_cast<u8*>(dest_pointer) + copy_amount;
});
}
template <typename Traits>
void DeviceMemoryManager<Traits>::WriteBlock(DAddr address, const void* src_pointer, size_t size) {
WalkBlock(
address, size,
[&](size_t copy_amount, DAddr current_vaddr) {
LOG_ERROR(
HW_Memory,
"Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, address, size);
},
[&](size_t copy_amount, u8* const dst_ptr) {
std::memcpy(dst_ptr, src_pointer, copy_amount);
},
[&](const std::size_t copy_amount) {
src_pointer = static_cast<const u8*>(src_pointer) + copy_amount;
});
device_inter->InvalidateRegion(address, size);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size) {
WalkBlock(
address, size,
[&](size_t copy_amount, DAddr current_vaddr) {
LOG_ERROR(
HW_Memory,
"Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, address, size);
std::memset(dest_pointer, 0, copy_amount);
},
[&](size_t copy_amount, const u8* const src_ptr) {
std::memcpy(dest_pointer, src_ptr, copy_amount);
},
[&](const std::size_t copy_amount) {
dest_pointer = static_cast<u8*>(dest_pointer) + copy_amount;
});
}
template <typename Traits>
void DeviceMemoryManager<Traits>::WriteBlockUnsafe(DAddr address, const void* src_pointer,
size_t size) {
WalkBlock(
address, size,
[&](size_t copy_amount, DAddr current_vaddr) {
LOG_ERROR(
HW_Memory,
"Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, address, size);
},
[&](size_t copy_amount, u8* const dst_ptr) {
std::memcpy(dst_ptr, src_pointer, copy_amount);
},
[&](const std::size_t copy_amount) {
src_pointer = static_cast<const u8*>(src_pointer) + copy_amount;
});
}
template <typename Traits>
Asid DeviceMemoryManager<Traits>::RegisterProcess(Memory::Memory* memory_device_inter) {
size_t new_id{};
if (!id_pool.empty()) {
new_id = id_pool.front();
id_pool.pop_front();
registered_processes[new_id] = memory_device_inter;
} else {
registered_processes.emplace_back(memory_device_inter);
new_id = registered_processes.size() - 1U;
}
return Asid{new_id};
}
template <typename Traits>
void DeviceMemoryManager<Traits>::UnregisterProcess(Asid asid) {
registered_processes[asid.id] = nullptr;
id_pool.push_front(asid.id);
}
template <typename Traits>
void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) {
std::unique_lock<std::mutex> lk(counter_guard, std::defer_lock);
const auto Lock = [&] {
if (!lk) {
lk.lock();
}
};
u64 uncache_begin = 0;
u64 cache_begin = 0;
u64 uncache_bytes = 0;
u64 cache_bytes = 0;
const auto MarkRegionCaching = &DeviceMemoryManager<Traits>::DeviceMethods::MarkRegionCaching;
std::atomic_thread_fence(std::memory_order_acquire);
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
size_t page = addr >> Memory::YUZU_PAGEBITS;
auto [asid, base_vaddress] = ExtractCPUBacking(page);
size_t vpage = base_vaddress >> Memory::YUZU_PAGEBITS;
auto* memory_device_inter = registered_processes[asid.id];
for (; page != page_end; ++page) {
std::atomic_uint8_t& count = cached_pages->at(page >> 3).Count(page);
if (delta > 0) {
ASSERT_MSG(count.load(std::memory_order::relaxed) < std::numeric_limits<u8>::max(),
"Count may overflow!");
} else if (delta < 0) {
ASSERT_MSG(count.load(std::memory_order::relaxed) > 0, "Count may underflow!");
} else {
ASSERT_MSG(false, "Delta must be non-zero!");
}
// Adds or subtracts 1, as count is a unsigned 8-bit value
count.fetch_add(static_cast<u8>(delta), std::memory_order_release);
// Assume delta is either -1 or 1
if (count.load(std::memory_order::relaxed) == 0) {
if (uncache_bytes == 0) {
uncache_begin = vpage;
}
uncache_bytes += Memory::YUZU_PAGESIZE;
} else if (uncache_bytes > 0) {
Lock();
MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS,
uncache_bytes, false);
uncache_bytes = 0;
}
if (count.load(std::memory_order::relaxed) == 1 && delta > 0) {
if (cache_bytes == 0) {
cache_begin = vpage;
}
cache_bytes += Memory::YUZU_PAGESIZE;
} else if (cache_bytes > 0) {
Lock();
MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes,
true);
cache_bytes = 0;
}
vpage++;
}
if (uncache_bytes > 0) {
Lock();
MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS, uncache_bytes,
false);
}
if (cache_bytes > 0) {
Lock();
MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes,
true);
}
}
} // namespace Core

View File

@ -466,12 +466,12 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
return romfs;
}
PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
if (title_id == 0) {
return {};
}
std::map<std::string, std::string, std::less<>> out;
std::vector<Patch> out;
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
@ -482,20 +482,28 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
const auto update_label = update_disabled ? "[D] Update" : "Update";
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = "",
.type = PatchType::Update,
.program_id = title_id,
.title_id = title_id};
if (nacp != nullptr) {
out.insert_or_assign(update_label, nacp->GetVersionString());
update_patch.version = nacp->GetVersionString();
out.push_back(update_patch);
} else {
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, "");
out.push_back(update_patch);
} else {
out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
update_patch.version = FormatTitleVersion(*meta_ver);
out.push_back(update_patch);
}
} else if (update_raw != nullptr) {
out.insert_or_assign(update_label, "PACKED");
update_patch.version = "PACKED";
out.push_back(update_patch);
}
}
@ -539,7 +547,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
out.push_back({.enabled = !mod_disabled,
.name = mod->GetName(),
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id});
}
}
@ -557,7 +570,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
if (!types.empty()) {
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end();
out.insert_or_assign(mod_disabled ? "[D] SDMC" : "SDMC", types);
out.push_back({.enabled = !mod_disabled,
.name = "SDMC",
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id});
}
}
@ -584,7 +602,12 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
out.push_back({.enabled = !dlc_disabled,
.name = "DLC",
.version = std::move(list),
.type = PatchType::DLC,
.program_id = title_id,
.title_id = dlc_match.back().title_id});
}
return out;

View File

@ -26,12 +26,22 @@ class ContentProvider;
class NCA;
class NACP;
enum class PatchType { Update, DLC, Mod };
struct Patch {
bool enabled;
std::string name;
std::string version;
PatchType type;
u64 program_id;
u64 title_id;
};
// A centralized class to manage patches to games.
class PatchManager {
public:
using BuildID = std::array<u8, 0x20>;
using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
explicit PatchManager(u64 title_id_,
const Service::FileSystem::FileSystemController& fs_controller_,
@ -66,9 +76,8 @@ public:
VirtualFile packed_update_raw = nullptr,
bool apply_layeredfs = true) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
[[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
// Returns a vector of patches
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be

View File

@ -97,8 +97,9 @@ std::string SaveDataAttribute::DebugInfo() const {
static_cast<u8>(rank), index);
}
SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_)
: dir{std::move(save_directory_)}, system{system_} {
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
VirtualDir save_directory_)
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
@ -110,7 +111,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
PrintSaveDataAttributeWarnings(meta);
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
return dir->CreateDirectoryRelative(save_directory);
}
@ -118,7 +119,7 @@ VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribut
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory =
GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
GetFullPath(program_id, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id);
auto out = dir->GetDirectoryRelative(save_directory);
@ -147,14 +148,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
}
}
std::string SaveDataFactory::GetFullPath(Core::System& system, VirtualDir dir,
std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) {
if (title_id == 0) {
title_id = system.GetApplicationProcessProgramID();
title_id = program_id;
}
}
@ -201,7 +202,7 @@ std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future)
SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
u128 user_id) const {
const auto path =
GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
@ -220,7 +221,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
SaveDataSize new_value) const {
const auto path =
GetFullPath(system, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
GetFullPath(program_id, dir, SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());

View File

@ -87,10 +87,13 @@ constexpr const char* GetSaveDataSizeFileName() {
return ".yuzu_save_size";
}
using ProgramId = u64;
/// File system interface to the SaveData archive
class SaveDataFactory {
public:
explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_);
explicit SaveDataFactory(Core::System& system_, ProgramId program_id_,
VirtualDir save_directory_);
~SaveDataFactory();
VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const;
@ -99,7 +102,7 @@ public:
VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
static std::string GetFullPath(Core::System& system, VirtualDir dir, SaveDataSpaceId space,
static std::string GetFullPath(ProgramId program_id, VirtualDir dir, SaveDataSpaceId space,
SaveDataType type, u64 title_id, u128 user_id, u64 save_id);
static std::string GetUserGameSaveDataRoot(u128 user_id, bool future);
@ -110,8 +113,9 @@ public:
void SetAutoCreate(bool state);
private:
VirtualDir dir;
Core::System& system;
ProgramId program_id;
VirtualDir dir;
bool auto_create{true};
};

View File

@ -6,7 +6,6 @@
#include "common/swap.h"
#include "core/file_sys/system_archive/time_zone_binary.h"
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/time/time_zone_types.h"
#include "nx_tzdb.h"

View File

@ -10,7 +10,7 @@
#include <utility>
#include <vector>
#include "core/memory.h"
#include "core/device_memory_manager.h"
namespace Core {
@ -23,7 +23,7 @@ public:
~GPUDirtyMemoryManager() = default;
void Collect(VAddr address, size_t size) {
void Collect(PAddr address, size_t size) {
TransformAddress t = BuildTransform(address, size);
TransformAddress tmp, original;
do {
@ -47,7 +47,7 @@ public:
std::memory_order_relaxed));
}
void Gather(std::function<void(VAddr, size_t)>& callback) {
void Gather(std::function<void(PAddr, size_t)>& callback) {
{
std::scoped_lock lk(guard);
TransformAddress t = current.exchange(default_transform, std::memory_order_relaxed);
@ -65,7 +65,7 @@ public:
mask = mask >> empty_bits;
const size_t continuous_bits = std::countr_one(mask);
callback((static_cast<VAddr>(transform.address) << page_bits) + offset,
callback((static_cast<PAddr>(transform.address) << page_bits) + offset,
continuous_bits << align_bits);
mask = continuous_bits < align_size ? (mask >> continuous_bits) : 0;
offset += continuous_bits << align_bits;
@ -80,7 +80,7 @@ private:
u32 mask;
};
constexpr static size_t page_bits = Memory::YUZU_PAGEBITS - 1;
constexpr static size_t page_bits = DEVICE_PAGEBITS - 1;
constexpr static size_t page_size = 1ULL << page_bits;
constexpr static size_t page_mask = page_size - 1;
@ -89,7 +89,7 @@ private:
constexpr static size_t align_mask = align_size - 1;
constexpr static TransformAddress default_transform = {.address = ~0U, .mask = 0U};
bool IsValid(VAddr address) {
bool IsValid(PAddr address) {
return address < (1ULL << 39);
}
@ -103,7 +103,7 @@ private:
return mask;
}
TransformAddress BuildTransform(VAddr address, size_t size) {
TransformAddress BuildTransform(PAddr address, size_t size) {
const size_t minor_address = address & page_mask;
const size_t minor_bit = minor_address >> align_bits;
const size_t top_bit = (minor_address + size + align_mask) >> align_bits;

214
src/core/guest_memory.h Normal file
View File

@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <memory>
#include <optional>
#include <span>
#include <vector>
#include "common/assert.h"
#include "common/scratch_buffer.h"
namespace Core::Memory {
enum GuestMemoryFlags : u32 {
Read = 1 << 0,
Write = 1 << 1,
Safe = 1 << 2,
Cached = 1 << 3,
SafeRead = Read | Safe,
SafeWrite = Write | Safe,
SafeReadWrite = SafeRead | SafeWrite,
SafeReadCachedWrite = SafeReadWrite | Cached,
UnsafeRead = Read,
UnsafeWrite = Write,
UnsafeReadWrite = UnsafeRead | UnsafeWrite,
UnsafeReadCachedWrite = UnsafeReadWrite | Cached,
};
namespace {
template <typename M, typename T, GuestMemoryFlags FLAGS>
class GuestMemory {
using iterator = T*;
using const_iterator = const T*;
using value_type = T;
using element_type = T;
using iterator_category = std::contiguous_iterator_tag;
public:
GuestMemory() = delete;
explicit GuestMemory(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
: m_memory{memory}, m_addr{addr}, m_size{size} {
static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write);
if constexpr (FLAGS & GuestMemoryFlags::Read) {
Read(addr, size, backup);
}
}
~GuestMemory() = default;
T* data() noexcept {
return m_data_span.data();
}
const T* data() const noexcept {
return m_data_span.data();
}
size_t size() const noexcept {
return m_size;
}
size_t size_bytes() const noexcept {
return this->size() * sizeof(T);
}
[[nodiscard]] T* begin() noexcept {
return this->data();
}
[[nodiscard]] const T* begin() const noexcept {
return this->data();
}
[[nodiscard]] T* end() noexcept {
return this->data() + this->size();
}
[[nodiscard]] const T* end() const noexcept {
return this->data() + this->size();
}
T& operator[](size_t index) noexcept {
return m_data_span[index];
}
const T& operator[](size_t index) const noexcept {
return m_data_span[index];
}
void SetAddressAndSize(u64 addr, std::size_t size) noexcept {
m_addr = addr;
m_size = size;
m_addr_changed = true;
}
std::span<T> Read(u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr) noexcept {
m_addr = addr;
m_size = size;
if (m_size == 0) {
m_is_data_copy = true;
return {};
}
if (this->TrySetSpan()) {
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
m_memory.FlushRegion(m_addr, this->size_bytes());
}
} else {
if (backup) {
backup->resize_destructive(this->size());
m_data_span = *backup;
} else {
m_data_copy.resize(this->size());
m_data_span = std::span(m_data_copy);
}
m_is_data_copy = true;
m_span_valid = true;
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
m_memory.ReadBlock(m_addr, this->data(), this->size_bytes());
} else {
m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes());
}
}
return m_data_span;
}
void Write(std::span<T> write_data) noexcept {
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes());
} else {
m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes());
}
}
bool TrySetSpan() noexcept {
if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) {
m_data_span = {reinterpret_cast<T*>(ptr), this->size()};
m_span_valid = true;
return true;
}
return false;
}
protected:
bool IsDataCopy() const noexcept {
return m_is_data_copy;
}
bool AddressChanged() const noexcept {
return m_addr_changed;
}
M& m_memory;
u64 m_addr{};
size_t m_size{};
std::span<T> m_data_span{};
std::vector<T> m_data_copy{};
bool m_span_valid{false};
bool m_is_data_copy{false};
bool m_addr_changed{false};
};
template <typename M, typename T, GuestMemoryFlags FLAGS>
class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> {
public:
GuestMemoryScoped() = delete;
explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size,
Common::ScratchBuffer<T>* backup = nullptr)
: GuestMemory<M, T, FLAGS>(memory, addr, size, backup) {
if constexpr (!(FLAGS & GuestMemoryFlags::Read)) {
if (!this->TrySetSpan()) {
if (backup) {
this->m_data_span = *backup;
this->m_span_valid = true;
this->m_is_data_copy = true;
}
}
}
}
~GuestMemoryScoped() {
if constexpr (FLAGS & GuestMemoryFlags::Write) {
if (this->size() == 0) [[unlikely]] {
return;
}
if (this->AddressChanged() || this->IsDataCopy()) {
ASSERT(this->m_span_valid);
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes());
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes());
} else {
this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes());
}
} else if constexpr ((FLAGS & GuestMemoryFlags::Safe) ||
(FLAGS & GuestMemoryFlags::Cached)) {
this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes());
}
}
}
};
} // namespace
} // namespace Core::Memory

View File

@ -28,14 +28,14 @@ Result KMemoryBlockManager::Initialize(KProcessAddress st, KProcessAddress nd,
}
void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager* slab_manager,
HostUnmapCallback&& host_unmap_callback) {
BlockCallback&& block_callback) {
// Erase every block until we have none left.
auto it = m_memory_block_tree.begin();
while (it != m_memory_block_tree.end()) {
KMemoryBlock* block = std::addressof(*it);
it = m_memory_block_tree.erase(it);
block_callback(block->GetAddress(), block->GetSize());
slab_manager->Free(block);
host_unmap_callback(block->GetAddress(), block->GetSize());
}
ASSERT(m_memory_block_tree.empty());

View File

@ -85,11 +85,11 @@ public:
public:
KMemoryBlockManager();
using HostUnmapCallback = std::function<void(Common::ProcessAddress, u64)>;
using BlockCallback = std::function<void(Common::ProcessAddress, u64)>;
Result Initialize(KProcessAddress st, KProcessAddress nd,
KMemoryBlockSlabManager* slab_manager);
void Finalize(KMemoryBlockSlabManager* slab_manager, HostUnmapCallback&& host_unmap_callback);
void Finalize(KMemoryBlockSlabManager* slab_manager, BlockCallback&& block_callback);
iterator end() {
return m_memory_block_tree.end();

View File

@ -431,15 +431,43 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
m_memory_block_slab_manager));
}
Result KPageTableBase::FinalizeProcess() {
// Only process tables should be finalized.
ASSERT(!this->IsKernel());
// NOTE: Here Nintendo calls an unknown OnFinalize function.
// this->OnFinalize();
// NOTE: Here Nintendo calls a second unknown OnFinalize function.
// this->OnFinalize2();
// NOTE: Here Nintendo does a page table walk to discover heap pages to free.
// We will use the block manager finalization below to free them.
R_SUCCEED();
}
void KPageTableBase::Finalize() {
auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
if (Settings::IsFastmemEnabled()) {
this->FinalizeProcess();
auto BlockCallback = [&](KProcessAddress addr, u64 size) {
if (m_impl->fastmem_arena) {
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false);
}
// Get physical pages.
KPageGroup pg(m_kernel, m_block_info_manager);
this->MakePageGroup(pg, addr, size / PageSize);
// Free the pages.
pg.CloseAndReset();
};
// Finalize memory blocks.
m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
{
KScopedLightLock lk(m_general_lock);
m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(BlockCallback));
}
// Free any unsafe mapped memory.
if (m_mapped_unsafe_physical_memory) {

View File

@ -241,6 +241,7 @@ public:
KResourceLimit* resource_limit, Core::Memory::Memory& memory,
KProcessAddress aslr_space_start);
Result FinalizeProcess();
void Finalize();
bool IsKernel() const {

View File

@ -5,6 +5,7 @@
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/gpu_dirty_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
@ -171,6 +172,12 @@ void KProcess::Finalize() {
m_resource_limit->Close();
}
// Clear expensive resources, as the destructor is not called for guest objects.
for (auto& interface : m_arm_interfaces) {
interface.reset();
}
m_exclusive_monitor.reset();
// Perform inherited finalization.
KSynchronizationObject::Finalize();
}
@ -314,7 +321,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
// Ensure our memory is initialized.
m_memory.SetCurrentPageTable(*this);
m_memory.SetGPUDirtyManagers(m_dirty_memory_managers);
m_memory.SetGPUDirtyManagers(m_kernel.System().GetGPUDirtyMemoryManager());
// Ensure we can insert the code region.
R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize,
@ -411,7 +418,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
// Ensure our memory is initialized.
m_memory.SetCurrentPageTable(*this);
m_memory.SetGPUDirtyManagers(m_dirty_memory_managers);
m_memory.SetGPUDirtyManagers(m_kernel.System().GetGPUDirtyMemoryManager());
// Ensure we can insert the code region.
R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code),
@ -1135,8 +1142,7 @@ void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
KProcess::KProcess(KernelCore& kernel)
: KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel},
m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()},
m_handle_table{kernel}, m_dirty_memory_managers{},
m_exclusive_monitor{}, m_memory{kernel.System()} {}
m_handle_table{kernel}, m_exclusive_monitor{}, m_memory{kernel.System()} {}
KProcess::~KProcess() = default;
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
@ -1233,10 +1239,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
#ifdef HAS_NCE
if (this->IsApplication() && Settings::IsNceEnabled()) {
const auto& patch = code_set.PatchSegment();
if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment();
const auto& patch = code_set.PatchSegment();
buffer.Protect(GetInteger(base_addr + code.addr), code.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
@ -1318,10 +1324,4 @@ bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointT
return true;
}
void KProcess::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
for (auto& manager : m_dirty_memory_managers) {
manager.Gather(callback);
}
}
} // namespace Kernel

View File

@ -7,7 +7,6 @@
#include "core/arm/arm_interface.h"
#include "core/file_sys/program_metadata.h"
#include "core/gpu_dirty_memory_manager.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_address_arbiter.h"
#include "core/hle/kernel/k_capabilities.h"
@ -128,7 +127,6 @@ private:
#ifdef HAS_NCE
std::unordered_map<u64, u64> m_post_handlers{};
#endif
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> m_dirty_memory_managers;
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
Core::Memory::Memory m_memory;
@ -511,8 +509,6 @@ public:
return m_memory;
}
void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
Core::ExclusiveMonitor& GetExclusiveMonitor() const {
return *m_exclusive_monitor;
}

View File

@ -112,7 +112,14 @@ struct KernelCore::Impl {
old_process->Close();
}
process_list.clear();
{
std::scoped_lock lk{process_list_lock};
for (auto* const process : process_list) {
process->Terminate();
process->Close();
}
process_list.clear();
}
next_object_id = 0;
next_kernel_process_id = KProcess::InitialProcessIdMin;
@ -770,6 +777,7 @@ struct KernelCore::Impl {
std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
std::mutex process_list_lock;
std::vector<KProcess*> process_list;
std::atomic<KProcess*> application_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
@ -869,9 +877,19 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() {
}
void KernelCore::AppendNewProcess(KProcess* process) {
process->Open();
std::scoped_lock lk{impl->process_list_lock};
impl->process_list.push_back(process);
}
void KernelCore::RemoveProcess(KProcess* process) {
std::scoped_lock lk{impl->process_list_lock};
if (std::erase(impl->process_list, process)) {
process->Close();
}
}
void KernelCore::MakeApplicationProcess(KProcess* process) {
impl->MakeApplicationProcess(process);
}
@ -884,8 +902,15 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process;
}
const std::vector<KProcess*>& KernelCore::GetProcessList() const {
return impl->process_list;
std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
std::list<KScopedAutoObject<KProcess>> processes;
std::scoped_lock lk{impl->process_list_lock};
for (auto* const process : impl->process_list) {
processes.emplace_back(process);
}
return processes;
}
Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {

View File

@ -5,6 +5,7 @@
#include <array>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <unordered_map>
@ -116,8 +117,9 @@ public:
/// Retrieves a shared pointer to the system resource limit instance.
KResourceLimit* GetSystemResourceLimit();
/// Adds the given shared pointer to an internal list of active processes.
/// Adds/removes the given pointer to an internal list of active processes.
void AppendNewProcess(KProcess* process);
void RemoveProcess(KProcess* process);
/// Makes the given process the new application process.
void MakeApplicationProcess(KProcess* process);
@ -129,7 +131,7 @@ public:
const KProcess* ApplicationProcess() const;
/// Retrieves the list of processes.
const std::vector<KProcess*>& GetProcessList() const;
std::list<KScopedAutoObject<KProcess>> GetProcessList();
/// Gets the sole instance of the global scheduler
Kernel::GlobalSchedulerContext& GlobalSchedulerContext();

View File

@ -74,13 +74,15 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc
}
auto& memory = GetCurrentMemory(kernel);
const auto& process_list = kernel.GetProcessList();
auto process_list = kernel.GetProcessList();
auto it = process_list.begin();
const auto num_processes = process_list.size();
const auto copy_amount =
std::min(static_cast<std::size_t>(out_process_ids_size), num_processes);
for (std::size_t i = 0; i < copy_amount; ++i) {
memory.Write64(out_process_ids, process_list[i]->GetProcessId());
for (std::size_t i = 0; i < copy_amount && it != process_list.end(); ++i, ++it) {
memory.Write64(out_process_ids, (*it)->GetProcessId());
out_process_ids += sizeof(u64);
}

View File

@ -11,6 +11,7 @@
#include "common/fs/path_util.h"
#include "common/polyfill_ranges.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/hle/service/acc/profile_manager.h"
namespace Service::Account {
@ -61,9 +62,7 @@ ProfileManager::ProfileManager() {
OpenUser(*GetUser(current));
}
ProfileManager::~ProfileManager() {
WriteUserSaveFile();
}
ProfileManager::~ProfileManager() = default;
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
@ -113,6 +112,8 @@ Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username)
return ERROR_USER_ALREADY_EXISTS;
}
is_save_needed = true;
return AddUser({
.user_uuid = uuid,
.username = username,
@ -164,6 +165,22 @@ std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user)
return GetUserIndex(user.user_uuid);
}
/// Returns the first user profile seen based on username (which does not enforce uniqueness)
std::optional<std::size_t> ProfileManager::GetUserIndex(const std::string& username) const {
const auto iter =
std::find_if(profiles.begin(), profiles.end(), [&username](const ProfileInfo& p) {
const std::string profile_username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(p.username.data()), p.username.size());
return username.compare(profile_username) == 0;
});
if (iter == profiles.end()) {
return std::nullopt;
}
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
if (!index || index >= MAX_USERS) {
@ -326,6 +343,9 @@ bool ProfileManager::RemoveUser(UUID uuid) {
profiles[*index] = ProfileInfo{};
std::stable_partition(profiles.begin(), profiles.end(),
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
is_save_needed = true;
return true;
}
@ -340,6 +360,8 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
profile.username = profile_new.username;
profile.creation_time = profile_new.timestamp;
is_save_needed = true;
return true;
}
@ -348,6 +370,7 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
const auto index = GetUserIndex(uuid);
if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new;
is_save_needed = true;
return true;
}
@ -391,6 +414,10 @@ void ProfileManager::ParseUserSaveFile() {
}
void ProfileManager::WriteUserSaveFile() {
if (!is_save_needed) {
return;
}
ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) {
@ -423,7 +450,10 @@ void ProfileManager::WriteUserSaveFile() {
if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) {
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
"made in current session will be saved.");
return;
}
is_save_needed = false;
}
}; // namespace Service::Account

View File

@ -70,6 +70,7 @@ public:
std::optional<Common::UUID> GetUser(std::size_t index) const;
std::optional<std::size_t> GetUserIndex(const Common::UUID& uuid) const;
std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
std::optional<std::size_t> GetUserIndex(const std::string& username) const;
bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
bool GetProfileBase(Common::UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
@ -103,6 +104,7 @@ private:
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
bool is_save_needed{};
std::array<ProfileInfo, MAX_USERS> profiles{};
std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
std::size_t user_count{};

View File

@ -36,6 +36,7 @@
#include "core/hle/service/caps/caps_su.h"
#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/save_data_controller.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
@ -2178,7 +2179,7 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) {
attribute.type = FileSys::SaveDataType::SaveData;
FileSys::VirtualDir save_data{};
const auto res = system.GetFileSystemController().CreateSaveData(
const auto res = system.GetFileSystemController().OpenSaveDataController()->CreateSaveData(
&save_data, FileSys::SaveDataSpaceId::NandUser, attribute);
IPC::ResponseBuilder rb{ctx, 4};
@ -2353,7 +2354,7 @@ void IApplicationFunctions::ExtendSaveData(HLERequestContext& ctx) {
"new_journal={:016X}",
static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
system.GetFileSystemController().WriteSaveDataSize(
system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
type, system.GetApplicationProcessProgramID(), user_id,
{new_normal_size, new_journal_size});
@ -2378,7 +2379,7 @@ void IApplicationFunctions::GetSaveDataSize(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1],
user_id[0]);
const auto size = system.GetFileSystemController().ReadSaveDataSize(
const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
type, system.GetApplicationProcessProgramID(), user_id);
IPC::ResponseBuilder rb{ctx, 6};

View File

@ -18,11 +18,11 @@ using namespace AudioCore::AudioIn;
class IAudioIn final : public ServiceFramework<IAudioIn> {
public:
explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
const std::string& device_name, const AudioInParameter& in_params, u32 handle,
u64 applet_resource_user_id)
const std::string& device_name, const AudioInParameter& in_params,
Kernel::KProcess* handle, u64 applet_resource_user_id)
: ServiceFramework{system_, "IAudioIn"},
service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
impl{std::make_shared<In>(system_, manager, event, session_id)} {
process{handle}, impl{std::make_shared<In>(system_, manager, event, session_id)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioIn::GetAudioInState, "GetAudioInState"},
@ -45,6 +45,8 @@ public:
RegisterHandlers(functions);
process->Open();
if (impl->GetSystem()
.Initialize(device_name, in_params, handle, applet_resource_user_id)
.IsError()) {
@ -55,6 +57,7 @@ public:
~IAudioIn() override {
impl->Free();
service_context.CloseEvent(event);
process->Close();
}
[[nodiscard]] std::shared_ptr<In> GetImpl() {
@ -196,6 +199,7 @@ private:
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* event;
Kernel::KProcess* process;
std::shared_ptr<AudioCore::AudioIn::In> impl;
Common::ScratchBuffer<u64> released_buffer;
};
@ -267,6 +271,14 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
auto device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)};
auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
if (process.IsNull()) {
LOG_ERROR(Service_Audio, "Failed to get process handle");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultUnknown);
return;
}
std::scoped_lock l{impl->mutex};
auto link{impl->LinkToManager()};
if (link.IsError()) {
@ -287,8 +299,9 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions);
auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
in_params, handle, applet_resource_user_id);
auto audio_in =
std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, in_params,
process.GetPointerUnsafe(), applet_resource_user_id);
impl->sessions[new_session_id] = audio_in->GetImpl();
impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
@ -318,6 +331,14 @@ void AudInU::OpenAudioInProtocolSpecified(HLERequestContext& ctx) {
auto device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)};
auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
if (process.IsNull()) {
LOG_ERROR(Service_Audio, "Failed to get process handle");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultUnknown);
return;
}
std::scoped_lock l{impl->mutex};
auto link{impl->LinkToManager()};
if (link.IsError()) {
@ -338,8 +359,9 @@ void AudInU::OpenAudioInProtocolSpecified(HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions);
auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
in_params, handle, applet_resource_user_id);
auto audio_in =
std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, in_params,
process.GetPointerUnsafe(), applet_resource_user_id);
impl->sessions[new_session_id] = audio_in->GetImpl();
impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;

View File

@ -26,9 +26,10 @@ class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
size_t session_id, const std::string& device_name,
const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
const AudioOutParameter& in_params, Kernel::KProcess* handle,
u64 applet_resource_user_id)
: ServiceFramework{system_, "IAudioOut"}, service_context{system_, "IAudioOut"},
event{service_context.CreateEvent("AudioOutEvent")},
event{service_context.CreateEvent("AudioOutEvent")}, process{handle},
impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
// clang-format off
@ -50,11 +51,14 @@ public:
};
// clang-format on
RegisterHandlers(functions);
process->Open();
}
~IAudioOut() override {
impl->Free();
service_context.CloseEvent(event);
process->Close();
}
[[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
@ -206,6 +210,7 @@ private:
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* event;
Kernel::KProcess* process;
std::shared_ptr<AudioCore::AudioOut::Out> impl;
Common::ScratchBuffer<u64> released_buffer;
};
@ -257,6 +262,14 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
auto device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)};
auto process{ctx.GetObjectFromHandle<Kernel::KProcess>(handle)};
if (process.IsNull()) {
LOG_ERROR(Service_Audio, "Failed to get process handle");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultUnknown);
return;
}
auto link{impl->LinkToManager()};
if (link.IsError()) {
LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager");
@ -276,10 +289,11 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions);
auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name,
in_params, handle, applet_resource_user_id);
result = audio_out->GetImpl()->GetSystem().Initialize(device_name, in_params, handle,
applet_resource_user_id);
auto audio_out =
std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, in_params,
process.GetPointerUnsafe(), applet_resource_user_id);
result = audio_out->GetImpl()->GetSystem().Initialize(
device_name, in_params, process.GetPointerUnsafe(), applet_resource_user_id);
if (result.IsError()) {
LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
IPC::ResponseBuilder rb{ctx, 2};

View File

@ -10,8 +10,10 @@
#include "core/core.h"
#include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_result.h"
#include "core/hle/service/time/time_manager.h"
#include "core/hle/service/time/time_zone_content_manager.h"
#include "core/hle/service/glue/time/static.h"
#include "core/hle/service/psc/time/system_clock.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
namespace Service::Capture {
@ -239,10 +241,15 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
const ApplicationData& app_data, std::span<const u8> image_data,
u64 aruid) {
const u64 title_id = system.GetApplicationProcessProgramID();
const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
auto static_service =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);
std::shared_ptr<Service::PSC::Time::SystemClock> user_clock{};
static_service->GetStandardUserSystemClock(user_clock);
s64 posix_time{};
Result result = user_clock.GetCurrentTime(system, posix_time);
auto result = user_clock->GetCurrentTime(posix_time);
if (result.IsError()) {
return result;
@ -257,10 +264,14 @@ Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry,
const ScreenShotAttribute& attribute,
const AlbumFileId& file_id,
std::span<const u8> image_data) {
const auto& user_clock = system.GetTimeManager().GetStandardUserSystemClockCore();
auto static_service =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);
std::shared_ptr<Service::PSC::Time::SystemClock> user_clock{};
static_service->GetStandardUserSystemClock(user_clock);
s64 posix_time{};
Result result = user_clock.GetCurrentTime(system, posix_time);
auto result = user_clock->GetCurrentTime(posix_time);
if (result.IsError()) {
return result;
@ -455,19 +466,23 @@ Result AlbumManager::SaveImage(ApplicationAlbumEntry& out_entry, std::span<const
}
AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
Time::TimeZone::CalendarInfo calendar_date{};
const auto& time_zone_manager =
system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
auto static_service =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);
time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
std::shared_ptr<Service::Glue::Time::TimeZoneService> timezone_service{};
static_service->GetTimeZoneService(timezone_service);
Service::PSC::Time::CalendarTime calendar_time{};
Service::PSC::Time::CalendarAdditionalInfo additional_info{};
timezone_service->ToCalendarTimeWithMyRule(calendar_time, additional_info, posix_time);
return {
.year = calendar_date.time.year,
.month = calendar_date.time.month,
.day = calendar_date.time.day,
.hour = calendar_date.time.hour,
.minute = calendar_date.time.minute,
.second = calendar_date.time.second,
.year = calendar_time.year,
.month = calendar_time.month,
.day = calendar_time.day,
.hour = calendar_time.hour,
.minute = calendar_time.minute,
.second = calendar_time.second,
.unique_id = 0,
};
}

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