Compare commits

..

77 Commits

Author SHA1 Message Date
e4f0886adb Android 199 2024-01-20 02:30:18 +00:00
f5cb7c9f3c Merge yuzu-emu#12715 2024-01-20 02:30:18 +00:00
e78f6e5a87 Merge yuzu-emu#12701 2024-01-20 02:30:18 +00:00
3e44d8cdfe Merge yuzu-emu#12688 2024-01-20 02:30:18 +00:00
da329b15e8 Merge yuzu-emu#12660 2024-01-20 02:30:18 +00:00
931686dbc3 Merge yuzu-emu#12579 2024-01-20 02:30:18 +00: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
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
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
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
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
817c7c445d Merge pull request #12667 from t895/version-info
android: Show version name instead of build hash in about fragment
2024-01-13 20:23:12 -05:00
da714a362b Merge pull request #12666 from t895/ktlint-yuzu-verify
android: Move ktlintCheck to yuzu-verify
2024-01-13 20:23:02 -05:00
7b3941e5d4 android: Show version name instead of git hash in the about fragment 2024-01-13 18:12:19 -05:00
15d8a40529 android: Clean up git commands in build.gradle 2024-01-13 18:06:33 -05:00
cdeaca73c4 android: Move ktlintCheck to yuzu-verify 2024-01-13 17:41:01 -05: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
f7a3c135e2 Merge pull request #12605 from german77/abstract
service: hid: Create abstracted pad structure
2024-01-12 10:02:13 -05:00
fcb0dff67c Merge pull request #12642 from t895/adapter-refactor
android: Refactor list adapters
2024-01-12 10:01:54 -05:00
b5dac5f525 service: hid: Create abstracted pad structure 2024-01-11 19:35:04 -06:00
a4d90a9a64 Merge pull request #12653 from liamwhite/once-more
ci: fix file mode check in format script
2024-01-11 19:58:41 -05:00
84787a2ada ci: fix file mode check in format script 2024-01-11 18:57:07 -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
d3ba6b334b android: Fix added driver path
While this didn't break anything, the extra separator was unnecessary
2024-01-10 23:14:04 -05:00
dac8c4ce4d android: Add button to use global driver value 2024-01-10 23:14:04 -05:00
9e974d4c7e android: Reload driver data on importing user data 2024-01-10 23:14:04 -05:00
6bfc3c530c android: Rework driver fragment
Applies settings upon selection and uses a new Driver model to represent the information in-view. Also switches from an async diff list to a plain one.
2024-01-10 23:14:04 -05:00
93239f191a android: Refactor DriverAdapter to use AbstractSingleSelectionList 2024-01-10 23:14:04 -05:00
b17db2b462 android: Create generic single selection list adapter 2024-01-10 23:14:04 -05:00
9130366a58 android: Refactor recycler view adapters to use AbstractListAdapter 2024-01-10 23:14:04 -05:00
ad0066a6b6 android: Create generic list adapter for basic lists
Simplifies basic setup for lists
2024-01-10 23:14:04 -05:00
78c323c4eb android: Refactor async diff adapters to use AbstractDiffAdapter 2024-01-10 23:14:04 -05:00
51ad2d10de android: Create generic adapter and viewholder
Eliminates repeated code associated with every async differ list
2024-01-10 23:14:04 -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
232 changed files with 6932 additions and 2100 deletions

View File

@ -25,10 +25,13 @@ for f in $FILES_TO_LINT; do
"$CLANG_FORMAT" -i "$f" "$CLANG_FORMAT" -i "$f"
done done
DIFF=$(git diff) DIFF=$(git -c core.fileMode=false diff)
if [ ! -z "$DIFF" ]; then if [ ! -z "$DIFF" ]; then
echo "!!! Not compliant to coding style, here is the fix:" echo "!!! Not compliant to coding style, here is the fix:"
echo "$DIFF" echo "$DIFF"
exit 1 exit 1
fi fi
cd src/android
./gradlew ktlintCheck

View File

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

View File

@ -13,13 +13,15 @@ jobs:
format: format:
name: 'verify format' name: 'verify format'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: yuzuemu/build-environments:linux-clang-format
options: -u 1001
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: false submodules: false
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: 'Verify Formatting' - name: 'Verify Formatting'
run: bash -ex ./.ci/scripts/format/script.sh run: bash -ex ./.ci/scripts/format/script.sh
build: build:

View File

