Compare commits

..

4 Commits

Author SHA1 Message Date
6f20123f17 Android #180 2024-01-03 00:57:34 +00:00
2429877d26 Merge PR 12558 2024-01-03 00:57:33 +00:00
444f0beaa2 Merge PR 12557 2024-01-03 00:57:33 +00:00
b996f07495 Merge PR 12549 2024-01-03 00:57:33 +00:00
475 changed files with 3945 additions and 9631 deletions

View File

@ -3,35 +3,38 @@
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project # SPDX-FileCopyrightText: 2019 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
shopt -s nullglob globstar if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
dist/*.svg dist/*.xml; then
if git grep -nrI '\s$' src **/*.yml **/*.txt **/*.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop dist/*.svg dist/*.xml; then
echo Trailing whitespace found, aborting echo Trailing whitespace found, aborting
exit 1 exit 1
fi fi
# Default clang-format points to default 3.5 version one # Default clang-format points to default 3.5 version one
CLANG_FORMAT="${CLANG_FORMAT:-clang-format-15}" CLANG_FORMAT=${CLANG_FORMAT:-clang-format-15}
"$CLANG_FORMAT" --version $CLANG_FORMAT --version
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
# Get list of every file modified in this pull request
files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
else
# Check everything for branch pushes
files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
fi
# Turn off tracing for this because it's too verbose # Turn off tracing for this because it's too verbose
set +x set +x
# Check everything for branch pushes for f in $files_to_lint; do
FILES_TO_LINT="$(find src/ -name '*.cpp' -or -name '*.h')" d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
if ! [ -z "$d" ]; then
for f in $FILES_TO_LINT; do echo "!!! $f not compliant to coding style, here is the fix:"
echo "$f" echo "$d"
"$CLANG_FORMAT" -i "$f" fail=1
fi
done done
DIFF=$(git -c core.fileMode=false diff) set -x
if [ ! -z "$DIFF" ]; then if [ "$fail" = 1 ]; then
echo "!!! Not compliant to coding style, here is the fix:"
echo "$DIFF"
exit 1 exit 1
fi fi
cd src/android
./gradlew ktlintCheck

View File

@ -9,7 +9,7 @@ chmod a+x ./.ci/scripts/linux/docker.sh
sudo chown -R 1027 ./ sudo chown -R 1027 ./
# The environment variables listed below: # The environment variables listed below:
# AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION # AZURECIREPO TITLEBARFORMATIDLE TITLEBARFORMATRUNNING DISPLAYVERSION
# are requested in src/common/CMakeLists.txt and appear to be provided somewhere in Azure DevOps # are requested in src/common/CMakeLists.txt and appear to be provided somewhere in Azure DevOps
docker run -e AZURECIREPO -e TITLEBARFORMATIDLE -e TITLEBARFORMATRUNNING -e DISPLAYVERSION -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1" docker run -e AZURECIREPO -e TITLEBARFORMATIDLE -e TITLEBARFORMATRUNNING -e DISPLAYVERSION -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v "$(pwd):/yuzu" -w /yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh "$1"

View File

@ -8,7 +8,17 @@ 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
@ -33,6 +43,7 @@ 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

@ -10,7 +10,7 @@ const CHANGE_LABEL = 'android-merge';
// how far back in time should we consider the changes are "recent"? (default: 24 hours) // how far back in time should we consider the changes are "recent"? (default: 24 hours)
const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000); const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000);
async function checkBaseChanges(github) { async function checkBaseChanges(github, context) {
// query the commit date of the latest commit on this branch // query the commit date of the latest commit on this branch
const query = `query($owner:String!, $name:String!, $ref:String!) { const query = `query($owner:String!, $name:String!, $ref:String!) {
repository(name:$name, owner:$owner) { repository(name:$name, owner:$owner) {
@ -22,8 +22,8 @@ async function checkBaseChanges(github) {
} }
}`; }`;
const variables = { const variables = {
owner: 'yuzu-emu', owner: context.repo.owner,
name: 'yuzu', name: context.repo.repo,
ref: 'refs/heads/master', ref: 'refs/heads/master',
}; };
const result = await github.graphql(query, variables); const result = await github.graphql(query, variables);
@ -38,8 +38,8 @@ async function checkBaseChanges(github) {
return false; return false;
} }
async function checkAndroidChanges(github) { async function checkAndroidChanges(github, context) {
if (checkBaseChanges(github)) return true; if (checkBaseChanges(github, context)) return true;
const query = `query($owner:String!, $name:String!, $label:String!) { const query = `query($owner:String!, $name:String!, $label:String!) {
repository(name:$name, owner:$owner) { repository(name:$name, owner:$owner) {
pullRequests(labels: [$label], states: OPEN, first: 100) { pullRequests(labels: [$label], states: OPEN, first: 100) {
@ -48,8 +48,8 @@ async function checkAndroidChanges(github) {
} }
}`; }`;
const variables = { const variables = {
owner: 'yuzu-emu', owner: context.repo.owner,
name: 'yuzu', name: context.repo.repo,
label: CHANGE_LABEL, label: CHANGE_LABEL,
}; };
const result = await github.graphql(query, variables); const result = await github.graphql(query, variables);
@ -90,8 +90,8 @@ async function tagAndPush(github, owner, repo, execa, commit=false) {
console.log(`New tag: ${newTag}`); console.log(`New tag: ${newTag}`);
if (commit) { if (commit) {
let channelName = channel[0].toUpperCase() + channel.slice(1); let channelName = channel[0].toUpperCase() + channel.slice(1);
console.info(`Committing pending commit as ${channelName} ${tagNumber + 1}`); console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`);
await execa("git", ['commit', '-m', `${channelName} ${tagNumber + 1}`]); await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]);
} }
console.info('Pushing tags to GitHub ...'); console.info('Pushing tags to GitHub ...');
await execa("git", ['tag', newTag]); await execa("git", ['tag', newTag]);
@ -157,7 +157,7 @@ async function mergePullRequests(pulls, execa) {
process1.stdout.pipe(process.stdout); process1.stdout.pipe(process.stdout);
await process1; await process1;
const process2 = execa("git", ["commit", "-m", `Merge yuzu-emu#${pr}`]); const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]);
process2.stdout.pipe(process.stdout); process2.stdout.pipe(process.stdout);
await process2; await process2;
@ -182,30 +182,7 @@ async function mergePullRequests(pulls, execa) {
return mergeResults; return mergeResults;
} }
async function resetBranch(execa) {
console.log("::group::Reset master branch");
let hasFailed = false;
try {
await execa("git", ["remote", "add", "source", "https://github.com/yuzu-emu/yuzu.git"]);
await execa("git", ["fetch", "source"]);
const process1 = await execa("git", ["rev-parse", "source/master"]);
const headCommit = process1.stdout;
await execa("git", ["reset", "--hard", headCommit]);
} catch (err) {
console.log(`::error title=Failed to reset master branch`);
hasFailed = true;
}
console.log("::endgroup::");
if (hasFailed) {
throw 'Failed to reset the master branch. Aborting!';
}
}
async function mergebot(github, context, execa) { async function mergebot(github, context, execa) {
// Reset our local copy of master to what appears on yuzu-emu/yuzu - master
await resetBranch(execa);
const query = `query ($owner:String!, $name:String!, $label:String!) { const query = `query ($owner:String!, $name:String!, $label:String!) {
repository(name:$name, owner:$owner) { repository(name:$name, owner:$owner) {
pullRequests(labels: [$label], states: OPEN, first: 100) { pullRequests(labels: [$label], states: OPEN, first: 100) {
@ -216,8 +193,8 @@ async function mergebot(github, context, execa) {
} }
}`; }`;
const variables = { const variables = {
owner: 'yuzu-emu', owner: context.repo.owner,
name: 'yuzu', name: context.repo.repo,
label: CHANGE_LABEL, label: CHANGE_LABEL,
}; };
const result = await github.graphql(query, variables); const result = await github.graphql(query, variables);
@ -232,7 +209,7 @@ async function mergebot(github, context, execa) {
await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa); await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
const mergeResults = await mergePullRequests(pulls, execa); const mergeResults = await mergePullRequests(pulls, execa);
await generateReadme(pulls, context, mergeResults, execa); await generateReadme(pulls, context, mergeResults, execa);
await tagAndPush(github, 'yuzu-emu', `yuzu-android`, execa, true); await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true);
} }
module.exports.mergebot = mergebot; module.exports.mergebot = mergebot;

View File

@ -16,7 +16,7 @@ on:
jobs: jobs:
android: android:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu-android' }} if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }}
steps: steps:
# this checkout is required to make sure the GitHub Actions scripts are available # this checkout is required to make sure the GitHub Actions scripts are available
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -33,7 +33,7 @@ jobs:
script: | script: |
if (context.payload.inputs && context.payload.inputs.android === 'true') return true; if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges; const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
return checkAndroidChanges(github); return checkAndroidChanges(github, context);
- run: npm install execa@5 - run: npm install execa@5
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -13,15 +13,13 @@ 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,4 +1,4 @@
Copyright (c) <year> <owner> Copyright (c) <year> <owner>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -1,4 +1,4 @@
Copyright (c) <year> <owner>. Copyright (c) <year> <owner>.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -35,7 +35,7 @@ Mozilla Public License Version 2.0
means any form of the work other than Source Code Form. means any form of the work other than Source Code Form.
1.7. "Larger Work" 1.7. "Larger Work"
means a work that combines Covered Software with other material, in means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software. a separate file or files, that is not Covered Software.
1.8. "License" 1.8. "License"

View File

@ -1,8 +1,8 @@
| Pull Request | Commit | Title | Author | Merged? | | Pull Request | Commit | Title | Author | Merged? |
|----|----|----|----|----| |----|----|----|----|----|
| [12579](https://github.com/yuzu-emu/yuzu-android//pull/12579) | [`93ef41f03`](https://github.com/yuzu-emu/yuzu-android//pull/12579/files) | Core: Implement Device Mapping & GPU SMMU | [FernandoS27](https://github.com/FernandoS27/) | Yes | | [12549](https://github.com/yuzu-emu/yuzu//pull/12549) | [`27259ff10`](https://github.com/yuzu-emu/yuzu//pull/12549/files) | service: hid: Implement NpadResource and NpadData | [german77](https://github.com/german77/) | Yes |
| [12644](https://github.com/yuzu-emu/yuzu-android//pull/12644) | [`2044a289f`](https://github.com/yuzu-emu/yuzu-android//pull/12644/files) | shader_recompiler: fix Offset operand usage for non-OpImage*Gather | [liamwhite](https://github.com/liamwhite/) | Yes | | [12557](https://github.com/yuzu-emu/yuzu//pull/12557) | [`0f7fc9411`](https://github.com/yuzu-emu/yuzu//pull/12557/files) | KThread: Send termination interrupt to all cores a thread has affinity to | [merryhime](https://github.com/merryhime/) | 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 | | [12558](https://github.com/yuzu-emu/yuzu//pull/12558) | [`dace726d0`](https://github.com/yuzu-emu/yuzu//pull/12558/files) | android: Disable compression for zip exports | [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

@ -138,7 +138,7 @@ if (NOT WIN32 AND NOT ANDROID)
--cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android- --cross-prefix=${TOOLCHAIN}/bin/aarch64-linux-android-
--sysroot=${SYSROOT} --sysroot=${SYSROOT}
--target-os=android --target-os=android
--extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld" --extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
--extra-ldflags="-nostdlib" --extra-ldflags="-nostdlib"
) )
endif() endif()

View File

@ -185,7 +185,6 @@ add_subdirectory(common)
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(audio_core) add_subdirectory(audio_core)
add_subdirectory(video_core) add_subdirectory(video_core)
add_subdirectory(hid_core)
add_subdirectory(network) add_subdirectory(network)
add_subdirectory(input_common) add_subdirectory(input_common)
add_subdirectory(frontend_common) add_subdirectory(frontend_common)

View File

@ -188,15 +188,8 @@ 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")
@ -235,33 +228,71 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }
fun runGitCommand(command: List<String>): String { fun getGitVersion(): String {
return try { var versionName = "0.0"
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 {
runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" } try {
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()
fun getBranch(): String = return if (process.exitValue() == 0) {
runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" } 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
}
}
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

@ -31,9 +31,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:dataExtractionRules="@xml/data_extraction_rules_api_31" android:dataExtractionRules="@xml/data_extraction_rules_api_31"
android:enableOnBackInvokedCallback="true"> android:enableOnBackInvokedCallback="true">
<meta-data android:name="android.game_mode_config"
android:resource="@xml/game_mode_config" />
<activity <activity
android:name="org.yuzu.yuzu_emu.ui.main.MainActivity" android:name="org.yuzu.yuzu_emu.ui.main.MainActivity"
android:exported="true" android:exported="true"

View File

@ -547,15 +547,6 @@ object NativeLibrary {
*/ */
external fun getSavePath(programId: String): String external fun getSavePath(programId: String): String
/**
* Gets the root save directory for the default profile as either
* /user/save/account/<user id raw string> or /user/save/000...000/<user id>
*
* @param future If true, returns the /user/save/account/... directory
* @return Save data path that may not exist yet
*/
external fun getDefaultProfileSaveDataRoot(future: Boolean): String
/** /**
* Adds a file to the manual filesystem provider in our EmulationSession instance * Adds a file to the manual filesystem provider in our EmulationSession instance
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or * @param path Path to the file we're adding. Can be a string representation of a [Uri] or

View File

@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.annotation.SuppressLint
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
import androidx.recyclerview.widget.RecyclerView
/**
* Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate
* code used in every [RecyclerView].
* Type assigned to [Model] must inherit from [Object] in order to be compared properly.
*/
abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>> :
ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>()).build()) {
override fun onBindViewHolder(holder: Holder, position: Int) =
holder.bind(currentList[position])
private class DiffCallback<Model> : DiffUtil.ItemCallback<Model>() {
override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
return oldItem === newItem
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
return oldItem == newItem
}
}
}

