Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b003224ff0 | ||
|
ed536603af | ||
|
9908ae7d45 | ||
|
3fded314f2 | ||
|
16c238e4b9 | ||
|
e0fb1d3d17 | ||
|
5bbc3aef13 | ||
|
5593bed08a | ||
|
8750564bd2 | ||
|
2461c78e3f | ||
|
50fe45f1e4 | ||
|
04868ab9da | ||
|
c3050c1b48 | ||
|
28598c9090 | ||
|
11cb4d88f0 | ||
|
4b870b28e8 | ||
|
ce7c418e0c | ||
|
169b198d08 | ||
|
e32ce6cc69 | ||
|
190748546d | ||
|
79e289404b | ||
|
a6e467cd55 | ||
|
8a87a41f2d | ||
|
f02226283e | ||
|
82568412f6 | ||
|
1255196731 | ||
|
9ce5d39829 | ||
|
4540bcfaf7 | ||
|
f80edad109 | ||
|
aa882cdaa8 | ||
|
738b37e508 | ||
|
82a105e2f8 | ||
|
d6a9ed32e6 | ||
|
e3937fe8ad | ||
|
c652e42492 | ||
|
eacec2ae12 | ||
|
45ea712d39 | ||
|
f463ef7dae | ||
|
95c5b715b1 | ||
|
8bf46f48f8 | ||
|
9d7671ec3b | ||
|
5d0a051abb | ||
|
4c84bce171 | ||
|
302a735135 | ||
|
d8eb37fbec | ||
|
ef7d44e243 | ||
|
f71140fbd9 | ||
|
0792139a5f | ||
|
f1cfd9c219 | ||
|
4467fd9993 | ||
|
1462db4694 | ||
|
44af2e32a4 | ||
|
3c88547c74 | ||
|
1fe003113e | ||
|
cef7aaa8ec | ||
|
005f0eb083 | ||
|
b41006004b | ||
|
408a9cd50d | ||
|
d2b62ae401 | ||
|
68f6f2671b | ||
|
6f7cb69c94 | ||
|
644c3ce609 | ||
|
ad1946b893 | ||
|
0ed1cb7266 | ||
|
0e191c2711 | ||
|
98685d48e3 | ||
|
14ea16e499 | ||
|
22263787e3 | ||
|
ddcd89afd4 | ||
|
dfa040502a | ||
|
d885dd5b64 | ||
|
b9f9d0a642 | ||
|
8b9c077826 | ||
|
cd4b8f037c | ||
|
ac939f08a4 | ||
|
42015de49b | ||
|
4a35569921 | ||
|
8905142f43 | ||
|
7cc428ddf6 | ||
|
8e703e08df |
@@ -56,7 +56,6 @@ for i in package/*.exe; do
|
||||
x86_64-w64-mingw32-strip "${i}"
|
||||
done
|
||||
|
||||
pip3 install pefile
|
||||
python3 .ci/scripts/windows/scan_dll.py package/*.exe package/imageformats/*.dll "package/"
|
||||
|
||||
# copy FFmpeg libraries
|
||||
|
33
.ci/scripts/windows/install-vulkan-sdk.ps1
Normal file
33
.ci/scripts/windows/install-vulkan-sdk.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$VulkanSDKVer = "1.3.250.1"
|
||||
$ExeFile = "VulkanSDK-$VulkanSDKVer-Installer.exe"
|
||||
$Uri = "https://sdk.lunarg.com/sdk/download/$VulkanSDKVer/windows/$ExeFile"
|
||||
$Destination = "./$ExeFile"
|
||||
|
||||
echo "Downloading Vulkan SDK $VulkanSDKVer from $Uri"
|
||||
$WebClient = New-Object System.Net.WebClient
|
||||
$WebClient.DownloadFile($Uri, $Destination)
|
||||
echo "Finished downloading $ExeFile"
|
||||
|
||||
$VULKAN_SDK = "C:/VulkanSDK/$VulkanSDKVer"
|
||||
$Arguments = "--root `"$VULKAN_SDK`" --accept-licenses --default-answer --confirm-command install"
|
||||
|
||||
echo "Installing Vulkan SDK $VulkanSDKVer"
|
||||
$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments
|
||||
$ExitCode = $InstallProcess.ExitCode
|
||||
|
||||
if ($ExitCode -ne 0) {
|
||||
echo "Error installing Vulkan SDK $VulkanSDKVer (Error: $ExitCode)"
|
||||
Exit $ExitCode
|
||||
}
|
||||
|
||||
echo "Finished installing Vulkan SDK $VulkanSDKVer"
|
||||
|
||||
if ("$env:GITHUB_ACTIONS" -eq "true") {
|
||||
echo "VULKAN_SDK=$VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "$VULKAN_SDK/Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
}
|
@@ -7,9 +7,12 @@ parameters:
|
||||
version: ''
|
||||
|
||||
steps:
|
||||
- script: choco install vulkan-sdk
|
||||
displayName: 'Install vulkan-sdk'
|
||||
- script: refreshenv && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
|
||||
- task: Powershell@2
|
||||
displayName: 'Install Vulkan SDK'
|
||||
inputs:
|
||||
targetType: 'filePath'
|
||||
filePath: './.ci/scripts/windows/install-vulkan-sdk.ps1'
|
||||
- script: refreshenv && glslangValidator --version && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
|
80
.github/workflows/android-build.yml
vendored
Normal file
80
.github/workflows/android-build.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: 'yuzu-android-build'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ "*" ]
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'yuzu-emu/yuzu-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
~/.ccache
|
||||
key: ${{ runner.os }}-android-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-
|
||||
- name: Query tag name
|
||||
uses: olegtarasov/get-tag@v2.1.2
|
||||
id: tagName
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
|
||||
- name: Build
|
||||
run: ./.ci/scripts/android/build.sh
|
||||
- name: Copy and sign artifacts
|
||||
env:
|
||||
ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
|
||||
run: ./.ci/scripts/android/upload.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: android
|
||||
path: artifacts/
|
||||
# release steps
|
||||
release-android:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [android]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- name: Query tag name
|
||||
uses: olegtarasov/get-tag@v2.1.2
|
||||
id: tagName
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.tagName.outputs.tag }}
|
||||
release_name: ${{ steps.tagName.outputs.tag }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload artifacts
|
||||
uses: alexellis/upload-assets@0.2.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
asset_paths: '["./**/*.apk","./**/*.aab"]'
|
218
.github/workflows/android-merge.js
vendored
Normal file
218
.github/workflows/android-merge.js
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Note: This is a GitHub Actions script
|
||||
// It is not meant to be executed directly on your machine without modifications
|
||||
|
||||
const fs = require("fs");
|
||||
// which label to check for changes
|
||||
const CHANGE_LABEL = 'android-merge';
|
||||
// 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);
|
||||
|
||||
async function checkBaseChanges(github, context) {
|
||||
// query the commit date of the latest commit on this branch
|
||||
const query = `query($owner:String!, $name:String!, $ref:String!) {
|
||||
repository(name:$name, owner:$owner) {
|
||||
ref(qualifiedName:$ref) {
|
||||
target {
|
||||
... on Commit { id pushedDate oid }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
ref: 'refs/heads/master',
|
||||
};
|
||||
const result = await github.graphql(query, variables);
|
||||
const pushedAt = result.repository.ref.target.pushedDate;
|
||||
console.log(`Last commit pushed at ${pushedAt}.`);
|
||||
const delta = new Date() - new Date(pushedAt);
|
||||
if (delta <= DETECTION_TIME_FRAME) {
|
||||
console.info('New changes detected, triggering a new build.');
|
||||
return true;
|
||||
}
|
||||
console.info('No new changes detected.');
|
||||
return false;
|
||||
}
|
||||
|
||||
async function checkAndroidChanges(github, context) {
|
||||
if (checkBaseChanges(github, context)) return true;
|
||||
const query = `query($owner:String!, $name:String!, $label:String!) {
|
||||
repository(name:$name, owner:$owner) {
|
||||
pullRequests(labels: [$label], states: OPEN, first: 100) {
|
||||
nodes { number headRepository { pushedAt } }
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
label: CHANGE_LABEL,
|
||||
};
|
||||
const result = await github.graphql(query, variables);
|
||||
const pulls = result.repository.pullRequests.nodes;
|
||||
for (let i = 0; i < pulls.length; i++) {
|
||||
let pull = pulls[i];
|
||||
if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
|
||||
console.info(`${pull.number} updated at ${pull.headRepository.pushedAt}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.info("No changes detected in any tagged pull requests.");
|
||||
return false;
|
||||
}
|
||||
|
||||
async function tagAndPush(github, owner, repo, execa, commit=false) {
|
||||
let altToken = process.env.ALT_GITHUB_TOKEN;
|
||||
if (!altToken) {
|
||||
throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`;
|
||||
}
|
||||
const query = `query ($owner:String!, $name:String!) {
|
||||
repository(name:$name, owner:$owner) {
|
||||
refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) {
|
||||
nodes { name }
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: owner,
|
||||
name: repo,
|
||||
};
|
||||
const tags = await github.graphql(query, variables);
|
||||
const tagList = tags.repository.refs.nodes;
|
||||
const lastTag = tagList[0] ? tagList[0].name : 'dummy-0';
|
||||
const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
|
||||
const channel = repo.split('-')[1];
|
||||
const newTag = `${channel}-${tagNumber + 1}`;
|
||||
console.log(`New tag: ${newTag}`);
|
||||
if (commit) {
|
||||
let channelName = channel[0].toUpperCase() + channel.slice(1);
|
||||
console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`);
|
||||
await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]);
|
||||
}
|
||||
console.info('Pushing tags to GitHub ...');
|
||||
await execa("git", ['tag', newTag]);
|
||||
await execa("git", ['remote', 'add', 'target', `https://${altToken}@github.com/${owner}/${repo}.git`]);
|
||||
await execa("git", ['push', 'target', 'master', '-f']);
|
||||
await execa("git", ['push', 'target', 'master', '--tags']);
|
||||
console.info('Successfully pushed new changes.');
|
||||
}
|
||||
|
||||
async function generateReadme(pulls, context, mergeResults, execa) {
|
||||
let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
|
||||
let output =
|
||||
"| Pull Request | Commit | Title | Author | Merged? |\n|----|----|----|----|----|\n";
|
||||
for (let pull of pulls) {
|
||||
let pr = pull.number;
|
||||
let result = mergeResults[pr];
|
||||
output += `| [${pr}](${baseUrl}/pull/${pr}) | [\`${result.rev || "N/A"}\`](${baseUrl}/pull/${pr}/files) | ${pull.title} | [${pull.author.login}](https://github.com/${pull.author.login}/) | ${result.success ? "Yes" : "No"} |\n`;
|
||||
}
|
||||
output +=
|
||||
"\n\nEnd of merge log. You can find the original README.md below the break.\n\n-----\n\n";
|
||||
output += fs.readFileSync("./README.md");
|
||||
fs.writeFileSync("./README.md", output);
|
||||
await execa("git", ["add", "README.md"]);
|
||||
}
|
||||
|
||||
async function fetchPullRequests(pulls, repoUrl, execa) {
|
||||
console.log("::group::Fetch pull requests");
|
||||
for (let pull of pulls) {
|
||||
let pr = pull.number;
|
||||
console.info(`Fetching PR ${pr} ...`);
|
||||
await execa("git", [
|
||||
"fetch",
|
||||
"-f",
|
||||
"--no-recurse-submodules",
|
||||
repoUrl,
|
||||
`pull/${pr}/head:pr-${pr}`,
|
||||
]);
|
||||
}
|
||||
console.log("::endgroup::");
|
||||
}
|
||||
|
||||
async function mergePullRequests(pulls, execa) {
|
||||
let mergeResults = {};
|
||||
console.log("::group::Merge pull requests");
|
||||
await execa("git", ["config", "--global", "user.name", "yuzubot"]);
|
||||
await execa("git", [
|
||||
"config",
|
||||
"--global",
|
||||
"user.email",
|
||||
"yuzu\x40yuzu-emu\x2eorg", // prevent email harvesters from scraping the address
|
||||
]);
|
||||
let hasFailed = false;
|
||||
for (let pull of pulls) {
|
||||
let pr = pull.number;
|
||||
console.info(`Merging PR ${pr} ...`);
|
||||
try {
|
||||
const process1 = execa("git", [
|
||||
"merge",
|
||||
"--squash",
|
||||
"--no-edit",
|
||||
`pr-${pr}`,
|
||||
]);
|
||||
process1.stdout.pipe(process.stdout);
|
||||
await process1;
|
||||
|
||||
const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]);
|
||||
process2.stdout.pipe(process.stdout);
|
||||
await process2;
|
||||
|
||||
const process3 = await execa("git", ["rev-parse", "--short", `pr-${pr}`]);
|
||||
mergeResults[pr] = {
|
||||
success: true,
|
||||
rev: process3.stdout,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`::error title=#${pr} not merged::Failed to merge pull request: ${pr}: ${err}`
|
||||
);
|
||||
mergeResults[pr] = { success: false };
|
||||
hasFailed = true;
|
||||
await execa("git", ["reset", "--hard"]);
|
||||
}
|
||||
}
|
||||
console.log("::endgroup::");
|
||||
if (hasFailed) {
|
||||
throw 'There are merge failures. Aborting!';
|
||||
}
|
||||
return mergeResults;
|
||||
}
|
||||
|
||||
async function mergebot(github, context, execa) {
|
||||
const query = `query ($owner:String!, $name:String!, $label:String!) {
|
||||
repository(name:$name, owner:$owner) {
|
||||
pullRequests(labels: [$label], states: OPEN, first: 100) {
|
||||
nodes {
|
||||
number title author { login }
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
const variables = {
|
||||
owner: context.repo.owner,
|
||||
name: context.repo.repo,
|
||||
label: CHANGE_LABEL,
|
||||
};
|
||||
const result = await github.graphql(query, variables);
|
||||
const pulls = result.repository.pullRequests.nodes;
|
||||
let displayList = [];
|
||||
for (let i = 0; i < pulls.length; i++) {
|
||||
let pull = pulls[i];
|
||||
displayList.push({ PR: pull.number, Title: pull.title });
|
||||
}
|
||||
console.info("The following pull requests will be merged:");
|
||||
console.table(displayList);
|
||||
await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
|
||||
const mergeResults = await mergePullRequests(pulls, execa);
|
||||
await generateReadme(pulls, context, mergeResults, execa);
|
||||
await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true);
|
||||
}
|
||||
|
||||
module.exports.mergebot = mergebot;
|
||||
module.exports.checkAndroidChanges = checkAndroidChanges;
|
||||
module.exports.tagAndPush = tagAndPush;
|
||||
module.exports.checkBaseChanges = checkBaseChanges;
|
57
.github/workflows/android-publish.yml
vendored
Normal file
57
.github/workflows/android-publish.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
name: yuzu-android-publish
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '37 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
android:
|
||||
description: 'Whether to trigger an Android build (true/false/auto)'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v3
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v6
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
# 24 hours
|
||||
DETECTION_TIME_FRAME: 86400000
|
||||
with:
|
||||
script: |
|
||||
if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
|
||||
const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
|
||||
return checkAndroidChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
path: 'yuzu-merge'
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v5
|
||||
name: 'Check and merge Android changes'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const execa = require("execa");
|
||||
const mergebot = require('./.github/workflows/android-merge.js').mergebot;
|
||||
process.chdir('${{ github.workspace }}/yuzu-merge');
|
||||
mergebot(github, context, execa);
|
34
.github/workflows/verify.yml
vendored
34
.github/workflows/verify.yml
vendored
@@ -73,6 +73,10 @@ jobs:
|
||||
needs: format
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -81,22 +85,22 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-msvc-
|
||||
- name: Install dependencies
|
||||
# due to how chocolatey works, only cmd.exe is supported here
|
||||
shell: cmd
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install vulkan-sdk wget
|
||||
call refreshenv
|
||||
wget https://github.com/mbitsnbites/buildcache/releases/download/v0.27.6/buildcache-windows.zip
|
||||
7z x buildcache-windows.zip
|
||||
copy buildcache\bin\buildcache.exe C:\ProgramData\chocolatey\bin
|
||||
rmdir buildcache
|
||||
echo %PATH% >> %GITHUB_PATH%
|
||||
$ErrorActionPreference = "Stop"
|
||||
$BuildCacheVer = "v0.28.4"
|
||||
$File = "buildcache-windows.zip"
|
||||
$Uri = "https://github.com/mbitsnbites/buildcache/releases/download/$BuildCacheVer/$File"
|
||||
$WebClient = New-Object System.Net.WebClient
|
||||
$WebClient.DownloadFile($Uri, $File)
|
||||
7z x $File
|
||||
$CurrentDir = Convert-Path .
|
||||
echo "$CurrentDir/buildcache/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
- name: Install Vulkan SDK
|
||||
shell: pwsh
|
||||
run: .\.ci\scripts\windows\install-vulkan-sdk.ps1
|
||||
- name: Set up MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Configure
|
||||
env:
|
||||
CC: cl.exe
|
||||
@@ -129,11 +133,12 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
distribution: 'temurin'
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -151,7 +156,6 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
|
||||
git -C ./externals/vcpkg/ fetch --all --unshallow
|
||||
- name: Build
|
||||
run: ./.ci/scripts/android/build.sh
|
||||
- name: Copy and sign artifacts
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -56,5 +56,5 @@
|
||||
path = externals/nx_tzdb/tzdb_to_nx
|
||||
url = https://github.com/lat9nq/tzdb_to_nx.git
|
||||
[submodule "VulkanMemoryAllocator"]
|
||||
path = externals/vma/VulkanMemoryAllocator
|
||||
path = externals/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
|
@@ -63,6 +63,18 @@ option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
||||
|
||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
||||
if (ANDROID OR WIN32 OR APPLE)
|
||||
# - Windows defaults to the Schannel backend.
|
||||
# - macOS defaults to the SecureTransport backend.
|
||||
# - Android currently has no SSL backend as the NDK doesn't include any SSL
|
||||
# library; a proper 'native' backend would have to go through Java.
|
||||
# But you can force builds for those platforms to use OpenSSL if you have
|
||||
# your own copy of it.
|
||||
set(DEFAULT_ENABLE_OPENSSL OFF)
|
||||
endif()
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||
|
||||
# On Android, fetch and compile libcxx before doing anything else
|
||||
if (ANDROID)
|
||||
set(CMAKE_SKIP_INSTALL_RULES ON)
|
||||
@@ -281,11 +293,12 @@ find_package(LLVM MODULE COMPONENTS Demangle)
|
||||
find_package(lz4 REQUIRED)
|
||||
find_package(nlohmann_json 3.8 REQUIRED)
|
||||
find_package(Opus 1.3 MODULE)
|
||||
find_package(VulkanMemoryAllocator CONFIG)
|
||||
find_package(ZLIB 1.2 REQUIRED)
|
||||
find_package(zstd 1.5 REQUIRED)
|
||||
|
||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||
find_package(Vulkan 1.3.246 REQUIRED)
|
||||
find_package(Vulkan 1.3.256 REQUIRED)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBUSB)
|
||||
@@ -322,6 +335,10 @@ if (MINGW)
|
||||
find_library(MSWSOCK_LIBRARY mswsock REQUIRED)
|
||||
endif()
|
||||
|
||||
if(ENABLE_OPENSSL)
|
||||
find_package(OpenSSL 1.1.1 REQUIRED)
|
||||
endif()
|
||||
|
||||
# Please consider this as a stub
|
||||
if(ENABLE_QT6 AND Qt6_LOCATION)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${Qt6_LOCATION}")
|
||||
@@ -489,7 +506,7 @@ if (ENABLE_SDL2)
|
||||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.28.0")
|
||||
set(SDL2_VER "SDL2-2.28.1")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
|
6
externals/CMakeLists.txt
vendored
6
externals/CMakeLists.txt
vendored
@@ -144,9 +144,9 @@ endif()
|
||||
add_subdirectory(nx_tzdb)
|
||||
|
||||
# VMA
|
||||
add_library(vma vma/vma.cpp)
|
||||
target_include_directories(vma PUBLIC ./vma/VulkanMemoryAllocator/include)
|
||||
target_link_libraries(vma PRIVATE Vulkan::Headers)
|
||||
if (NOT TARGET GPUOpen::VulkanMemoryAllocator)
|
||||
add_subdirectory(VulkanMemoryAllocator)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET LLVM::Demangle)
|
||||
add_library(demangle demangle/ItaniumDemangle.cpp)
|
||||
|
2
externals/SDL
vendored
2
externals/SDL
vendored
Submodule externals/SDL updated: 491fba1d06...116a5344ff
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: 63af1cf1ee...ed857118e2
1
externals/VulkanMemoryAllocator
vendored
Submodule
1
externals/VulkanMemoryAllocator
vendored
Submodule
Submodule externals/VulkanMemoryAllocator added at 9b0fc3e7b0
1
externals/vma/VulkanMemoryAllocator
vendored
1
externals/vma/VulkanMemoryAllocator
vendored
Submodule externals/vma/VulkanMemoryAllocator deleted from 0aa3989b8f
@@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
android:label="@string/app_name_suffixed"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:allowBackup="true"
|
||||
android:hasFragileUserData="true"
|
||||
android:hasFragileUserData="false"
|
||||
android:supportsRtl="true"
|
||||
android:isGame="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
@@ -54,6 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<activity
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
|
@@ -12,6 +12,7 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
|
||||
class HomeSettingAdapter(private val activity: AppCompatActivity, var options: List<HomeSetting>) :
|
||||
@@ -34,7 +35,14 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
|
||||
|
||||
override fun onClick(view: View) {
|
||||
val holder = view.tag as HomeOptionViewHolder
|
||||
holder.option.onClick.invoke()
|
||||
if (holder.option.isEnabled.invoke()) {
|
||||
holder.option.onClick.invoke()
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
holder.option.disabledTitleId,
|
||||
holder.option.disabledMessageId
|
||||
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
|
||||
@@ -65,6 +73,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
|
||||
R.drawable.premium_background
|
||||
)
|
||||
}
|
||||
|
||||
if (!option.isEnabled.invoke()) {
|
||||
binding.optionTitle.alpha = 0.5f
|
||||
binding.optionDescription.alpha = 0.5f
|
||||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -112,25 +112,36 @@ class Settings {
|
||||
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_INIT = "OverlayInit"
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
|
||||
const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
|
||||
const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
|
||||
val overlayLayoutPrefs = listOf(
|
||||
PREF_LANDSCAPE_OVERLAY_VERSION,
|
||||
PREF_PORTRAIT_OVERLAY_VERSION,
|
||||
PREF_FOLDABLE_OVERLAY_VERSION
|
||||
)
|
||||
|
||||
const val PREF_CONTROL_SCALE = "controlScale"
|
||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||
const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0"
|
||||
const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1"
|
||||
const val PREF_BUTTON_TOGGLE_2 = "buttonToggle2"
|
||||
const val PREF_BUTTON_TOGGLE_3 = "buttonToggle3"
|
||||
const val PREF_BUTTON_TOGGLE_4 = "buttonToggle4"
|
||||
const val PREF_BUTTON_TOGGLE_5 = "buttonToggle5"
|
||||
const val PREF_BUTTON_TOGGLE_6 = "buttonToggle6"
|
||||
const val PREF_BUTTON_TOGGLE_7 = "buttonToggle7"
|
||||
const val PREF_BUTTON_TOGGLE_8 = "buttonToggle8"
|
||||
const val PREF_BUTTON_TOGGLE_9 = "buttonToggle9"
|
||||
const val PREF_BUTTON_TOGGLE_10 = "buttonToggle10"
|
||||
const val PREF_BUTTON_TOGGLE_11 = "buttonToggle11"
|
||||
const val PREF_BUTTON_TOGGLE_12 = "buttonToggle12"
|
||||
const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13"
|
||||
const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14"
|
||||
const val PREF_BUTTON_A = "buttonToggle0"
|
||||
const val PREF_BUTTON_B = "buttonToggle1"
|
||||
const val PREF_BUTTON_X = "buttonToggle2"
|
||||
const val PREF_BUTTON_Y = "buttonToggle3"
|
||||
const val PREF_BUTTON_L = "buttonToggle4"
|
||||
const val PREF_BUTTON_R = "buttonToggle5"
|
||||
const val PREF_BUTTON_ZL = "buttonToggle6"
|
||||
const val PREF_BUTTON_ZR = "buttonToggle7"
|
||||
const val PREF_BUTTON_PLUS = "buttonToggle8"
|
||||
const val PREF_BUTTON_MINUS = "buttonToggle9"
|
||||
const val PREF_BUTTON_DPAD = "buttonToggle10"
|
||||
const val PREF_STICK_L = "buttonToggle11"
|
||||
const val PREF_STICK_R = "buttonToggle12"
|
||||
const val PREF_BUTTON_STICK_L = "buttonToggle13"
|
||||
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
||||
const val PREF_BUTTON_HOME = "buttonToggle15"
|
||||
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
||||
|
||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||
@@ -145,6 +156,30 @@ class Settings {
|
||||
|
||||
private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
|
||||
|
||||
val overlayPreferences = listOf(
|
||||
PREF_OVERLAY_VERSION,
|
||||
PREF_CONTROL_SCALE,
|
||||
PREF_CONTROL_OPACITY,
|
||||
PREF_TOUCH_ENABLED,
|
||||
PREF_BUTTON_A,
|
||||
PREF_BUTTON_B,
|
||||
PREF_BUTTON_X,
|
||||
PREF_BUTTON_Y,
|
||||
PREF_BUTTON_L,
|
||||
PREF_BUTTON_R,
|
||||
PREF_BUTTON_ZL,
|
||||
PREF_BUTTON_ZR,
|
||||
PREF_BUTTON_PLUS,
|
||||
PREF_BUTTON_MINUS,
|
||||
PREF_BUTTON_DPAD,
|
||||
PREF_STICK_L,
|
||||
PREF_STICK_R,
|
||||
PREF_BUTTON_HOME,
|
||||
PREF_BUTTON_SCREENSHOT,
|
||||
PREF_BUTTON_STICK_L,
|
||||
PREF_BUTTON_STICK_R
|
||||
)
|
||||
|
||||
const val LayoutOption_Unspecified = 0
|
||||
const val LayoutOption_MobilePortrait = 4
|
||||
const val LayoutOption_MobileLandscape = 5
|
||||
|
@@ -212,9 +212,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
|
||||
} else {
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
|
||||
}
|
||||
}
|
||||
if (!binding.surfaceInputOverlay.isInEditMode) {
|
||||
@@ -260,7 +260,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
.remove(Settings.PREF_CONTROL_SCALE)
|
||||
.remove(Settings.PREF_CONTROL_OPACITY)
|
||||
.apply()
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateShowFpsOverlay() {
|
||||
@@ -337,7 +339,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
|
||||
isInFoldableLayout = true
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
||||
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
|
||||
refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
@@ -410,9 +412,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
R.id.menu_toggle_controls -> {
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
val optionsArray = BooleanArray(15)
|
||||
for (i in 0..14) {
|
||||
optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 13)
|
||||
val optionsArray = BooleanArray(Settings.overlayPreferences.size)
|
||||
Settings.overlayPreferences.forEachIndexed { i, _ ->
|
||||
optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
|
||||
}
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
@@ -436,7 +438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.setOnClickListener {
|
||||
val isChecked = !optionsArray[0]
|
||||
for (i in 0..14) {
|
||||
Settings.overlayPreferences.forEachIndexed { i, _ ->
|
||||
optionsArray[i] = isChecked
|
||||
dialog.listView.setItemChecked(i, isChecked)
|
||||
preferences.edit()
|
||||
|
@@ -73,102 +73,113 @@ class HomeSettingsFragment : Fragment() {
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
R.drawable.ic_settings,
|
||||
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() }
|
||||
R.drawable.ic_folder_open,
|
||||
{ openFileManager() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.supportsCustomDriverLoading()) {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() }
|
||||
R.drawable.ic_palette,
|
||||
{ SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit,
|
||||
{ driverInstaller() },
|
||||
{ GpuDriverHelper.supportsCustomDriverLoading() },
|
||||
R.string.custom_driver_not_supported,
|
||||
R.string.custom_driver_not_supported_description
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
R.drawable.ic_nfc,
|
||||
{ mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
R.drawable.ic_system_update_alt,
|
||||
{ mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) {
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
}
|
||||
R.drawable.ic_add,
|
||||
{
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
}
|
||||
R.drawable.ic_save,
|
||||
{
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
R.drawable.ic_unlock,
|
||||
{ mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
R.drawable.ic_firmware,
|
||||
{ mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() }
|
||||
R.drawable.ic_log,
|
||||
{ shareLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
R.drawable.ic_info_outline,
|
||||
{
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,12 +189,13 @@ class HomeSettingsFragment : Fragment() {
|
||||
HomeSetting(
|
||||
R.string.get_early_access,
|
||||
R.string.get_early_access_description,
|
||||
R.drawable.ic_diamond
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_earlyAccessFragment)
|
||||
}
|
||||
R.drawable.ic_diamond,
|
||||
{
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_earlyAccessFragment)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -7,5 +7,8 @@ data class HomeSetting(
|
||||
val titleId: Int,
|
||||
val descriptionId: Int,
|
||||
val iconId: Int,
|
||||
val onClick: () -> Unit
|
||||
val onClick: () -> Unit,
|
||||
val isEnabled: () -> Boolean = { true },
|
||||
val disabledTitleId: Int = 0,
|
||||
val disabledMessageId: Int = 0
|
||||
)
|
||||
|
@@ -51,15 +51,23 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
private lateinit var windowInsets: WindowInsets
|
||||
|
||||
var orientation = LANDSCAPE
|
||||
var layout = LANDSCAPE
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
|
||||
windowInsets = rootWindowInsets
|
||||
|
||||
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||
defaultOverlay()
|
||||
val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0)
|
||||
if (overlayVersion != OVERLAY_VERSION) {
|
||||
resetAllLayouts()
|
||||
} else {
|
||||
val layoutIndex = overlayLayouts.indexOf(layout)
|
||||
val currentLayoutVersion =
|
||||
preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0)
|
||||
if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) {
|
||||
resetCurrentLayout()
|
||||
}
|
||||
}
|
||||
|
||||
// Load the controls.
|
||||
@@ -266,10 +274,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
|
||||
// Persist button position by saving new place.
|
||||
saveControlPosition(
|
||||
buttonBeingConfigured!!.buttonId,
|
||||
buttonBeingConfigured!!.prefId,
|
||||
buttonBeingConfigured!!.bounds.centerX(),
|
||||
buttonBeingConfigured!!.bounds.centerY(),
|
||||
orientation
|
||||
layout
|
||||
)
|
||||
buttonBeingConfigured = null
|
||||
}
|
||||
@@ -299,10 +307,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
|
||||
// Persist button position by saving new place.
|
||||
saveControlPosition(
|
||||
dpadBeingConfigured!!.upId,
|
||||
Settings.PREF_BUTTON_DPAD,
|
||||
dpadBeingConfigured!!.bounds.centerX(),
|
||||
dpadBeingConfigured!!.bounds.centerY(),
|
||||
orientation
|
||||
layout
|
||||
)
|
||||
dpadBeingConfigured = null
|
||||
}
|
||||
@@ -330,10 +338,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
MotionEvent.ACTION_UP,
|
||||
MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
|
||||
saveControlPosition(
|
||||
joystickBeingConfigured!!.buttonId,
|
||||
joystickBeingConfigured!!.prefId,
|
||||
joystickBeingConfigured!!.bounds.centerX(),
|
||||
joystickBeingConfigured!!.bounds.centerY(),
|
||||
orientation
|
||||
layout
|
||||
)
|
||||
joystickBeingConfigured = null
|
||||
}
|
||||
@@ -343,9 +351,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
return true
|
||||
}
|
||||
|
||||
private fun addOverlayControls(orientation: String) {
|
||||
private fun addOverlayControls(layout: String) {
|
||||
val windowSize = getSafeScreenSize(context)
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -353,11 +361,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_a,
|
||||
R.drawable.facebutton_a_depressed,
|
||||
ButtonType.BUTTON_A,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_A,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -365,11 +374,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_b,
|
||||
R.drawable.facebutton_b_depressed,
|
||||
ButtonType.BUTTON_B,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_B,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -377,11 +387,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_x,
|
||||
R.drawable.facebutton_x_depressed,
|
||||
ButtonType.BUTTON_X,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_X,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -389,11 +400,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_y,
|
||||
R.drawable.facebutton_y_depressed,
|
||||
ButtonType.BUTTON_Y,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_Y,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -401,11 +413,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.l_shoulder,
|
||||
R.drawable.l_shoulder_depressed,
|
||||
ButtonType.TRIGGER_L,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_L,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -413,11 +426,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.r_shoulder,
|
||||
R.drawable.r_shoulder_depressed,
|
||||
ButtonType.TRIGGER_R,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_R,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -425,11 +439,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.zl_trigger,
|
||||
R.drawable.zl_trigger_depressed,
|
||||
ButtonType.TRIGGER_ZL,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_ZL,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -437,11 +452,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.zr_trigger,
|
||||
R.drawable.zr_trigger_depressed,
|
||||
ButtonType.TRIGGER_ZR,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_ZR,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -449,11 +465,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_plus,
|
||||
R.drawable.facebutton_plus_depressed,
|
||||
ButtonType.BUTTON_PLUS,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_PLUS,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -461,11 +478,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_minus,
|
||||
R.drawable.facebutton_minus_depressed,
|
||||
ButtonType.BUTTON_MINUS,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_MINUS,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) {
|
||||
overlayDpads.add(
|
||||
initializeOverlayDpad(
|
||||
context,
|
||||
@@ -473,11 +491,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.dpad_standard,
|
||||
R.drawable.dpad_standard_cardinal_depressed,
|
||||
R.drawable.dpad_standard_diagonal_depressed,
|
||||
orientation
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_STICK_L, true)) {
|
||||
overlayJoysticks.add(
|
||||
initializeOverlayJoystick(
|
||||
context,
|
||||
@@ -487,11 +505,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.joystick_depressed,
|
||||
StickType.STICK_L,
|
||||
ButtonType.STICK_L,
|
||||
orientation
|
||||
Settings.PREF_STICK_L,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) {
|
||||
if (preferences.getBoolean(Settings.PREF_STICK_R, true)) {
|
||||
overlayJoysticks.add(
|
||||
initializeOverlayJoystick(
|
||||
context,
|
||||
@@ -501,11 +520,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.joystick_depressed,
|
||||
StickType.STICK_R,
|
||||
ButtonType.STICK_R,
|
||||
orientation
|
||||
Settings.PREF_STICK_R,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -513,11 +533,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_home,
|
||||
R.drawable.facebutton_home_depressed,
|
||||
ButtonType.BUTTON_HOME,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_HOME,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) {
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
@@ -525,7 +546,34 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.drawable.facebutton_screenshot,
|
||||
R.drawable.facebutton_screenshot_depressed,
|
||||
ButtonType.BUTTON_CAPTURE,
|
||||
orientation
|
||||
Settings.PREF_BUTTON_SCREENSHOT,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.button_l3,
|
||||
R.drawable.button_l3_depressed,
|
||||
ButtonType.STICK_L,
|
||||
Settings.PREF_BUTTON_STICK_L,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.button_r3,
|
||||
R.drawable.button_r3_depressed,
|
||||
ButtonType.STICK_R,
|
||||
Settings.PREF_BUTTON_STICK_R,
|
||||
layout
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -539,18 +587,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
// Add all the enabled overlay items back to the HashSet.
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
addOverlayControls(orientation)
|
||||
addOverlayControls(layout)
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) {
|
||||
private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
|
||||
val windowSize = getSafeScreenSize(context)
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||
.putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x)
|
||||
.putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y)
|
||||
.putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x)
|
||||
.putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y)
|
||||
.apply()
|
||||
}
|
||||
|
||||
@@ -558,19 +606,31 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
inEditMode = editMode
|
||||
}
|
||||
|
||||
private fun defaultOverlay() {
|
||||
if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) {
|
||||
defaultOverlayByLayout(orientation)
|
||||
}
|
||||
|
||||
resetButtonPlacement()
|
||||
private fun resetCurrentLayout() {
|
||||
defaultOverlayByLayout(layout)
|
||||
val layoutIndex = overlayLayouts.indexOf(layout)
|
||||
preferences.edit()
|
||||
.putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true)
|
||||
.putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex])
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun resetButtonPlacement() {
|
||||
defaultOverlayByLayout(orientation)
|
||||
private fun resetAllLayouts() {
|
||||
val editor = preferences.edit()
|
||||
overlayLayouts.forEachIndexed { i, layout ->
|
||||
defaultOverlayByLayout(layout)
|
||||
editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i])
|
||||
}
|
||||
editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
fun resetLayoutVisibilityAndPlacement() {
|
||||
defaultOverlayByLayout(layout)
|
||||
val editor = preferences.edit()
|
||||
Settings.overlayPreferences.forEachIndexed { _, pref ->
|
||||
editor.remove(pref)
|
||||
}
|
||||
editor.apply()
|
||||
refreshControls()
|
||||
}
|
||||
|
||||
@@ -604,7 +664,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.integer.SWITCH_STICK_R_X,
|
||||
R.integer.SWITCH_STICK_R_Y,
|
||||
R.integer.SWITCH_STICK_L_X,
|
||||
R.integer.SWITCH_STICK_L_Y
|
||||
R.integer.SWITCH_STICK_L_Y,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_X,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_Y,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_X,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_Y
|
||||
)
|
||||
|
||||
private val portraitResources = arrayOf(
|
||||
@@ -637,7 +701,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.integer.SWITCH_STICK_R_X_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_R_Y_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_L_X_PORTRAIT,
|
||||
R.integer.SWITCH_STICK_L_Y_PORTRAIT
|
||||
R.integer.SWITCH_STICK_L_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT
|
||||
)
|
||||
|
||||
private val foldableResources = arrayOf(
|
||||
@@ -670,139 +738,159 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
R.integer.SWITCH_STICK_R_X_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_R_Y_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_L_X_FOLDABLE,
|
||||
R.integer.SWITCH_STICK_L_Y_FOLDABLE
|
||||
R.integer.SWITCH_STICK_L_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE,
|
||||
R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE
|
||||
)
|
||||
|
||||
private fun getResourceValue(orientation: String, position: Int): Float {
|
||||
return when (orientation) {
|
||||
private fun getResourceValue(layout: String, position: Int): Float {
|
||||
return when (layout) {
|
||||
PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
|
||||
FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
|
||||
else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultOverlayByLayout(orientation: String) {
|
||||
private fun defaultOverlayByLayout(layout: String) {
|
||||
// Each value represents the position of the button in relation to the screen size without insets.
|
||||
preferences.edit()
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 0)
|
||||
"${Settings.PREF_BUTTON_A}-X$layout",
|
||||
getResourceValue(layout, 0)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 1)
|
||||
"${Settings.PREF_BUTTON_A}-Y$layout",
|
||||
getResourceValue(layout, 1)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 2)
|
||||
"${Settings.PREF_BUTTON_B}-X$layout",
|
||||
getResourceValue(layout, 2)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 3)
|
||||
"${Settings.PREF_BUTTON_B}-Y$layout",
|
||||
getResourceValue(layout, 3)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 4)
|
||||
"${Settings.PREF_BUTTON_X}-X$layout",
|
||||
getResourceValue(layout, 4)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 5)
|
||||
"${Settings.PREF_BUTTON_X}-Y$layout",
|
||||
getResourceValue(layout, 5)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 6)
|
||||
"${Settings.PREF_BUTTON_Y}-X$layout",
|
||||
getResourceValue(layout, 6)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 7)
|
||||
"${Settings.PREF_BUTTON_Y}-Y$layout",
|
||||
getResourceValue(layout, 7)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 8)
|
||||
"${Settings.PREF_BUTTON_ZL}-X$layout",
|
||||
getResourceValue(layout, 8)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 9)
|
||||
"${Settings.PREF_BUTTON_ZL}-Y$layout",
|
||||
getResourceValue(layout, 9)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 10)
|
||||
"${Settings.PREF_BUTTON_ZR}-X$layout",
|
||||
getResourceValue(layout, 10)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 11)
|
||||
"${Settings.PREF_BUTTON_ZR}-Y$layout",
|
||||
getResourceValue(layout, 11)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 12)
|
||||
"${Settings.PREF_BUTTON_DPAD}-X$layout",
|
||||
getResourceValue(layout, 12)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 13)
|
||||
"${Settings.PREF_BUTTON_DPAD}-Y$layout",
|
||||
getResourceValue(layout, 13)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 14)
|
||||
"${Settings.PREF_BUTTON_L}-X$layout",
|
||||
getResourceValue(layout, 14)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 15)
|
||||
"${Settings.PREF_BUTTON_L}-Y$layout",
|
||||
getResourceValue(layout, 15)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 16)
|
||||
"${Settings.PREF_BUTTON_R}-X$layout",
|
||||
getResourceValue(layout, 16)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 17)
|
||||
"${Settings.PREF_BUTTON_R}-Y$layout",
|
||||
getResourceValue(layout, 17)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 18)
|
||||
"${Settings.PREF_BUTTON_PLUS}-X$layout",
|
||||
getResourceValue(layout, 18)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 19)
|
||||
"${Settings.PREF_BUTTON_PLUS}-Y$layout",
|
||||
getResourceValue(layout, 19)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 20)
|
||||
"${Settings.PREF_BUTTON_MINUS}-X$layout",
|
||||
getResourceValue(layout, 20)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 21)
|
||||
"${Settings.PREF_BUTTON_MINUS}-Y$layout",
|
||||
getResourceValue(layout, 21)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 22)
|
||||
"${Settings.PREF_BUTTON_HOME}-X$layout",
|
||||
getResourceValue(layout, 22)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 23)
|
||||
"${Settings.PREF_BUTTON_HOME}-Y$layout",
|
||||
getResourceValue(layout, 23)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 24)
|
||||
"${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
|
||||
getResourceValue(layout, 24)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 25)
|
||||
"${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
|
||||
getResourceValue(layout, 25)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 26)
|
||||
"${Settings.PREF_STICK_R}-X$layout",
|
||||
getResourceValue(layout, 26)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 27)
|
||||
"${Settings.PREF_STICK_R}-Y$layout",
|
||||
getResourceValue(layout, 27)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-X$orientation",
|
||||
getResourceValue(orientation, 28)
|
||||
"${Settings.PREF_STICK_L}-X$layout",
|
||||
getResourceValue(layout, 28)
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-Y$orientation",
|
||||
getResourceValue(orientation, 29)
|
||||
"${Settings.PREF_STICK_L}-Y$layout",
|
||||
getResourceValue(layout, 29)
|
||||
)
|
||||
.putFloat(
|
||||
"${Settings.PREF_BUTTON_STICK_L}-X$layout",
|
||||
getResourceValue(layout, 30)
|
||||
)
|
||||
.putFloat(
|
||||
"${Settings.PREF_BUTTON_STICK_L}-Y$layout",
|
||||
getResourceValue(layout, 31)
|
||||
)
|
||||
.putFloat(
|
||||
"${Settings.PREF_BUTTON_STICK_R}-X$layout",
|
||||
getResourceValue(layout, 32)
|
||||
)
|
||||
.putFloat(
|
||||
"${Settings.PREF_BUTTON_STICK_R}-Y$layout",
|
||||
getResourceValue(layout, 33)
|
||||
)
|
||||
.apply()
|
||||
}
|
||||
@@ -812,12 +900,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Increase this number every time there is a breaking change to every overlay layout
|
||||
const val OVERLAY_VERSION = 1
|
||||
|
||||
// Increase the corresponding layout version number whenever that layout has a breaking change
|
||||
private const val LANDSCAPE_OVERLAY_VERSION = 1
|
||||
private const val PORTRAIT_OVERLAY_VERSION = 1
|
||||
private const val FOLDABLE_OVERLAY_VERSION = 1
|
||||
val overlayLayoutVersions = listOf(
|
||||
LANDSCAPE_OVERLAY_VERSION,
|
||||
PORTRAIT_OVERLAY_VERSION,
|
||||
FOLDABLE_OVERLAY_VERSION
|
||||
)
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
const val LANDSCAPE = ""
|
||||
const val LANDSCAPE = "_Landscape"
|
||||
const val PORTRAIT = "_Portrait"
|
||||
const val FOLDABLE = "_Foldable"
|
||||
val overlayLayouts = listOf(
|
||||
LANDSCAPE,
|
||||
PORTRAIT,
|
||||
FOLDABLE
|
||||
)
|
||||
|
||||
/**
|
||||
* Resizes a [Bitmap] by a given scale factor
|
||||
@@ -948,6 +1054,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
* @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
|
||||
* @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
|
||||
* @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
|
||||
* @param prefId Identifier for determining where a button appears on screen.
|
||||
* @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
|
||||
* @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
|
||||
*/
|
||||
private fun initializeOverlayButton(
|
||||
@@ -956,7 +1064,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
defaultResId: Int,
|
||||
pressedResId: Int,
|
||||
buttonId: Int,
|
||||
orientation: String
|
||||
prefId: String,
|
||||
layout: String
|
||||
): InputOverlayDrawableButton {
|
||||
// Resources handle for fetching the initial Drawable resource.
|
||||
val res = context.resources
|
||||
@@ -964,17 +1073,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
// SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
|
||||
val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
// Decide scale based on button ID and user preference
|
||||
var scale: Float = when (buttonId) {
|
||||
ButtonType.BUTTON_HOME,
|
||||
ButtonType.BUTTON_CAPTURE,
|
||||
ButtonType.BUTTON_PLUS,
|
||||
ButtonType.BUTTON_MINUS -> 0.07f
|
||||
// Decide scale based on button preference ID and user preference
|
||||
var scale: Float = when (prefId) {
|
||||
Settings.PREF_BUTTON_HOME,
|
||||
Settings.PREF_BUTTON_SCREENSHOT,
|
||||
Settings.PREF_BUTTON_PLUS,
|
||||
Settings.PREF_BUTTON_MINUS -> 0.07f
|
||||
|
||||
ButtonType.TRIGGER_L,
|
||||
ButtonType.TRIGGER_R,
|
||||
ButtonType.TRIGGER_ZL,
|
||||
ButtonType.TRIGGER_ZR -> 0.26f
|
||||
Settings.PREF_BUTTON_L,
|
||||
Settings.PREF_BUTTON_R,
|
||||
Settings.PREF_BUTTON_ZL,
|
||||
Settings.PREF_BUTTON_ZR -> 0.26f
|
||||
|
||||
Settings.PREF_BUTTON_STICK_L,
|
||||
Settings.PREF_BUTTON_STICK_R -> 0.155f
|
||||
|
||||
else -> 0.11f
|
||||
}
|
||||
@@ -984,8 +1096,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
// Initialize the InputOverlayDrawableButton.
|
||||
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
|
||||
val pressedStateBitmap = getBitmap(context, pressedResId, scale)
|
||||
val overlayDrawable =
|
||||
InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId)
|
||||
val overlayDrawable = InputOverlayDrawableButton(
|
||||
res,
|
||||
defaultStateBitmap,
|
||||
pressedStateBitmap,
|
||||
buttonId,
|
||||
prefId
|
||||
)
|
||||
|
||||
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
||||
val min = windowSize.first
|
||||
@@ -993,8 +1110,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val xKey = "$buttonId-X$orientation"
|
||||
val yKey = "$buttonId-Y$orientation"
|
||||
val xKey = "$prefId-X$layout"
|
||||
val yKey = "$prefId-Y$layout"
|
||||
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
||||
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
@@ -1029,7 +1146,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
* @param defaultResId The [Bitmap] resource ID of the default state.
|
||||
* @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
|
||||
* @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
|
||||
* @return the initialized [InputOverlayDrawableDpad]
|
||||
* @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
|
||||
* @return The initialized [InputOverlayDrawableDpad]
|
||||
*/
|
||||
private fun initializeOverlayDpad(
|
||||
context: Context,
|
||||
@@ -1037,7 +1155,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
defaultResId: Int,
|
||||
pressedOneDirectionResId: Int,
|
||||
pressedTwoDirectionsResId: Int,
|
||||
orientation: String
|
||||
layout: String
|
||||
): InputOverlayDrawableDpad {
|
||||
// Resources handle for fetching the initial Drawable resource.
|
||||
val res = context.resources
|
||||
@@ -1074,8 +1192,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f)
|
||||
val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val width = overlayDrawable.width
|
||||
@@ -1107,7 +1225,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
* @param pressedResInner Resource ID for the pressed inner image of the joystick.
|
||||
* @param joystick Identifier for which joystick this is.
|
||||
* @param button Identifier for which joystick button this is.
|
||||
* @return the initialized [InputOverlayDrawableJoystick].
|
||||
* @param prefId Identifier for determining where a button appears on screen.
|
||||
* @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
|
||||
* @return The initialized [InputOverlayDrawableJoystick].
|
||||
*/
|
||||
private fun initializeOverlayJoystick(
|
||||
context: Context,
|
||||
@@ -1117,7 +1237,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
pressedResInner: Int,
|
||||
joystick: Int,
|
||||
button: Int,
|
||||
orientation: String
|
||||
prefId: String,
|
||||
layout: String
|
||||
): InputOverlayDrawableJoystick {
|
||||
// Resources handle for fetching the initial Drawable resource.
|
||||
val res = context.resources
|
||||
@@ -1141,8 +1262,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f)
|
||||
val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val outerScale = 1.66f
|
||||
@@ -1168,7 +1289,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
outerRect,
|
||||
innerRect,
|
||||
joystick,
|
||||
button
|
||||
button,
|
||||
prefId
|
||||
)
|
||||
|
||||
// Need to set the image's position
|
||||
|
@@ -24,7 +24,8 @@ class InputOverlayDrawableButton(
|
||||
res: Resources,
|
||||
defaultStateBitmap: Bitmap,
|
||||
pressedStateBitmap: Bitmap,
|
||||
val buttonId: Int
|
||||
val buttonId: Int,
|
||||
val prefId: String
|
||||
) {
|
||||
// The ID value what motion event is tracking
|
||||
var trackId: Int
|
||||
|
@@ -37,7 +37,8 @@ class InputOverlayDrawableJoystick(
|
||||
rectOuter: Rect,
|
||||
rectInner: Rect,
|
||||
val joystickId: Int,
|
||||
val buttonId: Int
|
||||
val buttonId: Int,
|
||||
val prefId: String
|
||||
) {
|
||||
// The ID value what motion event is tracking
|
||||
var trackId = -1
|
||||
|
128
src/android/app/src/main/res/drawable/button_l3.xml
Normal file
128
src/android/app/src/main/res/drawable/button_l3.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="34.963dp"
|
||||
android:height="37.265dp"
|
||||
android:viewportWidth="34.963"
|
||||
android:viewportHeight="37.265">
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="21.568"
|
||||
android:endY="33.938"
|
||||
android:startX="21.568"
|
||||
android:startY="16.14"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="17.395"
|
||||
android:endY="18.74"
|
||||
android:startX="17.395"
|
||||
android:startY="-1.296"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:centerX="17.477"
|
||||
android:centerY="19.92"
|
||||
android:gradientRadius="17.201"
|
||||
android:type="radial">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0.58" />
|
||||
<item
|
||||
android:color="#FFC6C6C6"
|
||||
android:offset="0.84" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.88" />
|
||||
<item
|
||||
android:color="#FFC2C2C2"
|
||||
android:offset="0.91" />
|
||||
<item
|
||||
android:color="#FFB5B5B5"
|
||||
android:offset="0.94" />
|
||||
<item
|
||||
android:color="#FF9E9E9E"
|
||||
android:offset="0.98" />
|
||||
<item
|
||||
android:color="#FF8F8F8F"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="16.829"
|
||||
android:endY="46.882"
|
||||
android:startX="16.829"
|
||||
android:startY="20.479"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
@@ -0,0 +1,75 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="34.963dp"
|
||||
android:height="37.265dp"
|
||||
android:viewportWidth="34.963"
|
||||
android:viewportHeight="37.265">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="#151515"
|
||||
android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:fillColor="#151515"
|
||||
android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
|
||||
android:strokeAlpha="0.6" />
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="21.568"
|
||||
android:endY="33.938"
|
||||
android:startX="21.568"
|
||||
android:startY="16.14"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="16.829"
|
||||
android:endY="46.882"
|
||||
android:startX="16.829"
|
||||
android:startY="20.479"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
128
src/android/app/src/main/res/drawable/button_r3.xml
Normal file
128
src/android/app/src/main/res/drawable/button_r3.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="34.963dp"
|
||||
android:height="37.265dp"
|
||||
android:viewportWidth="34.963"
|
||||
android:viewportHeight="37.265">
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="15.506"
|
||||
android:endY="48.977"
|
||||
android:startX="15.506"
|
||||
android:startY="19.659"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="17.395"
|
||||
android:endY="18.74"
|
||||
android:startX="17.395"
|
||||
android:startY="-1.296"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:centerX="17.477"
|
||||
android:centerY="19.92"
|
||||
android:gradientRadius="17.201"
|
||||
android:type="radial">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0.58" />
|
||||
<item
|
||||
android:color="#FFC6C6C6"
|
||||
android:offset="0.84" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.88" />
|
||||
<item
|
||||
android:color="#FFC2C2C2"
|
||||
android:offset="0.91" />
|
||||
<item
|
||||
android:color="#FFB5B5B5"
|
||||
android:offset="0.94" />
|
||||
<item
|
||||
android:color="#FF9E9E9E"
|
||||
android:offset="0.98" />
|
||||
<item
|
||||
android:color="#FF8F8F8F"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.5"
|
||||
android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="23.949"
|
||||
android:endY="33.938"
|
||||
android:startX="23.949"
|
||||
android:startY="16.14"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
@@ -0,0 +1,75 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="34.963dp"
|
||||
android:height="37.265dp"
|
||||
android:viewportWidth="34.963"
|
||||
android:viewportHeight="37.265">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="#151515"
|
||||
android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:fillColor="#151515"
|
||||
android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
|
||||
android:strokeAlpha="0.6" />
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="15.506"
|
||||
android:endY="48.977"
|
||||
android:startX="15.506"
|
||||
android:startY="19.659"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillAlpha="0.6"
|
||||
android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
|
||||
android:strokeAlpha="0.6">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="23.949"
|
||||
android:endY="33.938"
|
||||
android:startX="23.949"
|
||||
android:startY="16.14"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFC3C4C5"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FFC5C6C6"
|
||||
android:offset="0.03" />
|
||||
<item
|
||||
android:color="#FFC7C7C7"
|
||||
android:offset="0.19" />
|
||||
<item
|
||||
android:color="#DBB5B5B5"
|
||||
android:offset="0.44" />
|
||||
<item
|
||||
android:color="#7F878787"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
@@ -205,6 +205,8 @@
|
||||
<item>@string/gamepad_d_pad</item>
|
||||
<item>@string/gamepad_left_stick</item>
|
||||
<item>@string/gamepad_right_stick</item>
|
||||
<item>L3</item>
|
||||
<item>R3</item>
|
||||
<item>@string/gamepad_home</item>
|
||||
<item>@string/gamepad_screenshot</item>
|
||||
</string-array>
|
||||
|
@@ -33,6 +33,10 @@
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X">260</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
|
||||
|
||||
<!-- Default SWITCH portrait layout -->
|
||||
<integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
|
||||
@@ -65,6 +69,10 @@
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
|
||||
|
||||
<!-- Default SWITCH foldable layout -->
|
||||
<integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
|
||||
@@ -97,5 +105,9 @@
|
||||
<integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
|
||||
<integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
|
||||
<integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
|
||||
|
||||
</resources>
|
||||
|
@@ -113,6 +113,8 @@
|
||||
<string name="install_game_content_success_install">%1$d installed successfully</string>
|
||||
<string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
|
||||
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
|
||||
<string name="custom_driver_not_supported">Custom drivers not supported</string>
|
||||
<string name="custom_driver_not_supported_description">Custom driver loading isn\'t currently supported for this device.\nCheck this option again in the future to see if support was added!</string>
|
||||
|
||||
<!-- About screen strings -->
|
||||
<string name="gaia_is_not_real">Gaia isn\'t real</string>
|
||||
@@ -230,7 +232,7 @@
|
||||
|
||||
<!-- ROM loading errors -->
|
||||
<string name="loader_error_encrypted">Your ROM is encrypted</string>
|
||||
<string name="loader_error_encrypted_roms_description"><![CDATA[Please follow the guides to redump your <a href="https://yuzu-emu.org/help/quickstart/#dumping-cartridge-games">game cartidges</a> or <a href="https://yuzu-emu.org/help/quickstart/#dumping-installed-titles-eshop">installed titles</a>.]]></string>
|
||||
<string name="loader_error_encrypted_roms_description"><![CDATA[Please follow the guides to redump your <a href="https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards">game cartidges</a> or <a href="https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop">installed titles</a>.]]></string>
|
||||
<string name="loader_error_encrypted_keys_description"><![CDATA[Please ensure your <a href="https://yuzu-emu.org/help/quickstart/#dumping-prodkeys-and-titlekeys">prod.keys</a> file is installed so that games can be decrypted.]]></string>
|
||||
<string name="loader_error_video_core">An error occurred initializing the video core</string>
|
||||
<string name="loader_error_video_core_description">This is usually caused by an incompatible GPU driver. Installing a custom GPU driver may resolve this problem.</string>
|
||||
|
@@ -92,9 +92,9 @@ void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) {
|
||||
if (type == Sink::StreamType::In) {
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
} else {
|
||||
system.ApplicationMemory().ReadBlockUnsafe(buffer.samples, tmp_samples.data(),
|
||||
buffer.size);
|
||||
stream->AppendBuffer(new_buffer, tmp_samples);
|
||||
Core::Memory::CpuGuestMemory<s16, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
|
||||
system.ApplicationMemory(), buffer.samples, buffer.size / sizeof(s16));
|
||||
stream->AppendBuffer(new_buffer, samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,6 @@ constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
|
||||
template <typename T>
|
||||
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
std::array<T, TempBufferSize> tmp_samples{};
|
||||
constexpr s32 min{std::numeric_limits<s16>::min()};
|
||||
constexpr s32 max{std::numeric_limits<s16>::max()};
|
||||
|
||||
@@ -49,19 +48,18 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const VAddr source{req.buffer +
|
||||
(((req.start_offset + req.offset) * channel_count) * sizeof(T))};
|
||||
const u64 size{channel_count * samples_to_decode};
|
||||
const u64 size_bytes{size * sizeof(T)};
|
||||
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), size_bytes);
|
||||
|
||||
Core::Memory::CpuGuestMemory<T, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
|
||||
memory, source, size);
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
out_buffer[i] = tmp_samples[i * channel_count + req.target_channel];
|
||||
out_buffer[i] = samples[i * channel_count + req.target_channel];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@@ -74,16 +72,17 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
}
|
||||
|
||||
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
|
||||
memory.ReadBlockUnsafe(source, tmp_samples.data(), samples_to_decode * sizeof(T));
|
||||
Core::Memory::CpuGuestMemory<T, Core::Memory::GuestMemoryFlags::UnsafeRead> samples(
|
||||
memory, source, samples_to_decode);
|
||||
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
for (u32 i = 0; i < samples_to_decode; i++) {
|
||||
auto sample{static_cast<s32>(tmp_samples[i * channel_count + req.target_channel] *
|
||||
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
|
||||
std::numeric_limits<s16>::max())};
|
||||
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
|
||||
}
|
||||
} else {
|
||||
std::memcpy(out_buffer.data(), tmp_samples.data(), samples_to_decode * sizeof(s16));
|
||||
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -101,7 +100,6 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
*/
|
||||
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
const DecodeArg& req) {
|
||||
std::array<u8, TempBufferSize> wavebuffer{};
|
||||
constexpr u32 SamplesPerFrame{14};
|
||||
constexpr u32 NibblesPerFrame{16};
|
||||
|
||||
@@ -139,7 +137,8 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
}
|
||||
|
||||
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
|
||||
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), size);
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> wavebuffer(
|
||||
memory, req.buffer + position_in_frame / 2, size);
|
||||
|
||||
auto context{req.adpcm_context};
|
||||
auto header{context->header};
|
||||
|
@@ -21,23 +21,13 @@ static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_in
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp info{};
|
||||
auto info_ptr{&info};
|
||||
bool host_safe{(aux_info & Core::Memory::YUZU_PAGEMASK) <=
|
||||
(Core::Memory::YUZU_PAGESIZE - sizeof(AuxInfo::AuxInfoDsp))};
|
||||
memory.ReadBlockUnsafe(aux_info, &info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
if (host_safe) [[likely]] {
|
||||
info_ptr = memory.GetPointer<AuxInfo::AuxInfoDsp>(aux_info);
|
||||
} else {
|
||||
memory.ReadBlockUnsafe(aux_info, info_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
}
|
||||
info.read_offset = 0;
|
||||
info.write_offset = 0;
|
||||
info.total_sample_count = 0;
|
||||
|
||||
info_ptr->read_offset = 0;
|
||||
info_ptr->write_offset = 0;
|
||||
info_ptr->total_sample_count = 0;
|
||||
|
||||
if (!host_safe) [[unlikely]] {
|
||||
memory.WriteBlockUnsafe(aux_info, info_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
}
|
||||
memory.WriteBlockUnsafe(aux_info, &info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,17 +76,9 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr send_info_,
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp send_info{};
|
||||
auto send_ptr = &send_info;
|
||||
bool host_safe = (send_info_ & Core::Memory::YUZU_PAGEMASK) <=
|
||||
(Core::Memory::YUZU_PAGESIZE - sizeof(AuxInfo::AuxInfoDsp));
|
||||
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
if (host_safe) [[likely]] {
|
||||
send_ptr = memory.GetPointer<AuxInfo::AuxInfoDsp>(send_info_);
|
||||
} else {
|
||||
memory.ReadBlockUnsafe(send_info_, send_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
}
|
||||
|
||||
u32 target_write_offset{send_ptr->write_offset + write_offset};
|
||||
u32 target_write_offset{send_info.write_offset + write_offset};
|
||||
if (target_write_offset > count_max) {
|
||||
return 0;
|
||||
}
|
||||
@@ -105,15 +87,9 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr send_info_,
|
||||
u32 read_pos{0};
|
||||
while (write_count > 0) {
|
||||
u32 to_write{std::min(count_max - target_write_offset, write_count)};
|
||||
const auto write_addr = send_buffer + target_write_offset * sizeof(s32);
|
||||
bool write_safe{(write_addr & Core::Memory::YUZU_PAGEMASK) <=
|
||||
(Core::Memory::YUZU_PAGESIZE - (write_addr + to_write * sizeof(s32)))};
|
||||
if (write_safe) [[likely]] {
|
||||
auto ptr = memory.GetPointer(write_addr);
|
||||
std::memcpy(ptr, &input[read_pos], to_write * sizeof(s32));
|
||||
} else {
|
||||
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
|
||||
&input[read_pos], to_write * sizeof(s32));
|
||||
if (to_write > 0) {
|
||||
const auto write_addr = send_buffer + target_write_offset * sizeof(s32);
|
||||
memory.WriteBlockUnsafe(write_addr, &input[read_pos], to_write * sizeof(s32));
|
||||
}
|
||||
target_write_offset = (target_write_offset + to_write) % count_max;
|
||||
write_count -= to_write;
|
||||
@@ -121,13 +97,10 @@ static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr send_info_,
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
send_ptr->write_offset = (send_ptr->write_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
if (!host_safe) [[unlikely]] {
|
||||
memory.WriteBlockUnsafe(send_info_, send_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
send_info.write_offset = (send_info.write_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
return write_count_;
|
||||
}
|
||||
|
||||
@@ -174,17 +147,9 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_,
|
||||
}
|
||||
|
||||
AuxInfo::AuxInfoDsp return_info{};
|
||||
auto return_ptr = &return_info;
|
||||
bool host_safe = (return_info_ & Core::Memory::YUZU_PAGEMASK) <=
|
||||
(Core::Memory::YUZU_PAGESIZE - sizeof(AuxInfo::AuxInfoDsp));
|
||||
memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
|
||||
if (host_safe) [[likely]] {
|
||||
return_ptr = memory.GetPointer<AuxInfo::AuxInfoDsp>(return_info_);
|
||||
} else {
|
||||
memory.ReadBlockUnsafe(return_info_, return_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
}
|
||||
|
||||
u32 target_read_offset{return_ptr->read_offset + read_offset};
|
||||
u32 target_read_offset{return_info.read_offset + read_offset};
|
||||
if (target_read_offset > count_max) {
|
||||
return 0;
|
||||
}
|
||||
@@ -193,15 +158,9 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_,
|
||||
u32 write_pos{0};
|
||||
while (read_count > 0) {
|
||||
u32 to_read{std::min(count_max - target_read_offset, read_count)};
|
||||
const auto read_addr = return_buffer + target_read_offset * sizeof(s32);
|
||||
bool read_safe{(read_addr & Core::Memory::YUZU_PAGEMASK) <=
|
||||
(Core::Memory::YUZU_PAGESIZE - (read_addr + to_read * sizeof(s32)))};
|
||||
if (read_safe) [[likely]] {
|
||||
auto ptr = memory.GetPointer(read_addr);
|
||||
std::memcpy(&output[write_pos], ptr, to_read * sizeof(s32));
|
||||
} else {
|
||||
memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
|
||||
&output[write_pos], to_read * sizeof(s32));
|
||||
if (to_read > 0) {
|
||||
const auto read_addr = return_buffer + target_read_offset * sizeof(s32);
|
||||
memory.ReadBlockUnsafe(read_addr, &output[write_pos], to_read * sizeof(s32));
|
||||
}
|
||||
target_read_offset = (target_read_offset + to_read) % count_max;
|
||||
read_count -= to_read;
|
||||
@@ -209,13 +168,10 @@ static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, CpuAddr return_info_,
|
||||
}
|
||||
|
||||
if (update_count) {
|
||||
return_ptr->read_offset = (return_ptr->read_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
if (!host_safe) [[unlikely]] {
|
||||
memory.WriteBlockUnsafe(return_info_, return_ptr, sizeof(AuxInfo::AuxInfoDsp));
|
||||
return_info.read_offset = (return_info.read_offset + update_count) % count_max;
|
||||
}
|
||||
|
||||
memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
|
||||
return read_count_;
|
||||
}
|
||||
|
||||
|
@@ -66,6 +66,7 @@ void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page
|
||||
<< (address_space_width_in_bits - page_size_in_bits)};
|
||||
pointers.resize(num_page_table_entries);
|
||||
backing_addr.resize(num_page_table_entries);
|
||||
blocks.resize(num_page_table_entries);
|
||||
current_address_space_width_in_bits = address_space_width_in_bits;
|
||||
page_size = 1ULL << page_size_in_bits;
|
||||
}
|
||||
|
@@ -122,6 +122,7 @@ struct PageTable {
|
||||
* corresponding attribute element is of type `Memory`.
|
||||
*/
|
||||
VirtualBuffer<PageInfo> pointers;
|
||||
VirtualBuffer<u64> blocks;
|
||||
|
||||
VirtualBuffer<u64> backing_addr;
|
||||
|
||||
|
@@ -40,8 +40,21 @@ public:
|
||||
~ScratchBuffer() = default;
|
||||
ScratchBuffer(const ScratchBuffer&) = delete;
|
||||
ScratchBuffer& operator=(const ScratchBuffer&) = delete;
|
||||
ScratchBuffer(ScratchBuffer&&) = default;
|
||||
ScratchBuffer& operator=(ScratchBuffer&&) = default;
|
||||
|
||||
ScratchBuffer(ScratchBuffer&& other) noexcept {
|
||||
swap(other);
|
||||
other.last_requested_size = 0;
|
||||
other.buffer_capacity = 0;
|
||||
other.buffer.reset();
|
||||
}
|
||||
|
||||
ScratchBuffer& operator=(ScratchBuffer&& other) noexcept {
|
||||
swap(other);
|
||||
other.last_requested_size = 0;
|
||||
other.buffer_capacity = 0;
|
||||
other.buffer.reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// This will only grow the buffer's capacity if size is greater than the current capacity.
|
||||
/// The previously held data will remain intact.
|
||||
|
@@ -27,8 +27,8 @@ std::string GetTimeZoneString() {
|
||||
std::string location_name;
|
||||
if (time_zone_index == 0) { // Auto
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
|
||||
try {
|
||||
const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
|
||||
const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
|
||||
std::string_view current_zone_name = current_zone->name();
|
||||
location_name = current_zone_name;
|
||||
|
@@ -3,17 +3,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
/// Address families
|
||||
enum class Domain : u8 {
|
||||
INET, ///< Address family for IPv4
|
||||
Unspecified, ///< Represents 0, used in getaddrinfo hints
|
||||
INET, ///< Address family for IPv4
|
||||
};
|
||||
|
||||
/// Socket types
|
||||
enum class Type {
|
||||
Unspecified, ///< Represents 0, used in getaddrinfo hints
|
||||
STREAM,
|
||||
DGRAM,
|
||||
RAW,
|
||||
@@ -22,6 +27,7 @@ enum class Type {
|
||||
|
||||
/// Protocol values for sockets
|
||||
enum class Protocol : u8 {
|
||||
Unspecified, ///< Represents 0, usable in various places
|
||||
ICMP,
|
||||
TCP,
|
||||
UDP,
|
||||
@@ -48,4 +54,13 @@ constexpr u32 FLAG_MSG_PEEK = 0x2;
|
||||
constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
|
||||
constexpr u32 FLAG_O_NONBLOCK = 0x800;
|
||||
|
||||
/// Cross-platform addrinfo structure
|
||||
struct AddrInfo {
|
||||
Domain family;
|
||||
Type socket_type;
|
||||
Protocol protocol;
|
||||
SockAddrIn addr;
|
||||
std::optional<std::string> canon_name;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
|
@@ -285,6 +285,7 @@ add_library(core STATIC
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory_types.h
|
||||
hle/kernel/message_buffer.h
|
||||
hle/kernel/physical_core.cpp
|
||||
hle/kernel/physical_core.h
|
||||
hle/kernel/physical_memory.h
|
||||
@@ -722,6 +723,7 @@ add_library(core STATIC
|
||||
hle/service/spl/spl_types.h
|
||||
hle/service/ssl/ssl.cpp
|
||||
hle/service/ssl/ssl.h
|
||||
hle/service/ssl/ssl_backend.h
|
||||
hle/service/time/clock_types.h
|
||||
hle/service/time/ephemeral_network_system_clock_context_writer.h
|
||||
hle/service/time/ephemeral_network_system_clock_core.h
|
||||
@@ -863,6 +865,23 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
||||
endif()
|
||||
|
||||
if(ENABLE_OPENSSL)
|
||||
target_sources(core PRIVATE
|
||||
hle/service/ssl/ssl_backend_openssl.cpp)
|
||||
target_link_libraries(core PRIVATE OpenSSL::SSL)
|
||||
elseif (APPLE)
|
||||
target_sources(core PRIVATE
|
||||
hle/service/ssl/ssl_backend_securetransport.cpp)
|
||||
target_link_libraries(core PRIVATE "-framework Security")
|
||||
elseif (WIN32)
|
||||
target_sources(core PRIVATE
|
||||
hle/service/ssl/ssl_backend_schannel.cpp)
|
||||
target_link_libraries(core PRIVATE crypt32 secur32)
|
||||
else()
|
||||
target_sources(core PRIVATE
|
||||
hle/service/ssl/ssl_backend_none.cpp)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(core PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
|
@@ -185,7 +185,7 @@ void ARM_Interface::Run() {
|
||||
// Notify the debugger and go to sleep if a breakpoint was hit,
|
||||
// or if the thread is unable to continue for any reason.
|
||||
if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) {
|
||||
if (!True(hr & HaltReason::InstructionBreakpoint)) {
|
||||
if (!True(hr & HaltReason::PrefetchAbort)) {
|
||||
RewindBreakpointInstruction();
|
||||
}
|
||||
if (system.DebuggerEnabled()) {
|
||||
|
@@ -70,7 +70,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
|
||||
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,6 @@ void CoreTiming::ThreadLoop() {
|
||||
#ifdef _WIN32
|
||||
while (!paused && !event.IsSet() && wait_time > 0) {
|
||||
wait_time = *next_time - GetGlobalTimeNs().count();
|
||||
|
||||
if (wait_time >= timer_resolution_ns) {
|
||||
Common::Windows::SleepForOneTick();
|
||||
} else {
|
||||
|
@@ -163,7 +163,7 @@ private:
|
||||
Common::Event pause_event{};
|
||||
std::mutex basic_lock;
|
||||
std::mutex advance_lock;
|
||||
std::unique_ptr<std::thread> timer_thread;
|
||||
std::unique_ptr<std::jthread> timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
|
@@ -57,11 +57,34 @@ struct NCASectionHeaderBlock {
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
|
||||
|
||||
struct NCABucketInfo {
|
||||
u64 table_offset;
|
||||
u64 table_size;
|
||||
std::array<u8, 0x10> table_header;
|
||||
};
|
||||
static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size.");
|
||||
|
||||
struct NCASparseInfo {
|
||||
NCABucketInfo bucket;
|
||||
u64 physical_offset;
|
||||
u16 generation;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x6);
|
||||
};
|
||||
static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size.");
|
||||
|
||||
struct NCACompressionInfo {
|
||||
NCABucketInfo bucket;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x8);
|
||||
};
|
||||
static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size.");
|
||||
|
||||
struct NCASectionRaw {
|
||||
NCASectionHeaderBlock header;
|
||||
std::array<u8, 0x138> block_data;
|
||||
std::array<u8, 0x8> section_ctr;
|
||||
INSERT_PADDING_BYTES_NOINIT(0xB8);
|
||||
NCASparseInfo sparse_info;
|
||||
NCACompressionInfo compression_info;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x60);
|
||||
};
|
||||
static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
|
||||
|
||||
@@ -225,6 +248,20 @@ bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_b
|
||||
for (std::size_t i = 0; i < sections.size(); ++i) {
|
||||
const auto& section = sections[i];
|
||||
|
||||
if (section.raw.sparse_info.bucket.table_offset != 0 &&
|
||||
section.raw.sparse_info.bucket.table_size != 0) {
|
||||
LOG_ERROR(Loader, "Sparse NCAs are not supported.");
|
||||
status = Loader::ResultStatus::ErrorSparseNCA;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.raw.compression_info.bucket.table_offset != 0 &&
|
||||
section.raw.compression_info.bucket.table_size != 0) {
|
||||
LOG_ERROR(Loader, "Compressed NCAs are not supported.");
|
||||
status = Loader::ResultStatus::ErrorCompressedNCA;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
|
||||
return false;
|
||||
|
@@ -283,7 +283,8 @@ std::size_t RealVfsFile::GetSize() const {
|
||||
if (size) {
|
||||
return *size;
|
||||
}
|
||||
return FS::GetSize(path);
|
||||
auto lk = base.RefreshReference(path, perms, *reference);
|
||||
return reference->file ? reference->file->GetSize() : 0;
|
||||
}
|
||||
|
||||
bool RealVfsFile::Resize(std::size_t new_size) {
|
||||
|
@@ -20,12 +20,132 @@
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_thread_queue.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/message_buffer.h"
|
||||
#include "core/hle/service/hle_ipc.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
namespace {
|
||||
|
||||
template <bool MoveHandleAllowed>
|
||||
Result ProcessMessageSpecialData(KProcess& dst_process, KProcess& src_process, KThread& src_thread,
|
||||
MessageBuffer& dst_msg, const MessageBuffer& src_msg,
|
||||
MessageBuffer::SpecialHeader& src_special_header) {
|
||||
// Copy the special header to the destination.
|
||||
s32 offset = dst_msg.Set(src_special_header);
|
||||
|
||||
// Copy the process ID.
|
||||
if (src_special_header.GetHasProcessId()) {
|
||||
offset = dst_msg.SetProcessId(offset, src_process.GetProcessId());
|
||||
}
|
||||
|
||||
// Prepare to process handles.
|
||||
auto& dst_handle_table = dst_process.GetHandleTable();
|
||||
auto& src_handle_table = src_process.GetHandleTable();
|
||||
Result result = ResultSuccess;
|
||||
|
||||
// Process copy handles.
|
||||
for (auto i = 0; i < src_special_header.GetCopyHandleCount(); ++i) {
|
||||
// Get the handles.
|
||||
const Handle src_handle = src_msg.GetHandle(offset);
|
||||
Handle dst_handle = Svc::InvalidHandle;
|
||||
|
||||
// If we're in a success state, try to move the handle to the new table.
|
||||
if (R_SUCCEEDED(result) && src_handle != Svc::InvalidHandle) {
|
||||
KScopedAutoObject obj =
|
||||
src_handle_table.GetObjectForIpc(src_handle, std::addressof(src_thread));
|
||||
if (obj.IsNotNull()) {
|
||||
Result add_result =
|
||||
dst_handle_table.Add(std::addressof(dst_handle), obj.GetPointerUnsafe());
|
||||
if (R_FAILED(add_result)) {
|
||||
result = add_result;
|
||||
dst_handle = Svc::InvalidHandle;
|
||||
}
|
||||
} else {
|
||||
result = ResultInvalidHandle;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the handle.
|
||||
offset = dst_msg.SetHandle(offset, dst_handle);
|
||||
}
|
||||
|
||||
// Process move handles.
|
||||
if constexpr (MoveHandleAllowed) {
|
||||
for (auto i = 0; i < src_special_header.GetMoveHandleCount(); ++i) {
|
||||
// Get the handles.
|
||||
const Handle src_handle = src_msg.GetHandle(offset);
|
||||
Handle dst_handle = Svc::InvalidHandle;
|
||||
|
||||
// Whether or not we've succeeded, we need to remove the handles from the source table.
|
||||
if (src_handle != Svc::InvalidHandle) {
|
||||
if (R_SUCCEEDED(result)) {
|
||||
KScopedAutoObject obj =
|
||||
src_handle_table.GetObjectForIpcWithoutPseudoHandle(src_handle);
|
||||
if (obj.IsNotNull()) {
|
||||
Result add_result = dst_handle_table.Add(std::addressof(dst_handle),
|
||||
obj.GetPointerUnsafe());
|
||||
|
||||
src_handle_table.Remove(src_handle);
|
||||
|
||||
if (R_FAILED(add_result)) {
|
||||
result = add_result;
|
||||
dst_handle = Svc::InvalidHandle;
|
||||
}
|
||||
} else {
|
||||
result = ResultInvalidHandle;
|
||||
}
|
||||
} else {
|
||||
src_handle_table.Remove(src_handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the handle.
|
||||
offset = dst_msg.SetHandle(offset, dst_handle);
|
||||
}
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
void CleanupSpecialData(KProcess& dst_process, u32* dst_msg_ptr, size_t dst_buffer_size) {
|
||||
// Parse the message.
|
||||
const MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size);
|
||||
const MessageBuffer::MessageHeader dst_header(dst_msg);
|
||||
const MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header);
|
||||
|
||||
// Check that the size is big enough.
|
||||
if (MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) > dst_buffer_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the special header.
|
||||
int offset = dst_msg.Set(dst_special_header);
|
||||
|
||||
// Clear the process id, if needed.
|
||||
if (dst_special_header.GetHasProcessId()) {
|
||||
offset = dst_msg.SetProcessId(offset, 0);
|
||||
}
|
||||
|
||||
// Clear handles, as relevant.
|
||||
auto& dst_handle_table = dst_process.GetHandleTable();
|
||||
for (auto i = 0;
|
||||
i < (dst_special_header.GetCopyHandleCount() + dst_special_header.GetMoveHandleCount());
|
||||
++i) {
|
||||
const Handle handle = dst_msg.GetHandle(offset);
|
||||
|
||||
if (handle != Svc::InvalidHandle) {
|
||||
dst_handle_table.Remove(handle);
|
||||
}
|
||||
|
||||
offset = dst_msg.SetHandle(offset, Svc::InvalidHandle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
using ThreadQueueImplForKServerSessionRequest = KThreadQueue;
|
||||
|
||||
KServerSession::KServerSession(KernelCore& kernel)
|
||||
@@ -223,12 +343,27 @@ Result KServerSession::SendReply(bool is_hle) {
|
||||
// the reply has already been written in this case.
|
||||
} else {
|
||||
Core::Memory::Memory& memory{client_thread->GetOwnerProcess()->GetMemory()};
|
||||
KThread* server_thread{GetCurrentThreadPointer(m_kernel)};
|
||||
KThread* server_thread = GetCurrentThreadPointer(m_kernel);
|
||||
KProcess& src_process = *client_thread->GetOwnerProcess();
|
||||
KProcess& dst_process = *server_thread->GetOwnerProcess();
|
||||
UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
|
||||
|
||||
auto* src_msg_buffer = memory.GetPointer(server_thread->GetTlsAddress());
|
||||
auto* dst_msg_buffer = memory.GetPointer(client_message);
|
||||
auto* src_msg_buffer = memory.GetPointer<u32>(server_thread->GetTlsAddress());
|
||||
auto* dst_msg_buffer = memory.GetPointer<u32>(client_message);
|
||||
std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
|
||||
|
||||
// Translate special header ad-hoc.
|
||||
MessageBuffer src_msg(src_msg_buffer, client_buffer_size);
|
||||
MessageBuffer::MessageHeader src_header(src_msg);
|
||||
MessageBuffer::SpecialHeader src_special_header(src_msg, src_header);
|
||||
if (src_header.GetHasSpecialHeader()) {
|
||||
MessageBuffer dst_msg(dst_msg_buffer, client_buffer_size);
|
||||
result = ProcessMessageSpecialData<true>(dst_process, src_process, *server_thread,
|
||||
dst_msg, src_msg, src_special_header);
|
||||
if (R_FAILED(result)) {
|
||||
CleanupSpecialData(dst_process, dst_msg_buffer, client_buffer_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = ResultSessionClosed;
|
||||
@@ -330,12 +465,28 @@ Result KServerSession::ReceiveRequest(std::shared_ptr<Service::HLERequestContext
|
||||
->PopulateFromIncomingCommandBuffer(client_thread->GetOwnerProcess()->GetHandleTable(),
|
||||
cmd_buf);
|
||||
} else {
|
||||
KThread* server_thread{GetCurrentThreadPointer(m_kernel)};
|
||||
UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
|
||||
KThread* server_thread = GetCurrentThreadPointer(m_kernel);
|
||||
KProcess& src_process = *client_thread->GetOwnerProcess();
|
||||
KProcess& dst_process = *server_thread->GetOwnerProcess();
|
||||
UNIMPLEMENTED_IF(client_thread->GetOwnerProcess() != server_thread->GetOwnerProcess());
|
||||
|
||||
auto* src_msg_buffer = memory.GetPointer(client_message);
|
||||
auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTlsAddress());
|
||||
auto* src_msg_buffer = memory.GetPointer<u32>(client_message);
|
||||
auto* dst_msg_buffer = memory.GetPointer<u32>(server_thread->GetTlsAddress());
|
||||
std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
|
||||
|
||||
// Translate special header ad-hoc.
|
||||
// TODO: fix this mess
|
||||
MessageBuffer src_msg(src_msg_buffer, client_buffer_size);
|
||||
MessageBuffer::MessageHeader src_header(src_msg);
|
||||
MessageBuffer::SpecialHeader src_special_header(src_msg, src_header);
|
||||
if (src_header.GetHasSpecialHeader()) {
|
||||
MessageBuffer dst_msg(dst_msg_buffer, client_buffer_size);
|
||||
Result res = ProcessMessageSpecialData<false>(dst_process, src_process, *client_thread,
|
||||
dst_msg, src_msg, src_special_header);
|
||||
if (R_FAILED(res)) {
|
||||
CleanupSpecialData(dst_process, dst_msg_buffer, client_buffer_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We succeeded.
|
||||
|
612
src/core/hle/kernel/message_buffer.h
Normal file
612
src/core/hle/kernel/message_buffer.h
Normal file
@@ -0,0 +1,612 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
constexpr inline size_t MessageBufferSize = 0x100;
|
||||
|
||||
class MessageBuffer {
|
||||
public:
|
||||
class MessageHeader {
|
||||
private:
|
||||
static constexpr inline u64 NullTag = 0;
|
||||
|
||||
public:
|
||||
enum class ReceiveListCountType : u32 {
|
||||
None = 0,
|
||||
ToMessageBuffer = 1,
|
||||
ToSingleBuffer = 2,
|
||||
|
||||
CountOffset = 2,
|
||||
CountMax = 13,
|
||||
};
|
||||
|
||||
private:
|
||||
union {
|
||||
std::array<u32, 2> raw;
|
||||
|
||||
struct {
|
||||
// Define fields for the first header word.
|
||||
union {
|
||||
BitField<0, 16, u16> tag;
|
||||
BitField<16, 4, u32> pointer_count;
|
||||
BitField<20, 4, u32> send_count;
|
||||
BitField<24, 4, u32> receive_count;
|
||||
BitField<28, 4, u32> exchange_count;
|
||||
};
|
||||
|
||||
// Define fields for the second header word.
|
||||
union {
|
||||
BitField<0, 10, u32> raw_count;
|
||||
BitField<10, 4, ReceiveListCountType> receive_list_count;
|
||||
BitField<14, 6, u32> reserved0;
|
||||
BitField<20, 11, u32> receive_list_offset;
|
||||
BitField<31, 1, u32> has_special_header;
|
||||
};
|
||||
};
|
||||
} m_header;
|
||||
|
||||
public:
|
||||
constexpr MessageHeader() : m_header{} {}
|
||||
|
||||
constexpr MessageHeader(u16 tag, bool special, s32 ptr, s32 send, s32 recv, s32 exch,
|
||||
s32 raw, ReceiveListCountType recv_list)
|
||||
: m_header{} {
|
||||
m_header.raw[0] = 0;
|
||||
m_header.raw[1] = 0;
|
||||
|
||||
m_header.tag.Assign(tag);
|
||||
m_header.pointer_count.Assign(ptr);
|
||||
m_header.send_count.Assign(send);
|
||||
m_header.receive_count.Assign(recv);
|
||||
m_header.exchange_count.Assign(exch);
|
||||
|
||||
m_header.raw_count.Assign(raw);
|
||||
m_header.receive_list_count.Assign(recv_list);
|
||||
m_header.has_special_header.Assign(special);
|
||||
}
|
||||
|
||||
explicit MessageHeader(const MessageBuffer& buf) : m_header{} {
|
||||
buf.Get(0, m_header.raw.data(), 2);
|
||||
}
|
||||
|
||||
explicit MessageHeader(const u32* msg) : m_header{{msg[0], msg[1]}} {}
|
||||
|
||||
constexpr u16 GetTag() const {
|
||||
return m_header.tag;
|
||||
}
|
||||
|
||||
constexpr s32 GetPointerCount() const {
|
||||
return m_header.pointer_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetSendCount() const {
|
||||
return m_header.send_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetReceiveCount() const {
|
||||
return m_header.receive_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetExchangeCount() const {
|
||||
return m_header.exchange_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetMapAliasCount() const {
|
||||
return this->GetSendCount() + this->GetReceiveCount() + this->GetExchangeCount();
|
||||
}
|
||||
|
||||
constexpr s32 GetRawCount() const {
|
||||
return m_header.raw_count;
|
||||
}
|
||||
|
||||
constexpr ReceiveListCountType GetReceiveListCount() const {
|
||||
return m_header.receive_list_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetReceiveListOffset() const {
|
||||
return m_header.receive_list_offset;
|
||||
}
|
||||
|
||||
constexpr bool GetHasSpecialHeader() const {
|
||||
return m_header.has_special_header.Value() != 0;
|
||||
}
|
||||
|
||||
constexpr void SetReceiveListCount(ReceiveListCountType recv_list) {
|
||||
m_header.receive_list_count.Assign(recv_list);
|
||||
}
|
||||
|
||||
constexpr const u32* GetData() const {
|
||||
return m_header.raw.data();
|
||||
}
|
||||
|
||||
static constexpr size_t GetDataSize() {
|
||||
return sizeof(m_header);
|
||||
}
|
||||
};
|
||||
|
||||
class SpecialHeader {
|
||||
private:
|
||||
union {
|
||||
std::array<u32, 1> raw;
|
||||
|
||||
// Define fields for the header word.
|
||||
BitField<0, 1, u32> has_process_id;
|
||||
BitField<1, 4, u32> copy_handle_count;
|
||||
BitField<5, 4, u32> move_handle_count;
|
||||
} m_header;
|
||||
bool m_has_header;
|
||||
|
||||
public:
|
||||
constexpr explicit SpecialHeader(bool pid, s32 copy, s32 move)
|
||||
: m_header{}, m_has_header(true) {
|
||||
m_header.has_process_id.Assign(pid);
|
||||
m_header.copy_handle_count.Assign(copy);
|
||||
m_header.move_handle_count.Assign(move);
|
||||
}
|
||||
|
||||
constexpr explicit SpecialHeader(bool pid, s32 copy, s32 move, bool _has_header)
|
||||
: m_header{}, m_has_header(_has_header) {
|
||||
m_header.has_process_id.Assign(pid);
|
||||
m_header.copy_handle_count.Assign(copy);
|
||||
m_header.move_handle_count.Assign(move);
|
||||
}
|
||||
|
||||
explicit SpecialHeader(const MessageBuffer& buf, const MessageHeader& hdr)
|
||||
: m_header{}, m_has_header(hdr.GetHasSpecialHeader()) {
|
||||
if (m_has_header) {
|
||||
buf.Get(static_cast<s32>(MessageHeader::GetDataSize() / sizeof(u32)),
|
||||
m_header.raw.data(), sizeof(m_header) / sizeof(u32));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool GetHasProcessId() const {
|
||||
return m_header.has_process_id.Value() != 0;
|
||||
}
|
||||
|
||||
constexpr s32 GetCopyHandleCount() const {
|
||||
return m_header.copy_handle_count;
|
||||
}
|
||||
|
||||
constexpr s32 GetMoveHandleCount() const {
|
||||
return m_header.move_handle_count;
|
||||
}
|
||||
|
||||
constexpr const u32* GetHeader() const {
|
||||
return m_header.raw.data();
|
||||
}
|
||||
|
||||
constexpr size_t GetHeaderSize() const {
|
||||
if (m_has_header) {
|
||||
return sizeof(m_header);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t GetDataSize() const {
|
||||
if (m_has_header) {
|
||||
return (this->GetHasProcessId() ? sizeof(u64) : 0) +
|
||||
(this->GetCopyHandleCount() * sizeof(Handle)) +
|
||||
(this->GetMoveHandleCount() * sizeof(Handle));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MapAliasDescriptor {
|
||||
public:
|
||||
enum class Attribute : u32 {
|
||||
Ipc = 0,
|
||||
NonSecureIpc = 1,
|
||||
NonDeviceIpc = 3,
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr u32 SizeLowCount = 32;
|
||||
static constexpr u32 SizeHighCount = 4;
|
||||
static constexpr u32 AddressLowCount = 32;
|
||||
static constexpr u32 AddressMidCount = 4;
|
||||
|
||||
constexpr u32 GetAddressMid(u64 address) {
|
||||
return static_cast<u32>(address >> AddressLowCount) & ((1U << AddressMidCount) - 1);
|
||||
}
|
||||
|
||||
constexpr u32 GetAddressHigh(u64 address) {
|
||||
return static_cast<u32>(address >> (AddressLowCount + AddressMidCount));
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
std::array<u32, 3> raw;
|
||||
|
||||
struct {
|
||||
// Define fields for the first two words.
|
||||
u32 size_low;
|
||||
u32 address_low;
|
||||
|
||||
// Define fields for the packed descriptor word.
|
||||
union {
|
||||
BitField<0, 2, Attribute> attributes;
|
||||
BitField<2, 3, u32> address_high;
|
||||
BitField<5, 19, u32> reserved;
|
||||
BitField<24, 4, u32> size_high;
|
||||
BitField<28, 4, u32> address_mid;
|
||||
};
|
||||
};
|
||||
} m_data;
|
||||
|
||||
public:
|
||||
constexpr MapAliasDescriptor() : m_data{} {}
|
||||
|
||||
MapAliasDescriptor(const void* buffer, size_t _size, Attribute attr = Attribute::Ipc)
|
||||
: m_data{} {
|
||||
const u64 address = reinterpret_cast<u64>(buffer);
|
||||
const u64 size = static_cast<u64>(_size);
|
||||
m_data.size_low = static_cast<u32>(size);
|
||||
m_data.address_low = static_cast<u32>(address);
|
||||
m_data.attributes.Assign(attr);
|
||||
m_data.address_mid.Assign(GetAddressMid(address));
|
||||
m_data.size_high.Assign(static_cast<u32>(size >> SizeLowCount));
|
||||
m_data.address_high.Assign(GetAddressHigh(address));
|
||||
}
|
||||
|
||||
MapAliasDescriptor(const MessageBuffer& buf, s32 index) : m_data{} {
|
||||
buf.Get(index, m_data.raw.data(), 3);
|
||||
}
|
||||
|
||||
constexpr uintptr_t GetAddress() const {
|
||||
return (static_cast<u64>((m_data.address_high << AddressMidCount) | m_data.address_mid)
|
||||
<< AddressLowCount) |
|
||||
m_data.address_low;
|
||||
}
|
||||
|
||||
constexpr uintptr_t GetSize() const {
|
||||
return (static_cast<u64>(m_data.size_high) << SizeLowCount) | m_data.size_low;
|
||||
}
|
||||
|
||||
constexpr Attribute GetAttribute() const {
|
||||
return m_data.attributes;
|
||||
}
|
||||
|
||||
constexpr const u32* GetData() const {
|
||||
return m_data.raw.data();
|
||||
}
|
||||
|
||||
static constexpr size_t GetDataSize() {
|
||||
return sizeof(m_data);
|
||||
}
|
||||
};
|
||||
|
||||
class PointerDescriptor {
|
||||
private:
|
||||
static constexpr u32 AddressLowCount = 32;
|
||||
static constexpr u32 AddressMidCount = 4;
|
||||
|
||||
constexpr u32 GetAddressMid(u64 address) {
|
||||
return static_cast<u32>(address >> AddressLowCount) & ((1u << AddressMidCount) - 1);
|
||||
}
|
||||
|
||||
constexpr u32 GetAddressHigh(u64 address) {
|
||||
return static_cast<u32>(address >> (AddressLowCount + AddressMidCount));
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
std::array<u32, 2> raw;
|
||||
|
||||
struct {
|
||||
// Define fields for the packed descriptor word.
|
||||
union {
|
||||
BitField<0, 4, u32> index;
|
||||
BitField<4, 2, u32> reserved0;
|
||||
BitField<6, 3, u32> address_high;
|
||||
BitField<9, 3, u32> reserved1;
|
||||
BitField<12, 4, u32> address_mid;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
// Define fields for the second word.
|
||||
u32 address_low;
|
||||
};
|
||||
} m_data;
|
||||
|
||||
public:
|
||||
constexpr PointerDescriptor() : m_data{} {}
|
||||
|
||||
PointerDescriptor(const void* buffer, size_t size, s32 index) : m_data{} {
|
||||
const u64 address = reinterpret_cast<u64>(buffer);
|
||||
|
||||
m_data.index.Assign(index);
|
||||
m_data.address_high.Assign(GetAddressHigh(address));
|
||||
m_data.address_mid.Assign(GetAddressMid(address));
|
||||
m_data.size.Assign(static_cast<u32>(size));
|
||||
|
||||
m_data.address_low = static_cast<u32>(address);
|
||||
}
|
||||
|
||||
PointerDescriptor(const MessageBuffer& buf, s32 index) : m_data{} {
|
||||
buf.Get(index, m_data.raw.data(), 2);
|
||||
}
|
||||
|
||||
constexpr s32 GetIndex() const {
|
||||
return m_data.index;
|
||||
}
|
||||
|
||||
constexpr uintptr_t GetAddress() const {
|
||||
return (static_cast<u64>((m_data.address_high << AddressMidCount) | m_data.address_mid)
|
||||
<< AddressLowCount) |
|
||||
m_data.address_low;
|
||||
}
|
||||
|
||||
constexpr size_t GetSize() const {
|
||||
return m_data.size;
|
||||
}
|
||||
|
||||
constexpr const u32* GetData() const {
|
||||
return m_data.raw.data();
|
||||
}
|
||||
|
||||
static constexpr size_t GetDataSize() {
|
||||
return sizeof(m_data);
|
||||
}
|
||||
};
|
||||
|
||||
class ReceiveListEntry {
|
||||
private:
|
||||
static constexpr u32 AddressLowCount = 32;
|
||||
|
||||
constexpr u32 GetAddressHigh(u64 address) {
|
||||
return static_cast<u32>(address >> (AddressLowCount));
|
||||
}
|
||||
|
||||
private:
|
||||
union {
|
||||
std::array<u32, 2> raw;
|
||||
|
||||
struct {
|
||||
// Define fields for the first word.
|
||||
u32 address_low;
|
||||
|
||||
// Define fields for the packed descriptor word.
|
||||
union {
|
||||
BitField<0, 7, u32> address_high;
|
||||
BitField<7, 9, u32> reserved;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
};
|
||||
} m_data;
|
||||
|
||||
public:
|
||||
constexpr ReceiveListEntry() : m_data{} {}
|
||||
|
||||
ReceiveListEntry(const void* buffer, size_t size) : m_data{} {
|
||||
const u64 address = reinterpret_cast<u64>(buffer);
|
||||
|
||||
m_data.address_low = static_cast<u32>(address);
|
||||
|
||||
m_data.address_high.Assign(GetAddressHigh(address));
|
||||
m_data.size.Assign(static_cast<u32>(size));
|
||||
}
|
||||
|
||||
ReceiveListEntry(u32 a, u32 b) : m_data{{a, b}} {}
|
||||
|
||||
constexpr uintptr_t GetAddress() const {
|
||||
return (static_cast<u64>(m_data.address_high) << AddressLowCount) | m_data.address_low;
|
||||
}
|
||||
|
||||
constexpr size_t GetSize() const {
|
||||
return m_data.size;
|
||||
}
|
||||
|
||||
constexpr const u32* GetData() const {
|
||||
return m_data.raw.data();
|
||||
}
|
||||
|
||||
static constexpr size_t GetDataSize() {
|
||||
return sizeof(m_data);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
u32* m_buffer;
|
||||
size_t m_size;
|
||||
|
||||
public:
|
||||
constexpr MessageBuffer(u32* b, size_t sz) : m_buffer(b), m_size(sz) {}
|
||||
constexpr explicit MessageBuffer(u32* b) : m_buffer(b), m_size(MessageBufferSize) {}
|
||||
|
||||
constexpr void* GetBufferForDebug() const {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
constexpr size_t GetBufferSize() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void Get(s32 index, u32* dst, size_t count) const {
|
||||
// Ensure that this doesn't get re-ordered.
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
// Get the words.
|
||||
static_assert(sizeof(*dst) == sizeof(*m_buffer));
|
||||
|
||||
memcpy(dst, m_buffer + index, count * sizeof(*dst));
|
||||
}
|
||||
|
||||
s32 Set(s32 index, u32* src, size_t count) const {
|
||||
// Ensure that this doesn't get re-ordered.
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
// Set the words.
|
||||
memcpy(m_buffer + index, src, count * sizeof(*src));
|
||||
|
||||
// Ensure that this doesn't get re-ordered.
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
|
||||
return static_cast<s32>(index + count);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& GetRaw(s32 index) const {
|
||||
return *reinterpret_cast<const T*>(m_buffer + index);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
s32 SetRaw(s32 index, const T& val) const {
|
||||
*reinterpret_cast<const T*>(m_buffer + index) = val;
|
||||
return index + (Common::AlignUp(sizeof(val), sizeof(*m_buffer)) / sizeof(*m_buffer));
|
||||
}
|
||||
|
||||
void GetRawArray(s32 index, void* dst, size_t len) const {
|
||||
memcpy(dst, m_buffer + index, len);
|
||||
}
|
||||
|
||||
void SetRawArray(s32 index, const void* src, size_t len) const {
|
||||
memcpy(m_buffer + index, src, len);
|
||||
}
|
||||
|
||||
void SetNull() const {
|
||||
this->Set(MessageHeader());
|
||||
}
|
||||
|
||||
s32 Set(const MessageHeader& hdr) const {
|
||||
memcpy(m_buffer, hdr.GetData(), hdr.GetDataSize());
|
||||
return static_cast<s32>(hdr.GetDataSize() / sizeof(*m_buffer));
|
||||
}
|
||||
|
||||
s32 Set(const SpecialHeader& spc) const {
|
||||
const s32 index = static_cast<s32>(MessageHeader::GetDataSize() / sizeof(*m_buffer));
|
||||
memcpy(m_buffer + index, spc.GetHeader(), spc.GetHeaderSize());
|
||||
return static_cast<s32>(index + (spc.GetHeaderSize() / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 SetHandle(s32 index, const Handle& hnd) const {
|
||||
memcpy(m_buffer + index, std::addressof(hnd), sizeof(hnd));
|
||||
return static_cast<s32>(index + (sizeof(hnd) / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 SetProcessId(s32 index, const u64 pid) const {
|
||||
memcpy(m_buffer + index, std::addressof(pid), sizeof(pid));
|
||||
return static_cast<s32>(index + (sizeof(pid) / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 Set(s32 index, const MapAliasDescriptor& desc) const {
|
||||
memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize());
|
||||
return static_cast<s32>(index + (desc.GetDataSize() / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 Set(s32 index, const PointerDescriptor& desc) const {
|
||||
memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize());
|
||||
return static_cast<s32>(index + (desc.GetDataSize() / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 Set(s32 index, const ReceiveListEntry& desc) const {
|
||||
memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize());
|
||||
return static_cast<s32>(index + (desc.GetDataSize() / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
s32 Set(s32 index, const u32 val) const {
|
||||
memcpy(m_buffer + index, std::addressof(val), sizeof(val));
|
||||
return static_cast<s32>(index + (sizeof(val) / sizeof(*m_buffer)));
|
||||
}
|
||||
|
||||
Result GetAsyncResult() const {
|
||||
MessageHeader hdr(m_buffer);
|
||||
MessageHeader null{};
|
||||
if (memcmp(hdr.GetData(), null.GetData(), MessageHeader::GetDataSize()) != 0) [[unlikely]] {
|
||||
R_SUCCEED();
|
||||
}
|
||||
return Result(m_buffer[MessageHeader::GetDataSize() / sizeof(*m_buffer)]);
|
||||
}
|
||||
|
||||
void SetAsyncResult(Result res) const {
|
||||
const s32 index = this->Set(MessageHeader());
|
||||
const auto value = res.raw;
|
||||
memcpy(m_buffer + index, std::addressof(value), sizeof(value));
|
||||
}
|
||||
|
||||
u32 Get32(s32 index) const {
|
||||
return m_buffer[index];
|
||||
}
|
||||
|
||||
u64 Get64(s32 index) const {
|
||||
u64 value;
|
||||
memcpy(std::addressof(value), m_buffer + index, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
u64 GetProcessId(s32 index) const {
|
||||
return this->Get64(index);
|
||||
}
|
||||
|
||||
Handle GetHandle(s32 index) const {
|
||||
static_assert(sizeof(Handle) == sizeof(*m_buffer));
|
||||
return Handle(m_buffer[index]);
|
||||
}
|
||||
|
||||
static constexpr s32 GetSpecialDataIndex(const MessageHeader& hdr, const SpecialHeader& spc) {
|
||||
return static_cast<s32>((MessageHeader::GetDataSize() / sizeof(u32)) +
|
||||
(spc.GetHeaderSize() / sizeof(u32)));
|
||||
}
|
||||
|
||||
static constexpr s32 GetPointerDescriptorIndex(const MessageHeader& hdr,
|
||||
const SpecialHeader& spc) {
|
||||
return static_cast<s32>(GetSpecialDataIndex(hdr, spc) + (spc.GetDataSize() / sizeof(u32)));
|
||||
}
|
||||
|
||||
static constexpr s32 GetMapAliasDescriptorIndex(const MessageHeader& hdr,
|
||||
const SpecialHeader& spc) {
|
||||
return GetPointerDescriptorIndex(hdr, spc) +
|
||||
static_cast<s32>(hdr.GetPointerCount() * PointerDescriptor::GetDataSize() /
|
||||
sizeof(u32));
|
||||
}
|
||||
|
||||
static constexpr s32 GetRawDataIndex(const MessageHeader& hdr, const SpecialHeader& spc) {
|
||||
return GetMapAliasDescriptorIndex(hdr, spc) +
|
||||
static_cast<s32>(hdr.GetMapAliasCount() * MapAliasDescriptor::GetDataSize() /
|
||||
sizeof(u32));
|
||||
}
|
||||
|
||||
static constexpr s32 GetReceiveListIndex(const MessageHeader& hdr, const SpecialHeader& spc) {
|
||||
if (const s32 recv_list_index = hdr.GetReceiveListOffset()) {
|
||||
return recv_list_index;
|
||||
} else {
|
||||
return GetRawDataIndex(hdr, spc) + hdr.GetRawCount();
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t GetMessageBufferSize(const MessageHeader& hdr,
|
||||
const SpecialHeader& spc) {
|
||||
// Get the size of the plain message.
|
||||
size_t msg_size = GetReceiveListIndex(hdr, spc) * sizeof(u32);
|
||||
|
||||
// Add the size of the receive list.
|
||||
const auto count = hdr.GetReceiveListCount();
|
||||
switch (count) {
|
||||
case MessageHeader::ReceiveListCountType::None:
|
||||
break;
|
||||
case MessageHeader::ReceiveListCountType::ToMessageBuffer:
|
||||
break;
|
||||
case MessageHeader::ReceiveListCountType::ToSingleBuffer:
|
||||
msg_size += ReceiveListEntry::GetDataSize();
|
||||
break;
|
||||
default:
|
||||
msg_size += (static_cast<s32>(count) -
|
||||
static_cast<s32>(MessageHeader::ReceiveListCountType::CountOffset)) *
|
||||
ReceiveListEntry::GetDataSize();
|
||||
break;
|
||||
}
|
||||
|
||||
return msg_size;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
@@ -14,21 +14,11 @@ constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
|
||||
constexpr Result VibrationInvalidNpadId{ErrorModule::HID, 123};
|
||||
constexpr Result VibrationDeviceIndexOutOfRange{ErrorModule::HID, 124};
|
||||
constexpr Result InvalidSixAxisFusionRange{ErrorModule::HID, 423};
|
||||
|
||||
constexpr Result ResultMcuInvalidHandle{ErrorModule::HID, 541};
|
||||
constexpr Result ResultMcuNotEnabled{ErrorModule::HID, 542};
|
||||
constexpr Result ResultMcuNoDeviceConnected{ErrorModule::HID, 543};
|
||||
constexpr Result ResultMcuUnknown544{ErrorModule::HID, 544};
|
||||
constexpr Result ResultMcuUnknown546{ErrorModule::HID, 546};
|
||||
constexpr Result ResultMcuUnknown548{ErrorModule::HID, 548};
|
||||
|
||||
constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
|
||||
constexpr Result NpadIsSameType{ErrorModule::HID, 602};
|
||||
constexpr Result InvalidNpadId{ErrorModule::HID, 709};
|
||||
constexpr Result NpadNotConnected{ErrorModule::HID, 710};
|
||||
constexpr Result InvalidArraySize{ErrorModule::HID, 715};
|
||||
|
||||
constexpr Result ResultControllerUpdateFailed{ErrorModule::HID, 3201};
|
||||
constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
|
||||
|
||||
} // namespace Service::HID
|
||||
@@ -39,17 +29,3 @@ constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78};
|
||||
constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
|
||||
|
||||
} // namespace Service::IRS
|
||||
|
||||
namespace Service::HIDBUS {
|
||||
|
||||
constexpr Result ResultNotEnabled{ErrorModule::HIDBUS, 1};
|
||||
constexpr Result ResultControllerUpdateFailed{ErrorModule::HIDBUS, 3};
|
||||
constexpr Result ResultInvalidBusHandle{ErrorModule::HIDBUS, 4};
|
||||
constexpr Result ResultNoDeviceConnected{ErrorModule::HIDBUS, 5};
|
||||
constexpr Result ResultUknown6{ErrorModule::HIDBUS, 6};
|
||||
constexpr Result ResultPollingRecieveDataNotAvailable{ErrorModule::HIDBUS, 7};
|
||||
constexpr Result ResultDeviceEnabledNotSet{ErrorModule::HIDBUS, 8};
|
||||
constexpr Result ResultBusHandleAlreadyInitialized{ErrorModule::HIDBUS, 10};
|
||||
constexpr Result ResultInvalidBusType{ErrorModule::HIDBUS, 11};
|
||||
|
||||
} // namespace Service::HIDBUS
|
||||
|
@@ -10,8 +10,6 @@
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/k_shared_memory.h"
|
||||
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/errors.h"
|
||||
#include "core/hle/service/hid/hidbus.h"
|
||||
#include "core/hle/service/hid/hidbus/ringcon.h"
|
||||
#include "core/hle/service/hid/hidbus/starlink.h"
|
||||
@@ -66,7 +64,25 @@ HidBus::~HidBus() {
|
||||
system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
|
||||
}
|
||||
|
||||
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {}
|
||||
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
if (is_hidbus_enabled) {
|
||||
for (std::size_t i = 0; i < devices.size(); ++i) {
|
||||
if (!devices[i].is_device_initializated) {
|
||||
continue;
|
||||
}
|
||||
auto& device = devices[i].device;
|
||||
device->OnUpdate();
|
||||
auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index];
|
||||
cur_entry.is_polling_mode = device->IsPollingMode();
|
||||
cur_entry.polling_mode = device->GetPollingMode();
|
||||
cur_entry.is_enabled = device->IsEnabled();
|
||||
|
||||
u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer();
|
||||
std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status,
|
||||
sizeof(HidbusStatusManagerEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
|
||||
for (std::size_t i = 0; i < devices.size(); ++i) {
|
||||
@@ -92,6 +108,45 @@ void HidBus::GetBusHandle(HLERequestContext& ctx) {
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
|
||||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
|
||||
LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
|
||||
parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
|
||||
|
||||
bool is_handle_found = 0;
|
||||
std::size_t handle_index = 0;
|
||||
|
||||
for (std::size_t i = 0; i < devices.size(); i++) {
|
||||
const auto& handle = devices[i].handle;
|
||||
if (!handle.is_valid) {
|
||||
continue;
|
||||
}
|
||||
if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id &&
|
||||
handle.bus_type_id == static_cast<u8>(parameters.bus_type)) {
|
||||
is_handle_found = true;
|
||||
handle_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle not found. Create a new one
|
||||
if (!is_handle_found) {
|
||||
for (std::size_t i = 0; i < devices.size(); i++) {
|
||||
if (devices[i].handle.is_valid) {
|
||||
continue;
|
||||
}
|
||||
devices[i].handle = {
|
||||
.abstracted_pad_id = static_cast<u8>(i),
|
||||
.internal_index = static_cast<u8>(i),
|
||||
.player_number = static_cast<u8>(parameters.npad_id),
|
||||
.bus_type_id = static_cast<u8>(parameters.bus_type),
|
||||
.is_valid = true,
|
||||
};
|
||||
handle_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct OutData {
|
||||
bool is_valid;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
@@ -99,158 +154,16 @@ void HidBus::GetBusHandle(HLERequestContext& ctx) {
|
||||
};
|
||||
static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size.");
|
||||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
|
||||
LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
|
||||
parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
|
||||
|
||||
if (parameters.bus_type >= BusType::MaxBusType) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(HIDBUS::ResultInvalidBusType);
|
||||
return;
|
||||
}
|
||||
|
||||
OutData out_data{};
|
||||
out_data.is_valid = GetBusHandleImpl(out_data.handle, parameters.npad_id, parameters.bus_type);
|
||||
const OutData out_data{
|
||||
.is_valid = true,
|
||||
.handle = devices[handle_index].handle,
|
||||
};
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(out_data);
|
||||
}
|
||||
|
||||
bool HidBus::GetBusHandleImpl(BusHandle& handle, Core::HID::NpadIdType npad_id,
|
||||
BusType bus_type) const {
|
||||
if (!Controller_NPad::IsNpadIdValid(npad_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MakeBusHandle(handle, npad_id, bus_type).IsError()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bus_type == BusType::LeftJoyRail || bus_type == BusType::RightJoyRail) {
|
||||
// TODO: IsBusHandleValid(*npad_id,bus_type)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bus_type == BusType::InternalBus) {
|
||||
// TODO: IsBusHandleValid(*npad_id,BusType::RightJoyRail)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Result HidBus::MakeBusHandle(BusHandle& handle, Core::HID::NpadIdType npad_id,
|
||||
BusType bus_type) const {
|
||||
u32 abstracted_pad_id{};
|
||||
const Result result = GetAbstractedHidbusPadId(abstracted_pad_id, npad_id, bus_type);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
u8 internal_index = static_cast<u8>(npad_id);
|
||||
if (bus_type < BusType::MaxBusType) {
|
||||
internal_index = internal_index << 1 | 1;
|
||||
}
|
||||
if (bus_type == BusType::LeftJoyRail) {
|
||||
internal_index = internal_index << 1;
|
||||
}
|
||||
if (npad_id == Core::HID::NpadIdType::Handheld) {
|
||||
switch (bus_type) {
|
||||
case BusType::LeftJoyRail:
|
||||
internal_index = 0x10;
|
||||
break;
|
||||
case BusType::InternalBus:
|
||||
internal_index = 0;
|
||||
break;
|
||||
default:
|
||||
internal_index = 0x11;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handle = {
|
||||
.abstracted_pad_id = abstracted_pad_id,
|
||||
.internal_index = internal_index,
|
||||
.player_number = static_cast<u8>(npad_id) && 0xff,
|
||||
.bus_type_id = static_cast<u8>(bus_type) && 0xff,
|
||||
.is_valid = true, // This is hardcoded on the sysmodule
|
||||
};
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result HidBus::GetAbstractedHidbusPadId(u32& abstracted_pad_id, Core::HID::NpadIdType npad_id,
|
||||
BusType bus_type) const {
|
||||
// This struct is unknown
|
||||
struct LeftData {
|
||||
std::array<u8, 4> data;
|
||||
};
|
||||
|
||||
struct ControllerData {
|
||||
u32 data_1;
|
||||
u32 data_2;
|
||||
u32 is_connected; // ((d + 0x8) >> 1 & 1)
|
||||
u32 abstracted_pad_id;
|
||||
u32 data_5;
|
||||
u32 data_6;
|
||||
u32 data_7;
|
||||
u32 data_8;
|
||||
LeftData left_data;
|
||||
u32 right_data;
|
||||
u32 data_11;
|
||||
u32 data_12;
|
||||
u32 data_13;
|
||||
u32 has_battery; // ((d + 0x3a) & 6) ((d + 0x38) & 0x60000)
|
||||
u32 data_15;
|
||||
u32 data_16;
|
||||
};
|
||||
static_assert(sizeof(ControllerData) == 0x40, "ControllerData has incorrect size.");
|
||||
|
||||
// auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
const std::array<ControllerData, 5> data_pointer{}; // controller->GetControllerData(5);
|
||||
|
||||
if (data_pointer.size() == 0) {
|
||||
return ResultMcuInvalidHandle;
|
||||
}
|
||||
|
||||
for (const ControllerData& data : data_pointer) {
|
||||
bool is_valid = false;
|
||||
switch (bus_type) {
|
||||
case BusType::LeftJoyRail:
|
||||
if ((data.left_data.data[3] >> 1 & 1) != 0) {
|
||||
is_valid = true;
|
||||
}
|
||||
break;
|
||||
case BusType::RightJoyRail: {
|
||||
if (((data.right_data >> 9 & 1) == 0) && ((data.right_data >> 10 & 1) != 0)) {
|
||||
is_valid = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BusType::InternalBus:
|
||||
if ((data.right_data & 0xe00) == 0x800) {
|
||||
is_valid = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_valid) {
|
||||
abstracted_pad_id = data.abstracted_pad_id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
// STUB this function for now
|
||||
abstracted_pad_id = static_cast<u32>(npad_id);
|
||||
return ResultSuccess;
|
||||
|
||||
return ResultMcuInvalidHandle;
|
||||
}
|
||||
|
||||
void HidBus::IsExternalDeviceConnected(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto bus_handle_{rp.PopRaw<BusHandle>()};
|
||||
|
@@ -95,10 +95,6 @@ private:
|
||||
};
|
||||
|
||||
void GetBusHandle(HLERequestContext& ctx);
|
||||
bool GetBusHandleImpl(BusHandle& handle, Core::HID::NpadIdType npad_id, BusType bus_type) const;
|
||||
Result MakeBusHandle(BusHandle& handle, Core::HID::NpadIdType npad_id, BusType bus_type) const;
|
||||
Result GetAbstractedHidbusPadId(u32& abstracted_pad_id, Core::HID::NpadIdType npad_id,
|
||||
BusType bus_type) const;
|
||||
void IsExternalDeviceConnected(HLERequestContext& ctx);
|
||||
void Initialize(HLERequestContext& ctx);
|
||||
void Finalize(HLERequestContext& ctx);
|
||||
|
@@ -329,8 +329,22 @@ std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) cons
|
||||
}
|
||||
|
||||
std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
|
||||
static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_a;
|
||||
static thread_local std::array<Common::ScratchBuffer<u8>, 2> read_buffer_x;
|
||||
static thread_local std::array read_buffer_a{
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
|
||||
};
|
||||
static thread_local std::array read_buffer_data_a{
|
||||
Common::ScratchBuffer<u8>(),
|
||||
Common::ScratchBuffer<u8>(),
|
||||
};
|
||||
static thread_local std::array read_buffer_x{
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
|
||||
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
|
||||
};
|
||||
static thread_local std::array read_buffer_data_x{
|
||||
Common::ScratchBuffer<u8>(),
|
||||
Common::ScratchBuffer<u8>(),
|
||||
};
|
||||
|
||||
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
|
||||
BufferDescriptorA()[buffer_index].Size()};
|
||||
@@ -339,19 +353,17 @@ std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) cons
|
||||
BufferDescriptorA().size() > buffer_index, { return {}; },
|
||||
"BufferDescriptorA invalid buffer_index {}", buffer_index);
|
||||
auto& read_buffer = read_buffer_a[buffer_index];
|
||||
read_buffer.resize_destructive(BufferDescriptorA()[buffer_index].Size());
|
||||
memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), read_buffer.data(),
|
||||
read_buffer.size());
|
||||
return read_buffer;
|
||||
return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(),
|
||||
BufferDescriptorA()[buffer_index].Size(),
|
||||
&read_buffer_data_a[buffer_index]);
|
||||
} else {
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
BufferDescriptorX().size() > buffer_index, { return {}; },
|
||||
"BufferDescriptorX invalid buffer_index {}", buffer_index);
|
||||
auto& read_buffer = read_buffer_x[buffer_index];
|
||||
read_buffer.resize_destructive(BufferDescriptorX()[buffer_index].Size());
|
||||
memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), read_buffer.data(),
|
||||
read_buffer.size());
|
||||
return read_buffer;
|
||||
return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(),
|
||||
BufferDescriptorX()[buffer_index].Size(),
|
||||
&read_buffer_data_x[buffer_index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/nifm/nifm.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "network/network.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
@@ -4,14 +4,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include "network/network.h"
|
||||
#include "network/room.h"
|
||||
#include "network/room_member.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Network {
|
||||
class RoomNetwork;
|
||||
}
|
||||
|
||||
namespace Service::NIFM {
|
||||
|
||||
void LoopProcess(Core::System& system);
|
||||
|
@@ -20,6 +20,9 @@
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "network/network.h"
|
||||
|
||||
using Common::Expected;
|
||||
using Common::Unexpected;
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
namespace {
|
||||
@@ -265,16 +268,19 @@ void BSD::GetSockOpt(HLERequestContext& ctx) {
|
||||
const u32 level = rp.Pop<u32>();
|
||||
const auto optname = static_cast<OptName>(rp.Pop<u32>());
|
||||
|
||||
LOG_WARNING(Service, "(STUBBED) called. fd={} level={} optname=0x{:x}", fd, level, optname);
|
||||
|
||||
std::vector<u8> optval(ctx.GetWriteBufferSize());
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname,
|
||||
optval.size());
|
||||
|
||||
const Errno err = GetSockOptImpl(fd, level, optname, optval);
|
||||
|
||||
ctx.WriteBuffer(optval);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(-1);
|
||||
rb.PushEnum(Errno::NOTCONN);
|
||||
rb.Push<s32>(err == Errno::SUCCESS ? 0 : -1);
|
||||
rb.PushEnum(err);
|
||||
rb.Push<u32>(static_cast<u32>(optval.size()));
|
||||
}
|
||||
|
||||
@@ -436,6 +442,31 @@ void BSD::Close(HLERequestContext& ctx) {
|
||||
BuildErrnoResponse(ctx, CloseImpl(fd));
|
||||
}
|
||||
|
||||
void BSD::DuplicateSocket(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
s32 fd;
|
||||
u64 reserved;
|
||||
};
|
||||
static_assert(sizeof(InputParameters) == 0x10);
|
||||
|
||||
struct OutputParameters {
|
||||
s32 ret;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x8);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto input = rp.PopRaw<InputParameters>();
|
||||
|
||||
Expected<s32, Errno> res = DuplicateSocketImpl(input.fd);
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.ret = res.value_or(0),
|
||||
.bsd_errno = res ? Errno::SUCCESS : res.error(),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::EventFd(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 initval = rp.Pop<u64>();
|
||||
@@ -477,12 +508,12 @@ std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protoco
|
||||
|
||||
auto room_member = room_network.GetRoomMember().lock();
|
||||
if (room_member && room_member->IsConnected()) {
|
||||
descriptor.socket = std::make_unique<Network::ProxySocket>(room_network);
|
||||
descriptor.socket = std::make_shared<Network::ProxySocket>(room_network);
|
||||
} else {
|
||||
descriptor.socket = std::make_unique<Network::Socket>();
|
||||
descriptor.socket = std::make_shared<Network::Socket>();
|
||||
}
|
||||
|
||||
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
|
||||
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(protocol));
|
||||
descriptor.is_connection_based = IsConnectionBased(type);
|
||||
|
||||
return {fd, Errno::SUCCESS};
|
||||
@@ -538,7 +569,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
|
||||
Network::PollFD result;
|
||||
result.socket = file_descriptors[pollfd.fd]->socket.get();
|
||||
result.events = TranslatePollEventsToHost(pollfd.events);
|
||||
result.events = Translate(pollfd.events);
|
||||
result.revents = Network::PollEvents{};
|
||||
return result;
|
||||
});
|
||||
@@ -547,7 +578,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::span<con
|
||||
|
||||
const size_t num = host_pollfds.size();
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents);
|
||||
fds[i].revents = Translate(host_pollfds[i].revents);
|
||||
}
|
||||
std::memcpy(write_buffer.data(), fds.data(), length);
|
||||
|
||||
@@ -617,7 +648,8 @@ Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
ASSERT(write_buffer.size() == sizeof(guest_addrin));
|
||||
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
|
||||
write_buffer.resize(sizeof(guest_addrin));
|
||||
std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
@@ -633,7 +665,8 @@ Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
ASSERT(write_buffer.size() == sizeof(guest_addrin));
|
||||
ASSERT(write_buffer.size() >= sizeof(guest_addrin));
|
||||
write_buffer.resize(sizeof(guest_addrin));
|
||||
std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
@@ -671,13 +704,47 @@ std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
|
||||
}
|
||||
}
|
||||
|
||||
Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
|
||||
UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET
|
||||
|
||||
Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& optval) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||
UNIMPLEMENTED_MSG("Unknown getsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||
|
||||
switch (optname) {
|
||||
case OptName::ERROR_: {
|
||||
auto [pending_err, getsockopt_err] = socket->GetPendingError();
|
||||
if (getsockopt_err == Network::Errno::SUCCESS) {
|
||||
Errno translated_pending_err = Translate(pending_err);
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
optval.size() == sizeof(Errno), { return Errno::INVAL; },
|
||||
"Incorrect getsockopt option size");
|
||||
optval.resize(sizeof(Errno));
|
||||
memcpy(optval.data(), &translated_pending_err, sizeof(Errno));
|
||||
}
|
||||
return Translate(getsockopt_err);
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
if (level != static_cast<u32>(SocketLevel::SOCKET)) {
|
||||
UNIMPLEMENTED_MSG("Unknown setsockopt level");
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
Network::SocketBase* const socket = file_descriptors[fd]->socket.get();
|
||||
|
||||
if (optname == OptName::LINGER) {
|
||||
@@ -711,6 +778,9 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con
|
||||
return Translate(socket->SetSndTimeo(value));
|
||||
case OptName::RCVTIMEO:
|
||||
return Translate(socket->SetRcvTimeo(value));
|
||||
case OptName::NOSIGPIPE:
|
||||
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
||||
return Errno::SUCCESS;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", optname);
|
||||
return Errno::SUCCESS;
|
||||
@@ -841,6 +911,28 @@ Errno BSD::CloseImpl(s32 fd) {
|
||||
return bsd_errno;
|
||||
}
|
||||
|
||||
Expected<s32, Errno> BSD::DuplicateSocketImpl(s32 fd) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Unexpected(Errno::BADF);
|
||||
}
|
||||
|
||||
const s32 new_fd = FindFreeFileDescriptorHandle();
|
||||
if (new_fd < 0) {
|
||||
LOG_ERROR(Service, "No more file descriptors available");
|
||||
return Unexpected(Errno::MFILE);
|
||||
}
|
||||
|
||||
file_descriptors[new_fd] = file_descriptors[fd];
|
||||
return new_fd;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> BSD::GetSocket(s32 fd) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return file_descriptors[fd]->socket;
|
||||
}
|
||||
|
||||
s32 BSD::FindFreeFileDescriptorHandle() noexcept {
|
||||
for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) {
|
||||
if (!file_descriptors[fd]) {
|
||||
@@ -911,7 +1003,7 @@ BSD::BSD(Core::System& system_, const char* name)
|
||||
{24, &BSD::Write, "Write"},
|
||||
{25, &BSD::Read, "Read"},
|
||||
{26, &BSD::Close, "Close"},
|
||||
{27, nullptr, "DuplicateSocket"},
|
||||
{27, &BSD::DuplicateSocket, "DuplicateSocket"},
|
||||
{28, nullptr, "GetResourceStatistics"},
|
||||
{29, nullptr, "RecvMMsg"},
|
||||
{30, nullptr, "SendMMsg"},
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/expected.h"
|
||||
#include "common/socket_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
@@ -29,12 +30,19 @@ public:
|
||||
explicit BSD(Core::System& system_, const char* name);
|
||||
~BSD() override;
|
||||
|
||||
// These methods are called from SSL; the first two are also called from
|
||||
// this class for the corresponding IPC methods.
|
||||
// On the real device, the SSL service makes IPC calls to this service.
|
||||
Common::Expected<s32, Errno> DuplicateSocketImpl(s32 fd);
|
||||
Errno CloseImpl(s32 fd);
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> GetSocket(s32 fd);
|
||||
|
||||
private:
|
||||
/// Maximum number of file descriptors
|
||||
static constexpr size_t MAX_FD = 128;
|
||||
|
||||
struct FileDescriptor {
|
||||
std::unique_ptr<Network::SocketBase> socket;
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
s32 flags = 0;
|
||||
bool is_connection_based = false;
|
||||
};
|
||||
@@ -138,6 +146,7 @@ private:
|
||||
void Write(HLERequestContext& ctx);
|
||||
void Read(HLERequestContext& ctx);
|
||||
void Close(HLERequestContext& ctx);
|
||||
void DuplicateSocket(HLERequestContext& ctx);
|
||||
void EventFd(HLERequestContext& ctx);
|
||||
|
||||
template <typename Work>
|
||||
@@ -153,6 +162,7 @@ private:
|
||||
Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
|
||||
Errno ListenImpl(s32 fd, s32 backlog);
|
||||
std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
|
||||
Errno GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector<u8>& optval);
|
||||
Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval);
|
||||
Errno ShutdownImpl(s32 fd, s32 how);
|
||||
std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
|
||||
@@ -161,7 +171,6 @@ private:
|
||||
std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, std::span<const u8> message);
|
||||
std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
|
||||
std::span<const u8> addr);
|
||||
Errno CloseImpl(s32 fd);
|
||||
|
||||
s32 FindFreeFileDescriptorHandle() noexcept;
|
||||
bool IsFileDescriptorValid(s32 fd) const noexcept;
|
||||
|
@@ -1,10 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/sockets/nsd.h"
|
||||
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
constexpr Result ResultOverflow{ErrorModule::NSD, 6};
|
||||
|
||||
NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -15,8 +20,8 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
{13, nullptr, "DeleteSettings"},
|
||||
{14, nullptr, "ImportSettings"},
|
||||
{15, nullptr, "SetChangeEnvironmentIdentifierDisabled"},
|
||||
{20, nullptr, "Resolve"},
|
||||
{21, nullptr, "ResolveEx"},
|
||||
{20, &NSD::Resolve, "Resolve"},
|
||||
{21, &NSD::ResolveEx, "ResolveEx"},
|
||||
{30, nullptr, "GetNasServiceSetting"},
|
||||
{31, nullptr, "GetNasServiceSettingEx"},
|
||||
{40, nullptr, "GetNasRequestFqdn"},
|
||||
@@ -40,6 +45,55 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) {
|
||||
// The real implementation makes various substitutions.
|
||||
// For now we just return the string as-is, which is good enough when not
|
||||
// connecting to real Nintendo servers.
|
||||
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
|
||||
return fqdn_in;
|
||||
}
|
||||
|
||||
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
|
||||
const auto res = ResolveImpl(fqdn_in);
|
||||
if (res.Failed()) {
|
||||
return res.Code();
|
||||
}
|
||||
if (res->size() >= fqdn_out.size()) {
|
||||
return ResultOverflow;
|
||||
}
|
||||
std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void NSD::Resolve(HLERequestContext& ctx) {
|
||||
const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0));
|
||||
|
||||
std::array<char, 0x100> fqdn_out{};
|
||||
const Result res = ResolveCommon(fqdn_in, fqdn_out);
|
||||
|
||||
ctx.WriteBuffer(fqdn_out);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void NSD::ResolveEx(HLERequestContext& ctx) {
|
||||
const std::string fqdn_in = Common::StringFromBuffer(ctx.ReadBuffer(0));
|
||||
|
||||
std::array<char, 0x100> fqdn_out;
|
||||
const Result res = ResolveCommon(fqdn_in, fqdn_out);
|
||||
|
||||
if (res.IsError()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(fqdn_out);
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
NSD::~NSD() = default;
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
@@ -15,6 +15,10 @@ class NSD final : public ServiceFramework<NSD> {
|
||||
public:
|
||||
explicit NSD(Core::System& system_, const char* name);
|
||||
~NSD() override;
|
||||
|
||||
private:
|
||||
void Resolve(HLERequestContext& ctx);
|
||||
void ResolveEx(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
@@ -10,27 +10,18 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/sockets/sfdnsres.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/hle/service/sockets/sockets_translate.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#elif YUZU_UNIX
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#ifndef EAI_NODATA
|
||||
#define EAI_NODATA EAI_NONAME
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SetDnsAddressesPrivateRequest"},
|
||||
{1, nullptr, "GetDnsAddressPrivateRequest"},
|
||||
{2, nullptr, "GetHostByNameRequest"},
|
||||
{2, &SFDNSRES::GetHostByNameRequest, "GetHostByNameRequest"},
|
||||
{3, nullptr, "GetHostByAddrRequest"},
|
||||
{4, nullptr, "GetHostStringErrorRequest"},
|
||||
{5, nullptr, "GetGaiStringErrorRequest"},
|
||||
@@ -38,11 +29,11 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
|
||||
{7, nullptr, "GetNameInfoRequest"},
|
||||
{8, nullptr, "RequestCancelHandleRequest"},
|
||||
{9, nullptr, "CancelRequest"},
|
||||
{10, nullptr, "GetHostByNameRequestWithOptions"},
|
||||
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
||||
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
||||
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
||||
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
||||
{14, nullptr, "ResolverSetOptionRequest"},
|
||||
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
||||
{15, nullptr, "ResolverGetOptionRequest"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -59,188 +50,285 @@ enum class NetDbError : s32 {
|
||||
NoData = 4,
|
||||
};
|
||||
|
||||
static NetDbError AddrInfoErrorToNetDbError(s32 result) {
|
||||
// Best effort guess to map errors
|
||||
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
switch (result) {
|
||||
case 0:
|
||||
case GetAddrInfoError::SUCCESS:
|
||||
return NetDbError::Success;
|
||||
case EAI_AGAIN:
|
||||
case GetAddrInfoError::AGAIN:
|
||||
return NetDbError::TryAgain;
|
||||
case EAI_NODATA:
|
||||
return NetDbError::NoData;
|
||||
case GetAddrInfoError::NODATA:
|
||||
return NetDbError::HostNotFound;
|
||||
case GetAddrInfoError::SERVICE:
|
||||
return NetDbError::Success;
|
||||
default:
|
||||
return NetDbError::HostNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<u8> SerializeAddrInfo(const addrinfo* addrinfo, s32 result_code,
|
||||
static Errno GetAddrInfoErrorToErrno(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
switch (result) {
|
||||
case GetAddrInfoError::SUCCESS:
|
||||
// Note: Sometimes a successful lookup sets errno to EADDRNOTAVAIL for
|
||||
// some reason, but that doesn't seem useful to implement.
|
||||
return Errno::SUCCESS;
|
||||
case GetAddrInfoError::AGAIN:
|
||||
return Errno::SUCCESS;
|
||||
case GetAddrInfoError::NODATA:
|
||||
return Errno::SUCCESS;
|
||||
case GetAddrInfoError::SERVICE:
|
||||
return Errno::INVAL;
|
||||
default:
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void Append(std::vector<u8>& vec, T t) {
|
||||
const size_t offset = vec.size();
|
||||
vec.resize(offset + sizeof(T));
|
||||
std::memcpy(vec.data() + offset, &t, sizeof(T));
|
||||
}
|
||||
|
||||
static void AppendNulTerminated(std::vector<u8>& vec, std::string_view str) {
|
||||
const size_t offset = vec.size();
|
||||
vec.resize(offset + str.size() + 1);
|
||||
std::memmove(vec.data() + offset, str.data(), str.size());
|
||||
}
|
||||
|
||||
// We implement gethostbyname using the host's getaddrinfo rather than the
|
||||
// host's gethostbyname, because it simplifies portability: e.g., getaddrinfo
|
||||
// behaves the same on Unix and Windows, unlike gethostbyname where Windows
|
||||
// doesn't implement h_errno.
|
||||
static std::vector<u8> SerializeAddrInfoAsHostEnt(const std::vector<Network::AddrInfo>& vec,
|
||||
std::string_view host) {
|
||||
|
||||
std::vector<u8> data;
|
||||
// h_name: use the input hostname (append nul-terminated)
|
||||
AppendNulTerminated(data, host);
|
||||
// h_aliases: leave empty
|
||||
|
||||
Append<u32_be>(data, 0); // count of h_aliases
|
||||
// (If the count were nonzero, the aliases would be appended as nul-terminated here.)
|
||||
Append<u16_be>(data, static_cast<u16>(Domain::INET)); // h_addrtype
|
||||
Append<u16_be>(data, sizeof(Network::IPv4Address)); // h_length
|
||||
// h_addr_list:
|
||||
size_t count = vec.size();
|
||||
ASSERT(count <= UINT32_MAX);
|
||||
Append<u32_be>(data, static_cast<uint32_t>(count));
|
||||
for (const Network::AddrInfo& addrinfo : vec) {
|
||||
// On the Switch, this is passed through htonl despite already being
|
||||
// big-endian, so it ends up as little-endian.
|
||||
Append<u32_le>(data, Network::IPv4AddressToInteger(addrinfo.addr.ip));
|
||||
|
||||
LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host,
|
||||
Network::IPv4AddressToString(addrinfo.addr.ip));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u8 use_nsd_resolve;
|
||||
u32 cancel_handle;
|
||||
u64 process_id;
|
||||
};
|
||||
static_assert(sizeof(InputParameters) == 0x10);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_WARNING(
|
||||
Service,
|
||||
"called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}",
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
||||
|
||||
auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt);
|
||||
if (!res.has_value()) {
|
||||
return {0, Translate(res.error())};
|
||||
}
|
||||
|
||||
const std::vector<u8> data = SerializeAddrInfoAsHostEnt(res.value(), host);
|
||||
const u32 data_size = static_cast<u32>(data.size());
|
||||
ctx.WriteBuffer(data, 0);
|
||||
|
||||
return {data_size, GetAddrInfoError::SUCCESS};
|
||||
}
|
||||
|
||||
void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) {
|
||||
auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx);
|
||||
|
||||
struct OutputParameters {
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
u32 data_size;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0xc);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
||||
.data_size = data_size,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetHostByNameRequestWithOptions(HLERequestContext& ctx) {
|
||||
auto [data_size, emu_gai_err] = GetHostByNameRequestImpl(ctx);
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0xc);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
||||
});
|
||||
}
|
||||
|
||||
static std::vector<u8> SerializeAddrInfo(const std::vector<Network::AddrInfo>& vec,
|
||||
std::string_view host) {
|
||||
// Adapted from
|
||||
// https://github.com/switchbrew/libnx/blob/c5a9a909a91657a9818a3b7e18c9b91ff0cbb6e3/nx/source/runtime/resolver.c#L190
|
||||
std::vector<u8> data;
|
||||
|
||||
auto* current = addrinfo;
|
||||
while (current != nullptr) {
|
||||
struct SerializedResponseHeader {
|
||||
u32 magic;
|
||||
s32 flags;
|
||||
s32 family;
|
||||
s32 socket_type;
|
||||
s32 protocol;
|
||||
u32 address_length;
|
||||
};
|
||||
static_assert(sizeof(SerializedResponseHeader) == 0x18,
|
||||
"Response header size must be 0x18 bytes");
|
||||
for (const Network::AddrInfo& addrinfo : vec) {
|
||||
// serialized addrinfo:
|
||||
Append<u32_be>(data, 0xBEEFCAFE); // magic
|
||||
Append<u32_be>(data, 0); // ai_flags
|
||||
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.family))); // ai_family
|
||||
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.socket_type))); // ai_socktype
|
||||
Append<u32_be>(data, static_cast<u32>(Translate(addrinfo.protocol))); // ai_protocol
|
||||
Append<u32_be>(data, sizeof(SockAddrIn)); // ai_addrlen
|
||||
// ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size
|
||||
|
||||
constexpr auto header_size = sizeof(SerializedResponseHeader);
|
||||
const auto addr_size =
|
||||
current->ai_addr && current->ai_addrlen > 0 ? current->ai_addrlen : 4;
|
||||
const auto canonname_size = current->ai_canonname ? strlen(current->ai_canonname) + 1 : 1;
|
||||
// ai_addr:
|
||||
Append<u16_be>(data, static_cast<u16>(Translate(addrinfo.addr.family))); // sin_family
|
||||
// On the Switch, the following fields are passed through htonl despite
|
||||
// already being big-endian, so they end up as little-endian.
|
||||
Append<u16_le>(data, addrinfo.addr.portno); // sin_port
|
||||
Append<u32_le>(data, Network::IPv4AddressToInteger(addrinfo.addr.ip)); // sin_addr
|
||||
data.resize(data.size() + 8, 0); // sin_zero
|
||||
|
||||
const auto last_size = data.size();
|
||||
data.resize(last_size + header_size + addr_size + canonname_size);
|
||||
|
||||
// Header in network byte order
|
||||
SerializedResponseHeader header{};
|
||||
|
||||
constexpr auto HEADER_MAGIC = 0xBEEFCAFE;
|
||||
header.magic = htonl(HEADER_MAGIC);
|
||||
header.family = htonl(current->ai_family);
|
||||
header.flags = htonl(current->ai_flags);
|
||||
header.socket_type = htonl(current->ai_socktype);
|
||||
header.protocol = htonl(current->ai_protocol);
|
||||
header.address_length = current->ai_addr ? htonl((u32)current->ai_addrlen) : 0;
|
||||
|
||||
auto* header_ptr = data.data() + last_size;
|
||||
std::memcpy(header_ptr, &header, header_size);
|
||||
|
||||
if (header.address_length == 0) {
|
||||
std::memset(header_ptr + header_size, 0, 4);
|
||||
if (addrinfo.canon_name.has_value()) {
|
||||
AppendNulTerminated(data, *addrinfo.canon_name);
|
||||
} else {
|
||||
switch (current->ai_family) {
|
||||
case AF_INET: {
|
||||
struct SockAddrIn {
|
||||
s16 sin_family;
|
||||
u16 sin_port;
|
||||
u32 sin_addr;
|
||||
u8 sin_zero[8];
|
||||
};
|
||||
|
||||
SockAddrIn serialized_addr{};
|
||||
const auto addr = *reinterpret_cast<sockaddr_in*>(current->ai_addr);
|
||||
serialized_addr.sin_port = htons(addr.sin_port);
|
||||
serialized_addr.sin_family = htons(addr.sin_family);
|
||||
serialized_addr.sin_addr = htonl(addr.sin_addr.s_addr);
|
||||
std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn));
|
||||
|
||||
char addr_string_buf[64]{};
|
||||
inet_ntop(AF_INET, &addr.sin_addr, addr_string_buf, std::size(addr_string_buf));
|
||||
LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host, addr_string_buf);
|
||||
break;
|
||||
}
|
||||
case AF_INET6: {
|
||||
struct SockAddrIn6 {
|
||||
s16 sin6_family;
|
||||
u16 sin6_port;
|
||||
u32 sin6_flowinfo;
|
||||
u8 sin6_addr[16];
|
||||
u32 sin6_scope_id;
|
||||
};
|
||||
|
||||
SockAddrIn6 serialized_addr{};
|
||||
const auto addr = *reinterpret_cast<sockaddr_in6*>(current->ai_addr);
|
||||
serialized_addr.sin6_family = htons(addr.sin6_family);
|
||||
serialized_addr.sin6_port = htons(addr.sin6_port);
|
||||
serialized_addr.sin6_flowinfo = htonl(addr.sin6_flowinfo);
|
||||
serialized_addr.sin6_scope_id = htonl(addr.sin6_scope_id);
|
||||
std::memcpy(serialized_addr.sin6_addr, &addr.sin6_addr,
|
||||
sizeof(SockAddrIn6::sin6_addr));
|
||||
std::memcpy(header_ptr + header_size, &serialized_addr, sizeof(SockAddrIn6));
|
||||
|
||||
char addr_string_buf[64]{};
|
||||
inet_ntop(AF_INET6, &addr.sin6_addr, addr_string_buf, std::size(addr_string_buf));
|
||||
LOG_INFO(Service, "Resolved host '{}' to IPv6 address {}", host, addr_string_buf);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::memcpy(header_ptr + header_size, current->ai_addr, addr_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current->ai_canonname) {
|
||||
std::memcpy(header_ptr + addr_size, current->ai_canonname, canonname_size);
|
||||
} else {
|
||||
*(header_ptr + header_size + addr_size) = 0;
|
||||
data.push_back(0);
|
||||
}
|
||||
|
||||
current = current->ai_next;
|
||||
LOG_INFO(Service, "Resolved host '{}' to IPv4 address {}", host,
|
||||
Network::IPv4AddressToString(addrinfo.addr.ip));
|
||||
}
|
||||
|
||||
// 4-byte sentinel value
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
data.push_back(0);
|
||||
data.resize(data.size() + 4, 0); // 4-byte sentinel value
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static std::pair<u32, s32> GetAddrInfoRequestImpl(HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u8 use_nsd_resolve;
|
||||
u32 unknown;
|
||||
u32 cancel_handle;
|
||||
u64 process_id;
|
||||
};
|
||||
static_assert(sizeof(InputParameters) == 0x10);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
const auto parameters = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_WARNING(Service,
|
||||
"called with ignored parameters: use_nsd_resolve={}, unknown={}, process_id={}",
|
||||
parameters.use_nsd_resolve, parameters.unknown, parameters.process_id);
|
||||
LOG_WARNING(
|
||||
Service,
|
||||
"called with ignored parameters: use_nsd_resolve={}, cancel_handle={}, process_id={}",
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
// TODO: If use_nsd_resolve is true, pass the name through NSD::Resolve
|
||||
// before looking up.
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
|
||||
const auto service_buffer = ctx.ReadBuffer(1);
|
||||
const std::string service = Common::StringFromBuffer(service_buffer);
|
||||
|
||||
addrinfo* addrinfo;
|
||||
// Pass null for hints. Serialized hints are also passed in a buffer, but are ignored for now
|
||||
s32 result_code = getaddrinfo(host.c_str(), service.c_str(), nullptr, &addrinfo);
|
||||
|
||||
u32 data_size = 0;
|
||||
if (result_code == 0 && addrinfo != nullptr) {
|
||||
const std::vector<u8>& data = SerializeAddrInfo(addrinfo, result_code, host);
|
||||
data_size = static_cast<u32>(data.size());
|
||||
freeaddrinfo(addrinfo);
|
||||
|
||||
ctx.WriteBuffer(data, 0);
|
||||
std::optional<std::string> service = std::nullopt;
|
||||
if (ctx.CanReadBuffer(1)) {
|
||||
const std::span<const u8> service_buffer = ctx.ReadBuffer(1);
|
||||
service = Common::StringFromBuffer(service_buffer);
|
||||
}
|
||||
|
||||
return std::make_pair(data_size, result_code);
|
||||
// Serialized hints are also passed in a buffer, but are ignored for now.
|
||||
|
||||
auto res = Network::GetAddressInfo(host, service);
|
||||
if (!res.has_value()) {
|
||||
return {0, Translate(res.error())};
|
||||
}
|
||||
|
||||
const std::vector<u8> data = SerializeAddrInfo(res.value(), host);
|
||||
const u32 data_size = static_cast<u32>(data.size());
|
||||
ctx.WriteBuffer(data, 0);
|
||||
|
||||
return {data_size, GetAddrInfoError::SUCCESS};
|
||||
}
|
||||
|
||||
void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) {
|
||||
auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx);
|
||||
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
struct OutputParameters {
|
||||
Errno bsd_errno;
|
||||
GetAddrInfoError gai_error;
|
||||
u32 data_size;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0xc);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode
|
||||
rb.Push(result_code); // errno
|
||||
rb.Push(data_size); // serialized size
|
||||
rb.PushRaw(OutputParameters{
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
||||
.gai_error = emu_gai_err,
|
||||
.data_size = data_size,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
// Additional options are ignored
|
||||
auto [data_size, result_code] = GetAddrInfoRequestImpl(ctx);
|
||||
auto [data_size, emu_gai_err] = GetAddrInfoRequestImpl(ctx);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(data_size); // serialized size
|
||||
rb.Push(result_code); // errno
|
||||
rb.Push(static_cast<s32>(AddrInfoErrorToNetDbError(result_code))); // NetDBErrorCode
|
||||
rb.Push(0);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = emu_gai_err,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(emu_gai_err),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(emu_gai_err),
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
@@ -17,8 +17,11 @@ public:
|
||||
~SFDNSRES() override;
|
||||
|
||||
private:
|
||||
void GetHostByNameRequest(HLERequestContext& ctx);
|
||||
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequest(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
@@ -22,13 +22,35 @@ enum class Errno : u32 {
|
||||
CONNRESET = 104,
|
||||
NOTCONN = 107,
|
||||
TIMEDOUT = 110,
|
||||
INPROGRESS = 115,
|
||||
};
|
||||
|
||||
enum class GetAddrInfoError : s32 {
|
||||
SUCCESS = 0,
|
||||
ADDRFAMILY = 1,
|
||||
AGAIN = 2,
|
||||
BADFLAGS = 3,
|
||||
FAIL = 4,
|
||||
FAMILY = 5,
|
||||
MEMORY = 6,
|
||||
NODATA = 7,
|
||||
NONAME = 8,
|
||||
SERVICE = 9,
|
||||
SOCKTYPE = 10,
|
||||
SYSTEM = 11,
|
||||
BADHINTS = 12,
|
||||
PROTOCOL = 13,
|
||||
OVERFLOW_ = 14, // avoid name collision with Windows macro
|
||||
OTHER = 15,
|
||||
};
|
||||
|
||||
enum class Domain : u32 {
|
||||
Unspecified = 0,
|
||||
INET = 2,
|
||||
};
|
||||
|
||||
enum class Type : u32 {
|
||||
Unspecified = 0,
|
||||
STREAM = 1,
|
||||
DGRAM = 2,
|
||||
RAW = 3,
|
||||
@@ -36,12 +58,16 @@ enum class Type : u32 {
|
||||
};
|
||||
|
||||
enum class Protocol : u32 {
|
||||
UNSPECIFIED = 0,
|
||||
Unspecified = 0,
|
||||
ICMP = 1,
|
||||
TCP = 6,
|
||||
UDP = 17,
|
||||
};
|
||||
|
||||
enum class SocketLevel : u32 {
|
||||
SOCKET = 0xffff, // i.e. SOL_SOCKET
|
||||
};
|
||||
|
||||
enum class OptName : u32 {
|
||||
REUSEADDR = 0x4,
|
||||
KEEPALIVE = 0x8,
|
||||
@@ -51,6 +77,8 @@ enum class OptName : u32 {
|
||||
RCVBUF = 0x1002,
|
||||
SNDTIMEO = 0x1005,
|
||||
RCVTIMEO = 0x1006,
|
||||
ERROR_ = 0x1007, // avoid name collision with Windows macro
|
||||
NOSIGPIPE = 0x800, // at least according to libnx
|
||||
};
|
||||
|
||||
enum class ShutdownHow : s32 {
|
||||
@@ -80,6 +108,9 @@ enum class PollEvents : u16 {
|
||||
Err = 1 << 3,
|
||||
Hup = 1 << 4,
|
||||
Nval = 1 << 5,
|
||||
RdNorm = 1 << 6,
|
||||
RdBand = 1 << 7,
|
||||
WrBand = 1 << 8,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
|
||||
|
@@ -29,6 +29,8 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::TIMEDOUT;
|
||||
case Network::Errno::CONNRESET:
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||
return Errno::SUCCESS;
|
||||
@@ -39,8 +41,50 @@ std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
|
||||
return {value.first, Translate(value.second)};
|
||||
}
|
||||
|
||||
GetAddrInfoError Translate(Network::GetAddrInfoError error) {
|
||||
switch (error) {
|
||||
case Network::GetAddrInfoError::SUCCESS:
|
||||
return GetAddrInfoError::SUCCESS;
|
||||
case Network::GetAddrInfoError::ADDRFAMILY:
|
||||
return GetAddrInfoError::ADDRFAMILY;
|
||||
case Network::GetAddrInfoError::AGAIN:
|
||||
return GetAddrInfoError::AGAIN;
|
||||
case Network::GetAddrInfoError::BADFLAGS:
|
||||
return GetAddrInfoError::BADFLAGS;
|
||||
case Network::GetAddrInfoError::FAIL:
|
||||
return GetAddrInfoError::FAIL;
|
||||
case Network::GetAddrInfoError::FAMILY:
|
||||
return GetAddrInfoError::FAMILY;
|
||||
case Network::GetAddrInfoError::MEMORY:
|
||||
return GetAddrInfoError::MEMORY;
|
||||
case Network::GetAddrInfoError::NODATA:
|
||||
return GetAddrInfoError::NODATA;
|
||||
case Network::GetAddrInfoError::NONAME:
|
||||
return GetAddrInfoError::NONAME;
|
||||
case Network::GetAddrInfoError::SERVICE:
|
||||
return GetAddrInfoError::SERVICE;
|
||||
case Network::GetAddrInfoError::SOCKTYPE:
|
||||
return GetAddrInfoError::SOCKTYPE;
|
||||
case Network::GetAddrInfoError::SYSTEM:
|
||||
return GetAddrInfoError::SYSTEM;
|
||||
case Network::GetAddrInfoError::BADHINTS:
|
||||
return GetAddrInfoError::BADHINTS;
|
||||
case Network::GetAddrInfoError::PROTOCOL:
|
||||
return GetAddrInfoError::PROTOCOL;
|
||||
case Network::GetAddrInfoError::OVERFLOW_:
|
||||
return GetAddrInfoError::OVERFLOW_;
|
||||
case Network::GetAddrInfoError::OTHER:
|
||||
return GetAddrInfoError::OTHER;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented GetAddrInfoError={}", error);
|
||||
return GetAddrInfoError::OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
Network::Domain Translate(Domain domain) {
|
||||
switch (domain) {
|
||||
case Domain::Unspecified:
|
||||
return Network::Domain::Unspecified;
|
||||
case Domain::INET:
|
||||
return Network::Domain::INET;
|
||||
default:
|
||||
@@ -51,6 +95,8 @@ Network::Domain Translate(Domain domain) {
|
||||
|
||||
Domain Translate(Network::Domain domain) {
|
||||
switch (domain) {
|
||||
case Network::Domain::Unspecified:
|
||||
return Domain::Unspecified;
|
||||
case Network::Domain::INET:
|
||||
return Domain::INET;
|
||||
default:
|
||||
@@ -61,39 +107,69 @@ Domain Translate(Network::Domain domain) {
|
||||
|
||||
Network::Type Translate(Type type) {
|
||||
switch (type) {
|
||||
case Type::Unspecified:
|
||||
return Network::Type::Unspecified;
|
||||
case Type::STREAM:
|
||||
return Network::Type::STREAM;
|
||||
case Type::DGRAM:
|
||||
return Network::Type::DGRAM;
|
||||
case Type::RAW:
|
||||
return Network::Type::RAW;
|
||||
case Type::SEQPACKET:
|
||||
return Network::Type::SEQPACKET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
|
||||
return Network::Type{};
|
||||
}
|
||||
}
|
||||
|
||||
Network::Protocol Translate(Type type, Protocol protocol) {
|
||||
Type Translate(Network::Type type) {
|
||||
switch (type) {
|
||||
case Network::Type::Unspecified:
|
||||
return Type::Unspecified;
|
||||
case Network::Type::STREAM:
|
||||
return Type::STREAM;
|
||||
case Network::Type::DGRAM:
|
||||
return Type::DGRAM;
|
||||
case Network::Type::RAW:
|
||||
return Type::RAW;
|
||||
case Network::Type::SEQPACKET:
|
||||
return Type::SEQPACKET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
|
||||
return Type{};
|
||||
}
|
||||
}
|
||||
|
||||
Network::Protocol Translate(Protocol protocol) {
|
||||
switch (protocol) {
|
||||
case Protocol::UNSPECIFIED:
|
||||
LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type");
|
||||
switch (type) {
|
||||
case Type::DGRAM:
|
||||
return Network::Protocol::UDP;
|
||||
case Type::STREAM:
|
||||
return Network::Protocol::TCP;
|
||||
default:
|
||||
return Network::Protocol::TCP;
|
||||
}
|
||||
case Protocol::Unspecified:
|
||||
return Network::Protocol::Unspecified;
|
||||
case Protocol::TCP:
|
||||
return Network::Protocol::TCP;
|
||||
case Protocol::UDP:
|
||||
return Network::Protocol::UDP;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
|
||||
return Network::Protocol::TCP;
|
||||
return Network::Protocol::Unspecified;
|
||||
}
|
||||
}
|
||||
|
||||
Network::PollEvents TranslatePollEventsToHost(PollEvents flags) {
|
||||
Protocol Translate(Network::Protocol protocol) {
|
||||
switch (protocol) {
|
||||
case Network::Protocol::Unspecified:
|
||||
return Protocol::Unspecified;
|
||||
case Network::Protocol::TCP:
|
||||
return Protocol::TCP;
|
||||
case Network::Protocol::UDP:
|
||||
return Protocol::UDP;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
|
||||
return Protocol::Unspecified;
|
||||
}
|
||||
}
|
||||
|
||||
Network::PollEvents Translate(PollEvents flags) {
|
||||
Network::PollEvents result{};
|
||||
const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) {
|
||||
if (True(flags & from)) {
|
||||
@@ -107,12 +183,15 @@ Network::PollEvents TranslatePollEventsToHost(PollEvents flags) {
|
||||
translate(PollEvents::Err, Network::PollEvents::Err);
|
||||
translate(PollEvents::Hup, Network::PollEvents::Hup);
|
||||
translate(PollEvents::Nval, Network::PollEvents::Nval);
|
||||
translate(PollEvents::RdNorm, Network::PollEvents::RdNorm);
|
||||
translate(PollEvents::RdBand, Network::PollEvents::RdBand);
|
||||
translate(PollEvents::WrBand, Network::PollEvents::WrBand);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) {
|
||||
PollEvents Translate(Network::PollEvents flags) {
|
||||
PollEvents result{};
|
||||
const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) {
|
||||
if (True(flags & from)) {
|
||||
@@ -127,13 +206,18 @@ PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) {
|
||||
translate(Network::PollEvents::Err, PollEvents::Err);
|
||||
translate(Network::PollEvents::Hup, PollEvents::Hup);
|
||||
translate(Network::PollEvents::Nval, PollEvents::Nval);
|
||||
translate(Network::PollEvents::RdNorm, PollEvents::RdNorm);
|
||||
translate(Network::PollEvents::RdBand, PollEvents::RdBand);
|
||||
translate(Network::PollEvents::WrBand, PollEvents::WrBand);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value));
|
||||
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
|
||||
// sin_len to 6 when deserializing getaddrinfo results).
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
|
@@ -17,6 +17,9 @@ Errno Translate(Network::Errno value);
|
||||
/// Translate abstract return value errno pair to guest return value errno pair
|
||||
std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
|
||||
|
||||
/// Translate abstract getaddrinfo error to guest getaddrinfo error
|
||||
GetAddrInfoError Translate(Network::GetAddrInfoError value);
|
||||
|
||||
/// Translate guest domain to abstract domain
|
||||
Network::Domain Translate(Domain domain);
|
||||
|
||||
@@ -26,14 +29,20 @@ Domain Translate(Network::Domain domain);
|
||||
/// Translate guest type to abstract type
|
||||
Network::Type Translate(Type type);
|
||||
|
||||
/// Translate guest protocol to abstract protocol
|
||||
Network::Protocol Translate(Type type, Protocol protocol);
|
||||
/// Translate abstract type to guest type
|
||||
Type Translate(Network::Type type);
|
||||
|
||||
/// Translate abstract poll event flags to guest poll event flags
|
||||
Network::PollEvents TranslatePollEventsToHost(PollEvents flags);
|
||||
/// Translate guest protocol to abstract protocol
|
||||
Network::Protocol Translate(Protocol protocol);
|
||||
|
||||
/// Translate abstract protocol to guest protocol
|
||||
Protocol Translate(Network::Protocol protocol);
|
||||
|
||||
/// Translate guest poll event flags to abstract poll event flags
|
||||
PollEvents TranslatePollEventsToGuest(Network::PollEvents flags);
|
||||
Network::PollEvents Translate(PollEvents flags);
|
||||
|
||||
/// Translate abstract poll event flags to guest poll event flags
|
||||
PollEvents Translate(Network::PollEvents flags);
|
||||
|
||||
/// Translate guest socket address structure to abstract socket address structure
|
||||
Network::SockAddrIn Translate(SockAddrIn value);
|
||||
|
@@ -1,10 +1,18 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/sockets/bsd.h"
|
||||
#include "core/hle/service/ssl/ssl.h"
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
@@ -20,6 +28,18 @@ enum class ContextOption : u32 {
|
||||
CrlImportDateCheckEnable = 1,
|
||||
};
|
||||
|
||||
// This is nn::ssl::Connection::IoMode
|
||||
enum class IoMode : u32 {
|
||||
Blocking = 1,
|
||||
NonBlocking = 2,
|
||||
};
|
||||
|
||||
// This is nn::ssl::sf::OptionType
|
||||
enum class OptionType : u32 {
|
||||
DoNotCloseSocket = 0,
|
||||
GetServerCertChain = 1,
|
||||
};
|
||||
|
||||
// This is nn::ssl::sf::SslVersion
|
||||
struct SslVersion {
|
||||
union {
|
||||
@@ -34,35 +54,42 @@ struct SslVersion {
|
||||
};
|
||||
};
|
||||
|
||||
struct SslContextSharedData {
|
||||
u32 connection_count = 0;
|
||||
};
|
||||
|
||||
class ISslConnection final : public ServiceFramework<ISslConnection> {
|
||||
public:
|
||||
explicit ISslConnection(Core::System& system_, SslVersion version)
|
||||
: ServiceFramework{system_, "ISslConnection"}, ssl_version{version} {
|
||||
explicit ISslConnection(Core::System& system_in, SslVersion ssl_version_in,
|
||||
std::shared_ptr<SslContextSharedData>& shared_data_in,
|
||||
std::unique_ptr<SSLConnectionBackend>&& backend_in)
|
||||
: ServiceFramework{system_in, "ISslConnection"}, ssl_version{ssl_version_in},
|
||||
shared_data{shared_data_in}, backend{std::move(backend_in)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SetSocketDescriptor"},
|
||||
{1, nullptr, "SetHostName"},
|
||||
{2, nullptr, "SetVerifyOption"},
|
||||
{3, nullptr, "SetIoMode"},
|
||||
{0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"},
|
||||
{1, &ISslConnection::SetHostName, "SetHostName"},
|
||||
{2, &ISslConnection::SetVerifyOption, "SetVerifyOption"},
|
||||
{3, &ISslConnection::SetIoMode, "SetIoMode"},
|
||||
{4, nullptr, "GetSocketDescriptor"},
|
||||
{5, nullptr, "GetHostName"},
|
||||
{6, nullptr, "GetVerifyOption"},
|
||||
{7, nullptr, "GetIoMode"},
|
||||
{8, nullptr, "DoHandshake"},
|
||||
{9, nullptr, "DoHandshakeGetServerCert"},
|
||||
{10, nullptr, "Read"},
|
||||
{11, nullptr, "Write"},
|
||||
{12, nullptr, "Pending"},
|
||||
{8, &ISslConnection::DoHandshake, "DoHandshake"},
|
||||
{9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"},
|
||||
{10, &ISslConnection::Read, "Read"},
|
||||
{11, &ISslConnection::Write, "Write"},
|
||||
{12, &ISslConnection::Pending, "Pending"},
|
||||
{13, nullptr, "Peek"},
|
||||
{14, nullptr, "Poll"},
|
||||
{15, nullptr, "GetVerifyCertError"},
|
||||
{16, nullptr, "GetNeededServerCertBufferSize"},
|
||||
{17, nullptr, "SetSessionCacheMode"},
|
||||
{17, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"},
|
||||
{18, nullptr, "GetSessionCacheMode"},
|
||||
{19, nullptr, "FlushSessionCache"},
|
||||
{20, nullptr, "SetRenegotiationMode"},
|
||||
{21, nullptr, "GetRenegotiationMode"},
|
||||
{22, nullptr, "SetOption"},
|
||||
{22, &ISslConnection::SetOption, "SetOption"},
|
||||
{23, nullptr, "GetOption"},
|
||||
{24, nullptr, "GetVerifyCertErrors"},
|
||||
{25, nullptr, "GetCipherInfo"},
|
||||
@@ -80,21 +107,299 @@ public:
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_data->connection_count++;
|
||||
}
|
||||
|
||||
~ISslConnection() {
|
||||
shared_data->connection_count--;
|
||||
if (fd_to_close.has_value()) {
|
||||
const s32 fd = *fd_to_close;
|
||||
if (!do_not_close_socket) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"do_not_close_socket was changed after setting socket; is this right?");
|
||||
} else {
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
if (bsd) {
|
||||
auto err = bsd->CloseImpl(fd);
|
||||
if (err != Service::Sockets::Errno::SUCCESS) {
|
||||
LOG_ERROR(Service_SSL, "Failed to close duplicated socket: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SslVersion ssl_version;
|
||||
std::shared_ptr<SslContextSharedData> shared_data;
|
||||
std::unique_ptr<SSLConnectionBackend> backend;
|
||||
std::optional<int> fd_to_close;
|
||||
bool do_not_close_socket = false;
|
||||
bool get_server_cert_chain = false;
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
bool did_set_host_name = false;
|
||||
bool did_handshake = false;
|
||||
|
||||
ResultVal<s32> SetSocketDescriptorImpl(s32 fd) {
|
||||
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
|
||||
ASSERT(!did_handshake);
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
|
||||
s32 ret_fd;
|
||||
// Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor
|
||||
if (do_not_close_socket) {
|
||||
auto res = bsd->DuplicateSocketImpl(fd);
|
||||
if (!res.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
fd = *res;
|
||||
fd_to_close = fd;
|
||||
ret_fd = fd;
|
||||
} else {
|
||||
ret_fd = -1;
|
||||
}
|
||||
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd);
|
||||
if (!sock.has_value()) {
|
||||
LOG_ERROR(Service_SSL, "invalid socket fd {}", fd);
|
||||
return ResultInvalidSocket;
|
||||
}
|
||||
socket = std::move(*sock);
|
||||
backend->SetSocket(socket);
|
||||
return ret_fd;
|
||||
}
|
||||
|
||||
Result SetHostNameImpl(const std::string& hostname) {
|
||||
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
|
||||
ASSERT(!did_handshake);
|
||||
Result res = backend->SetHostName(hostname);
|
||||
if (res == ResultSuccess) {
|
||||
did_set_host_name = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Result SetVerifyOptionImpl(u32 option) {
|
||||
ASSERT(!did_handshake);
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called. option={}", option);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetIoModeImpl(u32 input_mode) {
|
||||
auto mode = static_cast<IoMode>(input_mode);
|
||||
ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking);
|
||||
ASSERT_OR_EXECUTE(socket, { return ResultNoSocket; });
|
||||
|
||||
const bool non_block = mode == IoMode::NonBlocking;
|
||||
const Network::Errno error = socket->SetNonBlock(non_block);
|
||||
if (error != Network::Errno::SUCCESS) {
|
||||
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result SetSessionCacheModeImpl(u32 mode) {
|
||||
ASSERT(!did_handshake);
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DoHandshakeImpl() {
|
||||
ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; });
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
did_set_host_name, { return ResultInternalError; },
|
||||
"Expected SetHostName before DoHandshake");
|
||||
Result res = backend->DoHandshake();
|
||||
did_handshake = res.IsSuccess();
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<u8> SerializeServerCerts(const std::vector<std::vector<u8>>& certs) {
|
||||
struct Header {
|
||||
u64 magic;
|
||||
u32 count;
|
||||
u32 pad;
|
||||
};
|
||||
struct EntryHeader {
|
||||
u32 size;
|
||||
u32 offset;
|
||||
};
|
||||
if (!get_server_cert_chain) {
|
||||
// Just return the first one, unencoded.
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
!certs.empty(), { return {}; }, "Should be at least one server cert");
|
||||
return certs[0];
|
||||
}
|
||||
std::vector<u8> ret;
|
||||
Header header{0x4E4D684374726543, static_cast<u32>(certs.size()), 0};
|
||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
|
||||
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
|
||||
for (auto& cert : certs) {
|
||||
EntryHeader entry_header{static_cast<u32>(cert.size()), static_cast<u32>(data_offset)};
|
||||
data_offset += cert.size();
|
||||
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header),
|
||||
reinterpret_cast<u8*>(&entry_header + 1));
|
||||
}
|
||||
for (auto& cert : certs) {
|
||||
ret.insert(ret.end(), cert.begin(), cert.end());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ResultVal<std::vector<u8>> ReadImpl(size_t size) {
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
std::vector<u8> res(size);
|
||||
ResultVal<size_t> actual = backend->Read(res);
|
||||
if (actual.Failed()) {
|
||||
return actual.Code();
|
||||
}
|
||||
res.resize(*actual);
|
||||
return res;
|
||||
}
|
||||
|
||||
ResultVal<size_t> WriteImpl(std::span<const u8> data) {
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Write(data);
|
||||
}
|
||||
|
||||
ResultVal<s32> PendingImpl() {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const ResultVal<s32> res = SetSocketDescriptorImpl(fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res.Code());
|
||||
rb.Push<s32>(res.ValueOr(-1));
|
||||
}
|
||||
|
||||
void SetHostName(HLERequestContext& ctx) {
|
||||
const std::string hostname = Common::StringFromBuffer(ctx.ReadBuffer());
|
||||
const Result res = SetHostNameImpl(hostname);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void SetVerifyOption(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 option = rp.Pop<u32>();
|
||||
const Result res = SetVerifyOptionImpl(option);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void SetIoMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 mode = rp.Pop<u32>();
|
||||
const Result res = SetIoModeImpl(mode);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
const Result res = DoHandshakeImpl();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void DoHandshakeGetServerCert(HLERequestContext& ctx) {
|
||||
struct OutputParameters {
|
||||
u32 certs_size;
|
||||
u32 certs_count;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x8);
|
||||
|
||||
const Result res = DoHandshakeImpl();
|
||||
OutputParameters out{};
|
||||
if (res == ResultSuccess) {
|
||||
auto certs = backend->GetServerCerts();
|
||||
if (certs.Succeeded()) {
|
||||
const std::vector<u8> certs_buf = SerializeServerCerts(*certs);
|
||||
ctx.WriteBuffer(certs_buf);
|
||||
out.certs_count = static_cast<u32>(certs->size());
|
||||
out.certs_size = static_cast<u32>(certs_buf.size());
|
||||
}
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(res);
|
||||
rb.PushRaw(out);
|
||||
}
|
||||
|
||||
void Read(HLERequestContext& ctx) {
|
||||
const ResultVal<std::vector<u8>> res = ReadImpl(ctx.GetWriteBufferSize());
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res.Code());
|
||||
if (res.Succeeded()) {
|
||||
rb.Push(static_cast<u32>(res->size()));
|
||||
ctx.WriteBuffer(*res);
|
||||
} else {
|
||||
rb.Push(static_cast<u32>(0));
|
||||
}
|
||||
}
|
||||
|
||||
void Write(HLERequestContext& ctx) {
|
||||
const ResultVal<size_t> res = WriteImpl(ctx.ReadBuffer());
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res.Code());
|
||||
rb.Push(static_cast<u32>(res.ValueOr(0)));
|
||||
}
|
||||
|
||||
void Pending(HLERequestContext& ctx) {
|
||||
const ResultVal<s32> res = PendingImpl();
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res.Code());
|
||||
rb.Push<s32>(res.ValueOr(0));
|
||||
}
|
||||
|
||||
void SetSessionCacheMode(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 mode = rp.Pop<u32>();
|
||||
const Result res = SetSessionCacheModeImpl(mode);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
void SetOption(HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
OptionType option;
|
||||
s32 value;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 0x8, "Parameters is an invalid size");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
switch (parameters.option) {
|
||||
case OptionType::DoNotCloseSocket:
|
||||
do_not_close_socket = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
case OptionType::GetServerCertChain:
|
||||
get_server_cert_chain = static_cast<bool>(parameters.value);
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option,
|
||||
parameters.value);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
class ISslContext final : public ServiceFramework<ISslContext> {
|
||||
public:
|
||||
explicit ISslContext(Core::System& system_, SslVersion version)
|
||||
: ServiceFramework{system_, "ISslContext"}, ssl_version{version} {
|
||||
: ServiceFramework{system_, "ISslContext"}, ssl_version{version},
|
||||
shared_data{std::make_shared<SslContextSharedData>()} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISslContext::SetOption, "SetOption"},
|
||||
{1, nullptr, "GetOption"},
|
||||
{2, &ISslContext::CreateConnection, "CreateConnection"},
|
||||
{3, nullptr, "GetConnectionCount"},
|
||||
{3, &ISslContext::GetConnectionCount, "GetConnectionCount"},
|
||||
{4, &ISslContext::ImportServerPki, "ImportServerPki"},
|
||||
{5, &ISslContext::ImportClientPki, "ImportClientPki"},
|
||||
{6, nullptr, "RemoveServerPki"},
|
||||
@@ -111,6 +416,7 @@ public:
|
||||
|
||||
private:
|
||||
SslVersion ssl_version;
|
||||
std::shared_ptr<SslContextSharedData> shared_data;
|
||||
|
||||
void SetOption(HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
@@ -130,11 +436,24 @@ private:
|
||||
}
|
||||
|
||||
void CreateConnection(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called");
|
||||
LOG_WARNING(Service_SSL, "called");
|
||||
|
||||
auto backend_res = CreateSSLConnectionBackend();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(backend_res.Code());
|
||||
if (backend_res.Succeeded()) {
|
||||
rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data,
|
||||
std::move(*backend_res));
|
||||
}
|
||||
}
|
||||
|
||||
void GetConnectionCount(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SSL, "connection_count={}", shared_data->connection_count);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<ISslConnection>(system, ssl_version);
|
||||
rb.Push(shared_data->connection_count);
|
||||
}
|
||||
|
||||
void ImportServerPki(HLERequestContext& ctx) {
|
||||
|
45
src/core/hle/service/ssl/ssl_backend.h
Normal file
45
src/core/hle/service/ssl/ssl_backend.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Network {
|
||||
class SocketBase;
|
||||
}
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
constexpr Result ResultNoSocket{ErrorModule::SSLSrv, 103};
|
||||
constexpr Result ResultInvalidSocket{ErrorModule::SSLSrv, 106};
|
||||
constexpr Result ResultTimeout{ErrorModule::SSLSrv, 205};
|
||||
constexpr Result ResultInternalError{ErrorModule::SSLSrv, 999}; // made up
|
||||
|
||||
// ResultWouldBlock is returned from Read and Write, and oddly, DoHandshake,
|
||||
// with no way in the latter case to distinguish whether the client should poll
|
||||
// for read or write. The one official client I've seen handles this by always
|
||||
// polling for read (with a timeout).
|
||||
constexpr Result ResultWouldBlock{ErrorModule::SSLSrv, 204};
|
||||
|
||||
class SSLConnectionBackend {
|
||||
public:
|
||||
virtual ~SSLConnectionBackend() {}
|
||||
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
|
||||
virtual Result SetHostName(const std::string& hostname) = 0;
|
||||
virtual Result DoHandshake() = 0;
|
||||
virtual ResultVal<size_t> Read(std::span<u8> data) = 0;
|
||||
virtual ResultVal<size_t> Write(std::span<const u8> data) = 0;
|
||||
virtual ResultVal<std::vector<std::vector<u8>>> GetServerCerts() = 0;
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend();
|
||||
|
||||
} // namespace Service::SSL
|
16
src/core/hle/service/ssl/ssl_backend_none.cpp
Normal file
16
src/core/hle/service/ssl/ssl_backend_none.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"Can't create SSL connection because no SSL backend is available on this platform");
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
} // namespace Service::SSL
|
351
src/core/hle/service/ssl/ssl_backend_openssl.cpp
Normal file
351
src/core/hle/service/ssl/ssl_backend_openssl.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
using namespace Common::FS;
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
// Import OpenSSL's `SSL` type into the namespace. This is needed because the
|
||||
// namespace is also named `SSL`.
|
||||
using ::SSL;
|
||||
|
||||
namespace {
|
||||
|
||||
std::once_flag one_time_init_flag;
|
||||
bool one_time_init_success = false;
|
||||
|
||||
SSL_CTX* ssl_ctx;
|
||||
IOFile key_log_file; // only open if SSLKEYLOGFILE set in environment
|
||||
BIO_METHOD* bio_meth;
|
||||
|
||||
Result CheckOpenSSLErrors();
|
||||
void OneTimeInit();
|
||||
void OneTimeInitLogFile();
|
||||
bool OneTimeInitBIO();
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
public:
|
||||
Result Init() {
|
||||
std::call_once(one_time_init_flag, OneTimeInit);
|
||||
|
||||
if (!one_time_init_success) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"Can't create SSL connection because OpenSSL one-time initialization failed");
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl) {
|
||||
LOG_ERROR(Service_SSL, "SSL_new failed");
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
|
||||
SSL_set_connect_state(ssl);
|
||||
|
||||
bio = BIO_new(bio_meth);
|
||||
if (!bio) {
|
||||
LOG_ERROR(Service_SSL, "BIO_new failed");
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
|
||||
BIO_set_data(bio, this);
|
||||
BIO_set_init(bio, 1);
|
||||
SSL_set_bio(ssl, bio, bio);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
|
||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
|
||||
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DoHandshake() override {
|
||||
SSL_set_verify_result(ssl, X509_V_OK);
|
||||
const int ret = SSL_do_handshake(ssl);
|
||||
const long verify_result = SSL_get_verify_result(ssl);
|
||||
if (verify_result != X509_V_OK) {
|
||||
LOG_ERROR(Service_SSL, "SSL cert verification failed because: {}",
|
||||
X509_verify_cert_error_string(verify_result));
|
||||
return CheckOpenSSLErrors();
|
||||
}
|
||||
if (ret <= 0) {
|
||||
const int ssl_err = SSL_get_error(ssl, ret);
|
||||
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||
(ssl_err == SSL_ERROR_SYSCALL && got_read_eof)) {
|
||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
return HandleReturn("SSL_do_handshake", 0, ret).Code();
|
||||
}
|
||||
|
||||
ResultVal<size_t> Read(std::span<u8> data) override {
|
||||
size_t actual;
|
||||
const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual);
|
||||
return HandleReturn("SSL_read_ex", actual, ret);
|
||||
}
|
||||
|
||||
ResultVal<size_t> Write(std::span<const u8> data) override {
|
||||
size_t actual;
|
||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual);
|
||||
return HandleReturn("SSL_write_ex", actual, ret);
|
||||
}
|
||||
|
||||
ResultVal<size_t> HandleReturn(const char* what, size_t actual, int ret) {
|
||||
const int ssl_err = SSL_get_error(ssl, ret);
|
||||
CheckOpenSSLErrors();
|
||||
switch (ssl_err) {
|
||||
case SSL_ERROR_NONE:
|
||||
return actual;
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what);
|
||||
// DoHandshake special-cases this, but for Read and Write:
|
||||
return size_t(0);
|
||||
case SSL_ERROR_WANT_READ:
|
||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what);
|
||||
return ResultWouldBlock;
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_WRITE", what);
|
||||
return ResultWouldBlock;
|
||||
default:
|
||||
if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) {
|
||||
LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what);
|
||||
return size_t(0);
|
||||
}
|
||||
LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
|
||||
STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
|
||||
if (!chain) {
|
||||
LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr");
|
||||
return ResultInternalError;
|
||||
}
|
||||
std::vector<std::vector<u8>> ret;
|
||||
int count = sk_X509_num(chain);
|
||||
ASSERT(count >= 0);
|
||||
for (int i = 0; i < count; i++) {
|
||||
X509* x509 = sk_X509_value(chain, i);
|
||||
ASSERT_OR_EXECUTE(x509 != nullptr, { continue; });
|
||||
unsigned char* buf = nullptr;
|
||||
int len = i2d_X509(x509, &buf);
|
||||
ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; });
|
||||
ret.emplace_back(buf, buf + len);
|
||||
OPENSSL_free(buf);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
// these are null-tolerant:
|
||||
SSL_free(ssl);
|
||||
BIO_free(bio);
|
||||
}
|
||||
|
||||
static void KeyLogCallback(const SSL* ssl, const char* line) {
|
||||
std::string str(line);
|
||||
str.push_back('\n');
|
||||
// Do this in a single WriteString for atomicity if multiple instances
|
||||
// are running on different threads (though that can't currently
|
||||
// happen).
|
||||
if (key_log_file.WriteString(str) != str.size() || !key_log_file.Flush()) {
|
||||
LOG_CRITICAL(Service_SSL, "Failed to write to SSLKEYLOGFILE");
|
||||
}
|
||||
LOG_DEBUG(Service_SSL, "Wrote to SSLKEYLOGFILE: {}", line);
|
||||
}
|
||||
|
||||
static int WriteCallback(BIO* bio, const char* buf, size_t len, size_t* actual_p) {
|
||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
self->socket, { return 0; }, "OpenSSL asked to send but we have no socket");
|
||||
BIO_clear_retry_flags(bio);
|
||||
auto [actual, err] = self->socket->Send({reinterpret_cast<const u8*>(buf), len}, 0);
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
*actual_p = actual;
|
||||
return 1;
|
||||
case Network::Errno::AGAIN:
|
||||
BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY);
|
||||
return 0;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int ReadCallback(BIO* bio, char* buf, size_t len, size_t* actual_p) {
|
||||
auto self = static_cast<SSLConnectionBackendOpenSSL*>(BIO_get_data(bio));
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
self->socket, { return 0; }, "OpenSSL asked to recv but we have no socket");
|
||||
BIO_clear_retry_flags(bio);
|
||||
auto [actual, err] = self->socket->Recv(0, {reinterpret_cast<u8*>(buf), len});
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
*actual_p = actual;
|
||||
if (actual == 0) {
|
||||
self->got_read_eof = true;
|
||||
}
|
||||
return actual ? 1 : 0;
|
||||
case Network::Errno::AGAIN:
|
||||
BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY);
|
||||
return 0;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static long CtrlCallback(BIO* bio, int cmd, long l_arg, void* p_arg) {
|
||||
switch (cmd) {
|
||||
case BIO_CTRL_FLUSH:
|
||||
// Nothing to flush.
|
||||
return 1;
|
||||
case BIO_CTRL_PUSH:
|
||||
case BIO_CTRL_POP:
|
||||
#ifdef BIO_CTRL_GET_KTLS_SEND
|
||||
case BIO_CTRL_GET_KTLS_SEND:
|
||||
case BIO_CTRL_GET_KTLS_RECV:
|
||||
#endif
|
||||
// We don't support these operations, but don't bother logging them
|
||||
// as they're nothing unusual.
|
||||
return 0;
|
||||
default:
|
||||
LOG_DEBUG(Service_SSL, "OpenSSL BIO got ctrl({}, {}, {})", cmd, l_arg, p_arg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SSL* ssl = nullptr;
|
||||
BIO* bio = nullptr;
|
||||
bool got_read_eof = false;
|
||||
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
|
||||
auto conn = std::make_unique<SSLConnectionBackendOpenSSL>();
|
||||
const Result res = conn->Init();
|
||||
if (res.IsFailure()) {
|
||||
return res;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Result CheckOpenSSLErrors() {
|
||||
unsigned long rc;
|
||||
const char* file;
|
||||
int line;
|
||||
const char* func;
|
||||
const char* data;
|
||||
int flags;
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||
while ((rc = ERR_get_error_all(&file, &line, &func, &data, &flags)))
|
||||
#else
|
||||
// Can't get function names from OpenSSL on this version, so use mine:
|
||||
func = __func__;
|
||||
while ((rc = ERR_get_error_line_data(&file, &line, &data, &flags)))
|
||||
#endif
|
||||
{
|
||||
std::string msg;
|
||||
msg.resize(1024, '\0');
|
||||
ERR_error_string_n(rc, msg.data(), msg.size());
|
||||
msg.resize(strlen(msg.data()), '\0');
|
||||
if (flags & ERR_TXT_STRING) {
|
||||
msg.append(" | ");
|
||||
msg.append(data);
|
||||
}
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::Service_SSL, Common::Log::Level::Error,
|
||||
Common::Log::TrimSourcePath(file), line, func, "OpenSSL: {}",
|
||||
msg);
|
||||
}
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
void OneTimeInit() {
|
||||
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!ssl_ctx) {
|
||||
LOG_ERROR(Service_SSL, "SSL_CTX_new failed");
|
||||
CheckOpenSSLErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
|
||||
|
||||
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
||||
LOG_ERROR(Service_SSL, "SSL_CTX_set_default_verify_paths failed");
|
||||
CheckOpenSSLErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
OneTimeInitLogFile();
|
||||
|
||||
if (!OneTimeInitBIO()) {
|
||||
return;
|
||||
}
|
||||
|
||||
one_time_init_success = true;
|
||||
}
|
||||
|
||||
void OneTimeInitLogFile() {
|
||||
const char* logfile = getenv("SSLKEYLOGFILE");
|
||||
if (logfile) {
|
||||
key_log_file.Open(logfile, FileAccessMode::Append, FileType::TextFile,
|
||||
FileShareFlag::ShareWriteOnly);
|
||||
if (key_log_file.IsOpen()) {
|
||||
SSL_CTX_set_keylog_callback(ssl_ctx, &SSLConnectionBackendOpenSSL::KeyLogCallback);
|
||||
} else {
|
||||
LOG_CRITICAL(Service_SSL,
|
||||
"SSLKEYLOGFILE was set but file could not be opened; not logging keys!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OneTimeInitBIO() {
|
||||
bio_meth =
|
||||
BIO_meth_new(BIO_get_new_index() | BIO_TYPE_SOURCE_SINK, "SSLConnectionBackendOpenSSL");
|
||||
if (!bio_meth ||
|
||||
!BIO_meth_set_write_ex(bio_meth, &SSLConnectionBackendOpenSSL::WriteCallback) ||
|
||||
!BIO_meth_set_read_ex(bio_meth, &SSLConnectionBackendOpenSSL::ReadCallback) ||
|
||||
!BIO_meth_set_ctrl(bio_meth, &SSLConnectionBackendOpenSSL::CtrlCallback)) {
|
||||
LOG_ERROR(Service_SSL, "Failed to create BIO_METHOD");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Service::SSL
|
544
src/core/hle/service/ssl/ssl_backend_schannel.cpp
Normal file
544
src/core/hle/service/ssl/ssl_backend_schannel.cpp
Normal file
@@ -0,0 +1,544 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// These includes are inside the namespace to avoid a conflict on MinGW where
|
||||
// the headers define an enum containing Network and Service as enumerators
|
||||
// (which clash with the correspondingly named namespaces).
|
||||
#define SECURITY_WIN32
|
||||
#include <schnlsp.h>
|
||||
#include <security.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
std::once_flag one_time_init_flag;
|
||||
bool one_time_init_success = false;
|
||||
|
||||
SCHANNEL_CRED schannel_cred{};
|
||||
CredHandle cred_handle;
|
||||
|
||||
static void OneTimeInit() {
|
||||
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
|
||||
schannel_cred.dwFlags =
|
||||
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
|
||||
SCH_CRED_AUTO_CRED_VALIDATION | // validate certs
|
||||
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
|
||||
// ^ I'm assuming that nobody would want to connect Yuzu to a
|
||||
// service that requires some OS-provided corporate client
|
||||
// certificate, and presenting one to some arbitrary server
|
||||
// might be a privacy concern? Who knows, though.
|
||||
|
||||
const SECURITY_STATUS ret =
|
||||
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
|
||||
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
|
||||
if (ret != SEC_E_OK) {
|
||||
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
|
||||
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if (getenv("SSLKEYLOGFILE")) {
|
||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
|
||||
"keys; not logging keys!");
|
||||
// Not fatal.
|
||||
}
|
||||
|
||||
one_time_init_success = true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
|
||||
public:
|
||||
Result Init() {
|
||||
std::call_once(one_time_init_flag, OneTimeInit);
|
||||
|
||||
if (!one_time_init_success) {
|
||||
LOG_ERROR(
|
||||
Service_SSL,
|
||||
"Can't create SSL connection because Schannel one-time initialization failed");
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
hostname = hostname_in;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DoHandshake() override {
|
||||
while (1) {
|
||||
Result r;
|
||||
switch (handshake_state) {
|
||||
case HandshakeState::Initial:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
||||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// CallInitializeSecurityContext updated `handshake_state`.
|
||||
continue;
|
||||
case HandshakeState::ContinueNeeded:
|
||||
case HandshakeState::IncompleteMessage:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
|
||||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
if (ciphertext_read_buf.empty()) {
|
||||
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// CallInitializeSecurityContext updated `handshake_state`.
|
||||
continue;
|
||||
case HandshakeState::DoneAfterFlush:
|
||||
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
handshake_state = HandshakeState::Connected;
|
||||
return ResultSuccess;
|
||||
case HandshakeState::Connected:
|
||||
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
|
||||
return ResultInternalError;
|
||||
case HandshakeState::Error:
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result FillCiphertextReadBuf() {
|
||||
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
|
||||
read_buf_fill_size = 0;
|
||||
// This unnecessarily zeroes the buffer; oh well.
|
||||
const size_t offset = ciphertext_read_buf.size();
|
||||
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
|
||||
ciphertext_read_buf.resize(offset + fill_size, 0);
|
||||
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
|
||||
const auto [actual, err] = socket->Recv(0, read_span);
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
ASSERT(static_cast<size_t>(actual) <= fill_size);
|
||||
ciphertext_read_buf.resize(offset + actual);
|
||||
return ResultSuccess;
|
||||
case Network::Errno::AGAIN:
|
||||
ciphertext_read_buf.resize(offset);
|
||||
return ResultWouldBlock;
|
||||
default:
|
||||
ciphertext_read_buf.resize(offset);
|
||||
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns success if the write buffer has been completely emptied.
|
||||
Result FlushCiphertextWriteBuf() {
|
||||
while (!ciphertext_write_buf.empty()) {
|
||||
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
|
||||
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
|
||||
ciphertext_write_buf.begin() + actual);
|
||||
break;
|
||||
case Network::Errno::AGAIN:
|
||||
return ResultWouldBlock;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CallInitializeSecurityContext() {
|
||||
const unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
|
||||
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
|
||||
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
|
||||
ISC_REQ_USE_SUPPLIED_CREDS;
|
||||
unsigned long attr;
|
||||
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
|
||||
std::array<SecBuffer, 2> input_buffers{{
|
||||
// only used if `initial_call_done`
|
||||
{
|
||||
// [0]
|
||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
||||
.BufferType = SECBUFFER_TOKEN,
|
||||
.pvBuffer = ciphertext_read_buf.data(),
|
||||
},
|
||||
{
|
||||
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
|
||||
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
|
||||
// whole buffer wasn't used)
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_EMPTY,
|
||||
.pvBuffer = nullptr,
|
||||
},
|
||||
}};
|
||||
std::array<SecBuffer, 2> output_buffers{{
|
||||
{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_TOKEN,
|
||||
.pvBuffer = nullptr,
|
||||
}, // [0]
|
||||
{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_ALERT,
|
||||
.pvBuffer = nullptr,
|
||||
}, // [1]
|
||||
}};
|
||||
SecBufferDesc input_desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
|
||||
.pBuffers = input_buffers.data(),
|
||||
};
|
||||
SecBufferDesc output_desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
|
||||
.pBuffers = output_buffers.data(),
|
||||
};
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
||||
{ return ResultInternalError; }, "read buffer too large");
|
||||
|
||||
bool initial_call_done = handshake_state != HandshakeState::Initial;
|
||||
if (initial_call_done) {
|
||||
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
|
||||
ciphertext_read_buf.size());
|
||||
}
|
||||
|
||||
const SECURITY_STATUS ret =
|
||||
InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr,
|
||||
// Caller ensured we have set a hostname:
|
||||
const_cast<char*>(hostname.value().c_str()), req,
|
||||
0, // Reserved1
|
||||
0, // TargetDataRep not used with Schannel
|
||||
initial_call_done ? &input_desc : nullptr,
|
||||
0, // Reserved2
|
||||
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
|
||||
nullptr); // ptsExpiry
|
||||
|
||||
if (output_buffers[0].pvBuffer) {
|
||||
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
|
||||
output_buffers[0].cbBuffer);
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
|
||||
FreeContextBuffer(output_buffers[0].pvBuffer);
|
||||
}
|
||||
|
||||
if (output_buffers[1].pvBuffer) {
|
||||
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
|
||||
output_buffers[1].cbBuffer);
|
||||
// The documentation doesn't explain what format this data is in.
|
||||
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
|
||||
Common::HexToString(span));
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case SEC_I_CONTINUE_NEEDED:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
|
||||
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
|
||||
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
|
||||
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
|
||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
||||
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
|
||||
} else {
|
||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
|
||||
ciphertext_read_buf.clear();
|
||||
}
|
||||
handshake_state = HandshakeState::ContinueNeeded;
|
||||
return ResultSuccess;
|
||||
case SEC_E_INCOMPLETE_MESSAGE:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
|
||||
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
|
||||
read_buf_fill_size = input_buffers[1].cbBuffer;
|
||||
handshake_state = HandshakeState::IncompleteMessage;
|
||||
return ResultSuccess;
|
||||
case SEC_E_OK:
|
||||
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
|
||||
ciphertext_read_buf.clear();
|
||||
handshake_state = HandshakeState::DoneAfterFlush;
|
||||
return GrabStreamSizes();
|
||||
default:
|
||||
LOG_ERROR(Service_SSL,
|
||||
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
handshake_state = HandshakeState::Error;
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
|
||||
Result GrabStreamSizes() {
|
||||
const SECURITY_STATUS ret =
|
||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
handshake_state = HandshakeState::Error;
|
||||
return ResultInternalError;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
ResultVal<size_t> Read(std::span<u8> data) override {
|
||||
if (handshake_state != HandshakeState::Connected) {
|
||||
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if (data.size() == 0 || got_read_eof) {
|
||||
return size_t(0);
|
||||
}
|
||||
while (1) {
|
||||
if (!cleartext_read_buf.empty()) {
|
||||
const size_t read_size = std::min(cleartext_read_buf.size(), data.size());
|
||||
std::memcpy(data.data(), cleartext_read_buf.data(), read_size);
|
||||
cleartext_read_buf.erase(cleartext_read_buf.begin(),
|
||||
cleartext_read_buf.begin() + read_size);
|
||||
return read_size;
|
||||
}
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
SecBuffer empty{
|
||||
.cbBuffer = 0,
|
||||
.BufferType = SECBUFFER_EMPTY,
|
||||
.pvBuffer = nullptr,
|
||||
};
|
||||
std::array<SecBuffer, 5> buffers{{
|
||||
{
|
||||
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
|
||||
.BufferType = SECBUFFER_DATA,
|
||||
.pvBuffer = ciphertext_read_buf.data(),
|
||||
},
|
||||
empty,
|
||||
empty,
|
||||
empty,
|
||||
}};
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
buffers[0].cbBuffer == ciphertext_read_buf.size(),
|
||||
{ return ResultInternalError; }, "read buffer too large");
|
||||
SecBufferDesc desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
||||
.pBuffers = buffers.data(),
|
||||
};
|
||||
SECURITY_STATUS ret =
|
||||
DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr);
|
||||
switch (ret) {
|
||||
case SEC_E_OK:
|
||||
ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER,
|
||||
{ return ResultInternalError; });
|
||||
ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA,
|
||||
{ return ResultInternalError; });
|
||||
ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER,
|
||||
{ return ResultInternalError; });
|
||||
cleartext_read_buf.assign(static_cast<u8*>(buffers[1].pvBuffer),
|
||||
static_cast<u8*>(buffers[1].pvBuffer) +
|
||||
buffers[1].cbBuffer);
|
||||
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
|
||||
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
|
||||
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
|
||||
ciphertext_read_buf.end() - buffers[3].cbBuffer);
|
||||
} else {
|
||||
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
|
||||
ciphertext_read_buf.clear();
|
||||
}
|
||||
continue;
|
||||
case SEC_E_INCOMPLETE_MESSAGE:
|
||||
break;
|
||||
case SEC_I_CONTEXT_EXPIRED:
|
||||
// Server hung up by sending close_notify.
|
||||
got_read_eof = true;
|
||||
return size_t(0);
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
const Result r = FillCiphertextReadBuf();
|
||||
if (r != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
if (ciphertext_read_buf.empty()) {
|
||||
got_read_eof = true;
|
||||
return size_t(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<size_t> Write(std::span<const u8> data) override {
|
||||
if (handshake_state != HandshakeState::Connected) {
|
||||
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
|
||||
return ResultInternalError;
|
||||
}
|
||||
if (data.size() == 0) {
|
||||
return size_t(0);
|
||||
}
|
||||
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
|
||||
if (!cleartext_write_buf.empty()) {
|
||||
// Already in the middle of a write. It wouldn't make sense to not
|
||||
// finish sending the entire buffer since TLS has
|
||||
// header/MAC/padding/etc.
|
||||
if (data.size() != cleartext_write_buf.size() ||
|
||||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
|
||||
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
|
||||
return ResultInternalError;
|
||||
}
|
||||
return WriteAlreadyEncryptedData();
|
||||
} else {
|
||||
cleartext_write_buf.assign(data.begin(), data.end());
|
||||
}
|
||||
|
||||
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
|
||||
std::vector<u8> tmp_data_buf = cleartext_write_buf;
|
||||
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
|
||||
|
||||
std::array<SecBuffer, 3> buffers{{
|
||||
{
|
||||
.cbBuffer = stream_sizes.cbHeader,
|
||||
.BufferType = SECBUFFER_STREAM_HEADER,
|
||||
.pvBuffer = header_buf.data(),
|
||||
},
|
||||
{
|
||||
.cbBuffer = static_cast<unsigned long>(tmp_data_buf.size()),
|
||||
.BufferType = SECBUFFER_DATA,
|
||||
.pvBuffer = tmp_data_buf.data(),
|
||||
},
|
||||
{
|
||||
.cbBuffer = stream_sizes.cbTrailer,
|
||||
.BufferType = SECBUFFER_STREAM_TRAILER,
|
||||
.pvBuffer = trailer_buf.data(),
|
||||
},
|
||||
}};
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; },
|
||||
"temp buffer too large");
|
||||
SecBufferDesc desc{
|
||||
.ulVersion = SECBUFFER_VERSION,
|
||||
.cBuffers = static_cast<unsigned long>(buffers.size()),
|
||||
.pBuffers = buffers.data(),
|
||||
};
|
||||
|
||||
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
|
||||
header_buf.end());
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
|
||||
tmp_data_buf.end());
|
||||
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
|
||||
trailer_buf.end());
|
||||
return WriteAlreadyEncryptedData();
|
||||
}
|
||||
|
||||
ResultVal<size_t> WriteAlreadyEncryptedData() {
|
||||
const Result r = FlushCiphertextWriteBuf();
|
||||
if (r != ResultSuccess) {
|
||||
return r;
|
||||
}
|
||||
// write buf is empty
|
||||
const size_t cleartext_bytes_written = cleartext_write_buf.size();
|
||||
cleartext_write_buf.clear();
|
||||
return cleartext_bytes_written;
|
||||
}
|
||||
|
||||
ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
|
||||
PCCERT_CONTEXT returned_cert = nullptr;
|
||||
const SECURITY_STATUS ret =
|
||||
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
|
||||
if (ret != SEC_E_OK) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
|
||||
Common::NativeErrorToString(ret));
|
||||
return ResultInternalError;
|
||||
}
|
||||
PCCERT_CONTEXT some_cert = nullptr;
|
||||
std::vector<std::vector<u8>> certs;
|
||||
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) {
|
||||
certs.emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
|
||||
static_cast<u8*>(some_cert->pbCertEncoded) +
|
||||
some_cert->cbCertEncoded);
|
||||
}
|
||||
std::reverse(certs.begin(),
|
||||
certs.end()); // Windows returns certs in reverse order from what we want
|
||||
CertFreeCertificateContext(returned_cert);
|
||||
return certs;
|
||||
}
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
enum class HandshakeState {
|
||||
// Haven't called anything yet.
|
||||
Initial,
|
||||
// `SEC_I_CONTINUE_NEEDED` was returned by
|
||||
// `InitializeSecurityContext`; must finish sending data (if any) in
|
||||
// the write buffer, then read at least one byte before calling
|
||||
// `InitializeSecurityContext` again.
|
||||
ContinueNeeded,
|
||||
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
|
||||
// `InitializeSecurityContext`; hopefully the write buffer is empty;
|
||||
// must read at least one byte before calling
|
||||
// `InitializeSecurityContext` again.
|
||||
IncompleteMessage,
|
||||
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
|
||||
// finish sending data in the write buffer before having `DoHandshake`
|
||||
// report success.
|
||||
DoneAfterFlush,
|
||||
// We finished the above and are now connected. At this point, writing
|
||||
// and reading are separate 'state machines' represented by the
|
||||
// nonemptiness of the ciphertext and cleartext read and write buffers.
|
||||
Connected,
|
||||
// Another error was returned and we shouldn't allow initialization
|
||||
// to continue.
|
||||
Error,
|
||||
} handshake_state = HandshakeState::Initial;
|
||||
|
||||
CtxtHandle ctxt;
|
||||
SecPkgContext_StreamSizes stream_sizes;
|
||||
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
std::optional<std::string> hostname;
|
||||
|
||||
std::vector<u8> ciphertext_read_buf;
|
||||
std::vector<u8> ciphertext_write_buf;
|
||||
std::vector<u8> cleartext_read_buf;
|
||||
std::vector<u8> cleartext_write_buf;
|
||||
|
||||
bool got_read_eof = false;
|
||||
size_t read_buf_fill_size = 0;
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
|
||||
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
|
||||
const Result res = conn->Init();
|
||||
if (res.IsFailure()) {
|
||||
return res;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
} // namespace Service::SSL
|
222
src/core/hle/service/ssl/ssl_backend_securetransport.cpp
Normal file
222
src/core/hle/service/ssl/ssl_backend_securetransport.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
// SecureTransport has been deprecated in its entirety in favor of
|
||||
// Network.framework, but that does not allow layering TLS on top of an
|
||||
// arbitrary socket.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#include <Security/SecureTransport.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "core/hle/service/ssl/ssl_backend.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
struct CFReleaser {
|
||||
T ptr;
|
||||
|
||||
YUZU_NON_COPYABLE(CFReleaser);
|
||||
constexpr CFReleaser() : ptr(nullptr) {}
|
||||
constexpr CFReleaser(T ptr) : ptr(ptr) {}
|
||||
constexpr operator T() {
|
||||
return ptr;
|
||||
}
|
||||
~CFReleaser() {
|
||||
if (ptr) {
|
||||
CFRelease(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::string CFStringToString(CFStringRef cfstr) {
|
||||
CFReleaser<CFDataRef> cfdata(
|
||||
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
|
||||
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
|
||||
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
|
||||
CFDataGetLength(cfdata));
|
||||
}
|
||||
|
||||
std::string OSStatusToString(OSStatus status) {
|
||||
CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
|
||||
if (!cfstr) {
|
||||
return "[unknown error]";
|
||||
}
|
||||
return CFStringToString(cfstr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Service::SSL {
|
||||
|
||||
class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
|
||||
public:
|
||||
Result Init() {
|
||||
static std::once_flag once_flag;
|
||||
std::call_once(once_flag, []() {
|
||||
if (getenv("SSLKEYLOGFILE")) {
|
||||
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
|
||||
"support exporting keys; not logging keys!");
|
||||
// Not fatal.
|
||||
}
|
||||
});
|
||||
|
||||
context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
|
||||
if (!context) {
|
||||
LOG_ERROR(Service_SSL, "SSLCreateContext failed");
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
OSStatus status;
|
||||
if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
|
||||
(status = SSLSetConnection(context, this))) {
|
||||
LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
|
||||
OSStatusToString(status));
|
||||
return ResultInternalError;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
|
||||
socket = std::move(in_socket);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
|
||||
if (status) {
|
||||
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
|
||||
return ResultInternalError;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DoHandshake() override {
|
||||
OSStatus status = SSLHandshake(context);
|
||||
return HandleReturn("SSLHandshake", 0, status).Code();
|
||||
}
|
||||
|
||||
ResultVal<size_t> Read(std::span<u8> data) override {
|
||||
size_t actual;
|
||||
OSStatus status = SSLRead(context, data.data(), data.size(), &actual);
|
||||
;
|
||||
return HandleReturn("SSLRead", actual, status);
|
||||
}
|
||||
|
||||
ResultVal<size_t> Write(std::span<const u8> data) override {
|
||||
size_t actual;
|
||||
OSStatus status = SSLWrite(context, data.data(), data.size(), &actual);
|
||||
;
|
||||
return HandleReturn("SSLWrite", actual, status);
|
||||
}
|
||||
|
||||
ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return actual;
|
||||
case errSSLWouldBlock:
|
||||
return ResultWouldBlock;
|
||||
default: {
|
||||
std::string reason;
|
||||
if (got_read_eof) {
|
||||
reason = "server hung up";
|
||||
} else {
|
||||
reason = OSStatusToString(status);
|
||||
}
|
||||
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
|
||||
return ResultInternalError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override {
|
||||
CFReleaser<SecTrustRef> trust;
|
||||
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
|
||||
if (status) {
|
||||
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
|
||||
return ResultInternalError;
|
||||
}
|
||||
std::vector<std::vector<u8>> ret;
|
||||
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
|
||||
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
|
||||
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
|
||||
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
|
||||
const u8* ptr = CFDataGetBytePtr(data);
|
||||
ret.emplace_back(ptr, ptr + CFDataGetLength(data));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
|
||||
size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
|
||||
}
|
||||
|
||||
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
|
||||
bool is_read) {
|
||||
auto self =
|
||||
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
|
||||
ASSERT_OR_EXECUTE_MSG(
|
||||
self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
|
||||
is_read ? "read" : "write");
|
||||
|
||||
// SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
|
||||
// expected to read/write the full requested dataLength or return an
|
||||
// error, so we have to add a loop ourselves.
|
||||
size_t requested_len = *dataLength;
|
||||
size_t offset = 0;
|
||||
while (offset < requested_len) {
|
||||
std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
|
||||
auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
|
||||
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
|
||||
actual, cur.size(), static_cast<s32>(err));
|
||||
switch (err) {
|
||||
case Network::Errno::SUCCESS:
|
||||
offset += actual;
|
||||
if (actual == 0) {
|
||||
ASSERT(is_read);
|
||||
self->got_read_eof = true;
|
||||
return errSecEndOfData;
|
||||
}
|
||||
break;
|
||||
case Network::Errno::AGAIN:
|
||||
*dataLength = offset;
|
||||
return errSSLWouldBlock;
|
||||
default:
|
||||
LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
|
||||
is_read ? "recv" : "send", err);
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
ASSERT(offset == requested_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
CFReleaser<SSLContextRef> context = nullptr;
|
||||
bool got_read_eof = false;
|
||||
|
||||
std::shared_ptr<Network::SocketBase> socket;
|
||||
};
|
||||
|
||||
ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() {
|
||||
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
|
||||
const Result res = conn->Init();
|
||||
if (res.IsFailure()) {
|
||||
return res;
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
} // namespace Service::SSL
|
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/expected.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/internal_network/network.h"
|
||||
@@ -97,6 +98,8 @@ bool EnableNonBlock(SOCKET fd, bool enable) {
|
||||
|
||||
Errno TranslateNativeError(int e) {
|
||||
switch (e) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case WSAEBADF:
|
||||
return Errno::BADF;
|
||||
case WSAEINVAL:
|
||||
@@ -121,6 +124,8 @@ Errno TranslateNativeError(int e) {
|
||||
return Errno::MSGSIZE;
|
||||
case WSAETIMEDOUT:
|
||||
return Errno::TIMEDOUT;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -195,6 +200,8 @@ bool EnableNonBlock(int fd, bool enable) {
|
||||
|
||||
Errno TranslateNativeError(int e) {
|
||||
switch (e) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case EBADF:
|
||||
return Errno::BADF;
|
||||
case EINVAL:
|
||||
@@ -219,8 +226,10 @@ Errno TranslateNativeError(int e) {
|
||||
return Errno::MSGSIZE;
|
||||
case ETIMEDOUT:
|
||||
return Errno::TIMEDOUT;
|
||||
case EINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={} ({})", e, strerror(e));
|
||||
return Errno::OTHER;
|
||||
}
|
||||
}
|
||||
@@ -234,15 +243,84 @@ Errno GetAndLogLastError() {
|
||||
int e = errno;
|
||||
#endif
|
||||
const Errno err = TranslateNativeError(e);
|
||||
if (err == Errno::AGAIN || err == Errno::TIMEDOUT) {
|
||||
if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) {
|
||||
// These happen during normal operation, so only log them at debug level.
|
||||
LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
|
||||
return err;
|
||||
}
|
||||
LOG_ERROR(Network, "Socket operation error: {}", Common::NativeErrorToString(e));
|
||||
return err;
|
||||
}
|
||||
|
||||
int TranslateDomain(Domain domain) {
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
switch (gai_err) {
|
||||
case 0:
|
||||
return GetAddrInfoError::SUCCESS;
|
||||
#ifdef EAI_ADDRFAMILY
|
||||
case EAI_ADDRFAMILY:
|
||||
return GetAddrInfoError::ADDRFAMILY;
|
||||
#endif
|
||||
case EAI_AGAIN:
|
||||
return GetAddrInfoError::AGAIN;
|
||||
case EAI_BADFLAGS:
|
||||
return GetAddrInfoError::BADFLAGS;
|
||||
case EAI_FAIL:
|
||||
return GetAddrInfoError::FAIL;
|
||||
case EAI_FAMILY:
|
||||
return GetAddrInfoError::FAMILY;
|
||||
case EAI_MEMORY:
|
||||
return GetAddrInfoError::MEMORY;
|
||||
case EAI_NONAME:
|
||||
return GetAddrInfoError::NONAME;
|
||||
case EAI_SERVICE:
|
||||
return GetAddrInfoError::SERVICE;
|
||||
case EAI_SOCKTYPE:
|
||||
return GetAddrInfoError::SOCKTYPE;
|
||||
// These codes may not be defined on all systems:
|
||||
#ifdef EAI_SYSTEM
|
||||
case EAI_SYSTEM:
|
||||
return GetAddrInfoError::SYSTEM;
|
||||
#endif
|
||||
#ifdef EAI_BADHINTS
|
||||
case EAI_BADHINTS:
|
||||
return GetAddrInfoError::BADHINTS;
|
||||
#endif
|
||||
#ifdef EAI_PROTOCOL
|
||||
case EAI_PROTOCOL:
|
||||
return GetAddrInfoError::PROTOCOL;
|
||||
#endif
|
||||
#ifdef EAI_OVERFLOW
|
||||
case EAI_OVERFLOW:
|
||||
return GetAddrInfoError::OVERFLOW_;
|
||||
#endif
|
||||
default:
|
||||
#ifdef EAI_NODATA
|
||||
// This can't be a case statement because it would create a duplicate
|
||||
// case on Windows where EAI_NODATA is an alias for EAI_NONAME.
|
||||
if (gai_err == EAI_NODATA) {
|
||||
return GetAddrInfoError::NODATA;
|
||||
}
|
||||
#endif
|
||||
return GetAddrInfoError::OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
Domain TranslateDomainFromNative(int domain) {
|
||||
switch (domain) {
|
||||
case 0:
|
||||
return Domain::Unspecified;
|
||||
case AF_INET:
|
||||
return Domain::INET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled domain={}", domain);
|
||||
return Domain::INET;
|
||||
}
|
||||
}
|
||||
|
||||
int TranslateDomainToNative(Domain domain) {
|
||||
switch (domain) {
|
||||
case Domain::Unspecified:
|
||||
return 0;
|
||||
case Domain::INET:
|
||||
return AF_INET;
|
||||
default:
|
||||
@@ -251,20 +329,58 @@ int TranslateDomain(Domain domain) {
|
||||
}
|
||||
}
|
||||
|
||||
int TranslateType(Type type) {
|
||||
Type TranslateTypeFromNative(int type) {
|
||||
switch (type) {
|
||||
case 0:
|
||||
return Type::Unspecified;
|
||||
case SOCK_STREAM:
|
||||
return Type::STREAM;
|
||||
case SOCK_DGRAM:
|
||||
return Type::DGRAM;
|
||||
case SOCK_RAW:
|
||||
return Type::RAW;
|
||||
case SOCK_SEQPACKET:
|
||||
return Type::SEQPACKET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
|
||||
return Type::STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
int TranslateTypeToNative(Type type) {
|
||||
switch (type) {
|
||||
case Type::Unspecified:
|
||||
return 0;
|
||||
case Type::STREAM:
|
||||
return SOCK_STREAM;
|
||||
case Type::DGRAM:
|
||||
return SOCK_DGRAM;
|
||||
case Type::RAW:
|
||||
return SOCK_RAW;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int TranslateProtocol(Protocol protocol) {
|
||||
Protocol TranslateProtocolFromNative(int protocol) {
|
||||
switch (protocol) {
|
||||
case 0:
|
||||
return Protocol::Unspecified;
|
||||
case IPPROTO_TCP:
|
||||
return Protocol::TCP;
|
||||
case IPPROTO_UDP:
|
||||
return Protocol::UDP;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol);
|
||||
return Protocol::Unspecified;
|
||||
}
|
||||
}
|
||||
|
||||
int TranslateProtocolToNative(Protocol protocol) {
|
||||
switch (protocol) {
|
||||
case Protocol::Unspecified:
|
||||
return 0;
|
||||
case Protocol::TCP:
|
||||
return IPPROTO_TCP;
|
||||
case Protocol::UDP:
|
||||
@@ -275,21 +391,10 @@ int TranslateProtocol(Protocol protocol) {
|
||||
}
|
||||
}
|
||||
|
||||
SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
|
||||
sockaddr_in input;
|
||||
std::memcpy(&input, &input_, sizeof(input));
|
||||
|
||||
SockAddrIn TranslateToSockAddrIn(sockaddr_in input, size_t input_len) {
|
||||
SockAddrIn result;
|
||||
|
||||
switch (input.sin_family) {
|
||||
case AF_INET:
|
||||
result.family = Domain::INET;
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled sockaddr family={}", input.sin_family);
|
||||
result.family = Domain::INET;
|
||||
break;
|
||||
}
|
||||
result.family = TranslateDomainFromNative(input.sin_family);
|
||||
|
||||
result.portno = ntohs(input.sin_port);
|
||||
|
||||
@@ -301,22 +406,33 @@ SockAddrIn TranslateToSockAddrIn(sockaddr input_) {
|
||||
short TranslatePollEvents(PollEvents events) {
|
||||
short result = 0;
|
||||
|
||||
if (True(events & PollEvents::In)) {
|
||||
events &= ~PollEvents::In;
|
||||
result |= POLLIN;
|
||||
}
|
||||
if (True(events & PollEvents::Pri)) {
|
||||
events &= ~PollEvents::Pri;
|
||||
const auto translate = [&result, &events](PollEvents guest, short host) {
|
||||
if (True(events & guest)) {
|
||||
events &= ~guest;
|
||||
result |= host;
|
||||
}
|
||||
};
|
||||
|
||||
translate(PollEvents::In, POLLIN);
|
||||
translate(PollEvents::Pri, POLLPRI);
|
||||
translate(PollEvents::Out, POLLOUT);
|
||||
translate(PollEvents::Err, POLLERR);
|
||||
translate(PollEvents::Hup, POLLHUP);
|
||||
translate(PollEvents::Nval, POLLNVAL);
|
||||
translate(PollEvents::RdNorm, POLLRDNORM);
|
||||
translate(PollEvents::RdBand, POLLRDBAND);
|
||||
translate(PollEvents::WrBand, POLLWRBAND);
|
||||
|
||||
#ifdef _WIN32
|
||||
LOG_WARNING(Service, "Winsock doesn't support POLLPRI");
|
||||
#else
|
||||
result |= POLLPRI;
|
||||
short allowed_events = POLLRDBAND | POLLRDNORM | POLLWRNORM;
|
||||
// Unlike poll on other OSes, WSAPoll will complain if any other flags are set on input.
|
||||
if (result & ~allowed_events) {
|
||||
LOG_DEBUG(Network,
|
||||
"Removing WSAPoll input events 0x{:x} because Windows doesn't support them",
|
||||
result & ~allowed_events);
|
||||
}
|
||||
result &= allowed_events;
|
||||
#endif
|
||||
}
|
||||
if (True(events & PollEvents::Out)) {
|
||||
events &= ~PollEvents::Out;
|
||||
result |= POLLOUT;
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events);
|
||||
|
||||
@@ -337,6 +453,10 @@ PollEvents TranslatePollRevents(short revents) {
|
||||
translate(POLLOUT, PollEvents::Out);
|
||||
translate(POLLERR, PollEvents::Err);
|
||||
translate(POLLHUP, PollEvents::Hup);
|
||||
translate(POLLNVAL, PollEvents::Nval);
|
||||
translate(POLLRDNORM, PollEvents::RdNorm);
|
||||
translate(POLLRDBAND, PollEvents::RdBand);
|
||||
translate(POLLWRBAND, PollEvents::WrBand);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents);
|
||||
|
||||
@@ -360,12 +480,51 @@ std::optional<IPv4Address> GetHostIPv4Address() {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<char, 16> ip_addr = {};
|
||||
ASSERT(inet_ntop(AF_INET, &network_interface->ip_address, ip_addr.data(), sizeof(ip_addr)) !=
|
||||
nullptr);
|
||||
return TranslateIPv4(network_interface->ip_address);
|
||||
}
|
||||
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr) {
|
||||
std::array<char, INET_ADDRSTRLEN> buf = {};
|
||||
ASSERT(inet_ntop(AF_INET, &ip_addr, buf.data(), sizeof(buf)) == buf.data());
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr) {
|
||||
return static_cast<u32>(ip_addr[0]) << 24 | static_cast<u32>(ip_addr[1]) << 16 |
|
||||
static_cast<u32>(ip_addr[2]) << 8 | static_cast<u32>(ip_addr[3]);
|
||||
}
|
||||
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service) {
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_INET; // Switch only supports IPv4.
|
||||
addrinfo* addrinfo;
|
||||
s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr,
|
||||
&hints, &addrinfo);
|
||||
if (gai_err != 0) {
|
||||
return Common::Unexpected(TranslateGetAddrInfoErrorFromNative(gai_err));
|
||||
}
|
||||
std::vector<AddrInfo> ret;
|
||||
for (auto* current = addrinfo; current; current = current->ai_next) {
|
||||
// We should only get AF_INET results due to the hints value.
|
||||
ASSERT_OR_EXECUTE(addrinfo->ai_family == AF_INET &&
|
||||
addrinfo->ai_addrlen == sizeof(sockaddr_in),
|
||||
continue;);
|
||||
|
||||
AddrInfo& out = ret.emplace_back();
|
||||
out.family = TranslateDomainFromNative(current->ai_family);
|
||||
out.socket_type = TranslateTypeFromNative(current->ai_socktype);
|
||||
out.protocol = TranslateProtocolFromNative(current->ai_protocol);
|
||||
out.addr = TranslateToSockAddrIn(*reinterpret_cast<sockaddr_in*>(current->ai_addr),
|
||||
current->ai_addrlen);
|
||||
if (current->ai_canonname != nullptr) {
|
||||
out.canon_name = current->ai_canonname;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(addrinfo);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
const size_t num = pollfds.size();
|
||||
|
||||
@@ -411,9 +570,21 @@ Socket::Socket(Socket&& rhs) noexcept {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) {
|
||||
std::pair<T, Errno> Socket::GetSockOpt(SOCKET fd_so, int option) {
|
||||
T value{};
|
||||
socklen_t len = sizeof(value);
|
||||
const int result = getsockopt(fd_so, SOL_SOCKET, option, reinterpret_cast<char*>(&value), &len);
|
||||
if (result != SOCKET_ERROR) {
|
||||
ASSERT(len == sizeof(value));
|
||||
return {value, Errno::SUCCESS};
|
||||
}
|
||||
return {value, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Errno Socket::SetSockOpt(SOCKET fd_so, int option, T value) {
|
||||
const int result =
|
||||
setsockopt(fd_, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
setsockopt(fd_so, SOL_SOCKET, option, reinterpret_cast<const char*>(&value), sizeof(value));
|
||||
if (result != SOCKET_ERROR) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
@@ -421,7 +592,8 @@ Errno Socket::SetSockOpt(SOCKET fd_, int option, T value) {
|
||||
}
|
||||
|
||||
Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
|
||||
fd = socket(TranslateDomain(domain), TranslateType(type), TranslateProtocol(protocol));
|
||||
fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type),
|
||||
TranslateProtocolToNative(protocol));
|
||||
if (fd != INVALID_SOCKET) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
@@ -430,19 +602,17 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
|
||||
}
|
||||
|
||||
std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
|
||||
sockaddr addr;
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
const SOCKET new_socket = accept(fd, &addr, &addrlen);
|
||||
const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
|
||||
|
||||
if (new_socket == INVALID_SOCKET) {
|
||||
return {AcceptResult{}, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
ASSERT(addrlen == sizeof(sockaddr_in));
|
||||
|
||||
AcceptResult result{
|
||||
.socket = std::make_unique<Socket>(new_socket),
|
||||
.sockaddr_in = TranslateToSockAddrIn(addr),
|
||||
.sockaddr_in = TranslateToSockAddrIn(addr, addrlen),
|
||||
};
|
||||
|
||||
return {std::move(result), Errno::SUCCESS};
|
||||
@@ -458,25 +628,23 @@ Errno Socket::Connect(SockAddrIn addr_in) {
|
||||
}
|
||||
|
||||
std::pair<SockAddrIn, Errno> Socket::GetPeerName() {
|
||||
sockaddr addr;
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (getpeername(fd, &addr, &addrlen) == SOCKET_ERROR) {
|
||||
if (getpeername(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen) == SOCKET_ERROR) {
|
||||
return {SockAddrIn{}, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
ASSERT(addrlen == sizeof(sockaddr_in));
|
||||
return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
|
||||
return {TranslateToSockAddrIn(addr, addrlen), Errno::SUCCESS};
|
||||
}
|
||||
|
||||
std::pair<SockAddrIn, Errno> Socket::GetSockName() {
|
||||
sockaddr addr;
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (getsockname(fd, &addr, &addrlen) == SOCKET_ERROR) {
|
||||
if (getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen) == SOCKET_ERROR) {
|
||||
return {SockAddrIn{}, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
ASSERT(addrlen == sizeof(sockaddr_in));
|
||||
return {TranslateToSockAddrIn(addr), Errno::SUCCESS};
|
||||
return {TranslateToSockAddrIn(addr, addrlen), Errno::SUCCESS};
|
||||
}
|
||||
|
||||
Errno Socket::Bind(SockAddrIn addr) {
|
||||
@@ -519,7 +687,7 @@ Errno Socket::Shutdown(ShutdownHow how) {
|
||||
return GetAndLogLastError();
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
|
||||
std::pair<s32, Errno> Socket::Recv(int flags, std::span<u8> message) {
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
|
||||
@@ -532,21 +700,20 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::vector<u8>& message) {
|
||||
return {-1, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> Socket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
|
||||
std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) {
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
|
||||
sockaddr addr_in{};
|
||||
sockaddr_in addr_in{};
|
||||
socklen_t addrlen = sizeof(addr_in);
|
||||
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
|
||||
sockaddr* const p_addr_in = addr ? &addr_in : nullptr;
|
||||
sockaddr* const p_addr_in = addr ? reinterpret_cast<sockaddr*>(&addr_in) : nullptr;
|
||||
|
||||
const auto result = recvfrom(fd, reinterpret_cast<char*>(message.data()),
|
||||
static_cast<int>(message.size()), 0, p_addr_in, p_addrlen);
|
||||
if (result != SOCKET_ERROR) {
|
||||
if (addr) {
|
||||
ASSERT(addrlen == sizeof(addr_in));
|
||||
*addr = TranslateToSockAddrIn(addr_in);
|
||||
*addr = TranslateToSockAddrIn(addr_in, addrlen);
|
||||
}
|
||||
return {static_cast<s32>(result), Errno::SUCCESS};
|
||||
}
|
||||
@@ -597,6 +764,11 @@ Errno Socket::Close() {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
std::pair<Errno, Errno> Socket::GetPendingError() {
|
||||
auto [pending_err, getsockopt_err] = GetSockOpt<int>(fd, SO_ERROR);
|
||||
return {TranslateNativeError(pending_err), getsockopt_err};
|
||||
}
|
||||
|
||||
Errno Socket::SetLinger(bool enable, u32 linger) {
|
||||
return SetSockOpt(fd, SO_LINGER, MakeLinger(enable, linger));
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -16,6 +17,11 @@
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
template <typename T, typename E>
|
||||
class Expected;
|
||||
}
|
||||
|
||||
namespace Network {
|
||||
|
||||
class SocketBase;
|
||||
@@ -36,6 +42,26 @@ enum class Errno {
|
||||
NETUNREACH,
|
||||
TIMEDOUT,
|
||||
MSGSIZE,
|
||||
INPROGRESS,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
enum class GetAddrInfoError {
|
||||
SUCCESS,
|
||||
ADDRFAMILY,
|
||||
AGAIN,
|
||||
BADFLAGS,
|
||||
FAIL,
|
||||
FAMILY,
|
||||
MEMORY,
|
||||
NODATA,
|
||||
NONAME,
|
||||
SERVICE,
|
||||
SOCKTYPE,
|
||||
SYSTEM,
|
||||
BADHINTS,
|
||||
PROTOCOL,
|
||||
OVERFLOW_,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
@@ -49,6 +75,9 @@ enum class PollEvents : u16 {
|
||||
Err = 1 << 3,
|
||||
Hup = 1 << 4,
|
||||
Nval = 1 << 5,
|
||||
RdNorm = 1 << 6,
|
||||
RdBand = 1 << 7,
|
||||
WrBand = 1 << 8,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_FLAG_OPERATORS(PollEvents);
|
||||
@@ -82,4 +111,11 @@ constexpr IPv4Address TranslateIPv4(in_addr addr) {
|
||||
/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
|
||||
std::optional<IPv4Address> GetHostIPv4Address();
|
||||
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr);
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr);
|
||||
|
||||
// named to avoid name collision with Windows macro
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service);
|
||||
|
||||
} // namespace Network
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/internal_network/network_interface.h"
|
||||
#include "core/internal_network/socket_proxy.h"
|
||||
#include "network/network.h"
|
||||
|
||||
#if YUZU_UNIX
|
||||
#include <sys/socket.h>
|
||||
@@ -98,7 +99,7 @@ Errno ProxySocket::Shutdown(ShutdownHow how) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) {
|
||||
std::pair<s32, Errno> ProxySocket::Recv(int flags, std::span<u8> message) {
|
||||
LOG_WARNING(Network, "(STUBBED) called");
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
@@ -106,7 +107,7 @@ std::pair<s32, Errno> ProxySocket::Recv(int flags, std::vector<u8>& message) {
|
||||
return {static_cast<s32>(0), Errno::SUCCESS};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) {
|
||||
std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) {
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
|
||||
@@ -140,8 +141,8 @@ std::pair<s32, Errno> ProxySocket::RecvFrom(int flags, std::vector<u8>& message,
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& message,
|
||||
SockAddrIn* addr, std::size_t max_length) {
|
||||
std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::span<u8> message, SockAddrIn* addr,
|
||||
std::size_t max_length) {
|
||||
ProxyPacket& packet = received_packets.front();
|
||||
if (addr) {
|
||||
addr->family = Domain::INET;
|
||||
@@ -153,10 +154,7 @@ std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& mes
|
||||
std::size_t read_bytes;
|
||||
if (packet.data.size() > max_length) {
|
||||
read_bytes = max_length;
|
||||
message.clear();
|
||||
std::copy(packet.data.begin(), packet.data.begin() + read_bytes,
|
||||
std::back_inserter(message));
|
||||
message.resize(max_length);
|
||||
memcpy(message.data(), packet.data.data(), max_length);
|
||||
|
||||
if (protocol == Protocol::UDP) {
|
||||
if (!peek) {
|
||||
@@ -171,9 +169,7 @@ std::pair<s32, Errno> ProxySocket::ReceivePacket(int flags, std::vector<u8>& mes
|
||||
}
|
||||
} else {
|
||||
read_bytes = packet.data.size();
|
||||
message.clear();
|
||||
std::copy(packet.data.begin(), packet.data.end(), std::back_inserter(message));
|
||||
message.resize(max_length);
|
||||
memcpy(message.data(), packet.data.data(), read_bytes);
|
||||
if (!peek) {
|
||||
received_packets.pop();
|
||||
}
|
||||
@@ -293,6 +289,11 @@ Errno ProxySocket::SetNonBlock(bool enable) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
std::pair<Errno, Errno> ProxySocket::GetPendingError() {
|
||||
LOG_DEBUG(Network, "(STUBBED) called");
|
||||
return {Errno::SUCCESS, Errno::SUCCESS};
|
||||
}
|
||||
|
||||
bool ProxySocket::IsOpened() const {
|
||||
return fd != INVALID_SOCKET;
|
||||
}
|
||||
|
@@ -10,10 +10,12 @@
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "network/network.h"
|
||||
#include "network/room_member.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
class RoomNetwork;
|
||||
|
||||
class ProxySocket : public SocketBase {
|
||||
public:
|
||||
explicit ProxySocket(RoomNetwork& room_network_) noexcept;
|
||||
@@ -39,11 +41,11 @@ public:
|
||||
|
||||
Errno Shutdown(ShutdownHow how) override;
|
||||
|
||||
std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
|
||||
std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override;
|
||||
|
||||
std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
|
||||
std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) override;
|
||||
|
||||
std::pair<s32, Errno> ReceivePacket(int flags, std::vector<u8>& message, SockAddrIn* addr,
|
||||
std::pair<s32, Errno> ReceivePacket(int flags, std::span<u8> message, SockAddrIn* addr,
|
||||
std::size_t max_length);
|
||||
|
||||
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
|
||||
@@ -74,6 +76,8 @@ public:
|
||||
template <typename T>
|
||||
Errno SetSockOpt(SOCKET fd, int option, T value);
|
||||
|
||||
std::pair<Errno, Errno> GetPendingError() override;
|
||||
|
||||
bool IsOpened() const override;
|
||||
|
||||
private:
|
||||
|
@@ -15,12 +15,13 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "network/network.h"
|
||||
|
||||
// TODO: C++20 Replace std::vector usages with std::span
|
||||
|
||||
namespace Network {
|
||||
|
||||
struct ProxyPacket;
|
||||
|
||||
class SocketBase {
|
||||
public:
|
||||
#ifdef YUZU_UNIX
|
||||
@@ -59,10 +60,9 @@ public:
|
||||
|
||||
virtual Errno Shutdown(ShutdownHow how) = 0;
|
||||
|
||||
virtual std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) = 0;
|
||||
virtual std::pair<s32, Errno> Recv(int flags, std::span<u8> message) = 0;
|
||||
|
||||
virtual std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message,
|
||||
SockAddrIn* addr) = 0;
|
||||
virtual std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) = 0;
|
||||
|
||||
virtual std::pair<s32, Errno> Send(std::span<const u8> message, int flags) = 0;
|
||||
|
||||
@@ -87,6 +87,8 @@ public:
|
||||
|
||||
virtual Errno SetNonBlock(bool enable) = 0;
|
||||
|
||||
virtual std::pair<Errno, Errno> GetPendingError() = 0;
|
||||
|
||||
virtual bool IsOpened() const = 0;
|
||||
|
||||
virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
|
||||
@@ -126,9 +128,9 @@ public:
|
||||
|
||||
Errno Shutdown(ShutdownHow how) override;
|
||||
|
||||
std::pair<s32, Errno> Recv(int flags, std::vector<u8>& message) override;
|
||||
std::pair<s32, Errno> Recv(int flags, std::span<u8> message) override;
|
||||
|
||||
std::pair<s32, Errno> RecvFrom(int flags, std::vector<u8>& message, SockAddrIn* addr) override;
|
||||
std::pair<s32, Errno> RecvFrom(int flags, std::span<u8> message, SockAddrIn* addr) override;
|
||||
|
||||
std::pair<s32, Errno> Send(std::span<const u8> message, int flags) override;
|
||||
|
||||
@@ -156,6 +158,11 @@ public:
|
||||
template <typename T>
|
||||
Errno SetSockOpt(SOCKET fd, int option, T value);
|
||||
|
||||
std::pair<Errno, Errno> GetPendingError() override;
|
||||
|
||||
template <typename T>
|
||||
std::pair<T, Errno> GetSockOpt(SOCKET fd, int option);
|
||||
|
||||
bool IsOpened() const override;
|
||||
|
||||
void HandleProxyPacket(const ProxyPacket& packet) override;
|
||||
|
@@ -79,6 +79,8 @@ enum class ResultStatus : u16 {
|
||||
ErrorBadPFSHeader,
|
||||
ErrorIncorrectPFSFileSize,
|
||||
ErrorBadNCAHeader,
|
||||
ErrorCompressedNCA,
|
||||
ErrorSparseNCA,
|
||||
ErrorMissingProductionKeyFile,
|
||||
ErrorMissingHeaderKey,
|
||||
ErrorIncorrectHeaderKey,
|
||||
|
@@ -266,6 +266,22 @@ struct Memory::Impl {
|
||||
ReadBlockImpl<true>(*system.ApplicationProcess(), src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
const u8* GetSpan(const VAddr src_addr, const std::size_t size) const {
|
||||
if (current_page_table->blocks[src_addr >> YUZU_PAGEBITS] ==
|
||||
current_page_table->blocks[(src_addr + size) >> YUZU_PAGEBITS]) {
|
||||
return GetPointerSilent(src_addr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8* GetSpan(const VAddr src_addr, const std::size_t size) {
|
||||
if (current_page_table->blocks[src_addr >> YUZU_PAGEBITS] ==
|
||||
current_page_table->blocks[(src_addr + size) >> YUZU_PAGEBITS]) {
|
||||
return GetPointerSilent(src_addr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <bool UNSAFE>
|
||||
void WriteBlockImpl(const Kernel::KProcess& process, const Common::ProcessAddress dest_addr,
|
||||
const void* src_buffer, const std::size_t size) {
|
||||
@@ -559,7 +575,7 @@ struct Memory::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
const Common::ProcessAddress end = base + size;
|
||||
const auto end = base + size;
|
||||
ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
|
||||
base + page_table.pointers.size());
|
||||
|
||||
@@ -570,14 +586,18 @@ struct Memory::Impl {
|
||||
while (base != end) {
|
||||
page_table.pointers[base].Store(nullptr, type);
|
||||
page_table.backing_addr[base] = 0;
|
||||
|
||||
page_table.blocks[base] = 0;
|
||||
base += 1;
|
||||
}
|
||||
} else {
|
||||
auto orig_base = base;
|
||||
while (base != end) {
|
||||
page_table.pointers[base].Store(
|
||||
system.DeviceMemory().GetPointer<u8>(target) - (base << YUZU_PAGEBITS), type);
|
||||
page_table.backing_addr[base] = GetInteger(target) - (base << YUZU_PAGEBITS);
|
||||
auto host_ptr =
|
||||
system.DeviceMemory().GetPointer<u8>(target) - (base << YUZU_PAGEBITS);
|
||||
auto backing = GetInteger(target) - (base << YUZU_PAGEBITS);
|
||||
page_table.pointers[base].Store(host_ptr, type);
|
||||
page_table.backing_addr[base] = backing;
|
||||
page_table.blocks[base] = orig_base << YUZU_PAGEBITS;
|
||||
|
||||
ASSERT_MSG(page_table.pointers[base].Pointer(),
|
||||
"memory mapping base yield a nullptr within the table");
|
||||
@@ -747,6 +767,14 @@ struct Memory::Impl {
|
||||
VAddr last_address;
|
||||
};
|
||||
|
||||
void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size) {
|
||||
system.GPU().InvalidateRegion(GetInteger(dest_addr), size);
|
||||
}
|
||||
|
||||
void FlushRegion(Common::ProcessAddress dest_addr, size_t size) {
|
||||
system.GPU().FlushRegion(GetInteger(dest_addr), size);
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
std::array<VideoCore::RasterizerDownloadArea, Core::Hardware::NUM_CPU_CORES>
|
||||
@@ -881,6 +909,14 @@ void Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_b
|
||||
impl->ReadBlockUnsafe(src_addr, dest_buffer, size);
|
||||
}
|
||||
|
||||
const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const {
|
||||
return impl->GetSpan(src_addr, size);
|
||||
}
|
||||
|
||||
u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) {
|
||||
return impl->GetSpan(src_addr, size);
|
||||
}
|
||||
|
||||
void Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer,
|
||||
const std::size_t size) {
|
||||
impl->WriteBlock(dest_addr, src_buffer, size);
|
||||
@@ -924,4 +960,12 @@ void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug)
|
||||
impl->MarkRegionDebug(GetInteger(vaddr), size, debug);
|
||||
}
|
||||
|
||||
void Memory::InvalidateRegion(Common::ProcessAddress dest_addr, size_t size) {
|
||||
impl->InvalidateRegion(dest_addr, size);
|
||||
}
|
||||
|
||||
void Memory::FlushRegion(Common::ProcessAddress dest_addr, size_t size) {
|
||||
impl->FlushRegion(dest_addr, size);
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
@@ -5,8 +5,12 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/typed_address.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
@@ -24,6 +28,10 @@ class PhysicalMemory;
|
||||
class KProcess;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
/**
|
||||
@@ -343,6 +351,9 @@ public:
|
||||
*/
|
||||
void ReadBlockUnsafe(Common::ProcessAddress src_addr, void* dest_buffer, std::size_t size);
|
||||
|
||||
const u8* GetSpan(const VAddr src_addr, const std::size_t size) const;
|
||||
u8* GetSpan(const VAddr src_addr, const std::size_t size);
|
||||
|
||||
/**
|
||||
* Writes a range of bytes into the current process' address space at the specified
|
||||
* virtual address.
|
||||
@@ -461,6 +472,8 @@ public:
|
||||
void MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug);
|
||||
|
||||
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
|
||||
void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size);
|
||||
void FlushRegion(Common::ProcessAddress dest_addr, size_t size);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
@@ -469,4 +482,203 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
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)
|
||||
: memory{memory_}, addr{addr_}, 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 data_span.data();
|
||||
}
|
||||
|
||||
const T* data() const noexcept {
|
||||
return data_span.data();
|
||||
}
|
||||
|
||||
[[nodiscard]] T* begin() noexcept {
|
||||
return data();
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* begin() const noexcept {
|
||||
return data();
|
||||
}
|
||||
|
||||
[[nodiscard]] T* end() noexcept {
|
||||
return data() + size;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* end() const noexcept {
|
||||
return data() + size;
|
||||
}
|
||||
|
||||
T& operator[](size_t index) noexcept {
|
||||
return data_span[index];
|
||||
}
|
||||
|
||||
const T& operator[](size_t index) const noexcept {
|
||||
return data_span[index];
|
||||
}
|
||||
|
||||
void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept {
|
||||
addr = addr_;
|
||||
size = size_;
|
||||
addr_changed = true;
|
||||
}
|
||||
|
||||
std::span<T> Read(u64 addr_, std::size_t size_,
|
||||
Common::ScratchBuffer<T>* backup = nullptr) noexcept {
|
||||
addr = addr_;
|
||||
size = size_;
|
||||
if (size == 0) {
|
||||
is_data_copy = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (TrySetSpan()) {
|
||||
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
|
||||
memory.FlushRegion(addr, size * sizeof(T));
|
||||
}
|
||||
} else {
|
||||
if (backup) {
|
||||
backup->resize_destructive(size);
|
||||
data_span = *backup;
|
||||
} else {
|
||||
data_copy.resize(size);
|
||||
data_span = std::span(data_copy);
|
||||
}
|
||||
is_data_copy = true;
|
||||
span_valid = true;
|
||||
if constexpr (FLAGS & GuestMemoryFlags::Safe) {
|
||||
memory.ReadBlock(addr, data_span.data(), size * sizeof(T));
|
||||
} else {
|
||||
memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T));
|
||||
}
|
||||
}
|
||||
return data_span;
|
||||
}
|
||||
|
||||
void Write(std::span<T> write_data) noexcept {
|
||||
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
|
||||
memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T));
|
||||
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
|
||||
memory.WriteBlock(addr, write_data.data(), size * sizeof(T));
|
||||
} else {
|
||||
memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
bool TrySetSpan() noexcept {
|
||||
if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) {
|
||||
data_span = {reinterpret_cast<T*>(ptr), size};
|
||||
span_valid = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool IsDataCopy() const noexcept {
|
||||
return is_data_copy;
|
||||
}
|
||||
|
||||
bool AddressChanged() const noexcept {
|
||||
return addr_changed;
|
||||
}
|
||||
|
||||
M& memory;
|
||||
u64 addr;
|
||||
size_t size;
|
||||
std::span<T> data_span{};
|
||||
std::vector<T> data_copy;
|
||||
bool span_valid{false};
|
||||
bool is_data_copy{false};
|
||||
bool 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->data_span = *backup;
|
||||
this->span_valid = true;
|
||||
this->is_data_copy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~GuestMemoryScoped() {
|
||||
if constexpr (FLAGS & GuestMemoryFlags::Write) {
|
||||
if (this->size == 0) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->AddressChanged() || this->IsDataCopy()) {
|
||||
ASSERT(this->span_valid);
|
||||
if constexpr (FLAGS & GuestMemoryFlags::Cached) {
|
||||
this->memory.WriteBlockCached(this->addr, this->data_span.data(),
|
||||
this->size * sizeof(T));
|
||||
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
|
||||
this->memory.WriteBlock(this->addr, this->data_span.data(),
|
||||
this->size * sizeof(T));
|
||||
} else {
|
||||
this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(),
|
||||
this->size * sizeof(T));
|
||||
}
|
||||
} else if constexpr (FLAGS & GuestMemoryFlags::Safe) {
|
||||
this->memory.InvalidateRegion(this->addr, this->size * sizeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
template <typename T, GuestMemoryFlags FLAGS>
|
||||
using CpuGuestMemory = GuestMemory<Memory, T, FLAGS>;
|
||||
template <typename T, GuestMemoryFlags FLAGS>
|
||||
using CpuGuestMemoryScoped = GuestMemoryScoped<Memory, T, FLAGS>;
|
||||
template <typename T, GuestMemoryFlags FLAGS>
|
||||
using GpuGuestMemory = GuestMemory<Tegra::MemoryManager, T, FLAGS>;
|
||||
template <typename T, GuestMemoryFlags FLAGS>
|
||||
using GpuGuestMemoryScoped = GuestMemoryScoped<Tegra::MemoryManager, T, FLAGS>;
|
||||
} // namespace Core::Memory
|
||||
|
@@ -160,8 +160,9 @@ void Mouse::Move(int x, int y, int center_x, int center_y) {
|
||||
last_mouse_change.y += mouse_change.y * y_sensitivity;
|
||||
|
||||
// Bind the mouse change to [0 <= deadzone_counterweight <= 1.0]
|
||||
if (last_mouse_change.Length() < deadzone_counterweight) {
|
||||
last_mouse_change /= last_mouse_change.Length();
|
||||
const float length = last_mouse_change.Length();
|
||||
if (length < deadzone_counterweight && length != 0.0f) {
|
||||
last_mouse_change /= length;
|
||||
last_mouse_change *= deadzone_counterweight;
|
||||
}
|
||||
|
||||
|
@@ -523,6 +523,8 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1");
|
||||
// Share the same button mapping with non-Nintendo controllers
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
||||
|
||||
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
|
||||
// driver on Linux.
|
||||
@@ -800,16 +802,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
|
||||
|
||||
// This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
|
||||
// We will add those afterwards
|
||||
// This list also excludes Screenshot since there's not really a mapping for that
|
||||
ButtonBindings switch_to_sdl_button;
|
||||
|
||||
if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO ||
|
||||
SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
|
||||
SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) {
|
||||
switch_to_sdl_button = GetNintendoButtonBinding(joystick);
|
||||
} else {
|
||||
switch_to_sdl_button = GetDefaultButtonBinding();
|
||||
}
|
||||
switch_to_sdl_button = GetDefaultButtonBinding(joystick);
|
||||
|
||||
// Add the missing bindings for ZL/ZR
|
||||
static constexpr ZButtonBindings switch_to_sdl_axis{{
|
||||
@@ -830,32 +825,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
|
||||
return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
|
||||
}
|
||||
|
||||
ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
|
||||
return {
|
||||
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
|
||||
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
|
||||
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
|
||||
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
|
||||
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
|
||||
{Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
|
||||
{Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
|
||||
{Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
|
||||
{Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
|
||||
{Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
|
||||
{Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
{Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
|
||||
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
|
||||
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
|
||||
};
|
||||
}
|
||||
|
||||
ButtonBindings SDLDriver::GetNintendoButtonBinding(
|
||||
ButtonBindings SDLDriver::GetDefaultButtonBinding(
|
||||
const std::shared_ptr<SDLJoystick>& joystick) const {
|
||||
// Default SL/SR mapping for pro controllers
|
||||
// Default SL/SR mapping for other controllers
|
||||
auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
||||
auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
||||
|
||||
@@ -869,10 +841,10 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding(
|
||||
}
|
||||
|
||||
return {
|
||||
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
|
||||
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
|
||||
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
|
||||
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
|
||||
std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
|
||||
{Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
|
||||
{Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
|
||||
{Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
|
||||
{Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
|
||||
{Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
|
||||
{Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
|
||||
|
@@ -100,11 +100,8 @@ private:
|
||||
int axis_y, float offset_x,
|
||||
float offset_y) const;
|
||||
|
||||
/// Returns the default button bindings list for generic controllers
|
||||
ButtonBindings GetDefaultButtonBinding() const;
|
||||
|
||||
/// Returns the default button bindings list for nintendo controllers
|
||||
ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
|
||||
/// Returns the default button bindings list
|
||||
ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
|
||||
|
||||
/// Returns the button mappings from a single controller
|
||||
ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
|
||||
|
@@ -74,7 +74,7 @@ Common::Input::DriverResult JoyconDriver::InitializeDevice() {
|
||||
passive_enabled = false;
|
||||
irs_enabled = false;
|
||||
input_only_device = false;
|
||||
gyro_sensitivity = Joycon::GyroSensitivity::DPS250;
|
||||
gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
|
||||
gyro_performance = Joycon::GyroPerformance::HZ833;
|
||||
accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
|
||||
accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
|
||||
@@ -173,12 +173,6 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
|
||||
|
||||
void JoyconDriver::OnNewData(std::span<u8> buffer) {
|
||||
const auto report_mode = static_cast<ReportMode>(buffer[0]);
|
||||
std::string str = "";
|
||||
for (u8 dat : buffer) {
|
||||
str += fmt::format("{:02x} ", dat);
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Input, "{}", str);
|
||||
|
||||
// Packages can be a little bit inconsistent. Average the delta time to provide a smoother
|
||||
// motion experience
|
||||
@@ -209,7 +203,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
|
||||
if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) {
|
||||
InputReportActive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||
//calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
|
||||
// calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
|
||||
}
|
||||
|
||||
const RingStatus ring_status{
|
||||
@@ -270,8 +264,8 @@ Common::Input::DriverResult JoyconDriver::SetPollingMode() {
|
||||
|
||||
if (motion_enabled && supported_features.motion) {
|
||||
generic_protocol->EnableImu(true);
|
||||
//generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
|
||||
// accelerometer_sensitivity, accelerometer_performance);
|
||||
generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
|
||||
accelerometer_sensitivity, accelerometer_performance);
|
||||
} else {
|
||||
generic_protocol->EnableImu(false);
|
||||
}
|
||||
|
@@ -51,7 +51,7 @@ Common::Input::DriverResult GenericProtocol::GetControllerType(ControllerType& c
|
||||
|
||||
Common::Input::DriverResult GenericProtocol::EnableImu(bool enable) {
|
||||
ScopedSetBlocking sb(this);
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 3 : 0)};
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 2 : 0)};
|
||||
return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
|
||||
}
|
||||
|
||||
|
@@ -36,7 +36,7 @@ void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& moti
|
||||
}
|
||||
|
||||
if (ring_status.is_enabled) {
|
||||
// UpdateRing(data.ring_input, ring_status);
|
||||
// UpdateRing(data.ring_input, ring_status);
|
||||
}
|
||||
|
||||
callbacks.on_battery_data(data.battery_status);
|
||||
@@ -347,18 +347,14 @@ s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
|
||||
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) const {
|
||||
MotionData motion{};
|
||||
for (int i = 0; i < 1; i++) {
|
||||
const auto& accel_cal = motion_calibration.accelerometer;
|
||||
const auto& gyro_cal = motion_calibration.gyro;
|
||||
const s16 raw_accel_x = input.motion_input[1 + (i*6)];
|
||||
const s16 raw_accel_y = input.motion_input[0 + (i * 6)];
|
||||
const s16 raw_accel_z = input.motion_input[2 + (i * 6)];
|
||||
const s16 raw_gyro_x = input.motion_input[4 + (i * 6)];
|
||||
const s16 raw_gyro_y = input.motion_input[3 + (i * 6)];
|
||||
const s16 raw_gyro_z = input.motion_input[5 + (i * 6)];
|
||||
|
||||
LOG_WARNING(Input, "raw sample{}, accel ({},{},{}), gyro ({},{},{})", i, raw_accel_x, raw_accel_y, raw_accel_z,
|
||||
raw_gyro_x, raw_gyro_y, raw_gyro_z);
|
||||
const s16 raw_accel_x = input.motion_input[1];
|
||||
const s16 raw_accel_y = input.motion_input[0];
|
||||
const s16 raw_accel_z = input.motion_input[2];
|
||||
const s16 raw_gyro_x = input.motion_input[4];
|
||||
const s16 raw_gyro_y = input.motion_input[3];
|
||||
const s16 raw_gyro_z = input.motion_input[5];
|
||||
|
||||
motion.delta_timestamp = motion_status.delta_time;
|
||||
motion.accel_x =
|
||||
@@ -371,11 +367,72 @@ MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
|
||||
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
|
||||
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
|
||||
|
||||
std::array<u8, 18> gyro_raw{};
|
||||
memcpy(gyro_raw.data(), &input.motion_input[3], 2);
|
||||
memcpy(gyro_raw.data() + 2, &input.motion_input[4], 2);
|
||||
memcpy(gyro_raw.data() + 4, &input.motion_input[5], 2);
|
||||
memcpy(gyro_raw.data() + 6, &input.motion_input[9], 2);
|
||||
memcpy(gyro_raw.data() + 8, &input.motion_input[10], 2);
|
||||
memcpy(gyro_raw.data() + 10, &input.motion_input[11], 2);
|
||||
memcpy(gyro_raw.data() + 12, &input.motion_input[15], 2);
|
||||
memcpy(gyro_raw.data() + 14, &input.motion_input[16], 2);
|
||||
memcpy(gyro_raw.data() + 16, &input.motion_input[17], 2);
|
||||
|
||||
LOG_ERROR(Input, "calibrated samples{}, accel ({},{},{}), gyro ({},{},{})", i, motion.accel_x, motion.accel_y,
|
||||
motion.accel_z, motion.gyro_x, motion.gyro_y, motion.gyro_z);
|
||||
u32 aa = (gyro_raw[4] & 0xe0) << 0xb | (u32)gyro_raw[5] << 0x13 | (u32)gyro_raw[6] << 0x1b;
|
||||
u32 bb =
|
||||
(int)((gyro_raw[0] & 0xe0) << 0xb | (u32)gyro_raw[1] << 0x13 | (u32)gyro_raw[2] << 0x1b) >>
|
||||
0x10;
|
||||
u32 cc =
|
||||
(int)((gyro_raw[2] & 0xe0) << 0xb | (u32)gyro_raw[3] << 0x13 | (u32)gyro_raw[4] << 0x1b) >>
|
||||
0x10;
|
||||
// u32 aa = (gyro_raw[0] & 0xe0) << 0xb | gyro_raw[1] << 0x13 | gyro_raw[2] << 0x1b;
|
||||
// u32 bb = (gyro_raw[2] & 0xe0) << 0xb | gyro_raw[3] << 0x13 | gyro_raw[4] << 0x1b;
|
||||
// u32 cc = (gyro_raw[4] & 0xe0) << 0xb | gyro_raw[5] << 0x13 | gyro_raw[6] << 0x1b;
|
||||
// u32 dd = (gyro_raw[6] & 0xe0) << 0xb | gyro_raw[7] << 0x13 | gyro_raw[8] << 0x1b;
|
||||
// u32 ee = (gyro_raw[8] & 0xe0) << 0xb | gyro_raw[9] << 0x13 | gyro_raw[10] << 0x1b;
|
||||
// u32 ff = (gyro_raw[10] & 0xe0) << 0xb | gyro_raw[11] << 0x13 | gyro_raw[12] << 0x1b;
|
||||
|
||||
// u32 gg = (gyro_raw[0xc] & 0xe0) << 0x13 | gyro_raw[0xd] << 0x1b;
|
||||
// u32 hh = (gyro_raw[0xd] & 0xe0) << 0x13 | gyro_raw[0xe] << 0x1b;
|
||||
// u32 ii = (gyro_raw[0xe] & 0xe0) << 0x13 | gyro_raw[0xf] << 0x1b;
|
||||
|
||||
u32 uVar21 = (gyro_raw[0] & 0xf0) << 7 | (u32)gyro_raw[1] << 0xf | (u32)gyro_raw[2] << 0x17 |
|
||||
(u32)gyro_raw[3] << 0x1f;
|
||||
u32 uVar18 = (gyro_raw[3] & 0xfe) << 10 | (u32)gyro_raw[4] << 0x12 | (u32)gyro_raw[5] << 0x1a;
|
||||
u32 uVar20 = (gyro_raw[5] & 0xc0) << 5 | (u32)gyro_raw[6] << 0xd | (u32)gyro_raw[7] << 0x15 |
|
||||
(u32)gyro_raw[8] << 0x1d;
|
||||
|
||||
u32 iVar13 = (int)uVar21 >> 0xb;
|
||||
s32 iVar9 = iVar13 - ((int)((gyro_raw[8] & 0xf8) << 0x10 | (u32)gyro_raw[9] << 0x18) >> 0x13);
|
||||
u32 iVar26 = (int)uVar18 >> 0xb;
|
||||
s32 iVar10 = iVar26 - ((int)((u32)gyro_raw[10] << 0x13 | (u32)gyro_raw[0xb] << 0x1b) >> 0x13);
|
||||
u32 iVar25 = (int)uVar20 >> 0xb;
|
||||
s32 iVar11 = iVar25 - ((int)((gyro_raw[0xb] & 0xe0) << 0xe | (u32)gyro_raw[0xc] << 0x16 |
|
||||
(u32)gyro_raw[0xd] << 0x1e) >>
|
||||
0x13);
|
||||
|
||||
std::string str = "";
|
||||
for (u8 dddd : gyro_raw) {
|
||||
str += fmt::format("{:02x} ", dddd);
|
||||
}
|
||||
// LOG_ERROR(Input, "{}", str);
|
||||
|
||||
// LOG_ERROR(Input, "{}, {:08x} {:08x} {:08x} {:08x} {:08x} {:08x}, {:08x} {:08x} {:08x}",
|
||||
// gyro_raw[0] & 3, aa, bb, cc, dd, ee, ff, gg, hh, ii);
|
||||
|
||||
// LOG_ERROR(Input, "{}, {} {} {}",
|
||||
// gyro_raw[0] & 3, iVar9, iVar10, iVar11);
|
||||
LOG_ERROR(Input, "{}, {:08x} {:08x} {:08x}", gyro_raw[0] & 3, aa, bb, cc);
|
||||
//
|
||||
// TODO(German77): Return all three samples data
|
||||
|
||||
motion.accel_x = 0;
|
||||
motion.accel_y = 0;
|
||||
motion.accel_z = 0;
|
||||
motion.gyro_x = static_cast<float>(iVar9) / 5000000.0f;
|
||||
motion.gyro_y = static_cast<float>(iVar10) / 5000000.0f;
|
||||
motion.gyro_z = static_cast<float>(iVar11) / 5000000.0f;
|
||||
|
||||
return motion;
|
||||
}
|
||||
|
||||
|
@@ -274,6 +274,7 @@ add_library(video_core STATIC
|
||||
vulkan_common/vulkan_wrapper.h
|
||||
vulkan_common/nsight_aftermath_tracker.cpp
|
||||
vulkan_common/nsight_aftermath_tracker.h
|
||||
vulkan_common/vma.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(video_core)
|
||||
@@ -291,7 +292,7 @@ target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||
|
||||
add_dependencies(video_core host_shaders)
|
||||
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
|
||||
target_link_libraries(video_core PRIVATE sirit Vulkan::Headers vma)
|
||||
target_link_libraries(video_core PRIVATE sirit Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
|
||||
|
||||
if (ENABLE_NSIGHT_AFTERMATH)
|
||||
if (NOT DEFINED ENV{NSIGHT_AFTERMATH_SDK})
|
||||
@@ -324,6 +325,9 @@ else()
|
||||
|
||||
# xbyak
|
||||
set_source_files_properties(macro/macro_jit_x64.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-shadow")
|
||||
|
||||
# VMA
|
||||
set_source_files_properties(vulkan_common/vma.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-unused-variable;-Wno-unused-parameter;-Wno-missing-field-initializers")
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
|
@@ -234,9 +234,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
|
||||
if (has_new_downloads) {
|
||||
memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
|
||||
}
|
||||
tmp_buffer.resize_destructive(amount);
|
||||
cpu_memory.ReadBlockUnsafe(*cpu_src_address, tmp_buffer.data(), amount);
|
||||
cpu_memory.WriteBlockUnsafe(*cpu_dest_address, tmp_buffer.data(), amount);
|
||||
|
||||
Core::Memory::CpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadWrite> tmp(
|
||||
cpu_memory, *cpu_src_address, amount, &tmp_buffer);
|
||||
tmp.SetAddressAndSize(*cpu_dest_address, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
@@ -12,6 +13,8 @@
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
|
||||
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
|
||||
Control::ChannelState& channel_state_)
|
||||
: gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_,
|
||||
@@ -74,25 +77,16 @@ bool DmaPusher::Step() {
|
||||
}
|
||||
|
||||
// Push buffer non-empty, read a word
|
||||
command_headers.resize_destructive(command_list_header.size);
|
||||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
if (dma_state.method < MacroRegistersStart) {
|
||||
if (Settings::IsGPULevelHigh()) {
|
||||
memory_manager.ReadBlock(dma_state.dma_get, command_headers.data(),
|
||||
command_list_header.size * sizeof(u32));
|
||||
} else {
|
||||
memory_manager.ReadBlockUnsafe(dma_state.dma_get, command_headers.data(),
|
||||
command_list_header.size * sizeof(u32));
|
||||
}
|
||||
} else {
|
||||
const size_t copy_size = command_list_header.size * sizeof(u32);
|
||||
if (dma_state.method >= MacroRegistersStart) {
|
||||
if (subchannels[dma_state.subchannel]) {
|
||||
subchannels[dma_state.subchannel]->current_dirty =
|
||||
memory_manager.IsMemoryDirty(dma_state.dma_get, copy_size);
|
||||
subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(
|
||||
dma_state.dma_get, command_list_header.size * sizeof(u32));
|
||||
}
|
||||
memory_manager.ReadBlockUnsafe(dma_state.dma_get, command_headers.data(), copy_size);
|
||||
}
|
||||
ProcessCommands(command_headers);
|
||||
Core::Memory::GpuGuestMemory<Tegra::CommandHeader,
|
||||
Core::Memory::GuestMemoryFlags::UnsafeRead>
|
||||
headers(memory_manager, dma_state.dma_get, command_list_header.size, &command_headers);
|
||||
ProcessCommands(headers);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "common/algorithm.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/engine_upload.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
@@ -46,15 +47,11 @@ void State::ProcessData(const u32* data, size_t num_data) {
|
||||
void State::ProcessData(std::span<const u8> read_buffer) {
|
||||
const GPUVAddr address{regs.dest.Address()};
|
||||
if (is_linear) {
|
||||
if (regs.line_count == 1) {
|
||||
rasterizer->AccelerateInlineToMemory(address, copy_size, read_buffer);
|
||||
} else {
|
||||
for (size_t line = 0; line < regs.line_count; ++line) {
|
||||
const GPUVAddr dest_line = address + line * regs.dest.pitch;
|
||||
std::span<const u8> buffer(read_buffer.data() + line * regs.line_length_in,
|
||||
regs.line_length_in);
|
||||
rasterizer->AccelerateInlineToMemory(dest_line, regs.line_length_in, buffer);
|
||||
}
|
||||
for (size_t line = 0; line < regs.line_count; ++line) {
|
||||
const GPUVAddr dest_line = address + line * regs.dest.pitch;
|
||||
std::span<const u8> buffer(read_buffer.data() + line * regs.line_length_in,
|
||||
regs.line_length_in);
|
||||
rasterizer->AccelerateInlineToMemory(dest_line, regs.line_length_in, buffer);
|
||||
}
|
||||
} else {
|
||||
u32 width = regs.dest.width;
|
||||
@@ -70,13 +67,14 @@ void State::ProcessData(std::span<const u8> read_buffer) {
|
||||
const std::size_t dst_size = Tegra::Texture::CalculateSize(
|
||||
true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
|
||||
regs.dest.BlockHeight(), regs.dest.BlockDepth());
|
||||
tmp_buffer.resize_destructive(dst_size);
|
||||
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
|
||||
Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
|
||||
regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
|
||||
x_elements, regs.line_count, regs.dest.BlockHeight(),
|
||||
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp(memory_manager, address, dst_size, &tmp_buffer);
|
||||
|
||||
Tegra::Texture::SwizzleSubrect(tmp, read_buffer, bytes_per_pixel, width, regs.dest.height,
|
||||
regs.dest.depth, x_offset, regs.dest.y, x_elements,
|
||||
regs.line_count, regs.dest.BlockHeight(),
|
||||
regs.dest.BlockDepth(), regs.line_length_in);
|
||||
memory_manager.WriteBlockCached(address, tmp_buffer.data(), dst_size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -84,7 +84,6 @@ Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const {
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
return tic_entry;
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
@@ -679,17 +680,14 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
const GPUVAddr tic_address_gpu{regs.tex_header.Address() +
|
||||
tic_index * sizeof(Texture::TICEntry)};
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
return tic_entry;
|
||||
}
|
||||
|
||||
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
const GPUVAddr tsc_address_gpu{regs.tex_sampler.Address() +
|
||||
tsc_index * sizeof(Texture::TSCEntry)};
|
||||
|
||||
Texture::TSCEntry tsc_entry;
|
||||
memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
return tsc_entry;
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_dma.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -130,11 +131,12 @@ void MaxwellDMA::Launch() {
|
||||
UNIMPLEMENTED_IF(regs.offset_out % 16 != 0);
|
||||
read_buffer.resize_destructive(16);
|
||||
for (u32 offset = 0; offset < regs.line_length_in; offset += 16) {
|
||||
memory_manager.ReadBlock(
|
||||
convert_linear_2_blocklinear_addr(regs.offset_in + offset),
|
||||
read_buffer.data(), read_buffer.size());
|
||||
memory_manager.WriteBlockCached(regs.offset_out + offset, read_buffer.data(),
|
||||
read_buffer.size());
|
||||
Core::Memory::GpuGuestMemoryScoped<
|
||||
u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager,
|
||||
convert_linear_2_blocklinear_addr(regs.offset_in + offset),
|
||||
16, &read_buffer);
|
||||
tmp_write_buffer.SetAddressAndSize(regs.offset_out + offset, 16);
|
||||
}
|
||||
} else if (is_src_pitch && !is_dst_pitch) {
|
||||
UNIMPLEMENTED_IF(regs.line_length_in % 16 != 0);
|
||||
@@ -142,20 +144,19 @@ void MaxwellDMA::Launch() {
|
||||
UNIMPLEMENTED_IF(regs.offset_out % 16 != 0);
|
||||
read_buffer.resize_destructive(16);
|
||||
for (u32 offset = 0; offset < regs.line_length_in; offset += 16) {
|
||||
memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(),
|
||||
read_buffer.size());
|
||||
memory_manager.WriteBlockCached(
|
||||
convert_linear_2_blocklinear_addr(regs.offset_out + offset),
|
||||
read_buffer.data(), read_buffer.size());
|
||||
Core::Memory::GpuGuestMemoryScoped<
|
||||
u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager, regs.offset_in + offset, 16, &read_buffer);
|
||||
tmp_write_buffer.SetAddressAndSize(
|
||||
convert_linear_2_blocklinear_addr(regs.offset_out + offset), 16);
|
||||
}
|
||||
} else {
|
||||
if (!accelerate.BufferCopy(regs.offset_in, regs.offset_out, regs.line_length_in)) {
|
||||
read_buffer.resize_destructive(regs.line_length_in);
|
||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(),
|
||||
regs.line_length_in,
|
||||
VideoCommon::CacheType::NoBufferCache);
|
||||
memory_manager.WriteBlockCached(regs.offset_out, read_buffer.data(),
|
||||
regs.line_length_in);
|
||||
Core::Memory::GpuGuestMemoryScoped<
|
||||
u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager, regs.offset_in, regs.line_length_in,
|
||||
&read_buffer);
|
||||
tmp_write_buffer.SetAddressAndSize(regs.offset_out, regs.line_length_in);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,17 +223,15 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
|
||||
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
|
||||
|
||||
const size_t dst_size = dst_operand.pitch * regs.line_count;
|
||||
read_buffer.resize_destructive(src_size);
|
||||
write_buffer.resize_destructive(dst_size);
|
||||
|
||||
memory_manager.ReadBlock(src_operand.address, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(dst_operand.address, write_buffer.data(), dst_size);
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
|
||||
memory_manager, src_operand.address, src_size, &read_buffer);
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager, dst_operand.address, dst_size, &write_buffer);
|
||||
|
||||
UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
|
||||
src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
|
||||
dst_operand.pitch);
|
||||
|
||||
memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
|
||||
UnswizzleSubrect(tmp_write_buffer, tmp_read_buffer, bytes_per_pixel, width, height, depth,
|
||||
x_offset, src_params.origin.y, x_elements, regs.line_count, block_height,
|
||||
block_depth, dst_operand.pitch);
|
||||
}
|
||||
|
||||
void MaxwellDMA::CopyPitchToBlockLinear() {
|
||||
@@ -287,18 +286,17 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
|
||||
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
|
||||
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
|
||||
|
||||
read_buffer.resize_destructive(src_size);
|
||||
write_buffer.resize_destructive(dst_size);
|
||||
GPUVAddr src_addr = regs.offset_in;
|
||||
GPUVAddr dst_addr = regs.offset_out;
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
|
||||
memory_manager, src_addr, src_size, &read_buffer);
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager, dst_addr, dst_size, &write_buffer);
|
||||
|
||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
|
||||
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
|
||||
dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
|
||||
regs.pitch_in);
|
||||
|
||||
memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
SwizzleSubrect(tmp_write_buffer, tmp_read_buffer, bytes_per_pixel, width, height, depth,
|
||||
x_offset, dst_params.origin.y, x_elements, regs.line_count, block_height,
|
||||
block_depth, regs.pitch_in);
|
||||
}
|
||||
|
||||
void MaxwellDMA::CopyBlockLinearToBlockLinear() {
|
||||
@@ -342,23 +340,20 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {
|
||||
const u32 pitch = x_elements * bytes_per_pixel;
|
||||
const size_t mid_buffer_size = pitch * regs.line_count;
|
||||
|
||||
read_buffer.resize_destructive(src_size);
|
||||
write_buffer.resize_destructive(dst_size);
|
||||
|
||||
intermediate_buffer.resize_destructive(mid_buffer_size);
|
||||
|
||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_read_buffer(
|
||||
memory_manager, regs.offset_in, src_size, &read_buffer);
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadCachedWrite>
|
||||
tmp_write_buffer(memory_manager, regs.offset_out, dst_size, &write_buffer);
|
||||
|
||||
UnswizzleSubrect(intermediate_buffer, read_buffer, bytes_per_pixel, src_width, src.height,
|
||||
UnswizzleSubrect(intermediate_buffer, tmp_read_buffer, bytes_per_pixel, src_width, src.height,
|
||||
src.depth, src_x_offset, src.origin.y, x_elements, regs.line_count,
|
||||
src.block_size.height, src.block_size.depth, pitch);
|
||||
|
||||
SwizzleSubrect(write_buffer, intermediate_buffer, bytes_per_pixel, dst_width, dst.height,
|
||||
SwizzleSubrect(tmp_write_buffer, intermediate_buffer, bytes_per_pixel, dst_width, dst.height,
|
||||
dst.depth, dst_x_offset, dst.origin.y, x_elements, regs.line_count,
|
||||
dst.block_size.height, dst.block_size.depth, pitch);
|
||||
|
||||
memory_manager.WriteBlockCached(regs.offset_out, write_buffer.data(), dst_size);
|
||||
}
|
||||
|
||||
void MaxwellDMA::ReleaseSemaphore() {
|
||||
|
@@ -159,11 +159,11 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
|
||||
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
|
||||
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
|
||||
impl->tmp_buffer.resize_destructive(src_size);
|
||||
memory_manager.ReadBlock(src.Address(), impl->tmp_buffer.data(), src_size);
|
||||
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead> tmp_buffer(
|
||||
memory_manager, src.Address(), src_size, &impl->tmp_buffer);
|
||||
|
||||
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
|
||||
|
||||
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
|
||||
|
||||
impl->src_buffer.resize_destructive(src_copy_size);
|
||||
@@ -200,12 +200,11 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
|
||||
|
||||
impl->dst_buffer.resize_destructive(dst_copy_size);
|
||||
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
UnswizzleSubrect(impl->src_buffer, impl->tmp_buffer, src_bytes_per_pixel, src.width,
|
||||
src.height, src.depth, config.src_x0, config.src_y0, src_extent_x,
|
||||
src_extent_y, src.block_height, src.block_depth,
|
||||
src_extent_x * src_bytes_per_pixel);
|
||||
UnswizzleSubrect(impl->src_buffer, tmp_buffer, src_bytes_per_pixel, src.width, src.height,
|
||||
src.depth, config.src_x0, config.src_y0, src_extent_x, src_extent_y,
|
||||
src.block_height, src.block_depth, src_extent_x * src_bytes_per_pixel);
|
||||
} else {
|
||||
process_pitch_linear(false, impl->tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
|
||||
process_pitch_linear(false, tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
|
||||
src.pitch, config.src_x0, config.src_y0, src_bytes_per_pixel);
|
||||
}
|
||||
|
||||
@@ -221,20 +220,18 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
|
||||
}
|
||||
|
||||
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
|
||||
impl->tmp_buffer.resize_destructive(dst_size);
|
||||
memory_manager.ReadBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::SafeReadWrite>
|
||||
tmp_buffer2(memory_manager, dst.Address(), dst_size, &impl->tmp_buffer);
|
||||
|
||||
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
SwizzleSubrect(impl->tmp_buffer, impl->dst_buffer, dst_bytes_per_pixel, dst.width,
|
||||
dst.height, dst.depth, config.dst_x0, config.dst_y0, dst_extent_x,
|
||||
dst_extent_y, dst.block_height, dst.block_depth,
|
||||
dst_extent_x * dst_bytes_per_pixel);
|
||||
SwizzleSubrect(tmp_buffer2, impl->dst_buffer, dst_bytes_per_pixel, dst.width, dst.height,
|
||||
dst.depth, config.dst_x0, config.dst_y0, dst_extent_x, dst_extent_y,
|
||||
dst.block_height, dst.block_depth, dst_extent_x * dst_bytes_per_pixel);
|
||||
} else {
|
||||
process_pitch_linear(true, impl->dst_buffer, impl->tmp_buffer, dst_extent_x, dst_extent_y,
|
||||
process_pitch_linear(true, impl->dst_buffer, tmp_buffer2, dst_extent_x, dst_extent_y,
|
||||
dst.pitch, config.dst_x0, config.dst_y0,
|
||||
static_cast<size_t>(dst_bytes_per_pixel));
|
||||
}
|
||||
memory_manager.WriteBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -10,13 +10,13 @@
|
||||
#include "core/device_memory.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/invalidation_accumulator.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Tegra {
|
||||
using Core::Memory::GuestMemoryFlags;
|
||||
|
||||
std::atomic<size_t> MemoryManager::unique_identifier_generator{};
|
||||
|
||||
@@ -587,13 +587,10 @@ void MemoryManager::InvalidateRegion(GPUVAddr gpu_addr, size_t size,
|
||||
|
||||
void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size,
|
||||
VideoCommon::CacheType which) {
|
||||
tmp_buffer.resize_destructive(size);
|
||||
ReadBlock(gpu_src_addr, tmp_buffer.data(), size, which);
|
||||
|
||||
// The output block must be flushed in case it has data modified from the GPU.
|
||||
// Fixes NPC geometry in Zombie Panic in Wonderland DX
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, GuestMemoryFlags::SafeReadWrite> data(
|
||||
*this, gpu_src_addr, size);
|
||||
data.SetAddressAndSize(gpu_dest_addr, size);
|
||||
FlushRegion(gpu_dest_addr, size, which);
|
||||
WriteBlock(gpu_dest_addr, tmp_buffer.data(), size, which);
|
||||
}
|
||||
|
||||
bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const {
|
||||
@@ -758,4 +755,23 @@ void MemoryManager::FlushCaching() {
|
||||
accumulator->Clear();
|
||||
}
|
||||
|
||||
const u8* MemoryManager::GetSpan(const GPUVAddr src_addr, const std::size_t size) const {
|
||||
auto cpu_addr = GpuToCpuAddress(src_addr);
|
||||
if (cpu_addr) {
|
||||
return memory.GetSpan(*cpu_addr, size);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8* MemoryManager::GetSpan(const GPUVAddr src_addr, const std::size_t size) {
|
||||
if (!IsContinuousRange(src_addr, size)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto cpu_addr = GpuToCpuAddress(src_addr);
|
||||
if (cpu_addr) {
|
||||
return memory.GetSpan(*cpu_addr, size);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Tegra
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "common/range_map.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/cache_types.h"
|
||||
#include "video_core/pte_kind.h"
|
||||
|
||||
@@ -62,6 +63,20 @@ public:
|
||||
[[nodiscard]] u8* GetPointer(GPUVAddr addr);
|
||||
[[nodiscard]] const u8* GetPointer(GPUVAddr addr) const;
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T* GetPointer(GPUVAddr addr) {
|
||||
const auto address{GpuToCpuAddress(addr)};
|
||||
if (!address) {
|
||||
return {};
|
||||
}
|
||||
return memory.GetPointer(*address);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] const T* GetPointer(GPUVAddr addr) const {
|
||||
return GetPointer<T*>(addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* ReadBlock and WriteBlock are full read and write operations over virtual
|
||||
* GPU Memory. It's important to use these when GPU memory may not be continuous
|
||||
@@ -139,6 +154,9 @@ public:
|
||||
|
||||
void FlushCaching();
|
||||
|
||||
const u8* GetSpan(const GPUVAddr src_addr, const std::size_t size) const;
|
||||
u8* GetSpan(const GPUVAddr src_addr, const std::size_t size);
|
||||
|
||||
private:
|
||||
template <bool is_big_pages, typename FuncMapped, typename FuncReserved, typename FuncUnmapped>
|
||||
inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/control/channel_state.h"
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
@@ -598,6 +599,10 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
|
||||
[&](ImageId id, Image&) { deleted_images.push_back(id); });
|
||||
for (const ImageId id : deleted_images) {
|
||||
Image& image = slot_images[id];
|
||||
if (True(image.flags & ImageFlagBits::CpuModified)) {
|
||||
continue;
|
||||
}
|
||||
image.flags |= ImageFlagBits::CpuModified;
|
||||
if (True(image.flags & ImageFlagBits::Remapped)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1022,19 +1027,19 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
|
||||
runtime.AccelerateImageUpload(image, staging, uploads);
|
||||
return;
|
||||
}
|
||||
const size_t guest_size_bytes = image.guest_size_bytes;
|
||||
swizzle_data_buffer.resize_destructive(guest_size_bytes);
|
||||
gpu_memory->ReadBlockUnsafe(gpu_addr, swizzle_data_buffer.data(), guest_size_bytes);
|
||||
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(
|
||||
*gpu_memory, gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
|
||||
|
||||
if (True(image.flags & ImageFlagBits::Converted)) {
|
||||
unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
|
||||
auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data_buffer,
|
||||
unswizzle_data_buffer);
|
||||
auto copies =
|
||||
UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, unswizzle_data_buffer);
|
||||
ConvertImage(unswizzle_data_buffer, image.info, mapped_span, copies);
|
||||
image.UploadMemory(staging, copies);
|
||||
} else {
|
||||
const auto copies =
|
||||
UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data_buffer, mapped_span);
|
||||
UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, mapped_span);
|
||||
image.UploadMemory(staging, copies);
|
||||
}
|
||||
}
|
||||
@@ -1227,11 +1232,12 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
|
||||
decode->image_id = image_id;
|
||||
async_decodes.push_back(std::move(decode));
|
||||
|
||||
Common::ScratchBuffer<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes);
|
||||
const size_t guest_size_bytes = image.guest_size_bytes;
|
||||
swizzle_data_buffer.resize_destructive(guest_size_bytes);
|
||||
gpu_memory->ReadBlockUnsafe(image.gpu_addr, swizzle_data_buffer.data(), guest_size_bytes);
|
||||
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data_buffer,
|
||||
static Common::ScratchBuffer<u8> local_unswizzle_data_buffer;
|
||||
local_unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
|
||||
Core::Memory::GpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(
|
||||
*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
|
||||
|
||||
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data,
|
||||
local_unswizzle_data_buffer);
|
||||
const size_t out_size = MapSizeBytes(image);
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/compatible_formats.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -544,17 +545,15 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
|
||||
tile_size.height, info.tile_width_spacing);
|
||||
const size_t subresource_size = sizes[level];
|
||||
|
||||
tmp_buffer.resize_destructive(subresource_size);
|
||||
const std::span<u8> dst(tmp_buffer);
|
||||
|
||||
for (s32 layer = 0; layer < info.resources.layers; ++layer) {
|
||||
const std::span<const u8> src = input.subspan(host_offset);
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes());
|
||||
{
|
||||
Core::Memory::GpuGuestMemoryScoped<u8, Core::Memory::GuestMemoryFlags::UnsafeReadWrite>
|
||||
dst(gpu_memory, gpu_addr + guest_offset, subresource_size, &tmp_buffer);
|
||||
|
||||
SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,
|
||||
num_tiles.depth, block.height, block.depth);
|
||||
|
||||
gpu_memory.WriteBlockUnsafe(gpu_addr + guest_offset, dst.data(), dst.size_bytes());
|
||||
SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,
|
||||
num_tiles.depth, block.height, block.depth);
|
||||
}
|
||||
|
||||
host_offset += host_bytes_per_layer;
|
||||
guest_offset += layer_stride;
|
||||
@@ -837,6 +836,7 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
|
||||
const Extent3D size = info.size;
|
||||
|
||||
if (info.type == ImageType::Linear) {
|
||||
ASSERT(output.size_bytes() >= guest_size_bytes);
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), guest_size_bytes);
|
||||
|
||||
ASSERT((info.pitch >> bpp_log2) << bpp_log2 == info.pitch);
|
||||
@@ -904,16 +904,6 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
|
||||
return copies;
|
||||
}
|
||||
|
||||
BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
|
||||
const ImageBase& image, std::span<u8> output) {
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr, output.data(), image.guest_size_bytes);
|
||||
return BufferCopy{
|
||||
.src_offset = 0,
|
||||
.dst_offset = 0,
|
||||
.size = image.guest_size_bytes,
|
||||
};
|
||||
}
|
||||
|
||||
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
|
||||
std::span<BufferImageCopy> copies) {
|
||||
u32 output_offset = 0;
|
||||
|
@@ -66,9 +66,6 @@ struct OverlapResult {
|
||||
Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
|
||||
std::span<const u8> input, std::span<u8> output);
|
||||
|
||||
[[nodiscard]] BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
|
||||
const ImageBase& image, std::span<u8> output);
|
||||
|
||||
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
|
||||
std::span<BufferImageCopy> copies);
|
||||
|
||||
|
@@ -485,7 +485,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state2 && (is_radv || is_qualcomm)) {
|
||||
if (extensions.extended_dynamic_state2 && is_radv) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
|
||||
LOG_WARNING(
|
||||
@@ -498,6 +498,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state2 && is_qualcomm) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
|
||||
version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
|
||||
// Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
|
||||
features.extended_dynamic_state2.extendedDynamicState2 = false;
|
||||
features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
|
||||
features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
|
||||
extensions.extended_dynamic_state2 = false;
|
||||
loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.extended_dynamic_state3 && is_radv) {
|
||||
LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
|
||||
features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
|
||||
@@ -512,8 +526,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
dynamic_state3_enables = false;
|
||||
}
|
||||
}
|
||||
if (extensions.vertex_input_dynamic_state && (is_radv || is_qualcomm)) {
|
||||
// Qualcomm S8gen2 drivers do not properly support vertex_input_dynamic_state.
|
||||
if (extensions.vertex_input_dynamic_state && is_radv) {
|
||||
// TODO(ameerj): Blacklist only offending driver versions
|
||||
// TODO(ameerj): Confirm if RDNA1 is affected
|
||||
const bool is_rdna2 =
|
||||
@@ -526,6 +539,19 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
|
||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||
if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
|
||||
version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
|
||||
// Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state.
|
||||
LOG_WARNING(
|
||||
Render_Vulkan,
|
||||
"Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
|
||||
features.vertex_input_dynamic_state.vertexInputDynamicState = false;
|
||||
extensions.vertex_input_dynamic_state = false;
|
||||
loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
sets_per_pool = 64;
|
||||
if (extensions.extended_dynamic_state3 && is_amd_driver &&
|
||||
@@ -774,6 +800,17 @@ bool Device::ShouldBoostClocks() const {
|
||||
return validated_driver && !is_steam_deck && !is_debugging;
|
||||
}
|
||||
|
||||
bool Device::HasTimelineSemaphore() const {
|
||||
if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
|
||||
GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
// Timeline semaphores do not work properly on all Qualcomm drivers.
|
||||
// They generally work properly with Turnip drivers, but are problematic on some devices
|
||||
// (e.g. ZTE handsets with Snapdragon 870).
|
||||
return false;
|
||||
}
|
||||
return features.timeline_semaphore.timelineSemaphore;
|
||||
}
|
||||
|
||||
bool Device::GetSuitability(bool requires_swapchain) {
|
||||
// Assume we will be suitable.
|
||||
bool suitable = true;
|
||||
|
@@ -528,13 +528,7 @@ public:
|
||||
return extensions.shader_atomic_int64;
|
||||
}
|
||||
|
||||
bool HasTimelineSemaphore() const {
|
||||
if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
||||
// Timeline semaphores do not work properly on all Qualcomm drivers.
|
||||
return false;
|
||||
}
|
||||
return features.timeline_semaphore.timelineSemaphore;
|
||||
}
|
||||
bool HasTimelineSemaphore() const;
|
||||
|
||||
/// Returns the minimum supported version of SPIR-V.
|
||||
u32 SupportedSpirvVersion() const {
|
||||
|
@@ -221,8 +221,8 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
|
||||
const VmaAllocationCreateInfo alloc_ci = {
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
|
||||
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
.preferredFlags = 0,
|
||||
.requiredFlags = 0,
|
||||
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
.memoryTypeBits = 0,
|
||||
.pool = VK_NULL_HANDLE,
|
||||
.pUserData = nullptr,
|
||||
|
@@ -3,9 +3,10 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <discord_rpc.h>
|
||||
#include <fmt/format.h>
|
||||
#include <httplib.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
@@ -31,7 +32,7 @@ void DiscordImpl::Pause() {
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
|
||||
static std::string GetGameString(const std::string& title) {
|
||||
std::string DiscordImpl::GetGameString(const std::string& title) {
|
||||
// Convert to lowercase
|
||||
std::string icon_name = Common::ToLower(title);
|
||||
|
||||
@@ -56,51 +57,60 @@ static std::string GetGameString(const std::string& title) {
|
||||
return icon_name;
|
||||
}
|
||||
|
||||
void DiscordImpl::Update() {
|
||||
void DiscordImpl::UpdateGameStatus(QNetworkReply* reply) {
|
||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
|
||||
const std::string default_image = "yuzu_logo";
|
||||
std::string game_cover_url = "https://yuzu-emu.org";
|
||||
std::string title;
|
||||
|
||||
DiscordRichPresence presence{};
|
||||
std::string url = default_image;
|
||||
|
||||
if (system.IsPoweredOn()) {
|
||||
system.GetAppLoader().ReadTitle(title);
|
||||
|
||||
// Used to format Icon URL for yuzu website game compatibility page
|
||||
std::string icon_name = GetGameString(title);
|
||||
|
||||
// New Check for game cover
|
||||
httplib::Client cli(game_cover_url);
|
||||
cli.set_connection_timeout(std::chrono::seconds(3));
|
||||
cli.set_read_timeout(std::chrono::seconds(3));
|
||||
|
||||
if (auto res = cli.Head(fmt::format("/images/game/boxart/{}.png", icon_name))) {
|
||||
if (res->status == 200) {
|
||||
game_cover_url += fmt::format("/images/game/boxart/{}.png", icon_name);
|
||||
} else {
|
||||
game_cover_url = "yuzu_logo";
|
||||
}
|
||||
} else {
|
||||
game_cover_url = "yuzu_logo";
|
||||
}
|
||||
|
||||
presence.largeImageKey = game_cover_url.c_str();
|
||||
presence.largeImageText = title.c_str();
|
||||
|
||||
presence.smallImageKey = default_image.c_str();
|
||||
presence.smallImageText = default_text.c_str();
|
||||
presence.state = title.c_str();
|
||||
presence.details = "Currently in game";
|
||||
} else {
|
||||
presence.largeImageKey = default_image.c_str();
|
||||
presence.largeImageText = default_text.c_str();
|
||||
presence.details = "Currently not in game";
|
||||
// Set game cover url only if it exist
|
||||
if (!reply->error() &&
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
url = game_url;
|
||||
}
|
||||
|
||||
presence.largeImageKey = url.c_str();
|
||||
presence.largeImageText = game_title.c_str();
|
||||
presence.smallImageKey = default_image.c_str();
|
||||
presence.smallImageText = default_text.c_str();
|
||||
presence.state = game_title.c_str();
|
||||
presence.details = "Currently in game";
|
||||
presence.startTimestamp = start_time;
|
||||
Discord_UpdatePresence(&presence);
|
||||
}
|
||||
void DiscordImpl::Update() {
|
||||
if (system.IsPoweredOn()) {
|
||||
system.GetAppLoader().ReadTitle(game_title);
|
||||
|
||||
// Used to format Icon URL for yuzu website game compatibility page
|
||||
std::string icon_name = GetGameString(game_title);
|
||||
game_url = fmt::format("https://yuzu-emu.org/images/game/boxart/{}.png", icon_name);
|
||||
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager();
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(QString::fromStdString(game_url)));
|
||||
request.setTransferTimeout(3000);
|
||||
QNetworkReply* rep = manager->get(request);
|
||||
|
||||
QObject::connect(manager, &QNetworkAccessManager::finished,
|
||||
[this](QNetworkReply* reply) { UpdateGameStatus(reply); });
|
||||
QObject::connect(manager, &QNetworkAccessManager::finished, manager,
|
||||
&QNetworkAccessManager::deleteLater);
|
||||
QObject::connect(manager, &QNetworkAccessManager::finished, rep,
|
||||
&QNetworkReply::deleteLater);
|
||||
return;
|
||||
}
|
||||
|
||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
DiscordRichPresence presence{};
|
||||
presence.largeImageKey = default_image.c_str();
|
||||
presence.largeImageText = default_text.c_str();
|
||||
presence.details = "Currently not in game";
|
||||
presence.startTimestamp = start_time;
|
||||
Discord_UpdatePresence(&presence);
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "yuzu/discord.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -19,6 +21,16 @@ public:
|
||||
void Pause() override;
|
||||
void Update() override;
|
||||
|
||||
private:
|
||||
std::string GetGameString(const std::string& title);
|
||||
void UpdateGameStatus(QNetworkReply* reply);
|
||||
|
||||
const std::string default_text = "yuzu is an emulator for the Nintendo Switch";
|
||||
const std::string default_image = "yuzu_logo";
|
||||
|
||||
std::string game_url{};
|
||||
std::string game_title{};
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user