@ -1,10 +1,10 @@
| Pull Request | Commit | Title | Author | Merged? | | Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----| |----|----|----|----|----|
| [12579](https://github.com/yuzu-emu/yuzu-android//pull/12579) | [`66ae60a9e`](https://github.com/yuzu-emu/yuzu-android//pull/12579/files) | Core: Implement Device Mapping & GPU SMMU | [FernandoS27](https://github.com/FernandoS27/) | 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 |
| [12610](https://github.com/yuzu-emu/yuzu-android//pull/12610) | [`200b371d1`](https://github.com/yuzu-emu/yuzu-android//pull/12610/files) | server_manager: respond to session close correctly | [liamwhite](https://github.com/liamwhite/) | Yes | | [12660](https://github.com/yuzu-emu/yuzu-android//pull/12660) | [`2cacb9d48`](https://github.com/yuzu-emu/yuzu-android//pull/12660/files) | service: hid: Fully implement abstract vibration | [german77](https://github.com/german77/) | Yes |
| [12611](https://github.com/yuzu-emu/yuzu-android//pull/12611) | [`642a29923`](https://github.com/yuzu-emu/yuzu-android//pull/12611/files) | kernel: fix resource management issues | [liamwhite](https://github.com/liamwhite/) | Yes | | [12688](https://github.com/yuzu-emu/yuzu-android//pull/12688) | [`e9eb017aa`](https://github.com/yuzu-emu/yuzu-android//pull/12688/files) | renderer_vulkan: recreate swapchain when frame size changes | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12612](https://github.com/yuzu-emu/yuzu-android//pull/12612) | [`417df3b6e`](https://github.com/yuzu-emu/yuzu-android//pull/12612/files) | fsp-srv: use program registry for SetCurrentProcess | [liamwhite](https://github.com/liamwhite/) | Yes | | [12701](https://github.com/yuzu-emu/yuzu-android//pull/12701) | [`e4bbb24dc`](https://github.com/yuzu-emu/yuzu-android//pull/12701/files) | vi: check layer state before opening or closing | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12642](https://github.com/yuzu-emu/yuzu-android//pull/12642) | [`4037afea1`](https://github.com/yuzu-emu/yuzu-android//pull/12642/files) | android: Refactor list adapters | [t895](https://github.com/t895/) | Yes | | [12715](https://github.com/yuzu-emu/yuzu-android//pull/12715) | [`a363fa78e`](https://github.com/yuzu-emu/yuzu-android//pull/12715/files) | android: Add uninstall addon button | [t895](https://github.com/t895/) | Yes |
End of merge log. You can find the original README.md below the break. End of merge log. You can find the original README.md below the break.

View File

@ -188,8 +188,15 @@ tasks.create<Delete>("ktlintReset") {
delete(File(buildDir.path + File.separator + "intermediates/ktLint")) delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
} }
val showFormatHelp = {
logger.lifecycle(
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
"codestyle fixes"
)
}
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset") tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint { ktlint {
version.set("0.47.1") version.set("0.47.1")
@ -228,71 +235,33 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }
fun getGitVersion(): String { fun runGitCommand(command: List<String>): String {
var versionName = "0.0" return try {
ProcessBuilder(command)
try {
versionName = ProcessBuilder("git", "describe", "--always", "--long")
.directory(project.rootDir) .directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE) .redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE) .redirectError(ProcessBuilder.Redirect.PIPE)
.start().inputStream.bufferedReader().use { it.readText() } .start().inputStream.bufferedReader().use { it.readText() }
.trim() .trim()
} catch (e: Exception) {
logger.error("Cannot find git")
""
}
}
fun getGitVersion(): String {
val versionName = if (System.getenv("GITHUB_ACTIONS") != null) {
val gitTag = System.getenv("GIT_TAG_NAME") ?: ""
gitTag
} else {
runGitCommand(listOf("git", "describe", "--always", "--long"))
.replace(Regex("(-0)?-[^-]+$"), "") .replace(Regex("(-0)?-[^-]+$"), "")
} catch (e: Exception) {
logger.error("Cannot find git, defaulting to dummy version number")
} }
return versionName.ifEmpty { "0.0" }
if (System.getenv("GITHUB_ACTIONS") != null) {
val gitTag = System.getenv("GIT_TAG_NAME")
versionName = gitTag ?: versionName
}
return versionName
} }
fun getGitHash(): String { fun getGitHash(): String =
try { runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
val processBuilder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
processBuilder.directory(project.rootDir)
val process = processBuilder.start()
val inputStream = process.inputStream
val errorStream = process.errorStream
process.waitFor()
return if (process.exitValue() == 0) { fun getBranch(): String =
inputStream.bufferedReader() runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }
.use { it.readText().trim() } // return the value of gitHash
} else {
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
logger.error("Error running git command: $errorMessage")
"dummy-hash" // return a dummy hash value in case of an error
}
} catch (e: Exception) {
logger.error("$e: Cannot find git, defaulting to dummy build hash")
return "dummy-hash" // return a dummy hash value in case of an error
}
}
fun getBranch(): String {
try {
val processBuilder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
processBuilder.directory(project.rootDir)
val process = processBuilder.start()
val inputStream = process.inputStream
val errorStream = process.errorStream
process.waitFor()
return if (process.exitValue() == 0) {
inputStream.bufferedReader()
.use { it.readText().trim() } // return the value of gitHash
} else {
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
logger.error("Error running git command: $errorMessage")
"dummy-hash" // return a dummy hash value in case of an error
}
} catch (e: Exception) {
logger.error("$e: Cannot find git, defaulting to dummy build hash")
return "dummy-hash" // return a dummy hash value in case of an error
}
}

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.FileUtil
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 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 * Class which contains methods that interact
@ -235,9 +237,12 @@ object NativeLibrary {
/** /**
* Installs a nsp or xci file to nand * Installs a nsp or xci file to nand
* @param filename String representation of file uri * @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 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 path Path to game file. Can be a [Uri].
* @param programId String representation of a game's program ID * @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 * Gets the save location for a specific game
@ -609,15 +634,4 @@ object NativeLibrary {
const val RELEASED = 0 const val RELEASED = 0
const val PRESSED = 1 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.InputHandler
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.MemoryUtil 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.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat import java.text.NumberFormat
@ -171,11 +170,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener() stopMotionSensorListener()
} }
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { 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.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding 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 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) } .also { return AddonViewHolder(it) }
} }
inner class AddonViewHolder(val binding: ListItemAddonBinding) : inner class AddonViewHolder(val binding: ListItemAddonBinding) :
AbstractViewHolder<Addon>(binding) { AbstractViewHolder<Patch>(binding) {
override fun bind(model: Addon) { override fun bind(model: Patch) {
binding.root.setOnClickListener { 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.version.text = model.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked -> binding.addonCheckbox.setOnCheckedChangeListener { _, checked ->
model.enabled = checked model.enabled = checked
} }
binding.addonSwitch.isChecked = model.enabled binding.addonCheckbox.isChecked = model.enabled
binding.buttonDelete.setOnClickListener {
addonViewModel.setAddonToDelete(model)
}
} }
} }
} }

View File

@ -76,8 +76,8 @@ class AboutFragment : Fragment() {
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
} }
binding.textBuildHash.text = BuildConfig.GIT_HASH binding.textVersionName.text = BuildConfig.VERSION_NAME
binding.buttonBuildHash.setOnClickListener { binding.textVersionName.setOnClickListener {
val clipBoard = val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH) val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)

View File

@ -74,7 +74,7 @@ class AddonsFragment : Fragment() {
binding.listAddons.apply { binding.listAddons.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
adapter = AddonAdapter() adapter = AddonAdapter(addonViewModel)
} }
viewLifecycleOwner.lifecycleScope.apply { 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 { binding.buttonInstall.setOnClickListener {
@ -156,22 +171,22 @@ class AddonsFragment : Fragment() {
descriptionId = R.string.invalid_directory_description descriptionId = R.string.invalid_directory_description
) )
if (isValid) { if (isValid) {
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.installing_game_content, R.string.installing_game_content,
false false
) { ) { progressCallback, _ ->
val parentDirectoryName = externalAddonDirectory.name val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory = val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName) File(args.game.addonDir + parentDirectoryName)
try { try {
externalAddonDirectory.copyFilesTo(internalAddonDirectory) externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback)
} catch (_: Exception) { } catch (_: Exception) {
return@newInstance errorMessage return@newInstance errorMessage
} }
addonViewModel.refreshAddons() addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully) return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else { } else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG) errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
} }

View File

@ -173,11 +173,11 @@ class DriverManagerFragment : Fragment() {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.installing_driver, R.string.installing_driver,
false false
) { ) { _, _ ->
val driverPath = val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}" "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
val driverFile = File(driverPath) val driverFile = File(driverPath)
@ -213,6 +213,6 @@ class DriverManagerFragment : Fragment() {
} }
} }
return@newInstance Any() 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() findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
} }
popup.setOnDismissListener { NativeConfig.saveGlobalConfig() }
popup.setOnMenuItemClickListener { popup.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_toggle_fps -> { R.id.menu_toggle_fps -> {
@ -720,7 +721,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.emulation_control_adjust) .setTitle(R.string.emulation_control_adjust)
.setView(adjustBinding.root) .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 -> .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
setControlScale(50) setControlScale(50)
setControlOpacity(100) 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.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil import org.yuzu.yuzu_emu.utils.MemoryUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
@ -357,27 +356,17 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult return@registerForActivityResult
} }
val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir) val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir() cacheSaveDir.mkdir()
if (inputZip == null) { ProgressDialogFragment.newInstance(
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.save_files_importing, R.string.save_files_importing,
false false
) { ) { _, _ ->
try { try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) FileUtil.unzipToInternalStorage(result.toString(), cacheSaveDir)
val files = cacheSaveDir.listFiles() val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null var savesFolderFile: File? = null
if (files != null) { if (files != null) {
@ -422,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(parentFragmentManager, ProgressDialogFragment.TAG)
} }
/** /**
@ -436,11 +425,11 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.save_files_exporting, R.string.save_files_exporting,
false false
) { ) { _, _ ->
val saveLocation = args.game.saveDir val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage( val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation), File(saveLocation),
@ -452,6 +441,6 @@ class GamePropertiesFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success) TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) 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.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.math.BigInteger import java.math.BigInteger
@ -195,26 +194,20 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult return@registerForActivityResult
} }
val inputZip = requireContext().contentResolver.openInputStream(result)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir() cacheSaveDir.mkdir()
if (inputZip == null) { ProgressDialogFragment.newInstance(
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.save_files_importing, R.string.save_files_importing,
false false
) { ) { progressCallback, _ ->
try { try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) FileUtil.unzipToInternalStorage(
result.toString(),
cacheSaveDir,
progressCallback
)
val files = cacheSaveDir.listFiles() val files = cacheSaveDir.listFiles()
var successfulImports = 0 var successfulImports = 0
var failedImports = 0 var failedImports = 0
@ -287,7 +280,7 @@ class InstallableFragment : Fragment() {
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(parentFragmentManager, ProgressDialogFragment.TAG)
} }
private val exportSaves = registerForActivityResult( private val exportSaves = registerForActivityResult(
@ -297,11 +290,11 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
requireActivity(), requireActivity(),
R.string.save_files_exporting, R.string.save_files_exporting,
false false
) { ) { _, _ ->
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir() cacheSaveDir.mkdir()
@ -338,6 +331,6 @@ class InstallableFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success) TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) 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.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() { class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels() private val taskViewModel: TaskViewModel by activityViewModels()
private lateinit var binding: DialogProgressBarBinding private lateinit var binding: DialogProgressBarBinding
private val PROGRESS_BAR_RESOLUTION = 1000
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE) val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE) val cancellable = requireArguments().getBoolean(CANCELLABLE)
@ -61,6 +63,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.message.isSelected = true
viewLifecycleOwner.lifecycleScope.apply { viewLifecycleOwner.lifecycleScope.apply {
launch { launch {
repeatOnLifecycle(Lifecycle.State.CREATED) { 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) val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
negativeButton.setOnClickListener { negativeButton.setOnClickListener {
alertDialog.setTitle(getString(R.string.cancelling)) alertDialog.setTitle(getString(R.string.cancelling))
binding.progressBar.isIndeterminate = true
taskViewModel.setCancelled(true) taskViewModel.setCancelled(true)
} }
} }
@ -122,9 +155,12 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity, activity: FragmentActivity,
titleId: Int, titleId: Int,
cancellable: Boolean = false, cancellable: Boolean = false,
task: suspend () -> Any task: suspend (
): IndeterminateProgressDialogFragment { progressCallback: (max: Long, progress: Long) -> Boolean,
val dialog = IndeterminateProgressDialogFragment() messageCallback: (message: String) -> Unit
) -> Any
): ProgressDialogFragment {
val dialog = ProgressDialogFragment()
val args = Bundle() val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId) args.putInt(TITLE, titleId)

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 import java.util.concurrent.atomic.AtomicBoolean
class AddonViewModel : ViewModel() { class AddonViewModel : ViewModel() {
private val _addonList = MutableStateFlow(mutableListOf<Addon>()) private val _patchList = MutableStateFlow(mutableListOf<Patch>())
val addonList get() = _addonList.asStateFlow() val addonList get() = _patchList.asStateFlow()
private val _showModInstallPicker = MutableStateFlow(false) private val _showModInstallPicker = MutableStateFlow(false)
val showModInstallPicker get() = _showModInstallPicker.asStateFlow() val showModInstallPicker get() = _showModInstallPicker.asStateFlow()
@ -24,6 +24,9 @@ class AddonViewModel : ViewModel() {
private val _showModNoticeDialog = MutableStateFlow(false) private val _showModNoticeDialog = MutableStateFlow(false)
val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow() val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow()
private val _addonToDelete = MutableStateFlow<Patch?>(null)
val addonToDelete = _addonToDelete.asStateFlow()
var game: Game? = null var game: Game? = null
private val isRefreshing = AtomicBoolean(false) private val isRefreshing = AtomicBoolean(false)
@ -40,36 +43,47 @@ class AddonViewModel : ViewModel() {
isRefreshing.set(true) isRefreshing.set(true)
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val addonList = mutableListOf<Addon>() val patchList = (
val disabledAddons = NativeConfig.getDisabledAddons(game!!.programId) NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
NativeLibrary.getAddonsForFile(game!!.path, game!!.programId)?.forEach { ?: emptyArray()
val name = it.first.replace("[D] ", "") ).toMutableList()
addonList.add(Addon(!disabledAddons.contains(name), name, it.second)) patchList.sortBy { it.name }
} _patchList.value = patchList
addonList.sortBy { it.title }
_addonList.value = addonList
isRefreshing.set(false) 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() { fun onCloseAddons() {
if (_addonList.value.isEmpty()) { if (_patchList.value.isEmpty()) {
return return
} }
NativeConfig.setDisabledAddons( NativeConfig.setDisabledAddons(
game!!.programId, game!!.programId,
_addonList.value.mapNotNull { _patchList.value.mapNotNull {
if (it.enabled) { if (it.enabled) {
null null
} else { } else {
it.title it.name
} }
}.toTypedArray() }.toTypedArray()
) )
NativeConfig.saveGlobalConfig() NativeConfig.saveGlobalConfig()
_addonList.value.clear() _patchList.value.clear()
game = null 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.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() { class TaskViewModel : ViewModel() {
@ -23,13 +24,28 @@ class TaskViewModel : ViewModel() {
val cancelled: StateFlow<Boolean> get() = _cancelled val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false) 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() { fun clear() {
_result.value = Any() _result.value = Any()
_isComplete.value = false _isComplete.value = false
_isRunning.value = false _isRunning.value = false
_cancelled.value = false _cancelled.value = false
_progress.value = 0.0
_maxProgress.value = 0.0
_message.value = ""
} }
fun setCancelled(value: Boolean) { fun setCancelled(value: Boolean) {
@ -43,7 +59,16 @@ class TaskViewModel : ViewModel() {
_isRunning.value = true _isRunning.value = true
viewModelScope.launch(Dispatchers.IO) { 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 _result.value = res
_isComplete.value = true _isComplete.value = true
_isRunning.value = false _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.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment 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.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel 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.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
@ -369,26 +370,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult 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 filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath = val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/") val cacheFirmwareDir = File("${cacheDir.path}/registered/")
val task: () -> Any = { ProgressDialogFragment.newInstance(
this,
R.string.firmware_installing
) { progressCallback, _ ->
var messageToShow: Any var messageToShow: Any
try { try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir) FileUtil.unzipToInternalStorage(
result.toString(),
cacheFirmwareDir,
progressCallback
)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) { messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
@ -404,18 +402,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
getString(R.string.save_file_imported_success) getString(R.string.save_file_imported_success)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.error("[MainActivity] Firmware install failed - ${e.message}")
messageToShow = getString(R.string.fatal_error) messageToShow = getString(R.string.fatal_error)
} finally { } finally {
cacheFirmwareDir.deleteRecursively() cacheFirmwareDir.deleteRecursively()
} }
messageToShow messageToShow
} }.show(supportFragmentManager, ProgressDialogFragment.TAG)
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
} }
val getAmiiboKey = val getAmiiboKey =
@ -474,11 +467,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this@MainActivity, this@MainActivity,
R.string.verifying_content, R.string.verifying_content,
false false
) { ) { _, _ ->
var updatesMatchProgram = true var updatesMatchProgram = true
for (document in documents) { for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram( val valid = NativeLibrary.doesUpdateMatchProgram(
@ -501,44 +494,42 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
positiveAction = { homeViewModel.setContentToInstall(documents) } positiveAction = { homeViewModel.setContentToInstall(documents) }
) )
} }
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(supportFragmentManager, ProgressDialogFragment.TAG)
} }
private fun installContent(documents: List<Uri>) { private fun installContent(documents: List<Uri>) {
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this@MainActivity, this@MainActivity,
R.string.installing_game_content R.string.installing_game_content
) { ) { progressCallback, messageCallback ->
var installSuccess = 0 var installSuccess = 0
var installOverwrite = 0 var installOverwrite = 0
var errorBaseGame = 0 var errorBaseGame = 0
var errorExtension = 0 var error = 0
var errorOther = 0
documents.forEach { documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it))
when ( when (
NativeLibrary.installFileToNand( InstallResult.from(
it.toString(), NativeLibrary.installFileToNand(
FileUtil.getExtension(it) it.toString(),
progressCallback
)
) )
) { ) {
NativeLibrary.InstallFileToNandResult.Success -> { InstallResult.Success -> {
installSuccess += 1 installSuccess += 1
} }
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { InstallResult.Overwrite -> {
installOverwrite += 1 installOverwrite += 1
} }
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { InstallResult.BaseInstallAttempted -> {
errorBaseGame += 1 errorBaseGame += 1
} }
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { InstallResult.Failure -> {
errorExtension += 1 error += 1
}
else -> {
errorOther += 1
} }
} }
} }
@ -565,7 +556,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
) )
installResult.append(separator) installResult.append(separator)
} }
val errorTotal: Int = errorBaseGame + errorExtension + errorOther val errorTotal: Int = errorBaseGame + error
if (errorTotal > 0) { if (errorTotal > 0) {
installResult.append(separator) installResult.append(separator)
installResult.append( installResult.append(
@ -582,14 +573,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
) )
installResult.append(separator) installResult.append(separator)
} }
if (errorExtension > 0) { if (error > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_file_extension)
)
installResult.append(separator)
}
if (errorOther > 0) {
installResult.append( installResult.append(
getString(R.string.install_game_content_failure_description) getString(R.string.install_game_content_failure_description)
) )
@ -608,7 +592,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
descriptionString = installResult.toString().trim() descriptionString = installResult.toString().trim()
) )
} }
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(supportFragmentManager, ProgressDialogFragment.TAG)
} }
val exportUserData = registerForActivityResult( val exportUserData = registerForActivityResult(
@ -618,16 +602,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, this,
R.string.exporting_user_data, R.string.exporting_user_data,
true true
) { ) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage( val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!), File(DirectoryInitialization.userDirectory!!),
DirectoryInitialization.userDirectory!!, DirectoryInitialization.userDirectory!!,
BufferedOutputStream(contentResolver.openOutputStream(result)), BufferedOutputStream(contentResolver.openOutputStream(result)),
taskViewModel.cancelled, progressCallback,
compression = false compression = false
) )
return@newInstance when (zipResult) { return@newInstance when (zipResult) {
@ -635,7 +619,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
TaskState.Failed -> R.string.export_failed TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled TaskState.Cancelled -> R.string.user_data_export_cancelled
} }
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(supportFragmentManager, ProgressDialogFragment.TAG)
} }
val importUserData = val importUserData =
@ -644,10 +628,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult return@registerForActivityResult
} }
IndeterminateProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, this,
R.string.importing_user_data R.string.importing_user_data
) { ) { progressCallback, _ ->
val checkStream = val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false var isYuzuBackup = false
@ -676,8 +660,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Copy archive to internal storage // Copy archive to internal storage
try { try {
FileUtil.unzipToInternalStorage( FileUtil.unzipToInternalStorage(
BufferedInputStream(contentResolver.openInputStream(result)), result.toString(),
File(DirectoryInitialization.userDirectory!!) File(DirectoryInitialization.userDirectory!!),
progressCallback
) )
} catch (e: Exception) { } catch (e: Exception) {
return@newInstance MessageDialogFragment.newInstance( return@newInstance MessageDialogFragment.newInstance(
@ -694,6 +679,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
driverViewModel.reloadDriverData() driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success) 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.net.Uri
import android.provider.DocumentsContract import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.IOException 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.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.OutputStream
import java.lang.NullPointerException import java.lang.NullPointerException
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.zip.Deflater import java.util.zip.Deflater
@ -104,7 +104,7 @@ object FileUtil {
/** /**
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow * 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. * @param uri Directory uri.
* @return CheapDocument lists. * @return CheapDocument lists.
*/ */
@ -283,12 +283,34 @@ object FileUtil {
/** /**
* Extracts the given zip file into the given directory. * 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) @Throws(SecurityException::class)
fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) { fun unzipToInternalStorage(
ZipInputStream(zipStream).use { zis -> 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 var entry: ZipEntry? = zis.nextEntry
while (entry != null) { while (entry != null) {
if (progressCallback.invoke(totalEntries, progress)) {
return@use
}
val newFile = File(destDir, entry.name) val newFile = File(destDir, entry.name)
val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
@ -304,6 +326,7 @@ object FileUtil {
newFile.outputStream().use { fos -> zis.copyTo(fos) } newFile.outputStream().use { fos -> zis.copyTo(fos) }
} }
entry = zis.nextEntry entry = zis.nextEntry
progress++
} }
} }
} }
@ -313,14 +336,15 @@ object FileUtil {
* @param inputFile File representation of the item that will be zipped * @param inputFile File representation of the item that will be zipped
* @param rootDir Directory containing the inputFile * @param rootDir Directory containing the inputFile
* @param outputStream Stream where the zip file will be output * @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 * @param compression Disables compression if true
*/ */
fun zipFromInternalStorage( fun zipFromInternalStorage(
inputFile: File, inputFile: File,
rootDir: String, rootDir: String,
outputStream: BufferedOutputStream, outputStream: BufferedOutputStream,
cancelled: StateFlow<Boolean>? = null, progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false },
compression: Boolean = true compression: Boolean = true
): TaskState { ): TaskState {
try { try {
@ -330,8 +354,10 @@ object FileUtil {
zos.setLevel(Deflater.NO_COMPRESSION) zos.setLevel(Deflater.NO_COMPRESSION)
} }
var count = 0L
val totalFiles = inputFile.walkTopDown().count().toLong()
inputFile.walkTopDown().forEach { file -> inputFile.walkTopDown().forEach { file ->
if (cancelled?.value == true) { if (progressCallback.invoke(totalFiles, count)) {
return TaskState.Cancelled return TaskState.Cancelled
} }
@ -343,6 +369,7 @@ object FileUtil {
if (file.isFile) { if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) } 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] * Helper function that copies the contents of a DocumentFile folder into a [File]
* @param file [File] representation of the folder to copy into * @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 * @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() file.mkdirs()
if (!this.isDirectory || !file.isDirectory) { if (!this.isDirectory || !file.isDirectory) {
throw IllegalStateException( throw IllegalStateException(
@ -366,7 +398,13 @@ object FileUtil {
) )
} }
var count = 0L
val totalFiles = this.listFiles().size.toLong()
this.listFiles().forEach { this.listFiles().forEach {
if (progressCallback.invoke(totalFiles, count)) {
return
}
val newFile = File(file, it.name!!) val newFile = File(file, it.name!!)
if (it.isDirectory) { if (it.isDirectory) {
newFile.mkdirs() newFile.mkdirs()
@ -381,6 +419,7 @@ object FileUtil {
newFile.outputStream().use { os -> bos.copyTo(os) } 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) @Throws(IOException::class)
fun getStringFromFile(file: File): String = fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8) String(file.readBytes(), StandardCharsets.UTF_8)
@ -434,4 +485,19 @@ object FileUtil {
@Throws(IOException::class) @Throws(IOException::class)
fun getStringFromInputStream(stream: InputStream): String = fun getStringFromInputStream(stream: InputStream): String =
String(stream.readBytes(), StandardCharsets.UTF_8) 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.net.Uri
import android.os.Build import android.os.Build
import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
@ -123,7 +122,7 @@ object GpuDriverHelper {
// Unzip the driver. // Unzip the driver.
try { try {
FileUtil.unzipToInternalStorage( FileUtil.unzipToInternalStorage(
BufferedInputStream(copiedFile.inputStream()), copiedFile.path,
File(driverInstallationPath!!) File(driverInstallationPath!!)
) )
} catch (e: SecurityException) { } catch (e: SecurityException) {
@ -156,7 +155,7 @@ object GpuDriverHelper {
// Unzip the driver to the private installation directory // Unzip the driver to the private installation directory
try { try {
FileUtil.unzipToInternalStorage( FileUtil.unzipToInternalStorage(
BufferedInputStream(driver.inputStream()), driver.path,
File(driverInstallationPath!!) File(driverInstallationPath!!)
) )
} catch (e: SecurityException) { } catch (e: SecurityException) {

View File

@ -42,3 +42,19 @@ double GetJDouble(JNIEnv* env, jobject jdouble) {
jobject ToJDouble(JNIEnv* env, double value) { jobject ToJDouble(JNIEnv* env, double value) {
return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), 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 <string>
#include <jni.h> #include <jni.h>
#include "common/common_types.h"
std::string GetJString(JNIEnv* env, jstring jstr); std::string GetJString(JNIEnv* env, jstring jstr);
jstring ToJString(JNIEnv* env, std::string_view str); 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); double GetJDouble(JNIEnv* env, jobject jdouble);
jobject ToJDouble(JNIEnv* env, double value); 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() { void AndroidConfig::SaveAllValues() {
Save(); SaveValues();
SaveAndroidValues(); 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_portrait_position_field;
static jfieldID s_overlay_control_data_foldable_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 jclass s_double_class;
static jmethodID s_double_constructor; static jmethodID s_double_constructor;
static jfieldID s_double_value_field; 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; static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
namespace IDCache { namespace IDCache {
@ -186,6 +203,38 @@ jfieldID GetOverlayControlDataFoldablePositionField() {
return s_overlay_control_data_foldable_position_field; 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() { jclass GetDoubleClass() {
return s_double_class; return s_double_class;
} }
@ -198,6 +247,30 @@ jfieldID GetDoubleValueField() {
return s_double_value_field; 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 } // namespace IDCache
#ifdef __cplusplus #ifdef __cplusplus
@ -278,12 +351,37 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
env->DeleteLocalRef(overlay_control_data_class); 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"); const jclass double_class = env->FindClass("java/lang/Double");
s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class)); s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V"); s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
s_double_value_field = env->GetFieldID(double_class, "value", "D"); s_double_value_field = env->GetFieldID(double_class, "value", "D");
env->DeleteLocalRef(double_class); 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 // Initialize Android Storage
Common::FS::Android::RegisterCallbacks(env, s_native_library_class); 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_string_class);
env->DeleteGlobalRef(s_pair_class); env->DeleteGlobalRef(s_pair_class);
env->DeleteGlobalRef(s_overlay_control_data_class); env->DeleteGlobalRef(s_overlay_control_data_class);
env->DeleteGlobalRef(s_patch_class);
env->DeleteGlobalRef(s_double_class); env->DeleteGlobalRef(s_double_class);
env->DeleteGlobalRef(s_integer_class);
env->DeleteGlobalRef(s_boolean_class);
// UnInitialize applets // UnInitialize applets
SoftwareKeyboard::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env);

View File

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

View File

@ -17,6 +17,7 @@
#include <core/file_sys/patch_manager.h> #include <core/file_sys/patch_manager.h>
#include <core/file_sys/savedata_factory.h> #include <core/file_sys/savedata_factory.h>
#include <core/loader/nro.h> #include <core/loader/nro.h>
#include <frontend_common/content_manager.h>
#include <jni.h> #include <jni.h>
#include "common/detached_tasks.h" #include "common/detached_tasks.h"
@ -100,67 +101,6 @@ void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
m_native_window = 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, void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
const std::string& custom_driver_dir, const std::string& custom_driver_dir,
const std::string& custom_driver_name, const std::string& custom_driver_name,
@ -410,8 +350,8 @@ void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
handheld->Disconnect(); handheld->Disconnect();
} }
} }
@ -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, int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject instance,
jstring j_file, jstring j_file, jobject jcallback) {
jstring j_file_extension) { auto jlambdaClass = env->GetObjectClass(jcallback);
return EmulationSession::GetInstance().InstallFileToNand(GetJString(env, j_file), auto jlambdaInvokeMethod = env->GetMethodID(
GetJString(env, j_file_extension)); 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, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
@ -824,9 +774,9 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env,
return true; return true;
} }
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, jobject jobj, jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj,
jstring jpath, jstring jpath,
jstring jprogramId) { jstring jprogramId) {
const auto path = GetJString(env, jpath); const auto path = GetJString(env, jpath);
const auto vFile = const auto vFile =
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); 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; FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw); loader->ReadUpdateRaw(update_raw);
auto addons = pm.GetPatchVersionNames(update_raw); auto patches = pm.GetPatches(update_raw);
auto jemptyString = ToJString(env, ""); jobjectArray jpatchArray =
auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), env->NewObjectArray(patches.size(), IDCache::GetPatchClass(), nullptr);
jemptyString, jemptyString);
jobjectArray jaddonsArray =
env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair);
int i = 0; int i = 0;
for (const auto& addon : addons) { for (const auto& patch : patches) {
jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), jobject jpatch = env->NewObject(
ToJString(env, addon.first), ToJString(env, addon.second)); IDCache::GetPatchClass(), IDCache::GetPatchConstructor(), patch.enabled,
env->SetObjectArrayElement(jaddonsArray, i, jaddon); 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; ++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, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,

View File

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

View File

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

View File

@ -147,7 +147,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_build_hash" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
@ -164,7 +164,7 @@
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_build_hash" android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -1,8 +1,30 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/progress_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="24dp" android:orientation="vertical">
app:trackCornerRadius="4dp" />
<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

@ -148,7 +148,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_build_hash" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:paddingVertical="16dp"
@ -165,7 +165,7 @@
android:text="@string/build" /> android:text="@string/build" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_build_hash" android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -14,12 +14,11 @@
android:id="@+id/text_container" android:id="@+id/text_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/addon_switch" android:layout_marginEnd="16dp"
app:layout_constraintEnd_toStartOf="@+id/addon_switch" app:layout_constraintEnd_toStartOf="@+id/addon_checkbox"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/addon_switch"> app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/title" android:id="@+id/title"
@ -42,16 +41,29 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch <com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/addon_switch" android:id="@+id/addon_checkbox"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:focusable="true" android:focusable="true"
android:gravity="center" android:gravity="center"
android:nextFocusLeft="@id/addon_container" android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent" 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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/text_container" app:layout_constraintTop_toTopOf="@+id/addon_checkbox"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintBottom_toBottomOf="@+id/addon_checkbox" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -286,6 +286,7 @@
<string name="custom">Custom</string> <string name="custom">Custom</string>
<string name="notice">Notice</string> <string name="notice">Notice</string>
<string name="import_complete">Import complete</string> <string name="import_complete">Import complete</string>
<string name="more_options">More options</string>
<!-- GPU driver installation --> <!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string> <string name="select_gpu_driver">Select GPU driver</string>
@ -348,6 +349,8 @@
<string name="verifying_content">Verifying content…</string> <string name="verifying_content">Verifying content…</string>
<string name="content_install_notice">Content install notice</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="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 --> <!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is encrypted</string> <string name="loader_error_encrypted">Your ROM is encrypted</string>
@ -410,7 +413,7 @@
<string name="language_japanese" translatable="false">日本語</string> <string name="language_japanese" translatable="false">日本語</string>
<string name="language_english" translatable="false">English</string> <string name="language_english" translatable="false">English</string>
<string name="language_french" translatable="false">Français</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_italian" translatable="false">Italiano</string>
<string name="language_spanish" translatable="false">Español</string> <string name="language_spanish" translatable="false">Español</string>
<string name="language_chinese" translatable="false">简体中文</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 = std::make_unique<OpusDecoder::OpusDecoder>(system);
opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start);
if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { 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; return;
} }
} }

View File

@ -11,6 +11,8 @@
#include "core/guest_memory.h" #include "core/guest_memory.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/hle/kernel/k_process.h"
namespace AudioCore { namespace AudioCore {
using namespace std::literals; using namespace std::literals;
@ -26,7 +28,7 @@ DeviceSession::~DeviceSession() {
} }
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_, 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_) { u64 applet_resource_user_id_, Sink::StreamType type_) {
if (stream) { if (stream) {
Finalize(); Finalize();
@ -37,6 +39,7 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for
channel_count = channel_count_; channel_count = channel_count_;
session_id = session_id_; session_id = session_id_;
handle = handle_; handle = handle_;
handle->Open();
applet_resource_user_id = applet_resource_user_id_; applet_resource_user_id = applet_resource_user_id_;
if (type == Sink::StreamType::In) { if (type == Sink::StreamType::In) {
@ -55,6 +58,11 @@ void DeviceSession::Finalize() {
sink->CloseStream(stream); sink->CloseStream(stream);
stream = nullptr; stream = nullptr;
} }
if (handle) {
handle->Close();
handle = nullptr;
}
} }
void DeviceSession::Start() { void DeviceSession::Start() {
@ -92,7 +100,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
stream->AppendBuffer(new_buffer, tmp_samples); stream->AppendBuffer(new_buffer, tmp_samples);
} else { } else {
Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples( 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); stream->AppendBuffer(new_buffer, samples);
} }
} }
@ -101,7 +109,7 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const { void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
if (type == Sink::StreamType::In) { if (type == Sink::StreamType::In) {
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; 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 Timing
} // namespace Core } // namespace Core
namespace Kernel {
class KProcess;
} // namespace Kernel
namespace AudioCore { namespace AudioCore {
namespace Sink { namespace Sink {
@ -44,13 +48,13 @@ public:
* @param sample_format - Sample format for this device's output. * @param sample_format - Sample format for this device's output.
* @param channel_count - Number of channels for this device (2 or 6). * @param channel_count - Number of channels for this device (2 or 6).
* @param session_id - This session's id. * @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 applet_resource_user_id - Applet resource user id for this device session (unused).
* @param type - Type of this stream (Render, In, Out). * @param type - Type of this stream (Render, In, Out).
* @return Result code for this call. * @return Result code for this call.
*/ */
Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count, 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); Sink::StreamType type);
/** /**
@ -137,8 +141,8 @@ private:
u16 channel_count{}; u16 channel_count{};
/// Session id of this device session /// Session id of this device session
size_t session_id{}; size_t session_id{};
/// Handle of this device session /// Process handle of device memory owner
u32 handle{}; Kernel::KProcess* handle{};
/// Applet resource user id of this device session /// Applet resource user id of this device session
u64 applet_resource_user_id{}; u64 applet_resource_user_id{};
/// Total number of samples played by this device session /// 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, 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)}; auto result{IsConfigValid(device_name, in_params)};
if (result.IsError()) { if (result.IsError()) {
return result; return result;

View File

@ -19,7 +19,8 @@ class System;
namespace Kernel { namespace Kernel {
class KEvent; class KEvent;
} class KProcess;
} // namespace Kernel
namespace AudioCore::AudioIn { namespace AudioCore::AudioIn {
@ -93,12 +94,12 @@ public:
* *
* @param device_name - The name of the requested input device. * @param device_name - The name of the requested input device.
* @param in_params - Input parameters, see AudioInParameter. * @param in_params - Input parameters, see AudioInParameter.
* @param handle - Unused. * @param handle - Process handle.
* @param applet_resource_user_id - Unused. * @param applet_resource_user_id - Unused.
* @return Result code. * @return Result code.
*/ */
Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle, Result Initialize(std::string device_name, const AudioInParameter& in_params,
u64 applet_resource_user_id); Kernel::KProcess* handle, u64 applet_resource_user_id);
/** /**
* Start this system. * Start this system.
@ -244,8 +245,8 @@ public:
private: private:
/// Core system /// Core system
Core::System& system; Core::System& system;
/// (Unused) /// Process handle
u32 handle{}; Kernel::KProcess* handle{};
/// (Unused) /// (Unused)
u64 applet_resource_user_id{}; u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready /// 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; return Service::Audio::ResultInvalidChannelCount;
} }
Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_, Result System::Initialize(std::string device_name, const AudioOutParameter& in_params,
u64 applet_resource_user_id_) { Kernel::KProcess* handle_, u64 applet_resource_user_id_) {
auto result = IsConfigValid(device_name, in_params); auto result = IsConfigValid(device_name, in_params);
if (result.IsError()) { if (result.IsError()) {
return result; return result;

View File

@ -19,7 +19,8 @@ class System;
namespace Kernel { namespace Kernel {
class KEvent; class KEvent;
} class KProcess;
} // namespace Kernel
namespace AudioCore::AudioOut { namespace AudioCore::AudioOut {
@ -84,12 +85,12 @@ public:
* *
* @param device_name - The name of the requested output device. * @param device_name - The name of the requested output device.
* @param in_params - Input parameters, see AudioOutParameter. * @param in_params - Input parameters, see AudioOutParameter.
* @param handle - Unused. * @param handle - Process handle.
* @param applet_resource_user_id - Unused. * @param applet_resource_user_id - Unused.
* @return Result code. * @return Result code.
*/ */
Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle, Result Initialize(std::string device_name, const AudioOutParameter& in_params,
u64 applet_resource_user_id); Kernel::KProcess* handle, u64 applet_resource_user_id);
/** /**
* Start this system. * Start this system.
@ -228,8 +229,8 @@ public:
private: private:
/// Core system /// Core system
Core::System& system; Core::System& system;
/// (Unused) /// Process handle
u32 handle{}; Kernel::KProcess* handle{};
/// (Unused) /// (Unused)
u64 applet_resource_user_id{}; u64 applet_resource_user_id{};
/// Buffer event, signalled when a buffer is ready /// 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) { const VoiceState& voice_state, const s8 channel) {
if (voice_info.mix_id == UnusedMixId) { if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) { 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}; u32 dest_id{0};
while (destination != nullptr) { while (destination != nullptr) {
if (destination->IsConfigured()) { if (destination->IsConfigured()) {
@ -55,7 +55,7 @@ void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
} }
} }
dest_id++; dest_id++;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); destination = splitter_context.GetDestinationData(voice_info.splitter_id, dest_id);
} }
} }
} else { } else {
@ -234,7 +234,7 @@ void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
if (voice_info.mix_id == UnusedMixId) { if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) { if (voice_info.splitter_id != UnusedSplitterId) {
auto i{channel}; 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) { while (destination != nullptr) {
if (destination->IsConfigured()) { if (destination->IsConfigured()) {
const auto mix_id{destination->GetMixId()}; const auto mix_id{destination->GetMixId()};
@ -249,7 +249,7 @@ void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
} }
} }
i += voice_info.channel_count; i += voice_info.channel_count;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); destination = splitter_context.GetDestinationData(voice_info.splitter_id, i);
} }
} }
} else { } else {
@ -591,7 +591,7 @@ void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
if (mix_info.dst_splitter_id != UnusedSplitterId) { if (mix_info.dst_splitter_id != UnusedSplitterId) {
s16 dest_id{0}; s16 dest_id{0};
auto destination{ 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) { while (destination != nullptr) {
if (destination->IsConfigured()) { if (destination->IsConfigured()) {
auto splitter_mix_id{destination->GetMixId()}; auto splitter_mix_id{destination->GetMixId()};
@ -612,7 +612,7 @@ void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
} }
dest_id++; dest_id++;
destination = destination =
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); splitter_context.GetDestinationData(mix_info.dst_splitter_id, dest_id);
} }
} }
} else { } else {

View File

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

View File

@ -9,7 +9,7 @@
namespace AudioCore::Renderer { namespace AudioCore::Renderer {
SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, SplitterDestinationData* SplitterContext::GetDestinationData(const s32 splitter_id,
const s32 destination_id) { const s32 destination_id) {
return splitter_infos[splitter_id].GetData(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. * @param destination_id - Destination index within the splitter.
* @return Pointer to the found destination. May be nullptr. * @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. * Get a splitter from the given index.

View File

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

View File

@ -186,7 +186,7 @@ public:
/** /**
* @returns true if the current setting can be changed while the guest is running. * @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. * @returns A unique number corresponding to the setting.

View File

@ -716,22 +716,23 @@ add_library(core STATIC
hle/service/server_manager.h hle/service/server_manager.h
hle/service/service.cpp hle/service/service.cpp
hle/service/service.h hle/service/service.h
hle/service/set/appln_settings.cpp hle/service/set/setting_formats/appln_settings.cpp
hle/service/set/appln_settings.h hle/service/set/setting_formats/appln_settings.h
hle/service/set/device_settings.cpp hle/service/set/setting_formats/device_settings.cpp
hle/service/set/device_settings.h 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.cpp
hle/service/set/factory_settings_server.h hle/service/set/factory_settings_server.h
hle/service/set/firmware_debug_settings_server.cpp hle/service/set/firmware_debug_settings_server.cpp
hle/service/set/firmware_debug_settings_server.h 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.cpp
hle/service/set/settings.h hle/service/set/settings.h
hle/service/set/settings_server.cpp hle/service/set/settings_server.cpp
hle/service/set/settings_server.h hle/service/set/settings_server.h
hle/service/set/system_settings.cpp hle/service/set/settings_types.h
hle/service/set/system_settings.h
hle/service/set/system_settings_server.cpp hle/service/set/system_settings_server.cpp
hle/service/set/system_settings_server.h hle/service/set/system_settings_server.h
hle/service/sm/sm.cpp hle/service/sm/sm.cpp

View File

@ -22,14 +22,10 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
constexpr size_t MaxRelativeBranch = 128_MiB; constexpr size_t MaxRelativeBranch = 128_MiB;
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32); constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
Patcher::Patcher() : c(m_patch_instructions) {} Patcher::Patcher() : c(m_patch_instructions) {
// The first word of the patch section is always a branch to the first instruction of the
Patcher::~Patcher() = default; // module.
c.dw(0);
void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// Branch to the first instruction of the module.
this->BranchToModule(0);
// Write save context helper function. // Write save context helper function.
c.l(m_save_context); c.l(m_save_context);
@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Write load context helper function. // Write load context helper function.
c.l(m_load_context); c.l(m_load_context);
WriteLoadContext(); 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. // Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size); 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()) { 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 // 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; 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, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) { EntryTrampolines* out_trampolines) {
@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) { if (mode == PatchMode::PreText) {
rc.B(rel.patch_offset - patch_size - rel.module_offset); rc.B(rel.patch_offset - patch_size - rel.module_offset);
} else { } 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) { if (mode == PatchMode::PreText) {
rc.B(patch_size - rel.patch_offset + rel.module_offset); rc.B(patch_size - rel.patch_offset + rel.module_offset);
} else { } 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) { if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_offset; return GetInteger(load_base) + patch_offset;
} else { } 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! // 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); 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), ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel); rel);
} }
// Rewrite PC constants and record post trampolines // 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)}; oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset)); 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)}); 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. // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
// Convert to ordered to preserve this assumption. // 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]}; auto exclusive = Exclusive{text_words[i]};
text_words[i] = exclusive.AsOrdered(); text_words[i] = exclusive.AsOrdered();
} }
// Copy to program image // Remove the patched module size from the total. This is done so total_program_size
if (this->mode == PatchMode::PreText) { // always represents the distance from the currently patched module to the patch section.
std::memcpy(program_image.data(), m_patch_instructions.data(), total_program_size -= image_size;
m_patch_instructions.size() * sizeof(u32));
} else { // Only copy to the program image of the last module
program_image.resize(image_size + patch_size); if (m_relocate_module_index == modules.size()) {
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(), if (this->mode == PatchMode::PreText) {
m_patch_instructions.size() * sizeof(u32)); 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 { 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 // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
// state. // 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 // Host called this location. Save the return address so we can
// unwind the stack properly when jumping back. // unwind the stack properly when jumping back.

View File

@ -31,9 +31,9 @@ public:
explicit Patcher(); explicit Patcher();
~Patcher(); ~Patcher();
void PatchText(const Kernel::PhysicalMemory& program_image, bool PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code); 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); Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept; size_t GetSectionSize() const noexcept;
@ -61,16 +61,16 @@ private:
private: private:
void BranchToPatch(uintptr_t module_dest) { 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) { 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); c.dw(0);
} }
void WriteModulePc(uintptr_t module_dest) { 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); c.dx(0);
} }
@ -84,15 +84,22 @@ private:
uintptr_t module_offset; ///< Offset in bytes from the start of the text section. 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; 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_save_context{};
oaknut::Label m_load_context{}; oaknut::Label m_load_context{};
PatchMode mode{PatchMode::None}; 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 } // namespace Core::NCE

View File

@ -28,6 +28,10 @@ class Memory;
template <typename DTraits> template <typename DTraits>
struct DeviceMemoryManagerAllocator; struct DeviceMemoryManagerAllocator;
struct Asid {
size_t id;
};
template <typename Traits> template <typename Traits>
class DeviceMemoryManager { class DeviceMemoryManager {
using DeviceInterface = typename Traits::DeviceInterface; using DeviceInterface = typename Traits::DeviceInterface;
@ -43,15 +47,14 @@ public:
void AllocateFixed(DAddr start, size_t size); void AllocateFixed(DAddr start, size_t size);
void Free(DAddr start, size_t size); void Free(DAddr start, size_t size);
void Map(DAddr address, VAddr virtual_address, size_t size, size_t process_id, void Map(DAddr address, VAddr virtual_address, size_t size, Asid asid, bool track = false);
bool track = false);
void Unmap(DAddr address, size_t size); void Unmap(DAddr address, size_t size);
void TrackContinuityImpl(DAddr address, VAddr virtual_address, size_t size, size_t process_id); void TrackContinuityImpl(DAddr address, VAddr virtual_address, size_t size, Asid asid);
void TrackContinuity(DAddr address, VAddr virtual_address, size_t size, size_t process_id) { void TrackContinuity(DAddr address, VAddr virtual_address, size_t size, Asid asid) {
std::scoped_lock lk(mapping_guard); std::scoped_lock lk(mapping_guard);
TrackContinuityImpl(address, virtual_address, size, process_id); TrackContinuityImpl(address, virtual_address, size, asid);
} }
// Write / Read // Write / Read
@ -105,8 +108,8 @@ public:
void WriteBlock(DAddr address, const void* src_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); void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size);
size_t RegisterProcess(Memory::Memory* memory); Asid RegisterProcess(Memory::Memory* memory);
void UnregisterProcess(size_t id); void UnregisterProcess(Asid id);
void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta); void UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta);
@ -163,17 +166,17 @@ private:
static constexpr size_t guest_max_as_bits = 39; 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_as_size = 1ULL << guest_max_as_bits;
static constexpr size_t guest_mask = guest_as_size - 1ULL; static constexpr size_t guest_mask = guest_as_size - 1ULL;
static constexpr size_t process_id_start_bit = guest_max_as_bits; static constexpr size_t asid_start_bit = guest_max_as_bits;
std::pair<size_t, VAddr> ExtractCPUBacking(size_t page_index) { std::pair<Asid, VAddr> ExtractCPUBacking(size_t page_index) {
auto content = cpu_backing_address[page_index]; auto content = cpu_backing_address[page_index];
const VAddr address = content & guest_mask; const VAddr address = content & guest_mask;
const size_t process_id = static_cast<size_t>(content >> process_id_start_bit); const Asid asid{static_cast<size_t>(content >> asid_start_bit)};
return std::make_pair(process_id, address); return std::make_pair(asid, address);
} }
void InsertCPUBacking(size_t page_index, VAddr address, size_t process_id) { void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) {
cpu_backing_address[page_index] = address | (process_id << process_id_start_bit); cpu_backing_address[page_index] = address | (asid.id << asid_start_bit);
} }
Common::VirtualBuffer<VAddr> cpu_backing_address; Common::VirtualBuffer<VAddr> cpu_backing_address;
@ -205,4 +208,4 @@ private:
std::mutex mapping_guard; std::mutex mapping_guard;
}; };
} // namespace Core } // namespace Core

View File

@ -215,8 +215,8 @@ void DeviceMemoryManager<Traits>::Free(DAddr start, size_t size) {
template <typename Traits> template <typename Traits>
void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size_t size, void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size_t size,
size_t process_id, bool track) { Asid asid, bool track) {
Core::Memory::Memory* process_memory = registered_processes[process_id]; Core::Memory::Memory* process_memory = registered_processes[asid.id];
size_t start_page_d = address >> Memory::YUZU_PAGEBITS; size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS; size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
std::scoped_lock lk(mapping_guard); std::scoped_lock lk(mapping_guard);
@ -229,7 +229,7 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
} }
auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U; auto phys_addr = static_cast<u32>(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U;
compressed_physical_ptr[start_page_d + i] = phys_addr; compressed_physical_ptr[start_page_d + i] = phys_addr;
InsertCPUBacking(start_page_d + i, new_vaddress, process_id); InsertCPUBacking(start_page_d + i, new_vaddress, asid);
const u32 base_dev = compressed_device_addr[phys_addr - 1U]; const u32 base_dev = compressed_device_addr[phys_addr - 1U];
const u32 new_dev = static_cast<u32>(start_page_d + i); const u32 new_dev = static_cast<u32>(start_page_d + i);
if (base_dev == 0) [[likely]] { if (base_dev == 0) [[likely]] {
@ -244,7 +244,7 @@ void DeviceMemoryManager<Traits>::Map(DAddr address, VAddr virtual_address, size
impl->multi_dev_address.Register(new_dev, start_id); impl->multi_dev_address.Register(new_dev, start_id);
} }
if (track) { if (track) {
TrackContinuityImpl(address, virtual_address, size, process_id); TrackContinuityImpl(address, virtual_address, size, asid);
} }
} }
@ -277,8 +277,8 @@ void DeviceMemoryManager<Traits>::Unmap(DAddr address, size_t size) {
} }
template <typename Traits> template <typename Traits>
void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address, void DeviceMemoryManager<Traits>::TrackContinuityImpl(DAddr address, VAddr virtual_address,
size_t size, size_t process_id) { size_t size, Asid asid) {
Core::Memory::Memory* process_memory = registered_processes[process_id]; Core::Memory::Memory* process_memory = registered_processes[asid.id];
size_t start_page_d = address >> Memory::YUZU_PAGEBITS; size_t start_page_d = address >> Memory::YUZU_PAGEBITS;
size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS; size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS;
uintptr_t last_ptr = 0; uintptr_t last_ptr = 0;
@ -488,8 +488,8 @@ void DeviceMemoryManager<Traits>::WriteBlockUnsafe(DAddr address, const void* sr
} }
template <typename Traits> template <typename Traits>
size_t DeviceMemoryManager<Traits>::RegisterProcess(Memory::Memory* memory_device_inter) { Asid DeviceMemoryManager<Traits>::RegisterProcess(Memory::Memory* memory_device_inter) {
size_t new_id; size_t new_id{};
if (!id_pool.empty()) { if (!id_pool.empty()) {
new_id = id_pool.front(); new_id = id_pool.front();
id_pool.pop_front(); id_pool.pop_front();
@ -498,29 +498,23 @@ size_t DeviceMemoryManager<Traits>::RegisterProcess(Memory::Memory* memory_devic
registered_processes.emplace_back(memory_device_inter); registered_processes.emplace_back(memory_device_inter);
new_id = registered_processes.size() - 1U; new_id = registered_processes.size() - 1U;
} }
return new_id; return Asid{new_id};
} }
template <typename Traits> template <typename Traits>
void DeviceMemoryManager<Traits>::UnregisterProcess(size_t id) { void DeviceMemoryManager<Traits>::UnregisterProcess(Asid asid) {
registered_processes[id] = nullptr; registered_processes[asid.id] = nullptr;
id_pool.push_front(id); id_pool.push_front(asid.id);
} }
template <typename Traits> template <typename Traits>
void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) { void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) {
bool locked = false; std::unique_lock<std::mutex> lk(counter_guard, std::defer_lock);
auto lock = [&] { const auto Lock = [&] {
if (!locked) { if (!lk) {
counter_guard.lock(); lk.lock();
locked = true;
} }
}; };
SCOPE_EXIT({
if (locked) {
counter_guard.unlock();
}
});
u64 uncache_begin = 0; u64 uncache_begin = 0;
u64 cache_begin = 0; u64 cache_begin = 0;
u64 uncache_bytes = 0; u64 uncache_bytes = 0;
@ -530,9 +524,9 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE); const size_t page_end = Common::DivCeil(addr + size, Memory::YUZU_PAGESIZE);
size_t page = addr >> Memory::YUZU_PAGEBITS; size_t page = addr >> Memory::YUZU_PAGEBITS;
auto [process_id, base_vaddress] = ExtractCPUBacking(page); auto [asid, base_vaddress] = ExtractCPUBacking(page);
size_t vpage = base_vaddress >> Memory::YUZU_PAGEBITS; size_t vpage = base_vaddress >> Memory::YUZU_PAGEBITS;
auto* memory_device_inter = registered_processes[process_id]; auto* memory_device_inter = registered_processes[asid.id];
for (; page != page_end; ++page) { for (; page != page_end; ++page) {
std::atomic_uint8_t& count = cached_pages->at(page >> 3).Count(page); std::atomic_uint8_t& count = cached_pages->at(page >> 3).Count(page);
@ -555,7 +549,7 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
} }
uncache_bytes += Memory::YUZU_PAGESIZE; uncache_bytes += Memory::YUZU_PAGESIZE;
} else if (uncache_bytes > 0) { } else if (uncache_bytes > 0) {
lock(); Lock();
MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS, MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS,
uncache_bytes, false); uncache_bytes, false);
uncache_bytes = 0; uncache_bytes = 0;
@ -566,7 +560,7 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
} }
cache_bytes += Memory::YUZU_PAGESIZE; cache_bytes += Memory::YUZU_PAGESIZE;
} else if (cache_bytes > 0) { } else if (cache_bytes > 0) {
lock(); Lock();
MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes, MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes,
true); true);
cache_bytes = 0; cache_bytes = 0;
@ -574,12 +568,12 @@ void DeviceMemoryManager<Traits>::UpdatePagesCachedCount(DAddr addr, size_t size
vpage++; vpage++;
} }
if (uncache_bytes > 0) { if (uncache_bytes > 0) {
lock(); Lock();
MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS, uncache_bytes, MarkRegionCaching(memory_device_inter, uncache_begin << Memory::YUZU_PAGEBITS, uncache_bytes,
false); false);
} }
if (cache_bytes > 0) { if (cache_bytes > 0) {
lock(); Lock();
MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes, MarkRegionCaching(memory_device_inter, cache_begin << Memory::YUZU_PAGEBITS, cache_bytes,
true); true);
} }

View File

@ -466,12 +466,12 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
return romfs; return romfs;
} }
PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const { std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
if (title_id == 0) { if (title_id == 0) {
return {}; return {};
} }
std::map<std::string, std::string, std::less<>> out; std::vector<Patch> out;
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates // Game Updates
@ -482,20 +482,28 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
const auto update_disabled = const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); 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) { if (nacp != nullptr) {
out.insert_or_assign(update_label, nacp->GetVersionString()); update_patch.version = nacp->GetVersionString();
out.push_back(update_patch);
} else { } else {
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = content_provider.GetEntryVersion(update_tid); const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) { if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, ""); out.push_back(update_patch);
} else { } 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) { } 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 = const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); 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()) { if (!types.empty()) {
const auto mod_disabled = const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); 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 = const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); 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; return out;

View File

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

View File

@ -47,7 +47,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
// Connect controllers based on the following priority list from highest to lowest priority: // Connect controllers based on the following priority list from highest to lowest priority:
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
if (parameters.allow_pro_controller) { if (parameters.allow_pro_controller) {
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
controller->Connect(true); controller->Connect(true);
} else if (parameters.allow_dual_joycons) { } else if (parameters.allow_dual_joycons) {
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual); controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);

View File

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

View File

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

View File

@ -435,69 +435,14 @@ Result KPageTableBase::FinalizeProcess() {
// Only process tables should be finalized. // Only process tables should be finalized.
ASSERT(!this->IsKernel()); ASSERT(!this->IsKernel());
// HLE processes don't have memory mapped.
R_SUCCEED_IF(m_impl == nullptr);
// NOTE: Here Nintendo calls an unknown OnFinalize function. // NOTE: Here Nintendo calls an unknown OnFinalize function.
// this->OnFinalize(); // this->OnFinalize();
// NOTE: Here Nintendo calls a second unknown OnFinalize function. // NOTE: Here Nintendo calls a second unknown OnFinalize function.
// this->OnFinalize2(); // this->OnFinalize2();
// Get implementation objects. // NOTE: Here Nintendo does a page table walk to discover heap pages to free.
auto& impl = this->GetImpl(); // We will use the block manager finalization below to free them.
auto& mm = m_kernel.MemoryManager();
// Traverse, freeing all pages.
{
// Get the address space size.
const size_t as_size = this->GetAddressSpaceSize();
// Begin the traversal.
TraversalContext context;
TraversalEntry cur_entry = {
.phys_addr = 0,
.block_size = 0,
};
bool cur_valid = false;
TraversalEntry next_entry;
bool next_valid;
size_t tot_size = 0;
next_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context),
this->GetAddressSpaceStart());
// Iterate over entries.
while (true) {
if ((!next_valid && !cur_valid) ||
(next_valid && cur_valid &&
next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
cur_entry.block_size += next_entry.block_size;
} else {
if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
}
// Update tracking variables.
tot_size += cur_entry.block_size;
cur_entry = next_entry;
cur_valid = next_valid;
}
if (cur_entry.block_size + tot_size >= as_size) {
break;
}
next_valid =
impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context));
}
// Handle the last block.
if (cur_valid && IsHeapPhysicalAddressForFinalize(cur_entry.phys_addr)) {
mm.Close(cur_entry.phys_addr, cur_entry.block_size / PageSize);
}
}
R_SUCCEED(); R_SUCCEED();
} }
@ -505,14 +450,24 @@ Result KPageTableBase::FinalizeProcess() {
void KPageTableBase::Finalize() { void KPageTableBase::Finalize() {
this->FinalizeProcess(); this->FinalizeProcess();
auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) { auto BlockCallback = [&](KProcessAddress addr, u64 size) {
if (m_impl->fastmem_arena) { if (m_impl->fastmem_arena) {
m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size, false); 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. // 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. // Free any unsafe mapped memory.
if (m_mapped_unsafe_physical_memory) { if (m_mapped_unsafe_physical_memory) {

View File

@ -1239,10 +1239,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
#ifdef HAS_NCE #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; auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment(); const auto& code = code_set.CodeSegment();
const auto& patch = code_set.PatchSegment();
buffer.Protect(GetInteger(base_addr + code.addr), code.size, buffer.Protect(GetInteger(base_addr + code.addr), code.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute); Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,

View File

@ -112,7 +112,14 @@ struct KernelCore::Impl {
old_process->Close(); 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_object_id = 0;
next_kernel_process_id = KProcess::InitialProcessIdMin; next_kernel_process_id = KProcess::InitialProcessIdMin;

View File

@ -11,6 +11,7 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/polyfill_ranges.h" #include "common/polyfill_ranges.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/string_util.h"
#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/acc/profile_manager.h"
namespace Service::Account { namespace Service::Account {
@ -61,9 +62,7 @@ ProfileManager::ProfileManager() {
OpenUser(*GetUser(current)); OpenUser(*GetUser(current));
} }
ProfileManager::~ProfileManager() { ProfileManager::~ProfileManager() = default;
WriteUserSaveFile();
}
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles /// internal management of the users profiles
@ -113,6 +112,8 @@ Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username)
return ERROR_USER_ALREADY_EXISTS; return ERROR_USER_ALREADY_EXISTS;
} }
is_save_needed = true;
return AddUser({ return AddUser({
.user_uuid = uuid, .user_uuid = uuid,
.username = username, .username = username,
@ -164,6 +165,22 @@ std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user)
return GetUserIndex(user.user_uuid); 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:* /// 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 { bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
if (!index || index >= MAX_USERS) { if (!index || index >= MAX_USERS) {
@ -326,6 +343,9 @@ bool ProfileManager::RemoveUser(UUID uuid) {
profiles[*index] = ProfileInfo{}; profiles[*index] = ProfileInfo{};
std::stable_partition(profiles.begin(), profiles.end(), std::stable_partition(profiles.begin(), profiles.end(),
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); }); [](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
is_save_needed = true;
return true; return true;
} }
@ -340,6 +360,8 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
profile.username = profile_new.username; profile.username = profile_new.username;
profile.creation_time = profile_new.timestamp; profile.creation_time = profile_new.timestamp;
is_save_needed = true;
return true; return true;
} }
@ -348,6 +370,7 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
const auto index = GetUserIndex(uuid); const auto index = GetUserIndex(uuid);
if (index.has_value() && SetProfileBase(uuid, profile_new)) { if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new; profiles[*index].data = data_new;
is_save_needed = true;
return true; return true;
} }
@ -391,6 +414,10 @@ void ProfileManager::ParseUserSaveFile() {
} }
void ProfileManager::WriteUserSaveFile() { void ProfileManager::WriteUserSaveFile() {
if (!is_save_needed) {
return;
}
ProfileDataRaw raw{}; ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) { 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)) { 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 " LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
"made in current session will be saved."); "made in current session will be saved.");
return;
} }
is_save_needed = false;
} }
}; // namespace Service::Account }; // namespace Service::Account

View File

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

View File

@ -18,11 +18,11 @@ using namespace AudioCore::AudioIn;
class IAudioIn final : public ServiceFramework<IAudioIn> { class IAudioIn final : public ServiceFramework<IAudioIn> {
public: public:
explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id, explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
const std::string& device_name, const AudioInParameter& in_params, u32 handle, const std::string& device_name, const AudioInParameter& in_params,
u64 applet_resource_user_id) Kernel::KProcess* handle, u64 applet_resource_user_id)
: ServiceFramework{system_, "IAudioIn"}, : ServiceFramework{system_, "IAudioIn"},
service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")}, 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 // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &IAudioIn::GetAudioInState, "GetAudioInState"}, {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
@ -45,6 +45,8 @@ public:
RegisterHandlers(functions); RegisterHandlers(functions);
process->Open();
if (impl->GetSystem() if (impl->GetSystem()
.Initialize(device_name, in_params, handle, applet_resource_user_id) .Initialize(device_name, in_params, handle, applet_resource_user_id)
.IsError()) { .IsError()) {
@ -55,6 +57,7 @@ public:
~IAudioIn() override { ~IAudioIn() override {
impl->Free(); impl->Free();
service_context.CloseEvent(event); service_context.CloseEvent(event);
process->Close();
} }
[[nodiscard]] std::shared_ptr<In> GetImpl() { [[nodiscard]] std::shared_ptr<In> GetImpl() {
@ -196,6 +199,7 @@ private:
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;
Kernel::KEvent* event; Kernel::KEvent* event;
Kernel::KProcess* process;
std::shared_ptr<AudioCore::AudioIn::In> impl; std::shared_ptr<AudioCore::AudioIn::In> impl;
Common::ScratchBuffer<u64> released_buffer; Common::ScratchBuffer<u64> released_buffer;
}; };
@ -267,6 +271,14 @@ void AudInU::OpenAudioIn(HLERequestContext& ctx) {
auto device_name = Common::StringFromBuffer(device_name_data); auto device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)}; 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}; std::scoped_lock l{impl->mutex};
auto link{impl->LinkToManager()}; auto link{impl->LinkToManager()};
if (link.IsError()) { 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, LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions); impl->num_free_sessions);
auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, auto audio_in =
in_params, handle, applet_resource_user_id); 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->sessions[new_session_id] = audio_in->GetImpl();
impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; 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 device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)}; 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}; std::scoped_lock l{impl->mutex};
auto link{impl->LinkToManager()}; auto link{impl->LinkToManager()};
if (link.IsError()) { 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, LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions); impl->num_free_sessions);
auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name, auto audio_in =
in_params, handle, applet_resource_user_id); 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->sessions[new_session_id] = audio_in->GetImpl();
impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; 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: public:
explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager, explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
size_t session_id, const std::string& device_name, 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"}, : 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)} { impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
// clang-format off // clang-format off
@ -50,11 +51,14 @@ public:
}; };
// clang-format on // clang-format on
RegisterHandlers(functions); RegisterHandlers(functions);
process->Open();
} }
~IAudioOut() override { ~IAudioOut() override {
impl->Free(); impl->Free();
service_context.CloseEvent(event); service_context.CloseEvent(event);
process->Close();
} }
[[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() { [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
@ -206,6 +210,7 @@ private:
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;
Kernel::KEvent* event; Kernel::KEvent* event;
Kernel::KProcess* process;
std::shared_ptr<AudioCore::AudioOut::Out> impl; std::shared_ptr<AudioCore::AudioOut::Out> impl;
Common::ScratchBuffer<u64> released_buffer; Common::ScratchBuffer<u64> released_buffer;
}; };
@ -257,6 +262,14 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) {
auto device_name = Common::StringFromBuffer(device_name_data); auto device_name = Common::StringFromBuffer(device_name_data);
auto handle{ctx.GetCopyHandle(0)}; 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()}; auto link{impl->LinkToManager()};
if (link.IsError()) { if (link.IsError()) {
LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager"); 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, LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
impl->num_free_sessions); impl->num_free_sessions);
auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, auto audio_out =
in_params, handle, applet_resource_user_id); std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, in_params,
result = audio_out->GetImpl()->GetSystem().Initialize(device_name, in_params, handle, process.GetPointerUnsafe(), applet_resource_user_id);
applet_resource_user_id); result = audio_out->GetImpl()->GetSystem().Initialize(
device_name, in_params, process.GetPointerUnsafe(), applet_resource_user_id);
if (result.IsError()) { if (result.IsError()) {
LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};

View File

@ -40,7 +40,7 @@ Result SaveDataController::CreateSaveData(FileSys::VirtualDir* out_save_data,
FileSys::SaveDataSpaceId space, FileSys::SaveDataSpaceId space,
const FileSys::SaveDataAttribute& attribute) { const FileSys::SaveDataAttribute& attribute) {
LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space, LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space,
save_struct.DebugInfo()); attribute.DebugInfo());
auto save_data = factory->Create(space, attribute); auto save_data = factory->Create(space, attribute);
if (save_data == nullptr) { if (save_data == nullptr) {

View File

@ -20,12 +20,13 @@ void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
std::shared_ptr<ResourceManager> resource_manager = std::make_shared<ResourceManager>(system); std::shared_ptr<ResourceManager> resource_manager = std::make_shared<ResourceManager>(system);
std::shared_ptr<HidFirmwareSettings> firmware_settings = std::shared_ptr<HidFirmwareSettings> firmware_settings =
std::make_shared<HidFirmwareSettings>(); std::make_shared<HidFirmwareSettings>(system);
// TODO: Remove this hack when am is emulated properly. // TODO: Remove this hack when am is emulated properly.
resource_manager->Initialize(); resource_manager->Initialize();
resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(), resource_manager->RegisterAppletResourceUserId(system.ApplicationProcess()->GetProcessId(),
true); true);
resource_manager->SetAruidValidForVibration(system.ApplicationProcess()->GetProcessId(), true);
server_manager->RegisterNamedService( server_manager->RegisterNamedService(
"hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings)); "hid", std::make_shared<IHidServer>(system, resource_manager, firmware_settings));

View File

@ -22,12 +22,16 @@
#include "hid_core/resources/mouse/mouse.h" #include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/npad/npad.h" #include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h" #include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/npad/npad_vibration.h"
#include "hid_core/resources/palma/palma.h" #include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/six_axis/console_six_axis.h" #include "hid_core/resources/six_axis/console_six_axis.h"
#include "hid_core/resources/six_axis/seven_six_axis.h" #include "hid_core/resources/six_axis/seven_six_axis.h"
#include "hid_core/resources/six_axis/six_axis.h" #include "hid_core/resources/six_axis/six_axis.h"
#include "hid_core/resources/touch_screen/gesture.h" #include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h" #include "hid_core/resources/touch_screen/touch_screen.h"
#include "hid_core/resources/vibration/gc_vibration_device.h"
#include "hid_core/resources/vibration/n64_vibration_device.h"
#include "hid_core/resources/vibration/vibration_device.h"
namespace Service::HID { namespace Service::HID {
@ -38,7 +42,7 @@ public:
: ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) { : ServiceFramework{system_, "IActiveVibrationDeviceList"}, resource_manager(resource) {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"}, {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"},
}; };
// clang-format on // clang-format on
@ -46,22 +50,49 @@ public:
} }
private: private:
void InitializeVibrationDevice(HLERequestContext& ctx) { void ActivateVibrationDevice(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
if (resource_manager != nullptr && resource_manager->GetNpad()) {
resource_manager->GetNpad()->InitializeVibrationDevice(vibration_device_handle);
}
LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}",
vibration_device_handle.npad_type, vibration_device_handle.npad_id, vibration_device_handle.npad_type, vibration_device_handle.npad_id,
vibration_device_handle.device_index); vibration_device_handle.device_index);
const auto result = ActivateVibrationDeviceImpl(vibration_device_handle);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
Result ActivateVibrationDeviceImpl(const Core::HID::VibrationDeviceHandle& handle) {
std::scoped_lock lock{mutex};
const Result is_valid = IsVibrationHandleValid(handle);
if (is_valid.IsError()) {
return is_valid;
}
for (std::size_t i = 0; i < list_size; i++) {
if (handle.device_index == vibration_device_list[i].device_index &&
handle.npad_id == vibration_device_list[i].npad_id &&
handle.npad_type == vibration_device_list[i].npad_type) {
return ResultSuccess;
}
}
if (list_size == vibration_device_list.size()) {
return ResultVibrationDeviceIndexOutOfRange;
}
const Result result = resource_manager->GetVibrationDevice(handle)->Activate();
if (result.IsError()) {
return result;
}
vibration_device_list[list_size++] = handle;
return ResultSuccess;
}
mutable std::mutex mutex;
std::size_t list_size{};
std::array<Core::HID::VibrationDeviceHandle, 0x100> vibration_device_list{};
std::shared_ptr<ResourceManager> resource_manager; std::shared_ptr<ResourceManager> resource_manager;
}; };
@ -153,7 +184,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
{209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, {209, &IHidServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"}, {210, &IHidServer::EndPermitVibrationSession, "EndPermitVibrationSession"},
{211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, {211, &IHidServer::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
{212, nullptr, "SendVibrationValueInBool"}, {212, &IHidServer::SendVibrationValueInBool, "SendVibrationValueInBool"},
{300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, {300, &IHidServer::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {301, &IHidServer::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, {302, &IHidServer::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"},
@ -1492,59 +1523,13 @@ void IHidServer::ClearNpadCaptureButtonAssignment(HLERequestContext& ctx) {
void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) { void IHidServer::GetVibrationDeviceInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()}; const auto vibration_device_handle{rp.PopRaw<Core::HID::VibrationDeviceHandle>()};
const auto controller = GetResourceManager()->GetNpad();
Core::HID::VibrationDeviceInfo vibration_device_info; Core::HID::VibrationDeviceInfo vibration_device_info{};
bool check_device_index = false; const auto result = GetResourceManager()->GetVibrationDeviceInfo(vibration_device_info,
vibration_device_handle);
switch (vibration_device_handle.npad_type) {
case Core::HID::NpadStyleIndex::ProController:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::JoyconLeft:
case Core::HID::NpadStyleIndex::JoyconRight:
vibration_device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
check_device_index = true;
break;
case Core::HID::NpadStyleIndex::GameCube:
vibration_device_info.type = Core::HID::VibrationDeviceType::GcErm;
break;
case Core::HID::NpadStyleIndex::N64:
vibration_device_info.type = Core::HID::VibrationDeviceType::N64;
break;
default:
vibration_device_info.type = Core::HID::VibrationDeviceType::Unknown;
break;
}
vibration_device_info.position = Core::HID::VibrationDevicePosition::None;
if (check_device_index) {
switch (vibration_device_handle.device_index) {
case Core::HID::DeviceIndex::Left:
vibration_device_info.position = Core::HID::VibrationDevicePosition::Left;
break;
case Core::HID::DeviceIndex::Right:
vibration_device_info.position = Core::HID::VibrationDevicePosition::Right;
break;
case Core::HID::DeviceIndex::None:
default:
ASSERT_MSG(false, "DeviceIndex should never be None!");
break;
}
}
LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}",
vibration_device_info.type, vibration_device_info.position);
const auto result = IsVibrationHandleValid(vibration_device_handle);
if (result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(result);
rb.PushRaw(vibration_device_info); rb.PushRaw(vibration_device_info);
} }
@ -1560,16 +1545,16 @@ void IHidServer::SendVibrationValue(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()}; const auto parameters{rp.PopRaw<Parameters>()};
GetResourceManager()->GetNpad()->VibrateController(parameters.applet_resource_user_id,
parameters.vibration_device_handle,
parameters.vibration_value);
LOG_DEBUG(Service_HID, LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.vibration_device_handle.npad_type, parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id, parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
GetResourceManager()->SendVibrationValue(parameters.applet_resource_user_id,
parameters.vibration_device_handle,
parameters.vibration_value);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
@ -1591,10 +1576,28 @@ void IHidServer::GetActualVibrationValue(HLERequestContext& ctx) {
parameters.vibration_device_handle.npad_id, parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
bool has_active_aruid{};
NpadVibrationDevice* device{nullptr};
Core::HID::VibrationValue vibration_value{};
Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
has_active_aruid);
if (result.IsSuccess() && has_active_aruid) {
result = IsVibrationHandleValid(parameters.vibration_device_handle);
}
if (result.IsSuccess() && has_active_aruid) {
device = GetResourceManager()->GetNSVibrationDevice(parameters.vibration_device_handle);
}
if (device != nullptr) {
result = device->GetActualVibrationValue(vibration_value);
}
if (result.IsError()) {
vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE;
}
IPC::ResponseBuilder rb{ctx, 6}; IPC::ResponseBuilder rb{ctx, 6};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushRaw(GetResourceManager()->GetNpad()->GetLastVibration( rb.PushRaw(vibration_value);
parameters.applet_resource_user_id, parameters.vibration_device_handle));
} }
void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) { void IHidServer::CreateActiveVibrationDeviceList(HLERequestContext& ctx) {
@ -1609,25 +1612,27 @@ void IHidServer::PermitVibration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto can_vibrate{rp.Pop<bool>()}; const auto can_vibrate{rp.Pop<bool>()};
// nnSDK saves this value as a float. Since it can only be 1.0f or 0.0f we simplify this value
// by converting it to a bool
Settings::values.vibration_enabled.SetValue(can_vibrate);
LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume(
can_vibrate ? 1.0f : 0.0f);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) { void IHidServer::IsVibrationPermitted(HLERequestContext& ctx) {
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
// nnSDK checks if a float is greater than zero. We return the bool we stored earlier f32 master_volume{};
const auto is_enabled = Settings::values.vibration_enabled.GetValue(); const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume(
master_volume);
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(result);
rb.Push(is_enabled); rb.Push(master_volume > 0.0f);
} }
void IHidServer::SendVibrationValues(HLERequestContext& ctx) { void IHidServer::SendVibrationValues(HLERequestContext& ctx) {
@ -1645,13 +1650,22 @@ void IHidServer::SendVibrationValues(HLERequestContext& ctx) {
auto vibration_values = std::span( auto vibration_values = std::span(
reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count); reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count);
GetResourceManager()->GetNpad()->VibrateControllers(applet_resource_user_id,
vibration_device_handles, vibration_values);
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
Result result = ResultSuccess;
if (handle_count != vibration_count) {
result = ResultVibrationArraySizeMismatch;
}
for (std::size_t i = 0; i < handle_count; i++) {
if (result.IsSuccess()) {
result = GetResourceManager()->SendVibrationValue(
applet_resource_user_id, vibration_device_handles[i], vibration_values[i]);
}
}
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) { void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
@ -1666,43 +1680,6 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()}; const auto parameters{rp.PopRaw<Parameters>()};
/**
* Note: This uses yuzu-specific behavior such that the StopHard command produces
* vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below,
* in order to differentiate between Stop and StopHard commands.
* This is done to reuse the controller vibration functions made for regular controllers.
*/
const auto vibration_value = [parameters] {
switch (parameters.gc_erm_command) {
case Core::HID::VibrationGcErmCommand::Stop:
return Core::HID::VibrationValue{
.low_amplitude = 0.0f,
.low_frequency = 160.0f,
.high_amplitude = 0.0f,
.high_frequency = 320.0f,
};
case Core::HID::VibrationGcErmCommand::Start:
return Core::HID::VibrationValue{
.low_amplitude = 1.0f,
.low_frequency = 160.0f,
.high_amplitude = 1.0f,
.high_frequency = 320.0f,
};
case Core::HID::VibrationGcErmCommand::StopHard:
return Core::HID::VibrationValue{
.low_amplitude = 0.0f,
.low_frequency = 0.0f,
.high_amplitude = 0.0f,
.high_frequency = 0.0f,
};
default:
return Core::HID::DEFAULT_VIBRATION_VALUE;
}
}();
GetResourceManager()->GetNpad()->VibrateController(
parameters.applet_resource_user_id, parameters.vibration_device_handle, vibration_value);
LOG_DEBUG(Service_HID, LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, " "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
"gc_erm_command={}", "gc_erm_command={}",
@ -1711,8 +1688,23 @@ void IHidServer::SendVibrationGcErmCommand(HLERequestContext& ctx) {
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id, parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
parameters.gc_erm_command); parameters.gc_erm_command);
bool has_active_aruid{};
NpadGcVibrationDevice* gc_device{nullptr};
Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
has_active_aruid);
if (result.IsSuccess() && has_active_aruid) {
result = IsVibrationHandleValid(parameters.vibration_device_handle);
}
if (result.IsSuccess() && has_active_aruid) {
gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle);
}
if (gc_device != nullptr) {
result = gc_device->SendVibrationGcErmCommand(parameters.gc_erm_command);
}
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) { void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
@ -1725,33 +1717,31 @@ void IHidServer::GetActualVibrationGcErmCommand(HLERequestContext& ctx) {
const auto parameters{rp.PopRaw<Parameters>()}; const auto parameters{rp.PopRaw<Parameters>()};
const auto last_vibration = GetResourceManager()->GetNpad()->GetLastVibration(
parameters.applet_resource_user_id, parameters.vibration_device_handle);
const auto gc_erm_command = [last_vibration] {
if (last_vibration.low_amplitude != 0.0f || last_vibration.high_amplitude != 0.0f) {
return Core::HID::VibrationGcErmCommand::Start;
}
/**
* Note: This uses yuzu-specific behavior such that the StopHard command produces
* vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function
* SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
* This is done to reuse the controller vibration functions made for regular controllers.
*/
if (last_vibration.low_frequency == 0.0f && last_vibration.high_frequency == 0.0f) {
return Core::HID::VibrationGcErmCommand::StopHard;
}
return Core::HID::VibrationGcErmCommand::Stop;
}();
LOG_DEBUG(Service_HID, LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.vibration_device_handle.npad_type, parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id, parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
bool has_active_aruid{};
NpadGcVibrationDevice* gc_device{nullptr};
Core::HID::VibrationGcErmCommand gc_erm_command{};
Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
has_active_aruid);
if (result.IsSuccess() && has_active_aruid) {
result = IsVibrationHandleValid(parameters.vibration_device_handle);
}
if (result.IsSuccess() && has_active_aruid) {
gc_device = GetResourceManager()->GetGcVibrationDevice(parameters.vibration_device_handle);
}
if (gc_device != nullptr) {
result = gc_device->GetActualVibrationGcErmCommand(gc_erm_command);
}
if (result.IsError()) {
gc_erm_command = Core::HID::VibrationGcErmCommand::Stop;
}
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.PushEnum(gc_erm_command); rb.PushEnum(gc_erm_command);
@ -1761,21 +1751,24 @@ void IHidServer::BeginPermitVibrationSession(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()}; const auto applet_resource_user_id{rp.Pop<u64>()};
GetResourceManager()->GetNpad()->SetPermitVibrationSession(true);
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession(
applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) { void IHidServer::EndPermitVibrationSession(HLERequestContext& ctx) {
GetResourceManager()->GetNpad()->SetPermitVibrationSession(false);
LOG_DEBUG(Service_HID, "called"); LOG_DEBUG(Service_HID, "called");
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession();
IPC::ResponseBuilder rb{ctx, 2}; IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess); rb.Push(result);
} }
void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) { void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) {
@ -1795,10 +1788,61 @@ void IHidServer::IsVibrationDeviceMounted(HLERequestContext& ctx) {
parameters.vibration_device_handle.npad_id, parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
bool is_mounted{};
NpadVibrationBase* device{nullptr};
Result result = IsVibrationHandleValid(parameters.vibration_device_handle);
if (result.IsSuccess()) {
device = GetResourceManager()->GetVibrationDevice(parameters.vibration_device_handle);
}
if (device != nullptr) {
is_mounted = device->IsVibrationMounted();
}
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(result);
rb.Push(GetResourceManager()->GetNpad()->IsVibrationDeviceMounted( rb.Push(is_mounted);
parameters.applet_resource_user_id, parameters.vibration_device_handle)); }
void IHidServer::SendVibrationValueInBool(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Core::HID::VibrationDeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
bool is_vibrating;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
"is_vibrating={}",
parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
parameters.is_vibrating);
bool has_active_aruid{};
NpadN64VibrationDevice* n64_device{nullptr};
Result result = GetResourceManager()->IsVibrationAruidActive(parameters.applet_resource_user_id,
has_active_aruid);
if (result.IsSuccess() && has_active_aruid) {
result = IsVibrationHandleValid(parameters.vibration_device_handle);
}
if (result.IsSuccess() && has_active_aruid) {
n64_device =
GetResourceManager()->GetN64VibrationDevice(parameters.vibration_device_handle);
}
if (n64_device != nullptr) {
result = n64_device->SendValueInBool(parameters.is_vibrating);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
} }
void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) { void IHidServer::ActivateConsoleSixAxisSensor(HLERequestContext& ctx) {

View File

@ -97,6 +97,7 @@ private:
void BeginPermitVibrationSession(HLERequestContext& ctx); void BeginPermitVibrationSession(HLERequestContext& ctx);
void EndPermitVibrationSession(HLERequestContext& ctx); void EndPermitVibrationSession(HLERequestContext& ctx);
void IsVibrationDeviceMounted(HLERequestContext& ctx); void IsVibrationDeviceMounted(HLERequestContext& ctx);
void SendVibrationValueInBool(HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(HLERequestContext& ctx); void ActivateConsoleSixAxisSensor(HLERequestContext& ctx);
void StartConsoleSixAxisSensor(HLERequestContext& ctx); void StartConsoleSixAxisSensor(HLERequestContext& ctx);
void StopConsoleSixAxisSensor(HLERequestContext& ctx); void StopConsoleSixAxisSensor(HLERequestContext& ctx);

View File

@ -7,6 +7,7 @@
#include "hid_core/resource_manager.h" #include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h" #include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/npad/npad_types.h" #include "hid_core/resources/npad/npad_types.h"
#include "hid_core/resources/npad/npad_vibration.h"
#include "hid_core/resources/palma/palma.h" #include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/touch_screen/touch_screen.h" #include "hid_core/resources/touch_screen/touch_screen.h"
@ -67,14 +68,14 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
{501, &IHidSystemServer::RegisterAppletResourceUserId, "RegisterAppletResourceUserId"}, {501, &IHidSystemServer::RegisterAppletResourceUserId, "RegisterAppletResourceUserId"},
{502, &IHidSystemServer::UnregisterAppletResourceUserId, "UnregisterAppletResourceUserId"}, {502, &IHidSystemServer::UnregisterAppletResourceUserId, "UnregisterAppletResourceUserId"},
{503, &IHidSystemServer::EnableAppletToGetInput, "EnableAppletToGetInput"}, {503, &IHidSystemServer::EnableAppletToGetInput, "EnableAppletToGetInput"},
{504, nullptr, "SetAruidValidForVibration"}, {504, &IHidSystemServer::SetAruidValidForVibration, "SetAruidValidForVibration"},
{505, &IHidSystemServer::EnableAppletToGetSixAxisSensor, "EnableAppletToGetSixAxisSensor"}, {505, &IHidSystemServer::EnableAppletToGetSixAxisSensor, "EnableAppletToGetSixAxisSensor"},
{506, &IHidSystemServer::EnableAppletToGetPadInput, "EnableAppletToGetPadInput"}, {506, &IHidSystemServer::EnableAppletToGetPadInput, "EnableAppletToGetPadInput"},
{507, &IHidSystemServer::EnableAppletToGetTouchScreen, "EnableAppletToGetTouchScreen"}, {507, &IHidSystemServer::EnableAppletToGetTouchScreen, "EnableAppletToGetTouchScreen"},
{510, nullptr, "SetVibrationMasterVolume"}, {510, &IHidSystemServer::SetVibrationMasterVolume, "SetVibrationMasterVolume"},
{511, nullptr, "GetVibrationMasterVolume"}, {511, &IHidSystemServer::GetVibrationMasterVolume, "GetVibrationMasterVolume"},
{512, nullptr, "BeginPermitVibrationSession"}, {512, &IHidSystemServer::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{513, nullptr, "EndPermitVibrationSession"}, {513, &IHidSystemServer::EndPermitVibrationSession, "EndPermitVibrationSession"},
{514, nullptr, "Unknown514"}, {514, nullptr, "Unknown514"},
{520, nullptr, "EnableHandheldHids"}, {520, nullptr, "EnableHandheldHids"},
{521, nullptr, "DisableHandheldHids"}, {521, nullptr, "DisableHandheldHids"},
@ -156,7 +157,7 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptr<Resour
{1152, nullptr, "SetTouchScreenDefaultConfiguration"}, {1152, nullptr, "SetTouchScreenDefaultConfiguration"},
{1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, {1153, &IHidSystemServer::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"},
{1154, nullptr, "IsFirmwareAvailableForNotification"}, {1154, nullptr, "IsFirmwareAvailableForNotification"},
{1155, nullptr, "SetForceHandheldStyleVibration"}, {1155, &IHidSystemServer::SetForceHandheldStyleVibration, "SetForceHandheldStyleVibration"},
{1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"},
{1157, nullptr, "CancelConnectionTrigger"}, {1157, nullptr, "CancelConnectionTrigger"},
{1200, nullptr, "IsButtonConfigSupported"}, {1200, nullptr, "IsButtonConfigSupported"},
@ -538,6 +539,27 @@ void IHidSystemServer::EnableAppletToGetInput(HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void IHidSystemServer::SetAruidValidForVibration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
bool is_enabled;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_INFO(Service_HID, "called, is_enabled={}, applet_resource_user_id={}",
parameters.is_enabled, parameters.applet_resource_user_id);
GetResourceManager()->SetAruidValidForVibration(parameters.applet_resource_user_id,
parameters.is_enabled);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IHidSystemServer::EnableAppletToGetSixAxisSensor(HLERequestContext& ctx) { void IHidSystemServer::EnableAppletToGetSixAxisSensor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
struct Parameters { struct Parameters {
@ -601,6 +623,57 @@ void IHidSystemServer::EnableAppletToGetTouchScreen(HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void IHidSystemServer::SetVibrationMasterVolume(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto master_volume{rp.Pop<f32>()};
LOG_INFO(Service_HID, "called, volume={}", master_volume);
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->SetVibrationMasterVolume(
master_volume);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IHidSystemServer::GetVibrationMasterVolume(HLERequestContext& ctx) {
f32 master_volume{};
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->GetVibrationMasterVolume(
master_volume);
LOG_INFO(Service_HID, "called, volume={}", master_volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(master_volume);
}
void IHidSystemServer::BeginPermitVibrationSession(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->BeginPermitVibrationSession(
applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IHidSystemServer::EndPermitVibrationSession(HLERequestContext& ctx) {
LOG_INFO(Service_HID, "called");
const auto result =
GetResourceManager()->GetNpad()->GetVibrationHandler()->EndPermitVibrationSession();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
void IHidSystemServer::IsJoyConAttachedOnAllRail(HLERequestContext& ctx) { void IHidSystemServer::IsJoyConAttachedOnAllRail(HLERequestContext& ctx) {
const bool is_attached = true; const bool is_attached = true;
@ -749,6 +822,19 @@ void IHidSystemServer::GetTouchScreenDefaultConfiguration(HLERequestContext& ctx
rb.PushRaw(touchscreen_config); rb.PushRaw(touchscreen_config);
} }
void IHidSystemServer::SetForceHandheldStyleVibration(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto is_forced{rp.Pop<bool>()};
LOG_INFO(Service_HID, "called, is_forced={}", is_forced);
GetResourceManager()->SetForceHandheldStyleVibration(is_forced);
GetResourceManager()->GetNpad()->UpdateHandheldAbstractState();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IHidSystemServer::IsUsingCustomButtonConfig(HLERequestContext& ctx) { void IHidSystemServer::IsUsingCustomButtonConfig(HLERequestContext& ctx) {
const bool is_enabled = false; const bool is_enabled = false;

View File

@ -42,9 +42,14 @@ private:
void RegisterAppletResourceUserId(HLERequestContext& ctx); void RegisterAppletResourceUserId(HLERequestContext& ctx);
void UnregisterAppletResourceUserId(HLERequestContext& ctx); void UnregisterAppletResourceUserId(HLERequestContext& ctx);
void EnableAppletToGetInput(HLERequestContext& ctx); void EnableAppletToGetInput(HLERequestContext& ctx);
void SetAruidValidForVibration(HLERequestContext& ctx);
void EnableAppletToGetSixAxisSensor(HLERequestContext& ctx); void EnableAppletToGetSixAxisSensor(HLERequestContext& ctx);
void EnableAppletToGetPadInput(HLERequestContext& ctx); void EnableAppletToGetPadInput(HLERequestContext& ctx);
void EnableAppletToGetTouchScreen(HLERequestContext& ctx); void EnableAppletToGetTouchScreen(HLERequestContext& ctx);
void SetVibrationMasterVolume(HLERequestContext& ctx);
void GetVibrationMasterVolume(HLERequestContext& ctx);
void BeginPermitVibrationSession(HLERequestContext& ctx);
void EndPermitVibrationSession(HLERequestContext& ctx);
void IsJoyConAttachedOnAllRail(HLERequestContext& ctx); void IsJoyConAttachedOnAllRail(HLERequestContext& ctx);
void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx); void AcquireConnectionTriggerTimeoutEvent(HLERequestContext& ctx);
void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx); void AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx);
@ -61,6 +66,7 @@ private:
void FinalizeUsbFirmwareUpdate(HLERequestContext& ctx); void FinalizeUsbFirmwareUpdate(HLERequestContext& ctx);
void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx); void InitializeUsbFirmwareUpdateWithoutMemory(HLERequestContext& ctx);
void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx); void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx);
void SetForceHandheldStyleVibration(HLERequestContext& ctx);
void IsUsingCustomButtonConfig(HLERequestContext& ctx); void IsUsingCustomButtonConfig(HLERequestContext& ctx);
std::shared_ptr<ResourceManager> GetResourceManager(); std::shared_ptr<ResourceManager> GetResourceManager();

View File

@ -24,19 +24,6 @@
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/memory.h" #include "core/memory.h"
namespace {
static thread_local std::array read_buffer_data_a{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
static thread_local std::array read_buffer_data_x{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
} // Anonymous namespace
namespace Service { namespace Service {
SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_) SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_)
@ -344,48 +331,27 @@ std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) cons
} }
std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const { std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const {
static thread_local std::array read_buffer_a{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
};
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
BufferDescriptorA().size() > buffer_index, { return {}; }, BufferDescriptorA().size() > buffer_index, { return {}; },
"BufferDescriptorA invalid buffer_index {}", buffer_index); "BufferDescriptorA invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_a[buffer_index]; return gm.Read(BufferDescriptorA()[buffer_index].Address(),
return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(), BufferDescriptorA()[buffer_index].Size(), &read_buffer_data_a[buffer_index]);
BufferDescriptorA()[buffer_index].Size(),
&read_buffer_data_a[buffer_index]);
} }
std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const { std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const {
static thread_local std::array read_buffer_x{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
};
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
BufferDescriptorX().size() > buffer_index, { return {}; }, BufferDescriptorX().size() > buffer_index, { return {}; },
"BufferDescriptorX invalid buffer_index {}", buffer_index); "BufferDescriptorX invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_x[buffer_index]; return gm.Read(BufferDescriptorX()[buffer_index].Address(),
return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(), BufferDescriptorX()[buffer_index].Size(), &read_buffer_data_x[buffer_index]);
BufferDescriptorX()[buffer_index].Size(),
&read_buffer_data_x[buffer_index]);
} }
std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
static thread_local std::array read_buffer_a{ Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
};
static thread_local std::array read_buffer_x{
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead>(memory, 0, 0),
};
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()}; BufferDescriptorA()[buffer_index].Size()};
@ -402,18 +368,14 @@ std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) cons
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
BufferDescriptorA().size() > buffer_index, { return {}; }, BufferDescriptorA().size() > buffer_index, { return {}; },
"BufferDescriptorA invalid buffer_index {}", buffer_index); "BufferDescriptorA invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_a[buffer_index]; return gm.Read(BufferDescriptorA()[buffer_index].Address(),
return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(), BufferDescriptorA()[buffer_index].Size(), &read_buffer_data_a[buffer_index]);
BufferDescriptorA()[buffer_index].Size(),
&read_buffer_data_a[buffer_index]);
} else { } else {
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
BufferDescriptorX().size() > buffer_index, { return {}; }, BufferDescriptorX().size() > buffer_index, { return {}; },
"BufferDescriptorX invalid buffer_index {}", buffer_index); "BufferDescriptorX invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_x[buffer_index]; return gm.Read(BufferDescriptorX()[buffer_index].Address(),
return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(), BufferDescriptorX()[buffer_index].Size(), &read_buffer_data_x[buffer_index]);
BufferDescriptorX()[buffer_index].Size(),
&read_buffer_data_x[buffer_index]);
} }
} }