View File

@ -1,98 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.annotation.SuppressLint
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
/**
* Generic list class meant to take care of basic lists
* @param currentList The list to show initially
*/
abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
open var currentList: List<Model>
) : RecyclerView.Adapter<Holder>() {
override fun onBindViewHolder(holder: Holder, position: Int) =
holder.bind(currentList[position])
override fun getItemCount(): Int = currentList.size
/**
* Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter
* is passed in for position, [item] is added to the end of the list. Invokes [callback] last.
* @param item The item to add to the list
* @param position Index where [item] will be added
* @param callback Lambda that's called at the end of the list changes and has the added list
* position passed in as a parameter
*/
open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) {
val newList = currentList.toMutableList()
val positionToUpdate: Int
if (position == -1) {
newList.add(item)
currentList = newList
positionToUpdate = currentList.size - 1
} else {
newList.add(position, item)
currentList = newList
positionToUpdate = position
}
onItemAdded(positionToUpdate, callback)
}
protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) {
notifyItemInserted(position)
callback?.invoke(position)
}
/**
* Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter
* of the change. Invokes [callback] last.
* @param item New list item
* @param position Index where [item] will replace the existing list item
* @param callback Lambda that's called at the end of the list changes and has the changed list
* position passed in as a parameter
*/
fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) {
val newList = currentList.toMutableList()
newList[position] = item
currentList = newList
onItemChanged(position, callback)
}
protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) {
notifyItemChanged(position)
callback?.invoke(position)
}
/**
* Removes the list item at [position] in [currentList] and notifies the underlying adapter
* of the change. Invokes [callback] last.
* @param position Index where the list item will be removed
* @param callback Lambda that's called at the end of the list changes and has the removed list
* position passed in as a parameter
*/
fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
val newList = currentList.toMutableList()
newList.removeAt(position)
currentList = newList
onItemRemoved(position, callback)
}
protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) {
notifyItemRemoved(position)
callback?.invoke(position)
}
/**
* Replaces [currentList] with [newList] and notifies the underlying adapter of the change.
* @param newList The new list to replace [currentList]
*/
@SuppressLint("NotifyDataSetChanged")
open fun replaceList(newList: List<Model>) {
currentList = newList
notifyDataSetChanged()
}
}

View File

@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import org.yuzu.yuzu_emu.model.SelectableItem
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
/**
* Generic list class meant to take care of single selection UI updates
* @param currentList The list to show initially
* @param defaultSelection The default selection to use if no list items are selected by
* [SelectableItem.selected] or if the currently selected item is removed from the list
*/
abstract class AbstractSingleSelectionList<
Model : SelectableItem,
Holder : AbstractViewHolder<Model>
>(
final override var currentList: List<Model>,
private val defaultSelection: DefaultSelection = DefaultSelection.Start
) : AbstractListAdapter<Model, Holder>(currentList) {
var selectedItem = getDefaultSelection()
init {
findSelectedItem()
}
/**
* Changes the selection state of the [SelectableItem] that was selected and the previously selected
* item and notifies the underlying adapter of the change for those items. Invokes [callback] last.
* Does nothing if [position] is the same as the currently selected item.
* @param position Index of the item that was selected
* @param callback Lambda that's called at the end of the list changes and has the selected list
* position passed in as a parameter
*/
fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
if (position == selectedItem) {
return
}
val previouslySelectedItem = selectedItem
selectedItem = position
if (currentList.indices.contains(selectedItem)) {
currentList[selectedItem].onSelectionStateChanged(true)
}
if (currentList.indices.contains(previouslySelectedItem)) {
currentList[previouslySelectedItem].onSelectionStateChanged(false)
}
onItemChanged(previouslySelectedItem)
onItemChanged(selectedItem)
callback?.invoke(position)
}
/**
* Removes a given item from the list and notifies the underlying adapter of the change. If the
* currently selected item was the item that was removed, the item at the position provided
* by [defaultSelection] will be made the new selection. Invokes [callback] last.
* @param position Index of the item that was removed
* @param callback Lambda that's called at the end of the list changes and has the removed and
* selected list positions passed in as parameters
*/
fun removeSelectableItem(
position: Int,
callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)?
) {
removeItem(position)
if (position == selectedItem) {
selectedItem = getDefaultSelection()
currentList[selectedItem].onSelectionStateChanged(true)
onItemChanged(selectedItem)
} else if (position < selectedItem) {
selectedItem--
}
callback?.invoke(position, selectedItem)
}
override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) {
super.addItem(item, position, callback)
if (position <= selectedItem && position != -1) {
selectedItem++
}
}
override fun replaceList(newList: List<Model>) {
super.replaceList(newList)
findSelectedItem()
}
private fun findSelectedItem() {
for (i in currentList.indices) {
if (currentList[i].selected) {
selectedItem = i
break
}
}
}
private fun getDefaultSelection(): Int =
when (defaultSelection) {
DefaultSelection.Start -> currentList.indices.first
DefaultSelection.End -> currentList.indices.last
}
enum class DefaultSelection { Start, End }
}

View File

@ -5,28 +5,48 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
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.Addon
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class AddonAdapter : AbstractDiffAdapter<Addon, AddonAdapter.AddonViewHolder>() { class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
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) }
} }
override fun getItemCount(): Int = currentList.size
override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
holder.bind(currentList[position])
inner class AddonViewHolder(val binding: ListItemAddonBinding) : inner class AddonViewHolder(val binding: ListItemAddonBinding) :
AbstractViewHolder<Addon>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: Addon) { fun bind(addon: Addon) {
binding.root.setOnClickListener { binding.root.setOnClickListener {
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
} }
binding.title.text = model.title binding.title.text = addon.title
binding.version.text = model.version binding.version.text = addon.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked -> binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
model.enabled = checked addon.enabled = checked
} }
binding.addonSwitch.isChecked = model.enabled binding.addonSwitch.isChecked = addon.enabled
}
}
private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
} }
} }
} }

View File

@ -4,11 +4,13 @@
package org.yuzu.yuzu_emu.adapters package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -17,58 +19,72 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.Applet import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) : class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) { RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): AppletAdapter.AppletViewHolder { ): AppletAdapter.AppletViewHolder {
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false) CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@AppletAdapter) }
.also { return AppletViewHolder(it) } .also { return AppletViewHolder(it) }
} }
override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
holder.bind(applets[position])
override fun getItemCount(): Int = applets.size
override fun onClick(view: View) {
val applet = (view.tag as AppletViewHolder).applet
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
if (appletPath.isEmpty()) {
Toast.makeText(
YuzuApplication.appContext,
R.string.applets_error_applet,
Toast.LENGTH_SHORT
).show()
return
}
if (applet.appletInfo == AppletInfo.Cabinet) {
view.findNavController()
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
return
}
NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
val appletGame = Game(
title = YuzuApplication.appContext.getString(applet.titleId),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
view.findNavController().navigate(action)
}
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) : inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
AbstractViewHolder<Applet>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: Applet) { lateinit var applet: Applet
binding.title.setText(model.titleId)
binding.description.setText(model.descriptionId) init {
itemView.tag = this
}
fun bind(applet: Applet) {
this.applet = applet
binding.title.setText(applet.titleId)
binding.description.setText(applet.descriptionId)
binding.icon.setImageDrawable( binding.icon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
binding.icon.context.resources, binding.icon.context.resources,
model.iconId, applet.iconId,
binding.icon.context.theme binding.icon.context.theme
) )
) )
binding.root.setOnClickListener { onClick(model) }
}
fun onClick(applet: Applet) {
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
if (appletPath.isEmpty()) {
Toast.makeText(
binding.root.context,
R.string.applets_error_applet,
Toast.LENGTH_SHORT
).show()
return
}
if (applet.appletInfo == AppletInfo.Cabinet) {
binding.root.findNavController()
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
return
}
NativeLibrary.setCurrentAppletId(applet.appletInfo.appletId)
val appletGame = Game(
title = YuzuApplication.appContext.getString(applet.titleId),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
binding.root.findNavController().navigate(action)
} }
} }
} }

View File

@ -4,10 +4,12 @@
package org.yuzu.yuzu_emu.adapters package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -17,43 +19,54 @@ import org.yuzu.yuzu_emu.model.CabinetMode
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
import org.yuzu.yuzu_emu.model.AppletInfo import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class CabinetLauncherDialogAdapter(val fragment: Fragment) : class CabinetLauncherDialogAdapter(val fragment: Fragment) :
AbstractListAdapter<CabinetMode, CabinetModeViewHolder>( RecyclerView.Adapter<CabinetModeViewHolder>(),
CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList() View.OnClickListener {
) { private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
.also { return CabinetModeViewHolder(it) } .also { return CabinetModeViewHolder(it) }
} }
override fun getItemCount(): Int = cabinetModes.size
override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
holder.bind(cabinetModes[position])
override fun onClick(view: View) {
val mode = (view.tag as CabinetModeViewHolder).cabinetMode
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
NativeLibrary.setCabinetMode(mode.id)
val appletGame = Game(
title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
fragment.findNavController().navigate(action)
}
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) : inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
AbstractViewHolder<CabinetMode>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: CabinetMode) { lateinit var cabinetMode: CabinetMode
init {
itemView.tag = this
}
fun bind(cabinetMode: CabinetMode) {
this.cabinetMode = cabinetMode
binding.icon.setImageDrawable( binding.icon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
binding.icon.context.resources, binding.icon.context.resources,
model.iconId, cabinetMode.iconId,
binding.icon.context.theme binding.icon.context.theme
) )
) )
binding.title.setText(model.titleId) binding.title.setText(cabinetMode.titleId)
binding.root.setOnClickListener { onClick(model) }
}
private fun onClick(mode: CabinetMode) {
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
NativeLibrary.setCabinetMode(mode.id)
val appletGame = Game(
title = YuzuApplication.appContext.getString(R.string.cabinet_applet),
path = appletPath
)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
fragment.findNavController().navigate(action)
} }
} }
} }

View File

@ -7,39 +7,65 @@ import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.model.Driver
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
class DriverAdapter(private val driverViewModel: DriverViewModel) : class DriverAdapter(private val driverViewModel: DriverViewModel) :
AbstractSingleSelectionList<Driver, DriverAdapter.DriverViewHolder>( ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
driverViewModel.driverList.value AsyncDifferConfig.Builder(DiffCallback()).build()
) { ) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding =
.also { return DriverViewHolder(it) } CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return DriverViewHolder(binding)
}
override fun getItemCount(): Int = currentList.size
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
holder.bind(currentList[position])
private fun onSelectDriver(position: Int) {
driverViewModel.setSelectedDriverIndex(position)
notifyItemChanged(driverViewModel.previouslySelectedDriver)
notifyItemChanged(driverViewModel.selectedDriver)
}
private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
if (driverViewModel.selectedDriver > position) {
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
}
if (GpuDriverHelper.customDriverSettingData == driverData.second) {
driverViewModel.setSelectedDriverIndex(0)
}
driverViewModel.driversToDelete.add(driverData.first)
driverViewModel.removeDriver(driverData)
notifyItemRemoved(position)
notifyItemChanged(driverViewModel.selectedDriver)
} }
inner class DriverViewHolder(val binding: CardDriverOptionBinding) : inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
AbstractViewHolder<Driver>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: Driver) { private lateinit var driverData: Pair<String, GpuDriverMetadata>
fun bind(driverData: Pair<String, GpuDriverMetadata>) {
this.driverData = driverData
val driver = driverData.second
binding.apply { binding.apply {
radioButton.isChecked = model.selected radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
root.setOnClickListener { root.setOnClickListener {
selectItem(bindingAdapterPosition) { onSelectDriver(bindingAdapterPosition)
driverViewModel.onDriverSelected(it)
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
}
} }
buttonDelete.setOnClickListener { buttonDelete.setOnClickListener {
removeSelectableItem( onDeleteDriver(driverData, bindingAdapterPosition)
bindingAdapterPosition
) { removedPosition: Int, selectedPosition: Int ->
driverViewModel.onDriverRemoved(removedPosition, selectedPosition)
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
}
} }
// Delay marquee by 3s // Delay marquee by 3s
@ -54,19 +80,38 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
}, },
3000 3000
) )
title.text = model.title if (driver.name == null) {
version.text = model.version title.setText(R.string.system_gpu_driver)
description.text = model.description description.text = ""
if (model.description.isNotEmpty()) { version.text = ""
version.visibility = View.VISIBLE
description.visibility = View.VISIBLE
buttonDelete.visibility = View.VISIBLE
} else {
version.visibility = View.GONE version.visibility = View.GONE
description.visibility = View.GONE description.visibility = View.GONE
buttonDelete.visibility = View.GONE buttonDelete.visibility = View.GONE
} else {
title.text = driver.name
version.text = driver.version
description.text = driver.description
version.visibility = View.VISIBLE
description.visibility = View.VISIBLE
buttonDelete.visibility = View.VISIBLE
} }
} }
} }
} }
private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
override fun areItemsTheSame(
oldItem: Pair<String, GpuDriverMetadata>,
newItem: Pair<String, GpuDriverMetadata>
): Boolean {
return oldItem.first == newItem.first
}
override fun areContentsTheSame(
oldItem: Pair<String, GpuDriverMetadata>,
newItem: Pair<String, GpuDriverMetadata>
): Boolean {
return oldItem.second == newItem.second
}
}
} }

View File

@ -8,14 +8,19 @@ import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardFolderBinding import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() { ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
@ -24,11 +29,18 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
.also { return FolderViewHolder(it) } .also { return FolderViewHolder(it) }
} }
override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
holder.bind(currentList[position])
inner class FolderViewHolder(val binding: CardFolderBinding) : inner class FolderViewHolder(val binding: CardFolderBinding) :
AbstractViewHolder<GameDir>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: GameDir) { private lateinit var gameDir: GameDir
fun bind(gameDir: GameDir) {
this.gameDir = gameDir
binding.apply { binding.apply {
path.text = Uri.parse(model.uriString).path path.text = Uri.parse(gameDir.uriString).path
path.postDelayed( path.postDelayed(
{ {
path.isSelected = true path.isSelected = true
@ -38,7 +50,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
) )
buttonEdit.setOnClickListener { buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(model) GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
.show( .show(
activity.supportFragmentManager, activity.supportFragmentManager,
GameFolderPropertiesDialogFragment.TAG GameFolderPropertiesDialogFragment.TAG
@ -46,9 +58,19 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
} }
buttonDelete.setOnClickListener { buttonDelete.setOnClickListener {
gamesViewModel.removeFolder(model) gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
} }
} }
} }
} }
private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
}
} }

View File

@ -9,6 +9,7 @@ import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
@ -24,6 +25,10 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -31,26 +36,122 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GameAdapter(private val activity: AppCompatActivity) : class GameAdapter(private val activity: AppCompatActivity) :
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>() { ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener,
View.OnLongClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) // Create a new view.
.also { return GameViewHolder(it) } val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.cardGame.setOnClickListener(this)
binding.cardGame.setOnLongClickListener(this)
// Use that view to create a ViewHolder.
return GameViewHolder(binding)
}
override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
holder.bind(currentList[position])
override fun getItemCount(): Int = currentList.size
/**
* Launches the game that was clicked on.
*
* @param view The card representing the game the user wants to play.
*/
override fun onClick(view: View) {
val holder = view.tag as GameViewHolder
val gameExists = DocumentFile.fromSingleUri(
YuzuApplication.appContext,
Uri.parse(holder.game.path)
)?.exists() == true
if (!gameExists) {
Toast.makeText(
YuzuApplication.appContext,
R.string.loader_error_file_not_found,
Toast.LENGTH_LONG
).show()
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
return
}
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
preferences.edit()
.putLong(
holder.game.keyLastPlayedTime,
System.currentTimeMillis()
)
.apply()
val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
action = Intent.ACTION_VIEW
data = Uri.parse(holder.game.path)
}
activity.lifecycleScope.launch {
withContext(Dispatchers.IO) {
val layerDrawable = ResourcesCompat.getDrawable(
YuzuApplication.appContext.resources,
R.drawable.shortcut,
null
) as LayerDrawable
layerDrawable.setDrawableByLayerId(
R.id.shortcut_foreground,
GameIconUtils.getGameIcon(activity, holder.game)
.toDrawable(YuzuApplication.appContext.resources)
)
val inset = YuzuApplication.appContext.resources
.getDimensionPixelSize(R.dimen.icon_inset)
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
val shortcut =
ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
.setShortLabel(holder.game.title)
.setIcon(
IconCompat.createWithAdaptiveBitmap(
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
)
)
.setIntent(openIntent)
.build()
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
}
}
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
view.findNavController().navigate(action)
}
override fun onLongClick(view: View): Boolean {
val holder = view.tag as GameViewHolder
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
view.findNavController().navigate(action)
return true
} }
inner class GameViewHolder(val binding: CardGameBinding) : inner class GameViewHolder(val binding: CardGameBinding) :
AbstractViewHolder<Game>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: Game) { lateinit var game: Game
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
GameIconUtils.loadGameIcon(model, binding.imageGameScreen)
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ") init {
binding.cardGame.tag = this
}
fun bind(game: Game) {
this.game = game
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
binding.textGameTitle.postDelayed( binding.textGameTitle.postDelayed(
{ {
@ -59,79 +160,16 @@ class GameAdapter(private val activity: AppCompatActivity) :
}, },
3000 3000
) )
}
}
binding.cardGame.setOnClickListener { onClick(model) } private class DiffCallback : DiffUtil.ItemCallback<Game>() {
binding.cardGame.setOnLongClickListener { onLongClick(model) } override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
} }
fun onClick(game: Game) { override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
val gameExists = DocumentFile.fromSingleUri( return oldItem == newItem
YuzuApplication.appContext,
Uri.parse(game.path)
)?.exists() == true
if (!gameExists) {
Toast.makeText(
YuzuApplication.appContext,
R.string.loader_error_file_not_found,
Toast.LENGTH_LONG
).show()
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
return
}
val preferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
preferences.edit()
.putLong(
game.keyLastPlayedTime,
System.currentTimeMillis()
)
.apply()
val openIntent =
Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
action = Intent.ACTION_VIEW
data = Uri.parse(game.path)
}
activity.lifecycleScope.launch {
withContext(Dispatchers.IO) {
val layerDrawable = ResourcesCompat.getDrawable(
YuzuApplication.appContext.resources,
R.drawable.shortcut,
null
) as LayerDrawable
layerDrawable.setDrawableByLayerId(
R.id.shortcut_foreground,
GameIconUtils.getGameIcon(activity, game)
.toDrawable(YuzuApplication.appContext.resources)
)
val inset = YuzuApplication.appContext.resources
.getDimensionPixelSize(R.dimen.icon_inset)
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
val shortcut =
ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path)
.setShortLabel(game.title)
.setIcon(
IconCompat.createWithAdaptiveBitmap(
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
)
)
.setIntent(openIntent)
.build()
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
}
}
val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true)
binding.root.findNavController().navigate(action)
}
fun onLongClick(game: Game): Boolean {
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game)
binding.root.findNavController().navigate(action)
return true
} }
} }
} }

View File

@ -12,22 +12,23 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.InstallableProperty import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty import org.yuzu.yuzu_emu.model.SubmenuProperty
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class GamePropertiesAdapter( class GamePropertiesAdapter(
private val viewLifecycle: LifecycleOwner, private val viewLifecycle: LifecycleOwner,
private var properties: List<GameProperty> private var properties: List<GameProperty>
) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) { ) :
RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): AbstractViewHolder<GameProperty> { ): GamePropertyViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return when (viewType) { return when (viewType) {
PropertyType.Submenu.ordinal -> { PropertyType.Submenu.ordinal -> {
@ -50,6 +51,11 @@ class GamePropertiesAdapter(
} }
} }
override fun getItemCount(): Int = properties.size
override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
holder.bind(properties[position])
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (properties[position]) { return when (properties[position]) {
is SubmenuProperty -> PropertyType.Submenu.ordinal is SubmenuProperty -> PropertyType.Submenu.ordinal
@ -57,10 +63,14 @@ class GamePropertiesAdapter(
} }
} }
sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(property: GameProperty)
}
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) : inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
AbstractViewHolder<GameProperty>(binding) { GamePropertyViewHolder(binding.root) {
override fun bind(model: GameProperty) { override fun bind(property: GameProperty) {
val submenuProperty = model as SubmenuProperty val submenuProperty = property as SubmenuProperty
binding.root.setOnClickListener { binding.root.setOnClickListener {
submenuProperty.action.invoke() submenuProperty.action.invoke()
@ -98,9 +108,9 @@ class GamePropertiesAdapter(
} }
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) : inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
AbstractViewHolder<GameProperty>(binding) { GamePropertyViewHolder(binding.root) {
override fun bind(model: GameProperty) { override fun bind(property: GameProperty) {
val installableProperty = model as InstallableProperty val installableProperty = property as InstallableProperty
binding.title.setText(installableProperty.titleId) binding.title.setText(installableProperty.titleId)
binding.description.setText(installableProperty.descriptionId) binding.description.setText(installableProperty.descriptionId)

View File

@ -14,37 +14,69 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class HomeSettingAdapter( class HomeSettingAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner, private val viewLifecycle: LifecycleOwner,
options: List<HomeSetting> var options: List<HomeSetting>
) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) { ) :
RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding =
.also { return HomeOptionViewHolder(it) } CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.setOnClickListener(this)
return HomeOptionViewHolder(binding)
}
override fun getItemCount(): Int {
return options.size
}
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position])
}
override fun onClick(view: View) {
val holder = view.tag as HomeOptionViewHolder
if (holder.option.isEnabled.invoke()) {
holder.option.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
activity,
titleId = holder.option.disabledTitleId,
descriptionId = holder.option.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
}
} }
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) : inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
AbstractViewHolder<HomeSetting>(binding) { RecyclerView.ViewHolder(binding.root) {
override fun bind(model: HomeSetting) { lateinit var option: HomeSetting
binding.optionTitle.text = activity.resources.getString(model.titleId)
binding.optionDescription.text = activity.resources.getString(model.descriptionId) init {
itemView.tag = this
}
fun bind(option: HomeSetting) {
this.option = option
binding.optionTitle.text = activity.resources.getString(option.titleId)
binding.optionDescription.text = activity.resources.getString(option.descriptionId)
binding.optionIcon.setImageDrawable( binding.optionIcon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
activity.resources, activity.resources,
model.iconId, option.iconId,
activity.theme activity.theme
) )
) )
when (model.titleId) { when (option.titleId) {
R.string.get_early_access -> R.string.get_early_access ->
binding.optionLayout.background = binding.optionLayout.background =
ContextCompat.getDrawable( ContextCompat.getDrawable(
@ -53,7 +85,7 @@ class HomeSettingAdapter(
) )
} }
if (!model.isEnabled.invoke()) { if (!option.isEnabled.invoke()) {
binding.optionTitle.alpha = 0.5f binding.optionTitle.alpha = 0.5f
binding.optionDescription.alpha = 0.5f binding.optionDescription.alpha = 0.5f
binding.optionIcon.alpha = 0.5f binding.optionIcon.alpha = 0.5f
@ -61,7 +93,7 @@ class HomeSettingAdapter(
viewLifecycle.lifecycleScope.launch { viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
model.details.collect { updateOptionDetails(it) } option.details.collect { updateOptionDetails(it) }
} }
} }
binding.optionDetail.postDelayed( binding.optionDetail.postDelayed(
@ -71,20 +103,6 @@ class HomeSettingAdapter(
}, },
3000 3000
) )
binding.root.setOnClickListener { onClick(model) }
}
private fun onClick(model: HomeSetting) {
if (model.isEnabled.invoke()) {
model.onClick.invoke()
} else {
MessageDialogFragment.newInstance(
activity,
titleId = model.disabledTitleId,
descriptionId = model.disabledMessageId
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
}
} }
private fun updateOptionDetails(detailString: String) { private fun updateOptionDetails(detailString: String) {

View File

@ -6,33 +6,43 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
import org.yuzu.yuzu_emu.model.Installable import org.yuzu.yuzu_emu.model.Installable
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class InstallableAdapter(installables: List<Installable>) : class InstallableAdapter(private val installables: List<Installable>) :
AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) { RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): InstallableAdapter.InstallableViewHolder { ): InstallableAdapter.InstallableViewHolder {
CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding =
.also { return InstallableViewHolder(it) } CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return InstallableViewHolder(binding)
} }
inner class InstallableViewHolder(val binding: CardInstallableBinding) : override fun getItemCount(): Int = installables.size
AbstractViewHolder<Installable>(binding) {
override fun bind(model: Installable) {
binding.title.setText(model.titleId)
binding.description.setText(model.descriptionId)
if (model.install != null) { override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
holder.bind(installables[position])
inner class InstallableViewHolder(val binding: CardInstallableBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var installable: Installable
fun bind(installable: Installable) {
this.installable = installable
binding.title.setText(installable.titleId)
binding.description.setText(installable.descriptionId)
if (installable.install != null) {
binding.buttonInstall.visibility = View.VISIBLE binding.buttonInstall.visibility = View.VISIBLE
binding.buttonInstall.setOnClickListener { model.install.invoke() } binding.buttonInstall.setOnClickListener { installable.install.invoke() }
} }
if (model.export != null) { if (installable.export != null) {
binding.buttonExport.visibility = View.VISIBLE binding.buttonExport.visibility = View.VISIBLE
binding.buttonExport.setOnClickListener { model.export.invoke() } binding.buttonExport.setOnClickListener { installable.export.invoke() }
} }
} }
} }

View File

@ -7,33 +7,49 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
import org.yuzu.yuzu_emu.model.License import org.yuzu.yuzu_emu.model.License
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) : class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) { RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding =
.also { return LicenseViewHolder(it) } ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.root.setOnClickListener(this)
return LicenseViewHolder(binding)
} }
inner class LicenseViewHolder(val binding: ListItemSettingBinding) : override fun getItemCount(): Int = licenses.size
AbstractViewHolder<License>(binding) {
override fun bind(model: License) {
binding.apply {
textSettingName.text = root.context.getString(model.titleId)
textSettingDescription.text = root.context.getString(model.descriptionId)
textSettingValue.visibility = View.GONE
root.setOnClickListener { onClick(model) } override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
} holder.bind(licenses[position])
}
override fun onClick(view: View) {
val license = (view.tag as LicenseViewHolder).license
LicenseBottomSheetDialogFragment.newInstance(license)
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
}
inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
lateinit var license: License
init {
itemView.tag = this
} }
private fun onClick(license: License) { fun bind(license: License) {
LicenseBottomSheetDialogFragment.newInstance(license) this.license = license
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
val context = YuzuApplication.appContext
binding.textSettingName.text = context.getString(license.titleId)
binding.textSettingDescription.text = context.getString(license.descriptionId)
binding.textSettingValue.visibility = View.GONE
} }
} }
} }