View File

@ -426,6 +426,9 @@ private:
Kernel::KernelCore& kernel; Kernel::KernelCore& kernel;
Core::Memory::Memory& memory; Core::Memory::Memory& memory;
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
}; };
} // namespace Service } // namespace Service

View File

@ -441,7 +441,10 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
device_state = DeviceState::TagMounted; device_state = DeviceState::TagMounted;
mount_target = mount_target_; mount_target = mount_target_;
if (!is_corrupted && mount_target != NFP::MountTarget::Rom) { const bool create_backup =
mount_target == NFP::MountTarget::All || mount_target == NFP::MountTarget::Ram ||
(mount_target == NFP::MountTarget::Rom && HasBackup(encrypted_tag_data.uuid).IsError());
if (!is_corrupted && create_backup) {
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
WriteBackupData(encrypted_tag_data.uuid, data); WriteBackupData(encrypted_tag_data.uuid, data);

View File

@ -16,9 +16,8 @@
namespace Service::Nvidia::NvCore { namespace Service::Nvidia::NvCore {
Session::Session(size_t id_, Kernel::KProcess* process_, size_t smmu_id_) Session::Session(SessionId id_, Kernel::KProcess* process_, Core::Asid asid_)
: id{id_}, process{process_}, smmu_id{smmu_id_}, : id{id_}, process{process_}, asid{asid_}, has_preallocated_area{}, mapper{}, is_active{} {}
has_preallocated_area{}, mapper{}, is_active{} {}
Session::~Session() = default; Session::~Session() = default;
@ -41,7 +40,9 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
Container::~Container() = default; Container::~Container() = default;
size_t Container::OpenSession(Kernel::KProcess* process) { SessionId Container::OpenSession(Kernel::KProcess* process) {
using namespace Common::Literals;
std::scoped_lock lk(impl->session_guard); std::scoped_lock lk(impl->session_guard);
for (auto& session : impl->sessions) { for (auto& session : impl->sessions) {
if (!session.is_active) { if (!session.is_active) {
@ -54,14 +55,14 @@ size_t Container::OpenSession(Kernel::KProcess* process) {
size_t new_id{}; size_t new_id{};
auto* memory_interface = &process->GetMemory(); auto* memory_interface = &process->GetMemory();
auto& smmu = impl->host1x.MemoryManager(); auto& smmu = impl->host1x.MemoryManager();
auto smmu_id = smmu.RegisterProcess(memory_interface); auto asid = smmu.RegisterProcess(memory_interface);
if (!impl->id_pool.empty()) { if (!impl->id_pool.empty()) {
new_id = impl->id_pool.front(); new_id = impl->id_pool.front();
impl->id_pool.pop_front(); impl->id_pool.pop_front();
impl->sessions[new_id] = Session{new_id, process, smmu_id}; impl->sessions[new_id] = Session{SessionId{new_id}, process, asid};
} else { } else {
new_id = impl->new_ids++; new_id = impl->new_ids++;
impl->sessions.emplace_back(new_id, process, smmu_id); impl->sessions.emplace_back(SessionId{new_id}, process, asid);
} }
auto& session = impl->sessions[new_id]; auto& session = impl->sessions[new_id];
session.is_active = true; session.is_active = true;
@ -80,7 +81,7 @@ size_t Container::OpenSession(Kernel::KProcess* process) {
cur_addr)); cur_addr));
auto svc_mem_info = mem_info.GetSvcMemoryInfo(); auto svc_mem_info = mem_info.GetSvcMemoryInfo();
// check if this memory block is heap // Check if this memory block is heap.
if (svc_mem_info.state == Kernel::Svc::MemoryState::Normal) { if (svc_mem_info.state == Kernel::Svc::MemoryState::Normal) {
if (svc_mem_info.size > region_size) { if (svc_mem_info.size > region_size) {
region_size = svc_mem_info.size; region_size = svc_mem_info.size;
@ -97,21 +98,21 @@ size_t Container::OpenSession(Kernel::KProcess* process) {
cur_addr = next_address; cur_addr = next_address;
} }
session.has_preallocated_area = false; session.has_preallocated_area = false;
auto start_region = (region_size >> 15) >= 1024 ? smmu.Allocate(region_size) : 0; auto start_region = region_size >= 32_MiB ? smmu.Allocate(region_size) : 0;
if (start_region != 0) { if (start_region != 0) {
session.mapper = std::make_unique<HeapMapper>(region_start, start_region, region_size, session.mapper = std::make_unique<HeapMapper>(region_start, start_region, region_size,
smmu_id, impl->host1x); asid, impl->host1x);
smmu.TrackContinuity(start_region, region_start, region_size, smmu_id); smmu.TrackContinuity(start_region, region_start, region_size, asid);
session.has_preallocated_area = true; session.has_preallocated_area = true;
LOG_CRITICAL(Debug, "Preallocation created!"); LOG_DEBUG(Debug, "Preallocation created!");
} }
} }
return new_id; return SessionId{new_id};
} }
void Container::CloseSession(size_t id) { void Container::CloseSession(SessionId session_id) {
std::scoped_lock lk(impl->session_guard); std::scoped_lock lk(impl->session_guard);
auto& session = impl->sessions[id]; auto& session = impl->sessions[session_id.id];
auto& smmu = impl->host1x.MemoryManager(); auto& smmu = impl->host1x.MemoryManager();
if (session.has_preallocated_area) { if (session.has_preallocated_area) {
const DAddr region_start = session.mapper->GetRegionStart(); const DAddr region_start = session.mapper->GetRegionStart();
@ -121,13 +122,13 @@ void Container::CloseSession(size_t id) {
session.has_preallocated_area = false; session.has_preallocated_area = false;
} }
session.is_active = false; session.is_active = false;
smmu.UnregisterProcess(impl->sessions[id].smmu_id); smmu.UnregisterProcess(impl->sessions[session_id.id].asid);
impl->id_pool.emplace_front(id); impl->id_pool.emplace_front(session_id.id);
} }
Session* Container::GetSession(size_t id) { Session* Container::GetSession(SessionId session_id) {
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
return &impl->sessions[id]; return &impl->sessions[session_id.id];
} }
NvMap& Container::GetNvMapFile() { NvMap& Container::GetNvMapFile() {

View File

@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include "core/device_memory_manager.h"
#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdata.h"
namespace Kernel { namespace Kernel {
@ -26,8 +27,12 @@ class SyncpointManager;
struct ContainerImpl; struct ContainerImpl;
struct SessionId {
size_t id;
};
struct Session { struct Session {
Session(size_t id_, Kernel::KProcess* process_, size_t smmu_id_); Session(SessionId id_, Kernel::KProcess* process_, Core::Asid asid_);
~Session(); ~Session();
Session(const Session&) = delete; Session(const Session&) = delete;
@ -35,9 +40,9 @@ struct Session {
Session(Session&&) = default; Session(Session&&) = default;
Session& operator=(Session&&) = default; Session& operator=(Session&&) = default;
size_t id; SessionId id;
Kernel::KProcess* process; Kernel::KProcess* process;
size_t smmu_id; Core::Asid asid;
bool has_preallocated_area{}; bool has_preallocated_area{};
std::unique_ptr<HeapMapper> mapper{}; std::unique_ptr<HeapMapper> mapper{};
bool is_active{}; bool is_active{};
@ -48,10 +53,10 @@ public:
explicit Container(Tegra::Host1x::Host1x& host1x); explicit Container(Tegra::Host1x::Host1x& host1x);
~Container(); ~Container();
size_t OpenSession(Kernel::KProcess* process); SessionId OpenSession(Kernel::KProcess* process);
void CloseSession(size_t id); void CloseSession(SessionId id);
Session* GetSession(size_t id); Session* GetSession(SessionId id);
NvMap& GetNvMapFile(); NvMap& GetNvMapFile();

View File

@ -109,9 +109,9 @@ struct HeapMapper::HeapMapperInternal {
std::mutex guard; std::mutex guard;
}; };
HeapMapper::HeapMapper(VAddr start_vaddress, DAddr start_daddress, size_t size, size_t smmu_id, HeapMapper::HeapMapper(VAddr start_vaddress, DAddr start_daddress, size_t size, Core::Asid asid,
Tegra::Host1x::Host1x& host1x) Tegra::Host1x::Host1x& host1x)
: m_vaddress{start_vaddress}, m_daddress{start_daddress}, m_size{size}, m_smmu_id{smmu_id} { : m_vaddress{start_vaddress}, m_daddress{start_daddress}, m_size{size}, m_asid{asid} {
m_internal = std::make_unique<HeapMapperInternal>(host1x); m_internal = std::make_unique<HeapMapperInternal>(host1x);
} }
@ -138,7 +138,7 @@ DAddr HeapMapper::Map(VAddr start, size_t size) {
const size_t offset = inter_addr - m_vaddress; const size_t offset = inter_addr - m_vaddress;
const size_t sub_size = inter_addr_end - inter_addr; const size_t sub_size = inter_addr_end - inter_addr;
m_internal->device_memory.Map(m_daddress + offset, m_vaddress + offset, sub_size, m_internal->device_memory.Map(m_daddress + offset, m_vaddress + offset, sub_size,
m_smmu_id); m_asid);
} }
} }
m_internal->mapping_overlaps += std::make_pair(interval, 1); m_internal->mapping_overlaps += std::make_pair(interval, 1);
@ -172,4 +172,4 @@ void HeapMapper::Unmap(VAddr start, size_t size) {
m_internal->base_set.clear(); m_internal->base_set.clear();
} }
} // namespace Service::Nvidia::NvCore } // namespace Service::Nvidia::NvCore

View File

@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/device_memory_manager.h"
namespace Tegra::Host1x { namespace Tegra::Host1x {
class Host1x; class Host1x;
@ -15,7 +16,7 @@ namespace Service::Nvidia::NvCore {
class HeapMapper { class HeapMapper {
public: public:
HeapMapper(VAddr start_vaddress, DAddr start_daddress, size_t size, size_t smmu_id, HeapMapper(VAddr start_vaddress, DAddr start_daddress, size_t size, Core::Asid asid,
Tegra::Host1x::Host1x& host1x); Tegra::Host1x::Host1x& host1x);
~HeapMapper(); ~HeapMapper();
@ -41,8 +42,8 @@ private:
VAddr m_vaddress; VAddr m_vaddress;
DAddr m_daddress; DAddr m_daddress;
size_t m_size; size_t m_size;
size_t m_smmu_id; Core::Asid m_asid;
std::unique_ptr<HeapMapperInternal> m_internal; std::unique_ptr<HeapMapperInternal> m_internal;
}; };
} // namespace Service::Nvidia::NvCore } // namespace Service::Nvidia::NvCore

View File

@ -22,7 +22,8 @@ NvMap::Handle::Handle(u64 size_, Id id_)
flags.raw = 0; flags.raw = 0;
} }
NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress, size_t pSessionId) { NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress,
NvCore::SessionId pSessionId) {
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
// Handles cannot be allocated twice // Handles cannot be allocated twice
if (allocated) { if (allocated) {
@ -223,7 +224,7 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
} }
handle_description->d_address = address; handle_description->d_address = address;
smmu.Map(address, vaddress, map_size, session->smmu_id, true); smmu.Map(address, vaddress, map_size, session->asid, true);
handle_description->in_heap = false; handle_description->in_heap = false;
} }
} }

View File

@ -14,6 +14,7 @@
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdata.h"
namespace Tegra { namespace Tegra {
@ -71,7 +72,7 @@ public:
u8 kind{}; //!< Used for memory compression u8 kind{}; //!< Used for memory compression
bool allocated{}; //!< If the handle has been allocated with `Alloc` bool allocated{}; //!< If the handle has been allocated with `Alloc`
bool in_heap{}; bool in_heap{};
size_t session_id{}; NvCore::SessionId session_id{};
DAddr d_address{}; //!< The memory location in the device's AS that this handle corresponds DAddr d_address{}; //!< The memory location in the device's AS that this handle corresponds
//!< to, this can also be in the nvdrv tmem //!< to, this can also be in the nvdrv tmem
@ -83,7 +84,7 @@ public:
* if a 0 address is passed * if a 0 address is passed
*/ */
[[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress, [[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress,
size_t pSessionId); NvCore::SessionId pSessionId);
/** /**
* @brief Increases the dupe counter of the handle for the given session * @brief Increases the dupe counter of the handle for the given session

View File

@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdata.h"
namespace Core { namespace Core {
@ -62,7 +63,7 @@ public:
* Called once a device is opened * Called once a device is opened
* @param fd The device fd * @param fd The device fd
*/ */
virtual void OnOpen(size_t session_id, DeviceFD fd) = 0; virtual void OnOpen(NvCore::SessionId session_id, DeviceFD fd) = 0;
/** /**
* Called once a device is closed * Called once a device is closed

View File

@ -35,7 +35,7 @@ NvResult nvdisp_disp0::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvdisp_disp0::OnOpen(size_t session_id, DeviceFD fd) {} void nvdisp_disp0::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvdisp_disp0::OnClose(DeviceFD fd) {} void nvdisp_disp0::OnClose(DeviceFD fd) {}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,

View File

@ -32,7 +32,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle. /// Performs a screen flip, drawing the buffer pointed to by the handle.

View File

@ -86,7 +86,7 @@ NvResult nvhost_as_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> i
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_as_gpu::OnOpen(size_t session_id, DeviceFD fd) {} void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_as_gpu::OnClose(DeviceFD fd) {} void nvhost_as_gpu::OnClose(DeviceFD fd) {}
NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) {

View File

@ -55,7 +55,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
Kernel::KEvent* QueryEvent(u32 event_id) override; Kernel::KEvent* QueryEvent(u32 event_id) override;

View File

@ -76,7 +76,7 @@ NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> inp
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_ctrl::OnOpen(size_t session_id, DeviceFD fd) {} void nvhost_ctrl::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_ctrl::OnClose(DeviceFD fd) {} void nvhost_ctrl::OnClose(DeviceFD fd) {}

View File

@ -32,7 +32,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
Kernel::KEvent* QueryEvent(u32 event_id) override; Kernel::KEvent* QueryEvent(u32 event_id) override;

View File

@ -82,7 +82,7 @@ NvResult nvhost_ctrl_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8>
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_ctrl_gpu::OnOpen(size_t session_id, DeviceFD fd) {} void nvhost_ctrl_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {} void nvhost_ctrl_gpu::OnClose(DeviceFD fd) {}
NvResult nvhost_ctrl_gpu::GetCharacteristics1(IoctlCharacteristics& params) { NvResult nvhost_ctrl_gpu::GetCharacteristics1(IoctlCharacteristics& params) {

View File

@ -28,7 +28,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
Kernel::KEvent* QueryEvent(u32 event_id) override; Kernel::KEvent* QueryEvent(u32 event_id) override;

View File

@ -120,7 +120,7 @@ NvResult nvhost_gpu::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> inpu
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_gpu::OnOpen(size_t session_id, DeviceFD fd) {} void nvhost_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_gpu::OnClose(DeviceFD fd) {} void nvhost_gpu::OnClose(DeviceFD fd) {}
NvResult nvhost_gpu::SetNVMAPfd(IoctlSetNvmapFD& params) { NvResult nvhost_gpu::SetNVMAPfd(IoctlSetNvmapFD& params) {

View File

@ -47,7 +47,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
Kernel::KEvent* QueryEvent(u32 event_id) override; Kernel::KEvent* QueryEvent(u32 event_id) override;

View File

@ -68,7 +68,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_nvdec::OnOpen(size_t session_id, DeviceFD fd) { void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started"); LOG_INFO(Service_NVDRV, "NVDEC video stream started");
system.SetNVDECActive(true); system.SetNVDECActive(true);
sessions[fd] = session_id; sessions[fd] = session_id;

View File

@ -20,7 +20,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
}; };

View File

@ -127,7 +127,7 @@ protected:
NvCore::NvMap& nvmap; NvCore::NvMap& nvmap;
NvCore::ChannelType channel_type; NvCore::ChannelType channel_type;
std::array<u32, MaxSyncPoints> device_syncpoints{}; std::array<u32, MaxSyncPoints> device_syncpoints{};
std::unordered_map<DeviceFD, size_t> sessions; std::unordered_map<DeviceFD, NvCore::SessionId> sessions;
}; };
}; // namespace Devices }; // namespace Devices
} // namespace Service::Nvidia } // namespace Service::Nvidia

View File

@ -44,7 +44,7 @@ NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_nvjpg::OnOpen(size_t session_id, DeviceFD fd) {} void nvhost_nvjpg::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_nvjpg::OnClose(DeviceFD fd) {} void nvhost_nvjpg::OnClose(DeviceFD fd) {}
NvResult nvhost_nvjpg::SetNVMAPfd(IoctlSetNvmapFD& params) { NvResult nvhost_nvjpg::SetNVMAPfd(IoctlSetNvmapFD& params) {

View File

@ -22,7 +22,7 @@ public:
NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output, NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
std::span<u8> inline_output) override; std::span<u8> inline_output) override;
void OnOpen(size_t session_id, DeviceFD fd) override; void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
void OnClose(DeviceFD fd) override; void OnClose(DeviceFD fd) override;
private: private:

View File

@ -68,7 +68,7 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> inpu
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
void nvhost_vic::OnOpen(size_t session_id, DeviceFD fd) { void nvhost_vic::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
sessions[fd] = session_id; sessions[fd] = session_id;
} }
@ -78,10 +78,7 @@ void nvhost_vic::OnClose(DeviceFD fd) {
if (iter != host1x_file.fd_to_id.end()) { if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second); system.GPU().ClearCdmaInstance(iter->second);
} }
auto it = sessions.find(fd); sessions.erase(fd);
if (it != sessions.end()) {
sessions.erase(it);
}
} }
} // namespace Service::Nvidia::Devices } // namespace Service::Nvidia::Devices

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