View File

@ -10,6 +10,7 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.databinding.PageSetupBinding import org.yuzu.yuzu_emu.databinding.PageSetupBinding
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
@ -17,19 +18,31 @@ import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) : class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) { RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return SetupPageViewHolder(it) } return SetupPageViewHolder(binding)
} }
override fun getItemCount(): Int = pages.size
override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) : inner class SetupPageViewHolder(val binding: PageSetupBinding) :
AbstractViewHolder<SetupPage>(binding), SetupCallback { RecyclerView.ViewHolder(binding.root), SetupCallback {
override fun bind(model: SetupPage) { lateinit var page: SetupPage
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
init {
itemView.tag = this
}
fun bind(page: SetupPage) {
this.page = page
if (page.stepCompleted.invoke() == StepState.COMPLETE) {
binding.buttonAction.visibility = View.INVISIBLE binding.buttonAction.visibility = View.INVISIBLE
binding.textConfirmation.visibility = View.VISIBLE binding.textConfirmation.visibility = View.VISIBLE
} }
@ -37,31 +50,31 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
binding.icon.setImageDrawable( binding.icon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
activity.resources, activity.resources,
model.iconId, page.iconId,
activity.theme activity.theme
) )
) )
binding.textTitle.text = activity.resources.getString(model.titleId) binding.textTitle.text = activity.resources.getString(page.titleId)
binding.textDescription.text = binding.textDescription.text =
Html.fromHtml(activity.resources.getString(model.descriptionId), 0) Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
binding.buttonAction.apply { binding.buttonAction.apply {
text = activity.resources.getString(model.buttonTextId) text = activity.resources.getString(page.buttonTextId)
if (model.buttonIconId != 0) { if (page.buttonIconId != 0) {
icon = ResourcesCompat.getDrawable( icon = ResourcesCompat.getDrawable(
activity.resources, activity.resources,
model.buttonIconId, page.buttonIconId,
activity.theme activity.theme
) )
} }
iconGravity = iconGravity =
if (model.leftAlignedIcon) { if (page.leftAlignedIcon) {
MaterialButton.ICON_GRAVITY_START MaterialButton.ICON_GRAVITY_START
} else { } else {
MaterialButton.ICON_GRAVITY_END MaterialButton.ICON_GRAVITY_END
} }
setOnClickListener { setOnClickListener {
model.buttonAction.invoke(this@SetupPageViewHolder) page.buttonAction.invoke(this@SetupPageViewHolder)
} }
} }
} }

View File

@ -79,18 +79,7 @@ object Settings {
const val PREF_THEME_MODE = "ThemeMode" const val PREF_THEME_MODE = "ThemeMode"
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds" const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
enum class EmulationOrientation(val int: Int) { const val LayoutOption_Unspecified = 0
Unspecified(0), const val LayoutOption_MobilePortrait = 4
SensorLandscape(5), const val LayoutOption_MobileLandscape = 5
Landscape(1),
ReverseLandscape(2),
SensorPortrait(6),
Portrait(4),
ReversePortrait(3);
companion object {
fun from(int: Int): EmulationOrientation =
entries.firstOrNull { it.int == int } ?: Unspecified
}
}
} }

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.textVersionName.text = BuildConfig.VERSION_NAME binding.textBuildHash.text = BuildConfig.GIT_HASH
binding.textVersionName.setOnClickListener { binding.buttonBuildHash.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

@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -14,26 +13,20 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.DriverAdapter import org.yuzu.yuzu_emu.adapters.DriverAdapter
import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -62,43 +55,12 @@ class DriverManagerFragment : Fragment() {
return binding.root return binding.root
} }
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false) homeViewModel.setStatusBarShadeVisibility(visible = false)
driverViewModel.onOpenDriverManager(args.game) driverViewModel.onOpenDriverManager(args.game)
if (NativeConfig.isPerGameConfigLoaded()) {
binding.toolbarDrivers.inflateMenu(R.menu.menu_driver_manager)
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
binding.toolbarDrivers.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_driver_clear -> {
StringSetting.DRIVER_PATH.global = true
driverViewModel.updateDriverList()
(binding.listDrivers.adapter as DriverAdapter)
.replaceList(driverViewModel.driverList.value)
driverViewModel.showClearButton(false)
true
}
else -> false
}
}
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
driverViewModel.showClearButton.collect {
binding.toolbarDrivers.menu
.findItem(R.id.menu_driver_clear).isVisible = it
}
}
}
}
}
if (!driverViewModel.isInteractionAllowed.value) { if (!driverViewModel.isInteractionAllowed.value) {
DriversLoadingDialogFragment().show( DriversLoadingDialogFragment().show(
@ -123,6 +85,25 @@ class DriverManagerFragment : Fragment() {
adapter = DriverAdapter(driverViewModel) adapter = DriverAdapter(driverViewModel)
} }
viewLifecycleOwner.lifecycleScope.apply {
launch {
driverViewModel.driverList.collectLatest {
(binding.listDrivers.adapter as DriverAdapter).submitList(it)
}
}
launch {
driverViewModel.newDriverInstalled.collect {
if (_binding != null && it) {
(binding.listDrivers.adapter as DriverAdapter).apply {
notifyItemChanged(driverViewModel.previouslySelectedDriver)
notifyItemChanged(driverViewModel.selectedDriver)
driverViewModel.setNewDriverInstalled(false)
}
}
}
}
}
setInsets() setInsets()
} }
@ -179,7 +160,7 @@ class DriverManagerFragment : Fragment() {
false false
) { ) {
val driverPath = val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}" "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
val driverFile = File(driverPath) val driverFile = File(driverPath)
// Ignore file exceptions when a user selects an invalid zip // Ignore file exceptions when a user selects an invalid zip
@ -196,21 +177,12 @@ class DriverManagerFragment : Fragment() {
val driverData = GpuDriverHelper.getMetadataFromZip(driverFile) val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
val driverInList = val driverInList =
driverViewModel.driverData.firstOrNull { it.second == driverData } driverViewModel.driverList.value.firstOrNull { it.second == driverData }
if (driverInList != null) { if (driverInList != null) {
return@newInstance getString(R.string.driver_already_installed) return@newInstance getString(R.string.driver_already_installed)
} else { } else {
driverViewModel.onDriverAdded(Pair(driverPath, driverData)) driverViewModel.addDriver(Pair(driverPath, driverData))
withContext(Dispatchers.Main) { driverViewModel.setNewDriverInstalled(true)
if (_binding != null) {
val adapter = binding.listDrivers.adapter as DriverAdapter
adapter.addItem(driverData.toDriver())
adapter.selectItem(adapter.currentList.indices.last)
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
binding.listDrivers
.smoothScrollToPosition(adapter.currentList.indices.last)
}
}
} }
return@newInstance Any() return@newInstance Any()
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)

View File

@ -50,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
@ -100,7 +99,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
updateOrientation()
val intentUri: Uri? = requireActivity().intent.data val intentUri: Uri? = requireActivity().intent.data
var intentGame: Game? = null var intentGame: Game? = null
@ -460,23 +458,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() { private fun updateOrientation() {
emulationActivity?.let { emulationActivity?.let {
val orientationSetting = it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) {
EmulationOrientation.from(IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) Settings.LayoutOption_MobileLandscape ->
it.requestedOrientation = when (orientationSetting) {
EmulationOrientation.Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
EmulationOrientation.SensorLandscape ->
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
EmulationOrientation.Landscape -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
EmulationOrientation.ReverseLandscape -> Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE else -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
EmulationOrientation.SensorPortrait ->
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
EmulationOrientation.Portrait -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
EmulationOrientation.ReversePortrait ->
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
} }
} }
} }
@ -663,7 +651,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
private fun startConfiguringControls() { private fun startConfiguringControls() {
// Lock the current orientation to prevent editing inconsistencies // Lock the current orientation to prevent editing inconsistencies
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
emulationActivity?.let { emulationActivity?.let {
it.requestedOrientation = it.requestedOrientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
@ -681,7 +669,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.doneControlConfig.visibility = View.GONE binding.doneControlConfig.visibility = View.GONE
binding.surfaceInputOverlay.setIsInEditMode(false) binding.surfaceInputOverlay.setIsInEditMode(false)
// Unlock the orientation if it was locked for editing // Unlock the orientation if it was locked for editing
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
emulationActivity?.let { emulationActivity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
} }

View File

@ -7,39 +7,20 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.InstallableAdapter import org.yuzu.yuzu_emu.adapters.InstallableAdapter
import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.Installable import org.yuzu.yuzu_emu.model.Installable
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.FileUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.math.BigInteger
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class InstallableFragment : Fragment() { class InstallableFragment : Fragment() {
private var _binding: FragmentInstallablesBinding? = null private var _binding: FragmentInstallablesBinding? = null
@ -75,17 +56,6 @@ class InstallableFragment : Fragment() {
binding.root.findNavController().popBackStack() binding.root.findNavController().popBackStack()
} }
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
homeViewModel.openImportSaves.collect {
if (it) {
importSaves.launch(arrayOf("application/zip"))
homeViewModel.setOpenImportSaves(false)
}
}
}
}
val installables = listOf( val installables = listOf(
Installable( Installable(
R.string.user_data, R.string.user_data,
@ -93,43 +63,6 @@ class InstallableFragment : Fragment() {
install = { mainActivity.importUserData.launch(arrayOf("application/zip")) }, install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
export = { mainActivity.exportUserData.launch("export.zip") } export = { mainActivity.exportUserData.launch("export.zip") }
), ),
Installable(
R.string.manage_save_data,
R.string.manage_save_data_description,
install = {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.import_save_warning,
descriptionId = R.string.import_save_warning_description,
positiveAction = { homeViewModel.setOpenImportSaves(true) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
},
export = {
val oldSaveDataFolder = File(
"${DirectoryInitialization.userDirectory}/nand" +
NativeLibrary.getDefaultProfileSaveDataRoot(false)
)
val futureSaveDataFolder = File(
"${DirectoryInitialization.userDirectory}/nand" +
NativeLibrary.getDefaultProfileSaveDataRoot(true)
)
if (!oldSaveDataFolder.exists() && !futureSaveDataFolder.exists()) {
Toast.makeText(
YuzuApplication.appContext,
R.string.no_save_data_found,
Toast.LENGTH_SHORT
).show()
return@Installable
} else {
exportSaves.launch(
"${getString(R.string.save_data)} " +
LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
)
)
}
}
),
Installable( Installable(
R.string.install_game_content, R.string.install_game_content,
R.string.install_game_content_description, R.string.install_game_content_description,
@ -188,156 +121,4 @@ class InstallableFragment : Fragment() {
windowInsets windowInsets
} }
private val importSaves =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var successfulImports = 0
var failedImports = 0
if (files != null) {
for (file in files) {
if (file.isDirectory) {
val baseSaveDir =
NativeLibrary.getSavePath(BigInteger(file.name, 16).toString())
if (baseSaveDir.isEmpty()) {
failedImports++
continue
}
val internalSaveFolder = File(
"${DirectoryInitialization.userDirectory}/nand$baseSaveDir"
)
internalSaveFolder.deleteRecursively()
internalSaveFolder.mkdir()
file.copyRecursively(target = internalSaveFolder, overwrite = true)
successfulImports++
}
}
}
withContext(Dispatchers.Main) {
if (successfulImports == 0) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.save_file_invalid_zip_structure,
descriptionId = R.string.save_file_invalid_zip_structure_description
).show(parentFragmentManager, MessageDialogFragment.TAG)
return@withContext
}
val successString = if (failedImports > 0) {
"""
${
requireContext().resources.getQuantityString(
R.plurals.saves_import_success,
successfulImports,
successfulImports
)
}
${
requireContext().resources.getQuantityString(
R.plurals.saves_import_failed,
failedImports,
failedImports
)
}
"""
} else {
requireContext().resources.getQuantityString(
R.plurals.saves_import_success,
successfulImports,
successfulImports
)
}
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.import_complete,
descriptionString = successString
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
cacheSaveDir.deleteRecursively()
} catch (e: Exception) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
private val exportSaves = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
val oldSaveDataFolder = File(
"${DirectoryInitialization.userDirectory}/nand" +
NativeLibrary.getDefaultProfileSaveDataRoot(false)
)
if (oldSaveDataFolder.exists()) {
oldSaveDataFolder.copyRecursively(cacheSaveDir)
}
val futureSaveDataFolder = File(
"${DirectoryInitialization.userDirectory}/nand" +
NativeLibrary.getDefaultProfileSaveDataRoot(true)
)
if (futureSaveDataFolder.exists()) {
futureSaveDataFolder.copyRecursively(cacheSaveDir)
}
val saveFilesTotal = cacheSaveDir.listFiles()?.size ?: 0
if (saveFilesTotal == 0) {
cacheSaveDir.deleteRecursively()
return@newInstance getString(R.string.no_save_data_found)
}
val zipResult = FileUtil.zipFromInternalStorage(
cacheSaveDir,
cacheSaveDir.path,
BufferedOutputStream(requireContext().contentResolver.openOutputStream(result))
)
cacheSaveDir.deleteRecursively()
return@newInstance when (zipResult) {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
} }

View File

@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
data class Driver(
override var selected: Boolean,
val title: String,
val version: String = "",
val description: String = ""
) : SelectableItem {
override fun onSelectionStateChanged(selected: Boolean) {
this.selected = selected
}
companion object {
fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver =
Driver(
selected,
this.name ?: "",
this.version ?: "",
this.description ?: ""
)
}
}

View File

@ -9,7 +9,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -18,10 +17,11 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
import java.io.BufferedOutputStream
import java.io.File import java.io.File
class DriverViewModel : ViewModel() { class DriverViewModel : ViewModel() {
@ -38,81 +38,97 @@ class DriverViewModel : ViewModel() {
!loading && ready && !deleting !loading && ready && !deleting
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false)
var driverData = GpuDriverHelper.getDrivers() private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers())
val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
private val _driverList = MutableStateFlow(emptyList<Driver>()) var previouslySelectedDriver = 0
val driverList: StateFlow<List<Driver>> get() = _driverList var selectedDriver = -1
// Used for showing which driver is currently installed within the driver manager card // Used for showing which driver is currently installed within the driver manager card
private val _selectedDriverTitle = MutableStateFlow("") private val _selectedDriverTitle = MutableStateFlow("")
val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
private val _showClearButton = MutableStateFlow(false) private val _newDriverInstalled = MutableStateFlow(false)
val showClearButton = _showClearButton.asStateFlow() val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
private val driversToDelete = mutableListOf<String>() val driversToDelete = mutableListOf<String>()
init { init {
updateDriverList() val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData
findSelectedDriver(currentDriverMetadata)
// If a user had installed a driver before the manager was implemented, this zips
// the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
// be indexed and exported as expected.
if (selectedDriver == -1) {
val driverToSave =
File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
driverToSave.createNewFile()
FileUtil.zipFromInternalStorage(
File(GpuDriverHelper.driverInstallationPath!!),
GpuDriverHelper.driverInstallationPath!!,
BufferedOutputStream(driverToSave.outputStream())
)
_driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
setSelectedDriverIndex(_driverList.value.size - 1)
}
// If a user had installed a driver before the config was reworked to be multiplatform,
// we have save the path of the previously selected driver to the new setting.
if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 &&
StringSetting.DRIVER_PATH.global
) {
StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first)
NativeConfig.saveGlobalConfig()
} else {
findSelectedDriver(GpuDriverHelper.customDriverSettingData)
}
updateDriverNameForGame(null) updateDriverNameForGame(null)
} }
fun reloadDriverData() { fun setSelectedDriverIndex(value: Int) {
_areDriversLoading.value = true if (selectedDriver != -1) {
driverData = GpuDriverHelper.getDrivers() previouslySelectedDriver = selectedDriver
updateDriverList() }
_areDriversLoading.value = false selectedDriver = value
} }
fun updateDriverList() { fun setNewDriverInstalled(value: Boolean) {
val selectedDriver = GpuDriverHelper.customDriverSettingData _newDriverInstalled.value = value
val newDriverList = mutableListOf( }
Driver(
selectedDriver == GpuDriverMetadata(), fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
YuzuApplication.appContext.getString(R.string.system_gpu_driver) val driverIndex = _driverList.value.indexOfFirst { it == driverData }
) if (driverIndex == -1) {
) _driverList.value.add(driverData)
driverData.forEach { setSelectedDriverIndex(_driverList.value.size - 1)
newDriverList.add(it.second.toDriver(it.second == selectedDriver)) _selectedDriverTitle.value = driverData.second.name
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
} else {
setSelectedDriverIndex(driverIndex)
} }
_driverList.value = newDriverList }
fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
_driverList.value.remove(driverData)
} }
fun onOpenDriverManager(game: Game?) { fun onOpenDriverManager(game: Game?) {
if (game != null) { if (game != null) {
SettingsFile.loadCustomConfig(game) SettingsFile.loadCustomConfig(game)
} }
updateDriverList()
}
fun showClearButton(value: Boolean) { val driverPath = StringSetting.DRIVER_PATH.getString()
_showClearButton.value = value if (driverPath.isEmpty()) {
} setSelectedDriverIndex(0)
fun onDriverSelected(position: Int) {
if (position == 0) {
StringSetting.DRIVER_PATH.setString("")
} else { } else {
StringSetting.DRIVER_PATH.setString(driverData[position - 1].first) findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath)))
} }
} }
fun onDriverRemoved(removedPosition: Int, selectedPosition: Int) {
driversToDelete.add(driverData[removedPosition - 1].first)
driverData.removeAt(removedPosition - 1)
onDriverSelected(selectedPosition)
}
fun onDriverAdded(driver: Pair<String, GpuDriverMetadata>) {
if (driversToDelete.contains(driver.first)) {
driversToDelete.remove(driver.first)
}
driverData.add(driver)
onDriverSelected(driverData.size)
}
fun onCloseDriverManager(game: Game?) { fun onCloseDriverManager(game: Game?) {
_isDeletingDrivers.value = true _isDeletingDrivers.value = true
StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first)
updateDriverNameForGame(game) updateDriverNameForGame(game)
if (game == null) { if (game == null) {
NativeConfig.saveGlobalConfig() NativeConfig.saveGlobalConfig()
@ -165,6 +181,20 @@ class DriverViewModel : ViewModel() {
} }
} }
private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) {
if (driverList.value.size == 1) {
setSelectedDriverIndex(0)
return
}
driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> ->
if (driver.second == currentDriverMetadata) {
setSelectedDriverIndex(i)
return
}
}
}
fun updateDriverNameForGame(game: Game?) { fun updateDriverNameForGame(game: Game?) {
if (!GpuDriverHelper.supportsCustomDriverLoading()) { if (!GpuDriverHelper.supportsCustomDriverLoading()) {
return return
@ -187,6 +217,7 @@ class DriverViewModel : ViewModel() {
private fun setDriverReady() { private fun setDriverReady() {
_isDriverReady.value = true _isDriverReady.value = true
updateName() _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
} }
} }

View File

@ -167,14 +167,13 @@ class GamesViewModel : ViewModel() {
} }
} }
fun onCloseGameFoldersFragment() { fun onCloseGameFoldersFragment() =
NativeConfig.saveGlobalConfig()
viewModelScope.launch { viewModelScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
NativeConfig.saveGlobalConfig()
getGameDirs(true) getGameDirs(true)
} }
} }
}
private fun getGameDirs(reloadList: Boolean = false) { private fun getGameDirs(reloadList: Boolean = false) {
val gameDirs = NativeConfig.getGameDirs() val gameDirs = NativeConfig.getGameDirs()

View File

@ -1,9 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.model
interface SelectableItem {
var selected: Boolean
fun onSelectionStateChanged(selected: Boolean)
}

View File

@ -41,7 +41,6 @@ import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.TaskState import org.yuzu.yuzu_emu.model.TaskState
@ -59,7 +58,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private val gamesViewModel: GamesViewModel by viewModels() private val gamesViewModel: GamesViewModel by viewModels()
private val taskViewModel: TaskViewModel by viewModels() private val taskViewModel: TaskViewModel by viewModels()
private val addonViewModel: AddonViewModel by viewModels() private val addonViewModel: AddonViewModel by viewModels()
private val driverViewModel: DriverViewModel by viewModels()
override var themeId: Int = 0 override var themeId: Int = 0
@ -691,7 +689,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
NativeLibrary.initializeSystem(true) NativeLibrary.initializeSystem(true)
NativeConfig.initializeGlobalConfig() NativeConfig.initializeGlobalConfig()
gamesViewModel.reloadGames(false) gamesViewModel.reloadGames(false)
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, IndeterminateProgressDialogFragment.TAG)

View File

@ -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 DocumentFile.listFiles * This function will be faster than DoucmentFile.listFiles
* @param uri Directory uri. * @param uri Directory uri.
* @return CheapDocument lists. * @return CheapDocument lists.
*/ */

View File

@ -62,6 +62,9 @@ object GpuDriverHelper {
?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name } ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
?.distinct() ?.distinct()
?.toMutableList() ?: mutableListOf() ?.toMutableList() ?: mutableListOf()
// TODO: Get system driver information
drivers.add(0, Pair("", GpuDriverMetadata()))
return drivers return drivers
} }

View File

@ -1,18 +0,0 @@
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.viewholder
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import org.yuzu.yuzu_emu.adapters.AbstractDiffAdapter
import org.yuzu.yuzu_emu.adapters.AbstractListAdapter
/**
* [RecyclerView.ViewHolder] meant to work together with a [AbstractDiffAdapter] or a
* [AbstractListAdapter] so we can run [bind] on each list item without needing a manual hookup.
*/
abstract class AbstractViewHolder<Model>(binding: ViewBinding) :
RecyclerView.ViewHolder(binding.root) {
abstract fun bind(model: Model)
}

View File

@ -14,6 +14,12 @@ AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_t
} }
} }
AndroidConfig::~AndroidConfig() {
if (global) {
AndroidConfig::SaveAllValues();
}
}
void AndroidConfig::ReloadAllValues() { void AndroidConfig::ReloadAllValues() {
Reload(); Reload();
ReadAndroidValues(); ReadAndroidValues();
@ -21,7 +27,7 @@ void AndroidConfig::ReloadAllValues() {
} }
void AndroidConfig::SaveAllValues() { void AndroidConfig::SaveAllValues() {
SaveValues(); Save();
SaveAndroidValues(); SaveAndroidValues();
} }

View File

@ -9,6 +9,7 @@ class AndroidConfig final : public Config {
public: public:
explicit AndroidConfig(const std::string& config_name = "config", explicit AndroidConfig(const std::string& config_name = "config",
ConfigType config_type = ConfigType::GlobalConfig); ConfigType config_type = ConfigType::GlobalConfig);
~AndroidConfig() override;
void ReloadAllValues() override; void ReloadAllValues() override;
void SaveAllValues() override; void SaveAllValues() override;

View File

@ -45,15 +45,15 @@
#include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/profile_select.h"
#include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h" #include "core/frontend/applets/web_browser.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h" #include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "frontend_common/config.h" #include "frontend_common/config.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "jni/android_common/android_common.h" #include "jni/android_common/android_common.h"
#include "jni/id_cache.h" #include "jni/id_cache.h"
#include "jni/native.h" #include "jni/native.h"
@ -410,8 +410,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::Fullkey); handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
handheld->Disconnect(); handheld->Disconnect();
} }
} }
@ -770,8 +770,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
ASSERT(user_id); ASSERT(user_id);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
{}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, 1, EmulationSession::GetInstance().System(), vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser,
user_id->AsU128(), 0); FileSys::SaveDataType::SaveData, 1, user_id->AsU128(), 0);
const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path); const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
if (!Common::FS::CreateParentDirs(full_path)) { if (!Common::FS::CreateParentDirs(full_path)) {
@ -862,9 +862,6 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env,
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); auto program_id = EmulationSession::GetProgramId(env, jprogramId);
if (program_id == 0) {
return ToJString(env, "");
}
auto& system = EmulationSession::GetInstance().System(); auto& system = EmulationSession::GetInstance().System();
@ -878,24 +875,11 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
FileSys::Mode::Read); FileSys::Mode::Read);
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
{}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
program_id, user_id->AsU128(), 0); program_id, user_id->AsU128(), 0);
return ToJString(env, user_save_data_path); return ToJString(env, user_save_data_path);
} }
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDefaultProfileSaveDataRoot(JNIEnv* env,
jobject jobj,
jboolean jfuture) {
Service::Account::ProfileManager manager;
// TODO: Pass in a selected user once we get the relevant UI working
const auto user_id = manager.GetUser(static_cast<std::size_t>(0));
ASSERT(user_id);
const auto user_save_data_root =
FileSys::SaveDataFactory::GetUserGameSaveDataRoot(user_id->AsU128(), jfuture);
return ToJString(env, user_save_data_root);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* env, jobject jobj, void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* env, jobject jobj,
jstring jpath) { jstring jpath) {
EmulationSession::GetInstance().ConfigureFilesystemProvider(GetJString(env, jpath)); EmulationSession::GetInstance().ConfigureFilesystemProvider(GetJString(env, jpath));

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->RuntimeModifiable(); return setting->RuntimeModfiable();
} }
return true; return true;
} }

View File

@ -147,7 +147,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_version_name" android:id="@+id/button_build_hash"
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_version_name" android:id="@+id/text_build_hash"
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

@ -148,7 +148,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_version_name" android:id="@+id/button_build_hash"
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_version_name" android:id="@+id/text_build_hash"
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,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_driver_clear"
android:icon="@drawable/ic_clear"
android:title="@string/clear"
app:showAsAction="always" />
</menu>

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/language_german</item> <item>@string/langauge_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>
@ -118,23 +118,15 @@
</integer-array> </integer-array>
<string-array name="rendererScreenLayoutNames"> <string-array name="rendererScreenLayoutNames">
<item>@string/screen_layout_auto</item>
<item>@string/screen_layout_sensor_landscape</item>
<item>@string/screen_layout_landscape</item> <item>@string/screen_layout_landscape</item>
<item>@string/screen_layout_reverse_landscape</item>
<item>@string/screen_layout_sensor_portrait</item>
<item>@string/screen_layout_portrait</item> <item>@string/screen_layout_portrait</item>
<item>@string/screen_layout_reverse_portrait</item> <item>@string/screen_layout_auto</item>
</string-array> </string-array>
<integer-array name="rendererScreenLayoutValues"> <integer-array name="rendererScreenLayoutValues">
<item>0</item>
<item>5</item> <item>5</item>
<item>1</item>
<item>2</item>
<item>6</item>
<item>4</item> <item>4</item>
<item>3</item> <item>0</item>
</integer-array> </integer-array>
<string-array name="rendererAspectRatioNames"> <string-array name="rendererAspectRatioNames">
@ -228,10 +220,10 @@
<item>R</item> <item>R</item>
<item>ZL</item> <item>ZL</item>
<item>ZR</item> <item>ZR</item>
<item>L3</item>
<item>R3</item>
<item>@string/gamepad_left_stick</item> <item>@string/gamepad_left_stick</item>
<item>@string/gamepad_right_stick</item> <item>@string/gamepad_right_stick</item>
<item>L3</item>
<item>R3</item>
<item>@string/gamepad_d_pad</item> <item>@string/gamepad_d_pad</item>
</string-array> </string-array>

View File

@ -133,15 +133,6 @@
<string name="add_game_folder">Add game folder</string> <string name="add_game_folder">Add game folder</string>
<string name="folder_already_added">This folder was already added!</string> <string name="folder_already_added">This folder was already added!</string>
<string name="game_folder_properties">Game folder properties</string> <string name="game_folder_properties">Game folder properties</string>
<plurals name="saves_import_failed">
<item quantity="one">Failed to import %d save</item>
<item quantity="other">Failed to import %d saves</item>
</plurals>
<plurals name="saves_import_success">
<item quantity="one">Successfully imported %d save</item>
<item quantity="other">Successfully imported %d saves</item>
</plurals>
<string name="no_save_data_found">No save data found</string>
<!-- Applet launcher strings --> <!-- Applet launcher strings -->
<string name="applets">Applet launcher</string> <string name="applets">Applet launcher</string>
@ -285,7 +276,6 @@
<string name="global">Global</string> <string name="global">Global</string>
<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>
<!-- GPU driver installation --> <!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string> <string name="select_gpu_driver">Select GPU driver</string>
@ -410,7 +400,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="language_german" translatable="false">Deutsch</string> <string name="langauge_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>
@ -473,13 +463,9 @@
<string name="anti_aliasing_smaa">SMAA</string> <string name="anti_aliasing_smaa">SMAA</string>
<!-- Screen Layouts --> <!-- Screen Layouts -->
<string name="screen_layout_auto">Auto</string>
<string name="screen_layout_sensor_landscape">Sensor landscape</string>
<string name="screen_layout_landscape">Landscape</string> <string name="screen_layout_landscape">Landscape</string>
<string name="screen_layout_reverse_landscape">Reverse landscape</string>
<string name="screen_layout_sensor_portrait">Sensor portrait</string>
<string name="screen_layout_portrait">Portrait</string> <string name="screen_layout_portrait">Portrait</string>
<string name="screen_layout_reverse_portrait">Reverse portrait</string> <string name="screen_layout_auto">Auto</string>
<!-- Aspect Ratios --> <!-- Aspect Ratios -->
<string name="ratio_default">Default (16:9)</string> <string name="ratio_default">Default (16:9)</string>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<game-mode-config
xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsBatteryGameMode="true"
android:supportsPerformanceGameMode="true"
android:allowGameDownscaling="false"
android:allowGameFpsOverride="false"/>

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, "OpusDecoder failed to initialize."); LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize.");
return; return;
} }
} }

View File

@ -8,11 +8,8 @@
#include "audio_core/sink/sink_stream.h" #include "audio_core/sink/sink_stream.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.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;
@ -28,7 +25,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_, Kernel::KProcess* handle_, u16 channel_count_, size_t session_id_, u32 handle_,
u64 applet_resource_user_id_, Sink::StreamType type_) { u64 applet_resource_user_id_, Sink::StreamType type_) {
if (stream) { if (stream) {
Finalize(); Finalize();
@ -39,7 +36,6 @@ 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) {
@ -58,11 +54,6 @@ 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() {
@ -100,7 +91,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(
handle->GetMemory(), buffer.samples, buffer.size / sizeof(s16)); system.ApplicationMemory(), buffer.samples, buffer.size / sizeof(s16));
stream->AppendBuffer(new_buffer, samples); stream->AppendBuffer(new_buffer, samples);
} }
} }
@ -109,7 +100,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))};
handle->GetMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); system.ApplicationMemory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
} }
} }

View File

@ -20,10 +20,6 @@ struct EventType;
} // namespace Timing } // namespace Timing
} // namespace Core } // namespace Core
namespace Kernel {
class KProcess;
} // namespace Kernel
namespace AudioCore { namespace AudioCore {
namespace Sink { namespace Sink {
@ -48,13 +44,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 - Process handle for this device session. * @param handle - Handle for this device session (unused).
* @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, Kernel::KProcess* handle, u64 applet_resource_user_id, size_t session_id, u32 handle, u64 applet_resource_user_id,
Sink::StreamType type); Sink::StreamType type);
/** /**
@ -141,8 +137,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{};
/// Process handle of device memory owner /// Handle of this device session
Kernel::KProcess* handle{}; u32 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,
Kernel::KProcess* handle_, const u64 applet_resource_user_id_) { const u32 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,8 +19,7 @@ class System;
namespace Kernel { namespace Kernel {
class KEvent; class KEvent;
class KProcess; }
} // namespace Kernel
namespace AudioCore::AudioIn { namespace AudioCore::AudioIn {
@ -94,12 +93,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 - Process handle. * @param handle - Unused.
* @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, Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle,
Kernel::KProcess* handle, u64 applet_resource_user_id); u64 applet_resource_user_id);
/** /**
* Start this system. * Start this system.
@ -245,8 +244,8 @@ public:
private: private:
/// Core system /// Core system
Core::System& system; Core::System& system;
/// Process handle /// (Unused)
Kernel::KProcess* handle{}; u32 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, Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_,
Kernel::KProcess* handle_, u64 applet_resource_user_id_) { 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,8 +19,7 @@ class System;
namespace Kernel { namespace Kernel {
class KEvent; class KEvent;
class KProcess; }
} // namespace Kernel
namespace AudioCore::AudioOut { namespace AudioCore::AudioOut {
@ -85,12 +84,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 - Process handle. * @param handle - Unused.
* @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, Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle,
Kernel::KProcess* handle, u64 applet_resource_user_id); u64 applet_resource_user_id);
/** /**
* Start this system. * Start this system.
@ -229,8 +228,8 @@ public:
private: private:
/// Core system /// Core system
Core::System& system; Core::System& system;
/// Process handle /// (Unused)
Kernel::KProcess* handle{}; u32 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.GetDestinationData(voice_info.splitter_id, 0)}; auto destination{splitter_context.GetDesintationData(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.GetDestinationData(voice_info.splitter_id, dest_id); destination = splitter_context.GetDesintationData(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.GetDestinationData(voice_info.splitter_id, i)}; auto destination{splitter_context.GetDesintationData(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.GetDestinationData(voice_info.splitter_id, i); destination = splitter_context.GetDesintationData(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.GetDestinationData(mix_info.dst_splitter_id, dest_id)}; splitter_context.GetDesintationData(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.GetDestinationData(mix_info.dst_splitter_id, dest_id); splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
} }
} }
} else { } else {

View File

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

View File

@ -93,7 +93,7 @@ bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_pa
for (u32 i = 0; i < destination_count; i++) { for (u32 i = 0; i < destination_count; i++) {
auto destination{ auto destination{
splitter_context.GetDestinationData(in_params.dest_splitter_id, i)}; splitter_context.GetDesintationData(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::GetDestinationData(const s32 splitter_id, SplitterDestinationData* SplitterContext::GetDesintationData(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* GetDestinationData(s32 splitter_id, s32 destination_id); SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
/** /**
* Get a splitter from the given index. * Get a splitter from the given index.

View File

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

View File

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

View File

@ -35,7 +35,7 @@ bool BasicSetting::Save() const {
return save; return save;
} }
bool BasicSetting::RuntimeModifiable() const { bool BasicSetting::RuntimeModfiable() 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 RuntimeModifiable() const; [[nodiscard]] bool RuntimeModfiable() const;
/** /**
* @returns A unique number corresponding to the setting. * @returns A unique number corresponding to the setting.

View File

@ -37,8 +37,6 @@ add_library(core STATIC
debugger/gdbstub_arch.h debugger/gdbstub_arch.h
debugger/gdbstub.cpp debugger/gdbstub.cpp
debugger/gdbstub.h debugger/gdbstub.h
device_memory_manager.h
device_memory_manager.inc
device_memory.cpp device_memory.cpp
device_memory.h device_memory.h
file_sys/fssystem/fs_i_storage.h file_sys/fssystem/fs_i_storage.h
@ -185,6 +183,22 @@ add_library(core STATIC
frontend/framebuffer_layout.cpp frontend/framebuffer_layout.cpp
frontend/framebuffer_layout.h frontend/framebuffer_layout.h
frontend/graphics_context.h frontend/graphics_context.h
hid/emulated_console.cpp
hid/emulated_console.h
hid/emulated_controller.cpp
hid/emulated_controller.h
hid/emulated_devices.cpp
hid/emulated_devices.h
hid/hid_core.cpp
hid/hid_core.h
hid/hid_types.h
hid/input_converter.cpp
hid/input_converter.h
hid/input_interpreter.cpp
hid/input_interpreter.h
hid/irs_types.h
hid/motion_input.cpp
hid/motion_input.h
hle/api_version.h hle/api_version.h
hle/ipc.h hle/ipc.h
hle/kernel/board/nintendo/nx/k_memory_layout.cpp hle/kernel/board/nintendo/nx/k_memory_layout.cpp
@ -492,10 +506,6 @@ add_library(core STATIC
hle/service/filesystem/fsp_pr.h hle/service/filesystem/fsp_pr.h
hle/service/filesystem/fsp_srv.cpp hle/service/filesystem/fsp_srv.cpp
hle/service/filesystem/fsp_srv.h hle/service/filesystem/fsp_srv.h
hle/service/filesystem/romfs_controller.cpp
hle/service/filesystem/romfs_controller.h
hle/service/filesystem/save_data_controller.cpp
hle/service/filesystem/save_data_controller.h
hle/service/fgm/fgm.cpp hle/service/fgm/fgm.cpp
hle/service/fgm/fgm.h hle/service/fgm/fgm.h
hle/service/friend/friend.cpp hle/service/friend/friend.cpp
@ -521,16 +531,94 @@ add_library(core STATIC
hle/service/hid/hid.h hle/service/hid/hid.h
hle/service/hid/hid_debug_server.cpp hle/service/hid/hid_debug_server.cpp
hle/service/hid/hid_debug_server.h hle/service/hid/hid_debug_server.h
hle/service/hid/hid_firmware_settings.cpp
hle/service/hid/hid_firmware_settings.h
hle/service/hid/hid_server.cpp hle/service/hid/hid_server.cpp
hle/service/hid/hid_server.h hle/service/hid/hid_server.h
hle/service/hid/hid_system_server.cpp hle/service/hid/hid_system_server.cpp
hle/service/hid/hid_system_server.h hle/service/hid/hid_system_server.h
hle/service/hid/hid_util.h
hle/service/hid/hidbus.cpp hle/service/hid/hidbus.cpp
hle/service/hid/hidbus.h hle/service/hid/hidbus.h
hle/service/hid/irs.cpp hle/service/hid/irs.cpp
hle/service/hid/irs.h hle/service/hid/irs.h
hle/service/hid/irs_ring_lifo.h
hle/service/hid/resource_manager.cpp
hle/service/hid/resource_manager.h
hle/service/hid/ring_lifo.h
hle/service/hid/xcd.cpp hle/service/hid/xcd.cpp
hle/service/hid/xcd.h hle/service/hid/xcd.h
hle/service/hid/errors.h
hle/service/hid/controllers/npad/npad_data.cpp
hle/service/hid/controllers/npad/npad_data.h
hle/service/hid/controllers/npad/npad_resource.cpp
hle/service/hid/controllers/npad/npad_resource.h
hle/service/hid/controllers/types/debug_pad_types.h
hle/service/hid/controllers/types/keyboard_types.h
hle/service/hid/controllers/types/mouse_types.h
hle/service/hid/controllers/types/npad_types.h
hle/service/hid/controllers/types/shared_memory_format.h
hle/service/hid/controllers/types/touch_types.h
hle/service/hid/controllers/applet_resource.cpp
hle/service/hid/controllers/applet_resource.h
hle/service/hid/controllers/capture_button.cpp
hle/service/hid/controllers/capture_button.h
hle/service/hid/controllers/console_six_axis.cpp
hle/service/hid/controllers/console_six_axis.h
hle/service/hid/controllers/controller_base.cpp
hle/service/hid/controllers/controller_base.h
hle/service/hid/controllers/debug_mouse.cpp
hle/service/hid/controllers/debug_mouse.h
hle/service/hid/controllers/debug_pad.cpp
hle/service/hid/controllers/debug_pad.h
hle/service/hid/controllers/digitizer.cpp
hle/service/hid/controllers/digitizer.h
hle/service/hid/controllers/gesture.cpp
hle/service/hid/controllers/gesture.h
hle/service/hid/controllers/home_button.cpp
hle/service/hid/controllers/home_button.h
hle/service/hid/controllers/keyboard.cpp
hle/service/hid/controllers/keyboard.h
hle/service/hid/controllers/mouse.cpp
hle/service/hid/controllers/mouse.h
hle/service/hid/controllers/npad.cpp
hle/service/hid/controllers/npad.h
hle/service/hid/controllers/palma.cpp
hle/service/hid/controllers/palma.h
hle/service/hid/controllers/seven_six_axis.cpp
hle/service/hid/controllers/seven_six_axis.h
hle/service/hid/controllers/shared_memory_holder.cpp
hle/service/hid/controllers/shared_memory_holder.h
hle/service/hid/controllers/six_axis.cpp
hle/service/hid/controllers/six_axis.h
hle/service/hid/controllers/sleep_button.cpp
hle/service/hid/controllers/sleep_button.h
hle/service/hid/controllers/touchscreen.cpp
hle/service/hid/controllers/touchscreen.h
hle/service/hid/controllers/unique_pad.cpp
hle/service/hid/controllers/unique_pad.h
hle/service/hid/hidbus/hidbus_base.cpp
hle/service/hid/hidbus/hidbus_base.h
hle/service/hid/hidbus/ringcon.cpp
hle/service/hid/hidbus/ringcon.h
hle/service/hid/hidbus/starlink.cpp
hle/service/hid/hidbus/starlink.h
hle/service/hid/hidbus/stubbed.cpp
hle/service/hid/hidbus/stubbed.h
hle/service/hid/irsensor/clustering_processor.cpp
hle/service/hid/irsensor/clustering_processor.h
hle/service/hid/irsensor/image_transfer_processor.cpp
hle/service/hid/irsensor/image_transfer_processor.h
hle/service/hid/irsensor/ir_led_processor.cpp
hle/service/hid/irsensor/ir_led_processor.h
hle/service/hid/irsensor/moment_processor.cpp
hle/service/hid/irsensor/moment_processor.h
hle/service/hid/irsensor/pointing_processor.cpp
hle/service/hid/irsensor/pointing_processor.h
hle/service/hid/irsensor/processor_base.cpp
hle/service/hid/irsensor/processor_base.h
hle/service/hid/irsensor/tera_plugin_processor.cpp
hle/service/hid/irsensor/tera_plugin_processor.h
hle/service/lbl/lbl.cpp hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h hle/service/lbl/lbl.h
hle/service/ldn/lan_discovery.cpp hle/service/ldn/lan_discovery.cpp
@ -611,8 +699,6 @@ add_library(core STATIC
hle/service/ns/pdm_qry.h hle/service/ns/pdm_qry.h
hle/service/nvdrv/core/container.cpp hle/service/nvdrv/core/container.cpp
hle/service/nvdrv/core/container.h hle/service/nvdrv/core/container.h
hle/service/nvdrv/core/heap_mapper.cpp
hle/service/nvdrv/core/heap_mapper.h
hle/service/nvdrv/core/nvmap.cpp hle/service/nvdrv/core/nvmap.cpp
hle/service/nvdrv/core/nvmap.h hle/service/nvdrv/core/nvmap.h
hle/service/nvdrv/core/syncpoint_manager.cpp hle/service/nvdrv/core/syncpoint_manager.cpp
@ -716,24 +802,24 @@ 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/set.cpp
hle/service/set/set.h
hle/service/set/appln_settings.cpp hle/service/set/appln_settings.cpp
hle/service/set/appln_settings.h hle/service/set/appln_settings.h
hle/service/set/device_settings.cpp hle/service/set/device_settings.cpp
hle/service/set/device_settings.h hle/service/set/device_settings.h
hle/service/set/factory_settings_server.cpp
hle/service/set/factory_settings_server.h
hle/service/set/firmware_debug_settings_server.cpp
hle/service/set/firmware_debug_settings_server.h
hle/service/set/private_settings.cpp hle/service/set/private_settings.cpp
hle/service/set/private_settings.h hle/service/set/private_settings.h
hle/service/set/set_cal.cpp
hle/service/set/set_cal.h
hle/service/set/set_fd.cpp
hle/service/set/set_fd.h
hle/service/set/set_sys.cpp
hle/service/set/set_sys.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.h
hle/service/set/system_settings.cpp hle/service/set/system_settings.cpp
hle/service/set/system_settings.h hle/service/set/system_settings.h
hle/service/set/system_settings_server.cpp
hle/service/set/system_settings_server.h
hle/service/sm/sm.cpp hle/service/sm/sm.cpp
hle/service/sm/sm.h hle/service/sm/sm.h
hle/service/sm/sm_controller.cpp hle/service/sm/sm_controller.cpp
@ -873,7 +959,7 @@ endif()
create_target_directory_groups(core) create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API) target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API)
if (MINGW) if (MINGW)
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})

View File

@ -22,10 +22,14 @@ 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
// module. Patcher::~Patcher() = default;
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);
@ -34,25 +38,6 @@ Patcher::Patcher() : c(m_patch_instructions) {
// 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);
@ -109,17 +94,16 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
} }
if (auto exclusive = Exclusive{inst}; exclusive.Verify()) { if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
curr_patch->m_exclusives.push_back(i); m_exclusives.push_back(i);
} }
} }
// Determine patching mode for the final relocation step // Determine patching mode for the final relocation step
total_program_size += image_size; const size_t image_size = program_image.size();
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
return true;
} }
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, void 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) {
@ -136,7 +120,7 @@ bool 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(total_program_size - rel.module_offset + rel.patch_offset); rc.B(image_size - rel.module_offset + rel.patch_offset);
} }
}; };
@ -145,7 +129,7 @@ bool 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 - total_program_size - rel.patch_offset); rc.B(rel.module_offset - image_size - rel.patch_offset);
} }
}; };
@ -153,7 +137,7 @@ bool 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) + total_program_size + patch_offset; return GetInteger(load_base) + image_size + patch_offset;
} }
}; };
@ -166,50 +150,39 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
}; };
// We are now ready to relocate! // We are now ready to relocate!
auto& patch = modules[m_relocate_module_index++]; for (const Relocation& rel : m_branch_to_patch_relocations) {
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 : patch.m_branch_to_module_relocations) { for (const Relocation& rel : 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 : patch.m_write_module_pc_relocations) { for (const Relocation& rel : 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 : patch.m_trampolines) { for (const Trampoline& rel : 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 : patch.m_exclusives) { for (const ModuleTextAddress i : m_exclusives) {
auto exclusive = Exclusive{text_words[i]}; auto exclusive = Exclusive{text_words[i]};
text_words[i] = exclusive.AsOrdered(); text_words[i] = exclusive.AsOrdered();
} }
// Remove the patched module size from the total. This is done so total_program_size // Copy to program image
// always represents the distance from the currently patched module to the patch section. if (this->mode == PatchMode::PreText) {
total_program_size -= image_size; std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
// Only copy to the program image of the last module } else {
if (m_relocate_module_index == modules.size()) { program_image.resize(image_size + patch_size);
if (this->mode == PatchMode::PreText) { std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
ASSERT(image_size == total_program_size); m_patch_instructions.size() * sizeof(u32));
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 {
@ -349,7 +322,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.
curr_patch->m_trampolines.push_back({c.offset(), module_dest}); 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();
bool PatchText(const Kernel::PhysicalMemory& program_image, void PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code); const Kernel::CodeSet::Segment& code);
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, void 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) {
curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
} }
void BranchToModule(uintptr_t module_dest) { void BranchToModule(uintptr_t module_dest) {
curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest}); 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) {
curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest}); m_write_module_pc_relocations.push_back({c.offset(), module_dest});
c.dx(0); c.dx(0);
} }
@ -84,22 +84,15 @@ 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,7 +28,7 @@
#include "core/file_sys/savedata_factory.h" #include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_real.h" #include "core/file_sys/vfs_real.h"
#include "core/gpu_dirty_memory_manager.h" #include "core/hid/hid_core.h"
#include "core/hle/kernel/k_memory_manager.h" #include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/k_resource_limit.h"
@ -52,7 +52,6 @@
#include "core/telemetry_session.h" #include "core/telemetry_session.h"
#include "core/tools/freezer.h" #include "core/tools/freezer.h"
#include "core/tools/renderdoc.h" #include "core/tools/renderdoc.h"
#include "hid_core/hid_core.h"
#include "network/network.h" #include "network/network.h"
#include "video_core/host1x/host1x.h" #include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
@ -414,7 +413,6 @@ struct System::Impl {
kernel.ShutdownCores(); kernel.ShutdownCores();
services.reset(); services.reset();
service_manager.reset(); service_manager.reset();
fs_controller.Reset();
cheat_engine.reset(); cheat_engine.reset();
telemetry_session.reset(); telemetry_session.reset();
time_manager.Shutdown(); time_manager.Shutdown();
@ -566,9 +564,6 @@ struct System::Impl {
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{}; std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_managers;
std::deque<std::vector<u8>> user_channel; std::deque<std::vector<u8>> user_channel;
}; };
@ -655,14 +650,8 @@ size_t System::GetCurrentHostThreadID() const {
return impl->kernel.GetCurrentHostThreadID(); return impl->kernel.GetCurrentHostThreadID();
} }
std::span<GPUDirtyMemoryManager> System::GetGPUDirtyMemoryManager() { void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
return impl->gpu_dirty_memory_managers; return this->ApplicationProcess()->GatherGPUDirtyMemory(callback);
}
void System::GatherGPUDirtyMemory(std::function<void(PAddr, size_t)>& callback) {
for (auto& manager : impl->gpu_dirty_memory_managers) {
manager.Gather(callback);
}
} }
PerfStatsResults System::GetAndResetPerfStats() { PerfStatsResults System::GetAndResetPerfStats() {

View File

@ -8,7 +8,6 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <span>
#include <string> #include <string>
#include <vector> #include <vector>
@ -117,7 +116,6 @@ class CpuManager;
class Debugger; class Debugger;
class DeviceMemory; class DeviceMemory;
class ExclusiveMonitor; class ExclusiveMonitor;
class GPUDirtyMemoryManager;
class PerfStats; class PerfStats;
class Reporter; class Reporter;
class SpeedLimiter; class SpeedLimiter;
@ -226,9 +224,7 @@ public:
/// Prepare the core emulation for a reschedule /// Prepare the core emulation for a reschedule
void PrepareReschedule(u32 core_index); void PrepareReschedule(u32 core_index);
std::span<GPUDirtyMemoryManager> GetGPUDirtyMemoryManager(); void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
void GatherGPUDirtyMemory(std::function<void(PAddr, size_t)>& callback);
[[nodiscard]] size_t GetCurrentHostThreadID() const; [[nodiscard]] size_t GetCurrentHostThreadID() const;

View File

@ -64,7 +64,7 @@ public:
return [this] { ShutdownThreadFunction(); }; return [this] { ShutdownThreadFunction(); };
} }
void PreemptSingleCore(bool from_running_environment = true); void PreemptSingleCore(bool from_running_enviroment = true);
std::size_t CurrentCore() const { std::size_t CurrentCore() const {
return current_core.load(); return current_core.load();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@
#include "core/file_sys/vfs_vector.h" #include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/language.h" #include "core/hle/service/ns/language.h"
#include "core/hle/service/set/settings_server.h" #include "core/hle/service/set/set.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/loader/nso.h" #include "core/loader/nso.h"
#include "core/memory/cheat_engine.h" #include "core/memory/cheat_engine.h"

View File

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

View File

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

View File

@ -6,9 +6,9 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/settings_enums.h" #include "common/settings_enums.h"
#include "core/frontend/applets/controller.h" #include "core/frontend/applets/controller.h"
#include "hid_core/frontend/emulated_controller.h" #include "core/hid/emulated_controller.h"
#include "hid_core/hid_core.h" #include "core/hid/hid_core.h"
#include "hid_core/hid_types.h" #include "core/hid/hid_types.h"
namespace Core::Frontend { namespace Core::Frontend {
@ -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::Fullkey); controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
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

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

View File

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

View File

@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h" #include "common/settings.h"
#include "hid_core/frontend/emulated_console.h" #include "core/hid/emulated_console.h"
#include "hid_core/frontend/input_converter.h" #include "core/hid/input_converter.h"
namespace Core::HID { namespace Core::HID {
EmulatedConsole::EmulatedConsole() = default; EmulatedConsole::EmulatedConsole() = default;

View File

@ -17,8 +17,8 @@
#include "common/point.h" #include "common/point.h"
#include "common/quaternion.h" #include "common/quaternion.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h" #include "core/hid/hid_types.h"
#include "hid_core/hid_types.h" #include "core/hid/motion_input.h"
namespace Core::HID { namespace Core::HID {
static constexpr std::size_t MaxTouchDevices = 32; static constexpr std::size_t MaxTouchDevices = 32;

View File

@ -6,9 +6,9 @@
#include "common/polyfill_ranges.h" #include "common/polyfill_ranges.h"
#include "common/thread.h" #include "common/thread.h"
#include "hid_core/frontend/emulated_controller.h" #include "core/hid/emulated_controller.h"
#include "hid_core/frontend/input_converter.h" #include "core/hid/input_converter.h"
#include "hid_core/hid_util.h" #include "core/hle/service/hid/hid_util.h"
namespace Core::HID { namespace Core::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
@ -27,7 +27,7 @@ EmulatedController::~EmulatedController() = default;
NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) { NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
switch (type) { switch (type) {
case Settings::ControllerType::ProController: case Settings::ControllerType::ProController:
return NpadStyleIndex::Fullkey; return NpadStyleIndex::ProController;
case Settings::ControllerType::DualJoyconDetached: case Settings::ControllerType::DualJoyconDetached:
return NpadStyleIndex::JoyconDual; return NpadStyleIndex::JoyconDual;
case Settings::ControllerType::LeftJoycon: case Settings::ControllerType::LeftJoycon:
@ -49,13 +49,13 @@ NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerTyp
case Settings::ControllerType::SegaGenesis: case Settings::ControllerType::SegaGenesis:
return NpadStyleIndex::SegaGenesis; return NpadStyleIndex::SegaGenesis;
default: default:
return NpadStyleIndex::Fullkey; return NpadStyleIndex::ProController;
} }
} }
Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) { Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: case NpadStyleIndex::ProController:
return Settings::ControllerType::ProController; return Settings::ControllerType::ProController;
case NpadStyleIndex::JoyconDual: case NpadStyleIndex::JoyconDual:
return Settings::ControllerType::DualJoyconDetached; return Settings::ControllerType::DualJoyconDetached;
@ -106,7 +106,7 @@ void EmulatedController::ReloadFromSettings() {
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
original_npad_type = npad_type; original_npad_type = npad_type;
} else { } else {
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::ProController);
original_npad_type = npad_type; original_npad_type = npad_type;
} }
@ -509,11 +509,11 @@ void EmulatedController::ReloadInput() {
}); });
} }
turbo_button_state = 0; turbo_button_state = 0;
is_initialized = true; is_initalized = true;
} }
void EmulatedController::UnloadInput() { void EmulatedController::UnloadInput() {
is_initialized = false; is_initalized = false;
for (auto& button : button_devices) { for (auto& button : button_devices) {
button.reset(); button.reset();
} }
@ -1073,7 +1073,7 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
.body = GetNpadColor(controller.color_values[index].body), .body = GetNpadColor(controller.color_values[index].body),
.button = GetNpadColor(controller.color_values[index].buttons), .button = GetNpadColor(controller.color_values[index].buttons),
}; };
if (npad_type == NpadStyleIndex::Fullkey) { if (npad_type == NpadStyleIndex::ProController) {
controller.colors_state.left = { controller.colors_state.left = {
.body = GetNpadColor(controller.color_values[index].left_grip), .body = GetNpadColor(controller.color_values[index].left_grip),
.button = GetNpadColor(controller.color_values[index].buttons), .button = GetNpadColor(controller.color_values[index].buttons),
@ -1209,7 +1209,7 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
} }
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
if (device_index >= output_devices.size()) { if (device_index >= output_devices.size()) {
@ -1247,7 +1247,7 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index]; const auto& player = Settings::values.players.GetValue()[player_index];
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1270,7 +1270,7 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) { EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index); LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
if (!is_initialized) { if (!is_initalized) {
return Common::Input::DriverResult::InvalidHandle; return Common::Input::DriverResult::InvalidHandle;
} }
@ -1319,7 +1319,7 @@ bool EmulatedController::SetCameraFormat(
Core::IrSensor::ImageTransferProcessorFormat camera_format) { Core::IrSensor::ImageTransferProcessorFormat camera_format) {
LOG_INFO(Service_HID, "Set camera format {}", camera_format); LOG_INFO(Service_HID, "Set camera format {}", camera_format);
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1347,7 +1347,7 @@ void EmulatedController::SetRingParam(Common::ParamPackage param) {
bool EmulatedController::HasNfc() const { bool EmulatedController::HasNfc() const {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1356,7 +1356,7 @@ bool EmulatedController::HasNfc() const {
switch (npad_type) { switch (npad_type) {
case NpadStyleIndex::JoyconRight: case NpadStyleIndex::JoyconRight:
case NpadStyleIndex::JoyconDual: case NpadStyleIndex::JoyconDual:
case NpadStyleIndex::Fullkey: case NpadStyleIndex::ProController:
case NpadStyleIndex::Handheld: case NpadStyleIndex::Handheld:
break; break;
default: default:
@ -1388,7 +1388,7 @@ bool EmulatedController::RemoveNfcHandle() {
} }
bool EmulatedController::StartNfcPolling() { bool EmulatedController::StartNfcPolling() {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1403,7 +1403,7 @@ bool EmulatedController::StartNfcPolling() {
} }
bool EmulatedController::StopNfcPolling() { bool EmulatedController::StopNfcPolling() {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1418,7 +1418,7 @@ bool EmulatedController::StopNfcPolling() {
} }
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) { bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1434,7 +1434,7 @@ bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) { Common::Input::MifareRequest& out_data) {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1450,7 +1450,7 @@ bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& requ
} }
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1465,7 +1465,7 @@ bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& req
} }
bool EmulatedController::WriteNfc(const std::vector<u8>& data) { bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
if (!is_initialized) { if (!is_initalized) {
return false; return false;
} }
@ -1480,7 +1480,7 @@ bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
} }
void EmulatedController::SetLedPattern() { void EmulatedController::SetLedPattern() {
if (!is_initialized) { if (!is_initalized) {
return; return;
} }
@ -1508,8 +1508,8 @@ void EmulatedController::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode mode)
motion.emulated.SetGyroThreshold(motion.emulated.ThresholdLoose); motion.emulated.SetGyroThreshold(motion.emulated.ThresholdLoose);
break; break;
case GyroscopeZeroDriftMode::Tight: case GyroscopeZeroDriftMode::Tight:
motion_sensitivity = motion.emulated.IsAtRestTight; motion_sensitivity = motion.emulated.IsAtRestThight;
motion.emulated.SetGyroThreshold(motion.emulated.ThresholdTight); motion.emulated.SetGyroThreshold(motion.emulated.ThresholdThight);
break; break;
case GyroscopeZeroDriftMode::Standard: case GyroscopeZeroDriftMode::Standard:
default: default:
@ -1548,7 +1548,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Fullkey controllers to Pro controllers // Fallback Fullkey controllers to Pro controllers
if (IsControllerFullkey() && supported_style_tag.fullkey) { if (IsControllerFullkey() && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::ProController);
Connect(); Connect();
return; return;
} }
@ -1556,13 +1556,13 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles)
// Fallback Dual joycon controllers to Pro controllers // Fallback Dual joycon controllers to Pro controllers
if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) { if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type);
SetNpadStyleIndex(NpadStyleIndex::Fullkey); SetNpadStyleIndex(NpadStyleIndex::ProController);
Connect(); Connect();
return; return;
} }
// Fallback Pro controllers to Dual joycon // Fallback Pro controllers to Dual joycon
if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) { if (npad_type == NpadStyleIndex::ProController && supported_style_tag.joycon_dual) {
LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type); LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type);
SetNpadStyleIndex(NpadStyleIndex::JoyconDual); SetNpadStyleIndex(NpadStyleIndex::JoyconDual);
Connect(); Connect();
@ -1577,7 +1577,7 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: case NpadStyleIndex::ProController:
case NpadStyleIndex::GameCube: case NpadStyleIndex::GameCube:
case NpadStyleIndex::NES: case NpadStyleIndex::NES:
case NpadStyleIndex::SNES: case NpadStyleIndex::SNES:
@ -1593,7 +1593,7 @@ bool EmulatedController::IsControllerSupported(bool use_temporary_value) const {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type;
switch (type) { switch (type) {
case NpadStyleIndex::Fullkey: case NpadStyleIndex::ProController:
return supported_style_tag.fullkey.As<bool>(); return supported_style_tag.fullkey.As<bool>();
case NpadStyleIndex::Handheld: case NpadStyleIndex::Handheld:
return supported_style_tag.handheld.As<bool>(); return supported_style_tag.handheld.As<bool>();

View File

@ -15,9 +15,9 @@
#include "common/param_package.h" #include "common/param_package.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h" #include "core/hid/hid_types.h"
#include "hid_core/hid_types.h" #include "core/hid/irs_types.h"
#include "hid_core/irsensor/irs_types.h" #include "core/hid/motion_input.h"
namespace Core::HID { namespace Core::HID {
const std::size_t max_emulated_controllers = 2; const std::size_t max_emulated_controllers = 2;
@ -559,7 +559,7 @@ private:
NpadStyleTag supported_style_tag{NpadStyleSet::All}; NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false}; bool is_connected{false};
bool is_configuring{false}; bool is_configuring{false};
bool is_initialized{false}; bool is_initalized{false};
bool system_buttons_enabled{true}; bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0}; u32 turbo_button_state{0};

View File

@ -4,8 +4,8 @@
#include <algorithm> #include <algorithm>
#include <fmt/format.h> #include <fmt/format.h>
#include "hid_core/frontend/emulated_devices.h" #include "core/hid/emulated_devices.h"
#include "hid_core/frontend/input_converter.h" #include "core/hid/input_converter.h"
namespace Core::HID { namespace Core::HID {

View File

@ -14,7 +14,7 @@
#include "common/input.h" #include "common/input.h"
#include "common/param_package.h" #include "common/param_package.h"
#include "common/settings.h" #include "common/settings.h"
#include "hid_core/hid_types.h" #include "core/hid/hid_types.h"
namespace Core::HID { namespace Core::HID {
using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,

View File

@ -2,11 +2,11 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h" #include "common/assert.h"
#include "hid_core/frontend/emulated_console.h" #include "core/hid/emulated_console.h"
#include "hid_core/frontend/emulated_controller.h" #include "core/hid/emulated_controller.h"
#include "hid_core/frontend/emulated_devices.h" #include "core/hid/emulated_devices.h"
#include "hid_core/hid_core.h" #include "core/hid/hid_core.h"
#include "hid_core/hid_util.h" #include "core/hle/service/hid/hid_util.h"
namespace Core::HID { namespace Core::HID {

View File

@ -6,7 +6,7 @@
#include <memory> #include <memory>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "hid_core/hid_types.h" #include "core/hid/hid_types.h"
namespace Core::HID { namespace Core::HID {
class EmulatedConsole; class EmulatedConsole;

View File

@ -220,7 +220,6 @@ enum class NpadIdType : u32 {
}; };
enum class NpadInterfaceType : u8 { enum class NpadInterfaceType : u8 {
None = 0,
Bluetooth = 1, Bluetooth = 1,
Rail = 2, Rail = 2,
Usb = 3, Usb = 3,
@ -230,7 +229,7 @@ enum class NpadInterfaceType : u8 {
// This is nn::hid::NpadStyleIndex // This is nn::hid::NpadStyleIndex
enum class NpadStyleIndex : u8 { enum class NpadStyleIndex : u8 {
None = 0, None = 0,
Fullkey = 3, ProController = 3,
Handheld = 4, Handheld = 4,
HandheldNES = 4, HandheldNES = 4,
JoyconDual = 5, JoyconDual = 5,

View File

@ -5,7 +5,7 @@
#include <random> #include <random>
#include "common/input.h" #include "common/input.h"
#include "hid_core/frontend/input_converter.h" #include "core/hid/input_converter.h"
namespace Core::HID { namespace Core::HID {

View File

@ -2,12 +2,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h" #include "core/core.h"
#include "core/hid/hid_types.h"
#include "core/hid/input_interpreter.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid_server.h" #include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/hid/resource_manager.h"
#include "core/hle/service/sm/sm.h" #include "core/hle/service/sm/sm.h"
#include "hid_core/frontend/input_interpreter.h"
#include "hid_core/hid_types.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
InputInterpreter::InputInterpreter(Core::System& system) InputInterpreter::InputInterpreter(Core::System& system)
: npad{system.ServiceManager() : npad{system.ServiceManager()

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