Compare commits
272 Commits
Author | SHA1 | Date | |
---|---|---|---|
5440d4ad5c | |||
dde208b480 | |||
4c3d2d5d75 | |||
fab11ba3f1 | |||
332891b5ff | |||
7df4fcada7 | |||
d6698680be | |||
e5c9838b0b | |||
f8ec878796 | |||
9ff21f9ab6 | |||
aa021085cf | |||
1f5d881860 | |||
1f664100bd | |||
1f5e1ffa80 | |||
264438ff19 | |||
3b8ac1641a | |||
4250732353 | |||
4d1579acbf | |||
6279f5e430 | |||
b7d2bff6aa | |||
7c327fecb3 | |||
cc1a933a2f | |||
dd574146fb | |||
2c94ac455e | |||
e18d258fa0 | |||
36f10df775 | |||
680e548022 | |||
21c4176157 | |||
3b4ff2d6d9 | |||
12504f280c | |||
250fc51374 | |||
206e0882c2 | |||
609abc8b9b | |||
cee7121058 | |||
cd124bda58 | |||
9f12e50a54 | |||
097562bc6c | |||
db4242c5dc | |||
4dd77316f7 | |||
3f98369a17 | |||
c26aeefe03 | |||
666e05f5cb | |||
8d9d508dc7 | |||
e27f5522e2 | |||
add2a9d151 | |||
9e50dd99d7 | |||
0dec91bb42 | |||
d9b63353b0 | |||
eabd0ec93f | |||
138d5dc64a | |||
3e68a87d63 | |||
69b6ef7a4a | |||
40e87c634e | |||
79d1c190db | |||
2bc88467eb | |||
baf8752e74 | |||
d5e4378aea | |||
6dbcdfea47 | |||
c5258cf082 | |||
5c89e22bb9 | |||
11ecff2ff0 | |||
4c3f09644a | |||
e187a8870a | |||
a64fee29dc | |||
9ef94c8292 | |||
915d6d044c | |||
a4780ab33b | |||
a947a45d81 | |||
9db73f74cf | |||
a1efd87c45 | |||
49be977588 | |||
c95be55091 | |||
63dedbda86 | |||
c532118d94 | |||
52d6f2e656 | |||
c9bc4eaf58 | |||
3249f8ff41 | |||
1b41b285ac | |||
f5a6f45b27 | |||
210557951b | |||
4c2d9ff3ff | |||
8198b99935 | |||
460f96967d | |||
7ca779a26d | |||
b5032b3c91 | |||
f0a3dff136 | |||
f659dcb9d8 | |||
a34fb0e939 | |||
21ce8a9b80 | |||
9ecbee8032 | |||
80519af67d | |||
26e30faff3 | |||
0992310b76 | |||
009c1101d2 | |||
ba95ee54ab | |||
4ce4299ca2 | |||
17620d18db | |||
9f1cf6458c | |||
67b4e63cff | |||
c05c688ee8 | |||
b2623dc27d | |||
5131b71437 | |||
7870423671 | |||
b72916fbc1 | |||
da073fce61 | |||
1fc90e57d2 | |||
eafcc314a9 | |||
6e9bd4de13 | |||
05a41b31bc | |||
eed17f963e | |||
c09c0c002d | |||
d56d335c0b | |||
23c844b2aa | |||
81691b9e37 | |||
2dc422bc14 | |||
a80fa5e33f | |||
954e995321 | |||
dad9ab6bb6 | |||
f0562b9c75 | |||
b8556530f2 | |||
4f3af839be | |||
155736c986 | |||
dba908dc78 | |||
ecee34a50c | |||
9b5a0c3889 | |||
80b4972139 | |||
5d85468302 | |||
9b1cc2cec6 | |||
e691622f0a | |||
f663a5cd38 | |||
f7c2e867f4 | |||
cedd200745 | |||
58207685c0 | |||
095ad923ad | |||
f07ae7d53f | |||
c308f09722 | |||
f1eef29409 | |||
1f8d66db7c | |||
c3a5716a95 | |||
1f1e2a7f03 | |||
e54f9dc4b4 | |||
edfd4d70c0 | |||
fc43aecbbd | |||
58d7a1fe97 | |||
7aa430f1a5 | |||
6bf460e104 | |||
efb135b74c | |||
a707842e14 | |||
a5a9b9bc8b | |||
17078ad929 | |||
32450d45de | |||
ed7a0474c6 | |||
fe9c49949a | |||
052b23c83c | |||
e4f68592c3 | |||
1dcd44b94f | |||
61b1ce252f | |||
5f38086f94 | |||
7bae440d3a | |||
f1943fd0b6 | |||
ec8d4f3af5 | |||
b3f0978869 | |||
f614d2c435 | |||
40c9416097 | |||
618c8edc79 | |||
99fc4fa61b | |||
f6d5499a16 | |||
26bf13a65d | |||
96cf242bcf | |||
59755818ef | |||
f8beeeb7d3 | |||
cb250162cb | |||
7528f94536 | |||
43081c16c4 | |||
780627e7b0 | |||
9044cb38d1 | |||
a53cfdab78 | |||
c7f9962dde | |||
296c4a3d01 | |||
e7cf4e6eaf | |||
a1a4771ac1 | |||
2fd819613f | |||
ad6ff6ce99 | |||
dc30d94852 | |||
4f293f8cbe | |||
32a1cd83fd | |||
e3d0ccf8d5 | |||
c14844d12c | |||
7fea26e97e | |||
7b7f62c776 | |||
423dbc8888 | |||
6adf15e479 | |||
2747f12591 | |||
a47824f961 | |||
8474d52778 | |||
dd7a924596 | |||
a76eaf9a9a | |||
009e6bcd1b | |||
eb2cc159fa | |||
bb89e36fd8 | |||
de3134adbe | |||
36d53819a4 | |||
ae4324032a | |||
f449895e6d | |||
410be95ab6 | |||
cff9046fc7 | |||
86fd0643c2 | |||
43a83a401e | |||
f0e27a23a5 | |||
e68650237d | |||
1faff14e73 | |||
784cf9d594 | |||
64263c5218 | |||
065c4e520d | |||
139a930407 | |||
719dc97bbd | |||
41bba5310a | |||
8071c8c8c0 | |||
b402b4e7f6 | |||
93df366b2c | |||
cd3a15aea5 | |||
070136b3f7 | |||
08ab47c6c0 | |||
85faa9d8fa | |||
dca5b14493 | |||
4d2c8e2a44 | |||
8fa248ceb4 | |||
30862b5ffd | |||
9f57747c57 | |||
fe29a2ff6e | |||
e9a173e00c | |||
a11784fcbf | |||
fd36c8deca | |||
70638340b3 | |||
4b495f3333 | |||
934b5a64e5 | |||
cee667b491 | |||
94a64f2aea | |||
2355c2af62 | |||
5e0f8e8738 | |||
d16288a2a8 | |||
600f86dc7b | |||
7210c17c5e | |||
a16854e55a | |||
3e455a90a1 | |||
e4413542b2 | |||
8c720783f5 | |||
8734ea9dd4 | |||
c586e6d2b7 | |||
3a4eeb77fe | |||
51b3953cfc | |||
610eecc1c1 | |||
492056abf6 | |||
ee6e682ab4 | |||
6f60e102a2 | |||
eeb2af9953 | |||
550747eac6 | |||
3ffceab1fb | |||
b9f2a96595 | |||
cbaa845f5d | |||
9e2681f2d7 | |||
81fae0d1a6 | |||
38519f3b9a | |||
7f27aabbd0 | |||
e876c43ce9 | |||
8639245533 | |||
d6b86a6629 | |||
8f2b7b5b8e | |||
fc4b7cba2c | |||
08831eecf7 | |||
c6a139a6e7 | |||
02714a1291 |
@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# Namespace preferences
|
||||
csharp_style_namespace_declarations = block_scoped:warning
|
||||
resharper_csharp_namespace_body = block_scoped
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = false:silent
|
||||
csharp_style_var_for_built_in_types = false:silent
|
||||
|
61
.gitattributes
vendored
61
.gitattributes
vendored
@ -1,63 +1,4 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
* text=auto eol=lf
|
||||
|
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,43 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Bug Report
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ Lines between [ ] (square brackets) should be removed before posting. ]
|
||||
|
||||
### What's the issue you encountered?
|
||||
|
||||
[ Describe the issue in detail and what you were doing beforehand. ]</br>
|
||||
[ Did you make any changes related to Ryujinx itself? ]</br>
|
||||
[ If so, make sure to include details relating to what exactly you changed. ]
|
||||
|
||||
### How can the issue be reproduced?
|
||||
|
||||
[ Include a detailed step by step process for recreating your issue. ]
|
||||
|
||||
### Log file
|
||||
|
||||
[ Logs files can be found under ``Logs`` folder in Ryujinx program folder. ]</br>
|
||||
[ If you don't include a crash report in instances of crash related issues, we will ask you one to provide one. ]
|
||||
|
||||
### Environment?
|
||||
|
||||
- Ryujinx version: 1.0.X</br>
|
||||
[ Replace X's with the Ryujinx version at time of crash. ]
|
||||
- Game version: X.X.X</br>
|
||||
[ Replace X's with the game version at time of crash. ]
|
||||
- System Specs:
|
||||
- OS: *(e.g. Windows 10)*
|
||||
- CPU: *(e.g. i7-6700)*
|
||||
- GPU: *(e.g. NVIDIA RTX 2070)*
|
||||
- RAM: *(e.g. 16GiB)*
|
||||
- Applied Mods : [ Yes (Which ones) / No ]
|
||||
|
||||
### Additional context?
|
||||
|
||||
Additional info about your environment:</br>
|
||||
[ Any other information relevant to your issue. ]
|
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
86
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]"
|
||||
labels: bug
|
||||
body:
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Description of the issue
|
||||
description: What's the issue you encountered?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: How can the issue be reproduced?
|
||||
placeholder: Describe each step as precisely as possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log file
|
||||
description: A log file will help our developers to better diagnose and fix the issue.
|
||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: "e.g. Windows 10"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: ryujinx-version
|
||||
attributes:
|
||||
label: Ryujinx version
|
||||
placeholder: "e.g. 1.0.470"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: game-version
|
||||
attributes:
|
||||
label: Game version
|
||||
placeholder: "e.g. 1.1.1"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: cpu
|
||||
attributes:
|
||||
label: CPU
|
||||
placeholder: "e.g. i7-6700"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: gpu
|
||||
attributes:
|
||||
label: GPU
|
||||
placeholder: "e.g. NVIDIA RTX 2070"
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: ram
|
||||
attributes:
|
||||
label: RAM
|
||||
placeholder: "e.g. 16GB"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: mods
|
||||
attributes:
|
||||
label: List of applied mods
|
||||
placeholder: You can list applied mods here.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context?
|
||||
description: |
|
||||
- Additional info about your environment:
|
||||
- Any other information relevant to your issue.
|
||||
validations:
|
||||
required: false
|
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature for Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Feature Request
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]</br>
|
||||
|
||||
[ Please search for existing [feature requests](https://github.com/Ryujinx/Ryujinx/issues) before you make your own request. ]</br>
|
||||
[ Duplicate requests will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What feature are you suggesting?
|
||||
#### Overview:
|
||||
- [ Include the basic, high-level concepts for this feature here. ]
|
||||
|
||||
#### Smaller Details:
|
||||
- [ These may include specific methods of implementation etc. ]
|
||||
|
||||
#### Nature of Request:
|
||||
[ Remove all that do not apply to your request. ]
|
||||
- Addition
|
||||
- [ Ex: Addition of certain original features or features from other community projects. ]
|
||||
- [ If you are suggesting porting features or including features from other projects, include what license they are distributed under and what, if any libraries those project use. ]
|
||||
- Change
|
||||
- Removal
|
||||
- [Ex: Removal of certain features or implementation due to a specific issue/bug or because of low quality code, etc.]
|
||||
|
||||
### Why would this feature be useful?
|
||||
[ If this is a feature for an end-user, how does it benefit the end-user? ]</br>
|
||||
[ If this feature is for developers, what does it add to Ryujinx that did not already exist? ]
|
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature for Ryujinx.
|
||||
title: "[Feature Request]"
|
||||
body:
|
||||
- type: textarea
|
||||
id: overview
|
||||
attributes:
|
||||
label: Overview
|
||||
description: Include the basic, high-level concepts for this feature here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Smaller details
|
||||
description: These may include specific methods of implementation etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Nature of request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
validations:
|
||||
required: true
|
@ -1,34 +0,0 @@
|
||||
---
|
||||
name: Missing CPU Instruction
|
||||
about: CPU Instruction is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing CPU Instruction
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing CPU instruction](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1405) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What CPU instruction is missing?
|
||||
|
||||
Requires the *INSTRUCTION* instruction.</br>
|
||||
[ Replace *INSTRUCTION* by the instruction name, e.g. VADDL.U16 ]
|
||||
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Add the undefined instruction error message in the above code block ]
|
||||
|
||||
### Instruction name
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this instruction ]
|
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
26
.github/ISSUE_TEMPLATE/missing_cpu_instruction.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Missing CPU Instruction
|
||||
description: CPU Instruction is missing in Ryujinx.
|
||||
title: "[CPU]"
|
||||
labels: [cpu, not-implemented]
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: CPU instruction
|
||||
description: What CPU instruction is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Instruction name
|
||||
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||
validations:
|
||||
required: true
|
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
35
.github/ISSUE_TEMPLATE/missing_service_call.md
vendored
@ -1,35 +0,0 @@
|
||||
---
|
||||
name: Missing Service Call
|
||||
about: Service call is missing in Ryujinx.
|
||||
#assignees:
|
||||
---
|
||||
|
||||
## Missing Service Call
|
||||
|
||||
[ If any section does not apply, replace its contents with "N/A". ]</br>
|
||||
[ If you do not have the information needed for a section, replace its contents with "Unknown". ]</br>
|
||||
[ Lines between [ ] (square brackets) are to be removed before posting. ]
|
||||
|
||||
[ Please search for existing [missing service call](https://github.com/Ryujinx/Ryujinx/issues) before you make your own issue. ]</br>
|
||||
[ See the following [issue](https://github.com/Ryujinx/Ryujinx/issues/1431) as an example ]</br>
|
||||
[ Duplicate issue will be marked as such and you will be referred to the original request. ]
|
||||
|
||||
### What service call is missing?
|
||||
|
||||
*SERVICE* *INTERFACE*: *NUMBER* (*NAME*) is not implemented.</br>
|
||||
[ Replace *SERVICE* by the service name, e.g. appletAE ]</br>
|
||||
[ Replace *INTERFACE* by the interface name, e.g. IAllSystemAppletProxiesService ]</br>
|
||||
[ Replace *NUMBER* by the call number, e.g. 100 ]</br>
|
||||
[ Replace *NAME* by the call name, e.g. OpenSystemAppletProxy ]</br>
|
||||
[ e.g. appletAE IAllSystemAppletProxiesService: 100 (OpenSystemAppletProxy) ]
|
||||
|
||||
[ Add related links to the specific call from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) ]
|
||||
|
||||
### Service description
|
||||
```
|
||||
*
|
||||
```
|
||||
[ Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block ]
|
||||
|
||||
### Required by:
|
||||
[ Add our (games list database)[https://github.com/Ryujinx/Ryujinx-Games-List/issues] links of games who require this call ]
|
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/missing_service_call.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Missing Service Call
|
||||
description: Service call is missing in Ryujinx.
|
||||
labels: not-implemented
|
||||
body:
|
||||
- type: textarea
|
||||
id: instruction
|
||||
attributes:
|
||||
label: Service call
|
||||
description: What service call is missing?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: name
|
||||
attributes:
|
||||
label: Service description
|
||||
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: required
|
||||
attributes:
|
||||
label: Required by
|
||||
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this service.
|
||||
validations:
|
||||
required: true
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -61,13 +61,13 @@ jobs:
|
||||
- name: Test
|
||||
run: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx --self-contained true
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Headless.SDL2 --self-contained true
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Publish Ryujinx.Ava
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava --self-contained true
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
@ -86,4 +86,4 @@ jobs:
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
path: publish_ava
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request'
|
171
.github/workflows/flatpak.yml
vendored
Normal file
171
.github/workflows/flatpak.yml
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
name: Flatpak release job
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ryujinx_version:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
GIT_COMMITTER_NAME: "RyujinxBot"
|
||||
GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com"
|
||||
RYUJINX_PROJECT_FILE: "src/Ryujinx/Ryujinx.csproj"
|
||||
NUGET_SOURCES_DESTDIR: "nuget-sources"
|
||||
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: Ryujinx
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: Ryujinx/global.json
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
working-directory: Ryujinx
|
||||
run: |
|
||||
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: flathub/org.ryujinx.Ryujinx
|
||||
token: ${{ secrets.RYUJINX_BOT_PAT }}
|
||||
submodules: recursive
|
||||
path: flathub
|
||||
|
||||
- name: Install dependencies
|
||||
run: python -m pip install PyYAML lxml
|
||||
|
||||
- name: Restore Nuget packages
|
||||
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
run: |
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
|
||||
sources = []
|
||||
|
||||
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = '{}.{}.nupkg'.format(name, version)
|
||||
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
||||
|
||||
sources.append({
|
||||
'type': 'file',
|
||||
'url': url,
|
||||
'sha512': sha512,
|
||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
||||
'dest-filename': filename,
|
||||
})
|
||||
|
||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
|
||||
- name: Update flatpak metadata
|
||||
id: metadata
|
||||
env:
|
||||
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
|
||||
shell: python
|
||||
run: |
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from lxml import etree
|
||||
|
||||
|
||||
# Ensure we don't destroy multiline strings
|
||||
def str_presenter(dumper, data):
|
||||
if len(data.splitlines()) > 1:
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
||||
|
||||
|
||||
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||
|
||||
yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
|
||||
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"
|
||||
|
||||
with open(yaml_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for source in data["modules"][0]["sources"]:
|
||||
if type(source) is str:
|
||||
continue
|
||||
if (
|
||||
source["type"] == "git"
|
||||
and source["url"] == "https://github.com/Ryujinx/Ryujinx.git"
|
||||
):
|
||||
source["commit"] = os.environ['RYUJINX_GIT_HASH']
|
||||
|
||||
is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out:
|
||||
if is_same_version:
|
||||
gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}")
|
||||
else:
|
||||
gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}")
|
||||
|
||||
if not is_same_version:
|
||||
data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(yaml_file, "w") as f:
|
||||
yaml.safe_dump(data, f, sort_keys=False)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(xml_file, parser)
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
releases = root.find("releases")
|
||||
|
||||
element = etree.Element("release")
|
||||
element.set("version", os.environ['RYUJINX_VERSION'])
|
||||
element.set("date", datetime.now().date().isoformat())
|
||||
releases.insert(0, element)
|
||||
|
||||
# Ensure 4 spaces
|
||||
etree.indent(root, space=" ")
|
||||
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(
|
||||
etree.tostring(
|
||||
tree,
|
||||
pretty_print=True,
|
||||
encoding="UTF-8",
|
||||
doctype='<?xml version="1.0" encoding="UTF-8"?>',
|
||||
)
|
||||
)
|
||||
|
||||
- name: Push flatpak update
|
||||
working-directory: flathub
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }}
|
||||
run: |
|
||||
git config user.name "${{ env.GIT_COMMITTER_NAME }}"
|
||||
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
|
||||
git add .
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
git push origin master
|
79
.github/workflows/release.yml
vendored
79
.github/workflows/release.yml
vendored
@ -13,23 +13,22 @@ on:
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
global-json-file: global.json
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
@ -38,19 +37,19 @@ jobs:
|
||||
shell: bash
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformations.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
- name: Create output dir
|
||||
run: "mkdir release_output"
|
||||
- name: Publish Windows
|
||||
run: |
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||
- name: Packing Windows builds
|
||||
run: |
|
||||
pushd publish_windows
|
||||
@ -68,22 +67,34 @@ jobs:
|
||||
|
||||
- name: Publish Linux
|
||||
run: |
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||
|
||||
- name: Packing Linux builds
|
||||
run: |
|
||||
pushd publish_linux
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar --exclude "publish/Ryujinx" --exclude "publish/Ryujinx.sh" -cvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
|
||||
gzip -9 < ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
|
||||
rm ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
|
||||
popd
|
||||
|
||||
pushd publish_linux_sdl2_headless
|
||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar --exclude "publish/Ryujinx.Headless.SDL2" --exclude "publish/Ryujinx.sh" -cvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
|
||||
gzip -9 < ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
|
||||
rm ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
|
||||
popd
|
||||
|
||||
pushd publish_linux_ava
|
||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar --exclude "publish/Ryujinx.Ava" --exclude "publish/Ryujinx.sh" -cvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Ava" "publish/Ryujinx.Ava"
|
||||
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.sh" "publish/Ryujinx.sh"
|
||||
gzip -9 < ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
|
||||
rm ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
@ -93,10 +104,28 @@ jobs:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "release_output/*.tar.gz,release_output/*.zip"
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: "For more informations about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
|
||||
allowUpdates: true
|
||||
removeArtifacts: true
|
||||
replacesArtifacts: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
with:
|
||||
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||
secrets: inherit
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -170,3 +170,6 @@ launchSettings.json
|
||||
|
||||
# NetCore Publishing Profiles
|
||||
PublishProfiles/
|
||||
|
||||
# Glade backup files
|
||||
*.glade~
|
||||
|
@ -1,6 +0,0 @@
|
||||
namespace ARMeilleure.Decoders;
|
||||
|
||||
interface IOpCode32Exception
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
namespace ARMeilleure.IntermediateRepresentation
|
||||
{
|
||||
enum Intrinsic : ushort
|
||||
{
|
||||
X86Addpd,
|
||||
X86Addps,
|
||||
X86Addsd,
|
||||
X86Addss,
|
||||
X86Aesdec,
|
||||
X86Aesdeclast,
|
||||
X86Aesenc,
|
||||
X86Aesenclast,
|
||||
X86Aesimc,
|
||||
X86Andnpd,
|
||||
X86Andnps,
|
||||
X86Andpd,
|
||||
X86Andps,
|
||||
X86Blendvpd,
|
||||
X86Blendvps,
|
||||
X86Cmppd,
|
||||
X86Cmpps,
|
||||
X86Cmpsd,
|
||||
X86Cmpss,
|
||||
X86Comisdeq,
|
||||
X86Comisdge,
|
||||
X86Comisdlt,
|
||||
X86Comisseq,
|
||||
X86Comissge,
|
||||
X86Comisslt,
|
||||
X86Crc32,
|
||||
X86Crc32_16,
|
||||
X86Crc32_8,
|
||||
X86Cvtdq2pd,
|
||||
X86Cvtdq2ps,
|
||||
X86Cvtpd2dq,
|
||||
X86Cvtpd2ps,
|
||||
X86Cvtps2dq,
|
||||
X86Cvtps2pd,
|
||||
X86Cvtsd2si,
|
||||
X86Cvtsd2ss,
|
||||
X86Cvtsi2sd,
|
||||
X86Cvtsi2si,
|
||||
X86Cvtsi2ss,
|
||||
X86Cvtss2sd,
|
||||
X86Cvtss2si,
|
||||
X86Divpd,
|
||||
X86Divps,
|
||||
X86Divsd,
|
||||
X86Divss,
|
||||
X86Gf2p8affineqb,
|
||||
X86Haddpd,
|
||||
X86Haddps,
|
||||
X86Insertps,
|
||||
X86Maxpd,
|
||||
X86Maxps,
|
||||
X86Maxsd,
|
||||
X86Maxss,
|
||||
X86Minpd,
|
||||
X86Minps,
|
||||
X86Minsd,
|
||||
X86Minss,
|
||||
X86Movhlps,
|
||||
X86Movlhps,
|
||||
X86Movss,
|
||||
X86Mulpd,
|
||||
X86Mulps,
|
||||
X86Mulsd,
|
||||
X86Mulss,
|
||||
X86Mxcsrmb,
|
||||
X86Mxcsrub,
|
||||
X86Paddb,
|
||||
X86Paddd,
|
||||
X86Paddq,
|
||||
X86Paddw,
|
||||
X86Palignr,
|
||||
X86Pand,
|
||||
X86Pandn,
|
||||
X86Pavgb,
|
||||
X86Pavgw,
|
||||
X86Pblendvb,
|
||||
X86Pclmulqdq,
|
||||
X86Pcmpeqb,
|
||||
X86Pcmpeqd,
|
||||
X86Pcmpeqq,
|
||||
X86Pcmpeqw,
|
||||
X86Pcmpgtb,
|
||||
X86Pcmpgtd,
|
||||
X86Pcmpgtq,
|
||||
X86Pcmpgtw,
|
||||
X86Pmaxsb,
|
||||
X86Pmaxsd,
|
||||
X86Pmaxsw,
|
||||
X86Pmaxub,
|
||||
X86Pmaxud,
|
||||
X86Pmaxuw,
|
||||
X86Pminsb,
|
||||
X86Pminsd,
|
||||
X86Pminsw,
|
||||
X86Pminub,
|
||||
X86Pminud,
|
||||
X86Pminuw,
|
||||
X86Pmovsxbw,
|
||||
X86Pmovsxdq,
|
||||
X86Pmovsxwd,
|
||||
X86Pmovzxbw,
|
||||
X86Pmovzxdq,
|
||||
X86Pmovzxwd,
|
||||
X86Pmulld,
|
||||
X86Pmullw,
|
||||
X86Popcnt,
|
||||
X86Por,
|
||||
X86Pshufb,
|
||||
X86Pshufd,
|
||||
X86Pslld,
|
||||
X86Pslldq,
|
||||
X86Psllq,
|
||||
X86Psllw,
|
||||
X86Psrad,
|
||||
X86Psraw,
|
||||
X86Psrld,
|
||||
X86Psrlq,
|
||||
X86Psrldq,
|
||||
X86Psrlw,
|
||||
X86Psubb,
|
||||
X86Psubd,
|
||||
X86Psubq,
|
||||
X86Psubw,
|
||||
X86Punpckhbw,
|
||||
X86Punpckhdq,
|
||||
X86Punpckhqdq,
|
||||
X86Punpckhwd,
|
||||
X86Punpcklbw,
|
||||
X86Punpckldq,
|
||||
X86Punpcklqdq,
|
||||
X86Punpcklwd,
|
||||
X86Pxor,
|
||||
X86Rcpps,
|
||||
X86Rcpss,
|
||||
X86Roundpd,
|
||||
X86Roundps,
|
||||
X86Roundsd,
|
||||
X86Roundss,
|
||||
X86Rsqrtps,
|
||||
X86Rsqrtss,
|
||||
X86Sha256Msg1,
|
||||
X86Sha256Msg2,
|
||||
X86Sha256Rnds2,
|
||||
X86Shufpd,
|
||||
X86Shufps,
|
||||
X86Sqrtpd,
|
||||
X86Sqrtps,
|
||||
X86Sqrtsd,
|
||||
X86Sqrtss,
|
||||
X86Subpd,
|
||||
X86Subps,
|
||||
X86Subsd,
|
||||
X86Subss,
|
||||
X86Unpckhpd,
|
||||
X86Unpckhps,
|
||||
X86Unpcklpd,
|
||||
X86Unpcklps,
|
||||
X86Vcvtph2ps,
|
||||
X86Vcvtps2ph,
|
||||
X86Vfmadd231ps,
|
||||
X86Vfmadd231sd,
|
||||
X86Vfmadd231ss,
|
||||
X86Vfmsub231sd,
|
||||
X86Vfmsub231ss,
|
||||
X86Vfnmadd231ps,
|
||||
X86Vfnmadd231sd,
|
||||
X86Vfnmadd231ss,
|
||||
X86Vfnmsub231sd,
|
||||
X86Vfnmsub231ss,
|
||||
X86Xorpd,
|
||||
X86Xorps
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
using ARMeilleure.CodeGen.X86;
|
||||
|
||||
namespace ARMeilleure
|
||||
{
|
||||
public static class Optimizations
|
||||
{
|
||||
public static bool FastFP { get; set; } = true;
|
||||
|
||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||
|
||||
public static bool UseSseIfAvailable { get; set; } = true;
|
||||
public static bool UseSse2IfAvailable { get; set; } = true;
|
||||
public static bool UseSse3IfAvailable { get; set; } = true;
|
||||
public static bool UseSsse3IfAvailable { get; set; } = true;
|
||||
public static bool UseSse41IfAvailable { get; set; } = true;
|
||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||
public static bool UsePclmulqdqIfAvailable { get; set; } = true;
|
||||
public static bool UseShaIfAvailable { get; set; } = true;
|
||||
public static bool UseGfniIfAvailable { get; set; } = true;
|
||||
|
||||
public static bool ForceLegacySse
|
||||
{
|
||||
get => HardwareCapabilities.ForceLegacySse;
|
||||
set => HardwareCapabilities.ForceLegacySse = value;
|
||||
}
|
||||
|
||||
internal static bool UseSse => UseSseIfAvailable && HardwareCapabilities.SupportsSse;
|
||||
internal static bool UseSse2 => UseSse2IfAvailable && HardwareCapabilities.SupportsSse2;
|
||||
internal static bool UseSse3 => UseSse3IfAvailable && HardwareCapabilities.SupportsSse3;
|
||||
internal static bool UseSsse3 => UseSsse3IfAvailable && HardwareCapabilities.SupportsSsse3;
|
||||
internal static bool UseSse41 => UseSse41IfAvailable && HardwareCapabilities.SupportsSse41;
|
||||
internal static bool UseSse42 => UseSse42IfAvailable && HardwareCapabilities.SupportsSse42;
|
||||
internal static bool UsePopCnt => UsePopCntIfAvailable && HardwareCapabilities.SupportsPopcnt;
|
||||
internal static bool UseAvx => UseAvxIfAvailable && HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||
internal static bool UseF16c => UseF16cIfAvailable && HardwareCapabilities.SupportsF16c;
|
||||
internal static bool UseFma => UseFmaIfAvailable && HardwareCapabilities.SupportsFma;
|
||||
internal static bool UseAesni => UseAesniIfAvailable && HardwareCapabilities.SupportsAesni;
|
||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && HardwareCapabilities.SupportsPclmulqdq;
|
||||
internal static bool UseSha => UseShaIfAvailable && HardwareCapabilities.SupportsSha;
|
||||
internal static bool UseGfni => UseGfniIfAvailable && HardwareCapabilities.SupportsGfni;
|
||||
}
|
||||
}
|
@ -3,40 +3,40 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||
<PackageVersion Include="DynamicData" Version="7.13.5" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.17.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.24.2-build21" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.1" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
@ -44,11 +44,10 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.30.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
@ -40,7 +40,7 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
|
||||
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
@ -1,273 +0,0 @@
|
||||
{
|
||||
"MenuBarFile": "_Fichier",
|
||||
"MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier",
|
||||
"MenuBarFileOpenUnpacked": "Charger un jeu _extrait",
|
||||
"MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx",
|
||||
"MenuBarFileOpenLogsFolder": "Ouvrir le dossier de Logs",
|
||||
"MenuBarFileExit": "_Quitter",
|
||||
"MenuBarOptions": "Options",
|
||||
"MenuBarOptionsToggleFullscreen": "Basculer en plein écran",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "Démarrer le jeu en plein écran",
|
||||
"MenuBarOptionsStopEmulation": "Arrêter l'émulation",
|
||||
"MenuBarOptionsSettings": "_Paramètres",
|
||||
"MenuBarOptionsManageUserProfiles": "_Gêrer les profils d'utilisateurs",
|
||||
"MenuBarActions": "_Actions",
|
||||
"MenuBarOptionsSimulateWakeUpMessage": "Simuler une sortie de veille",
|
||||
"MenuBarActionsScanAmiibo": "Scanner un Amiibo",
|
||||
"MenuBarTools": "_Outils",
|
||||
"MenuBarToolsInstallFirmware": "Installer un firmware",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP",
|
||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier",
|
||||
"MenuBarHelp": "Aide",
|
||||
"MenuBarHelpCheckForUpdates": "Vérifier les mises à jour",
|
||||
"MenuBarHelpAbout": "Á propos",
|
||||
"MenuSearch": "Rechercher...",
|
||||
"GameListHeaderFavorite": "Favoris",
|
||||
"GameListHeaderIcon": "Icône",
|
||||
"GameListHeaderApplication": "Application",
|
||||
"GameListHeaderDeveloper": "Développeur",
|
||||
"GameListHeaderVersion": "Version",
|
||||
"GameListHeaderTimePlayed": "Temps de jeu",
|
||||
"GameListHeaderLastPlayed": "Dernière partie",
|
||||
"GameListHeaderFileExtension": "Extension du Fichier",
|
||||
"GameListHeaderFileSize": "Taille du Fichier",
|
||||
"GameListHeaderPath": "Chemin",
|
||||
"GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur",
|
||||
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu",
|
||||
"GameListContextMenuOpenUserDeviceDirectory": "Ouvrir le dossier de sauvegarde console",
|
||||
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu",
|
||||
"GameListContextMenuOpenUserBcatDirectory": "Ouvrir le dossier de sauvegarde BCAT",
|
||||
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu",
|
||||
"GameListContextMenuManageTitleUpdates": "Gérer les mises à jour",
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour",
|
||||
"GameListContextMenuManageDlc": "Gérer les DLC",
|
||||
"GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC",
|
||||
"GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu",
|
||||
"GameListContextMenuCacheManagement": "Gestion des caches",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC",
|
||||
"GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu",
|
||||
"GameListContextMenuExtractData": "Extraire les données",
|
||||
"GameListContextMenuExtractDataExeFS": "ExeFS",
|
||||
"GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)",
|
||||
"GameListContextMenuExtractDataRomFS": "RomFS",
|
||||
"GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)",
|
||||
"GameListContextMenuExtractDataLogo": "Logo",
|
||||
"GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)",
|
||||
"StatusBarGamesLoaded": "{0}/{1} Jeux chargés",
|
||||
"StatusBarSystemVersion": "Version du Firmware: {0}",
|
||||
"Settings": "Paramètres",
|
||||
"SettingsTabGeneral": "Général",
|
||||
"SettingsTabGeneralGeneral": "Général",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "Active Discord Rich Presence",
|
||||
"SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage",
|
||||
"SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de fermeture\"",
|
||||
"SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif",
|
||||
"SettingsTabGeneralGameDirectories": "Dossiers de Jeux",
|
||||
"SettingsTabGeneralAdd": "Ajouter",
|
||||
"SettingsTabGeneralRemove": "Supprimer",
|
||||
"SettingsTabSystem": "Système",
|
||||
"SettingsTabSystemCore": "Core",
|
||||
"SettingsTabSystemSystemRegion": "Région du système:",
|
||||
"SettingsTabSystemSystemRegionJapan": "Japon",
|
||||
"SettingsTabSystemSystemRegionUSA": "USA",
|
||||
"SettingsTabSystemSystemRegionEurope": "Europe",
|
||||
"SettingsTabSystemSystemRegionAustralia": "Australie",
|
||||
"SettingsTabSystemSystemRegionChina": "Chine",
|
||||
"SettingsTabSystemSystemRegionKorea": "Corée",
|
||||
"SettingsTabSystemSystemRegionTaiwan": "Taïwan",
|
||||
"SettingsTabSystemSystemLanguage": "Langue du système:",
|
||||
"SettingsTabSystemSystemLanguageJapanese": "Japonais",
|
||||
"SettingsTabSystemSystemLanguageAmericanEnglish": "Américain",
|
||||
"SettingsTabSystemSystemLanguageFrench": "Français",
|
||||
"SettingsTabSystemSystemLanguageGerman": "Allemand",
|
||||
"SettingsTabSystemSystemLanguageItalian": "Italien",
|
||||
"SettingsTabSystemSystemLanguageSpanish": "Espagnol",
|
||||
"SettingsTabSystemSystemLanguageChinese": "Chinois",
|
||||
"SettingsTabSystemSystemLanguageKorean": "Coréen",
|
||||
"SettingsTabSystemSystemLanguageDutch": "Néerlandais",
|
||||
"SettingsTabSystemSystemLanguagePortuguese": "Portugais",
|
||||
"SettingsTabSystemSystemLanguageRussian": "Russe",
|
||||
"SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais",
|
||||
"SettingsTabSystemSystemLanguageBritishEnglish": "Anglais",
|
||||
"SettingsTabSystemSystemLanguageCanadianFrench": "Canadien",
|
||||
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain",
|
||||
"SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié",
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel",
|
||||
"SettingsTabSystemSystemTimeZone": "Fuseau horaire du système:",
|
||||
"SettingsTabSystemSystemTime": "Heure du système:",
|
||||
"SettingsTabSystemEnableVsync": "Activer la VSync",
|
||||
"SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers",
|
||||
"SettingsTabSystemAudioBackend": "Bibliothèque Audio:",
|
||||
"SettingsTabSystemAudioBackendDummy": "Dummy",
|
||||
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
|
||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||
"SettingsTabSystemHacks": "Hacks",
|
||||
"SettingsTabSystemHacksNote": " (Cela peut causer des instabilitées)",
|
||||
"SettingsTabSystemExpandDramSize": "Augmenter la taille de la DRAM à 6GiB",
|
||||
"SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquant",
|
||||
"SettingsTabGraphics": "Graphique",
|
||||
"SettingsTabGraphicsAPI": "API Graphique",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders",
|
||||
"SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:",
|
||||
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
|
||||
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering4x": "4x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering8x": "8x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering16x": "16x",
|
||||
"SettingsTabGraphicsResolutionScale": "Échelle de résolution:",
|
||||
"SettingsTabGraphicsResolutionScaleCustom": "Customisée (Non recommandée)",
|
||||
"SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)",
|
||||
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
|
||||
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
|
||||
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
|
||||
"SettingsTabGraphicsAspectRatio": "Format:",
|
||||
"SettingsTabGraphicsAspectRatio4x3": "4:3",
|
||||
"SettingsTabGraphicsAspectRatio16x9": "16:9",
|
||||
"SettingsTabGraphicsAspectRatio16x10": "16:10",
|
||||
"SettingsTabGraphicsAspectRatio21x9": "21:9",
|
||||
"SettingsTabGraphicsAspectRatio32x9": "32:9",
|
||||
"SettingsTabGraphicsAspectRatioStretch": "Écran étiré",
|
||||
"SettingsTabGraphicsDeveloperOptions": "Options développeur",
|
||||
"SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:",
|
||||
"SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations",
|
||||
"SettingsTabLogging": "Journaux",
|
||||
"SettingsTabLoggingLogging": "Journaux",
|
||||
"SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier",
|
||||
"SettingsTabLoggingEnableStubLogs": "Activer les journaux stub",
|
||||
"SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations",
|
||||
"SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements",
|
||||
"SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs",
|
||||
"SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé",
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug",
|
||||
"SettingsTabInput": "Contrôles",
|
||||
"SettingsTabInputEnableDockedMode": "Active le mode station d'accueil",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
||||
"SettingsButtonSave": "Enregistrer",
|
||||
"SettingsButtonClose": "Fermer",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Annuler",
|
||||
"SettingsButtonApply": "Appliquer",
|
||||
"ControllerSettingsPlayer": "Joueur",
|
||||
"ControllerSettingsPlayer1": "Joueur 1",
|
||||
"ControllerSettingsPlayer2": "Joueur 2",
|
||||
"ControllerSettingsPlayer3": "Joueur 3",
|
||||
"ControllerSettingsPlayer4": "Joueur 4",
|
||||
"ControllerSettingsPlayer5": "Joueur 5",
|
||||
"ControllerSettingsPlayer6": "Joueur 6",
|
||||
"ControllerSettingsPlayer7": "Joueur 7",
|
||||
"ControllerSettingsPlayer8": "Joueur 8",
|
||||
"ControllerSettingsHandheld": "Portable",
|
||||
"ControllerSettingsInputDevice": "Périphériques",
|
||||
"ControllerSettingsRefresh": "Actualiser",
|
||||
"ControllerSettingsDeviceDisabled": "Désactivé",
|
||||
"ControllerSettingsControllerType": "Type de Controleur",
|
||||
"ControllerSettingsControllerTypeHandheld": "Portable",
|
||||
"ControllerSettingsControllerTypeProController": "Pro Controller",
|
||||
"ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints",
|
||||
"ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche",
|
||||
"ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite",
|
||||
"ControllerSettingsProfile": "Profil",
|
||||
"ControllerSettingsProfileDefault": "Défaut",
|
||||
"ControllerSettingsLoad": "Charger",
|
||||
"ControllerSettingsAdd": "Ajouter",
|
||||
"ControllerSettingsRemove": "Supprimer",
|
||||
"ControllerSettingsButtons": "Boutons",
|
||||
"ControllerSettingsButtonA": "A",
|
||||
"ControllerSettingsButtonB": "B",
|
||||
"ControllerSettingsButtonX": "X",
|
||||
"ControllerSettingsButtonY": "Y",
|
||||
"ControllerSettingsButtonPlus": "+",
|
||||
"ControllerSettingsButtonMinus": "-",
|
||||
"ControllerSettingsDPad": "Croix Directionnelle",
|
||||
"ControllerSettingsDPadUp": "Haut",
|
||||
"ControllerSettingsDPadDown": "Bas",
|
||||
"ControllerSettingsDPadLeft": "Gauche",
|
||||
"ControllerSettingsDPadRight": "Droite",
|
||||
"ControllerSettingsLStick": "Joystick Gauche",
|
||||
"ControllerSettingsLStickButton": "Bouton",
|
||||
"ControllerSettingsLStickUp": "Haut",
|
||||
"ControllerSettingsLStickDown": "Bas",
|
||||
"ControllerSettingsLStickLeft": "Gauche",
|
||||
"ControllerSettingsLStickRight": "Droite",
|
||||
"ControllerSettingsLStickStick": "Joystick",
|
||||
"ControllerSettingsLStickInvertXAxis": "Inverser l'axe X",
|
||||
"ControllerSettingsLStickInvertYAxis": "Inverser l'axe Y",
|
||||
"ControllerSettingsLStickDeadzone": "Zone morte:",
|
||||
"ControllerSettingsRStick": "Joystick Droit",
|
||||
"ControllerSettingsRStickButton": "Bouton",
|
||||
"ControllerSettingsRStickUp": "Haut",
|
||||
"ControllerSettingsRStickDown": "Bas",
|
||||
"ControllerSettingsRStickLeft": "Gauche",
|
||||
"ControllerSettingsRStickRight": "Droite",
|
||||
"ControllerSettingsRStickStick": "Joystick",
|
||||
"ControllerSettingsRStickInvertXAxis": "Inverser l'axe X",
|
||||
"ControllerSettingsRStickInvertYAxis": "Inverser l'axe Y",
|
||||
"ControllerSettingsRStickDeadzone": "Zone morte:",
|
||||
"ControllerSettingsTriggers": "Gachettes",
|
||||
"ControllerSettingsTriggersLeft": "Gachettes Gauche",
|
||||
"ControllerSettingsTriggersRight": "Gachettes Droite",
|
||||
"ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche",
|
||||
"ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite",
|
||||
"ControllerSettingsTriggerL": "L",
|
||||
"ControllerSettingsTriggerR": "R",
|
||||
"ControllerSettingsTriggerZL": "ZL",
|
||||
"ControllerSettingsTriggerZR": "ZR",
|
||||
"ControllerSettingsLeftSL": "SL",
|
||||
"ControllerSettingsLeftSR": "SR",
|
||||
"ControllerSettingsRightSL": "SL",
|
||||
"ControllerSettingsRightSR": "SR",
|
||||
"ControllerSettingsExtraButtonsLeft": "Boutons Gauche",
|
||||
"ControllerSettingsExtraButtonsRight": "Boutons Droite",
|
||||
"ControllerSettingsMisc": "Divers",
|
||||
"ControllerSettingsTriggerThreshold": "Seuil de gachettes:",
|
||||
"ControllerSettingsMotion": "Mouvements",
|
||||
"ControllerSettingsConfigureGeneral": "Configurer",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook",
|
||||
"ControllerSettingsMotionControllerSlot": "Contrôleur ID:",
|
||||
"ControllerSettingsMotionMirrorInput": "Inverser les contrôles",
|
||||
"ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:",
|
||||
"ControllerSettingsMotionServerHost": "Addresse du Server:",
|
||||
"ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:",
|
||||
"ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:",
|
||||
"ControllerSettingsSave": "Enregistrer",
|
||||
"ControllerSettingsClose": "Fermer",
|
||||
"UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:",
|
||||
"UserProfilesSaveProfileName": "Enregistrer le nom du profil",
|
||||
"UserProfilesChangeProfileImage": "Changer l'image du profil",
|
||||
"UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:",
|
||||
"UserProfilesAddNewProfile": "Ajouter un nouveau profil",
|
||||
"UserProfilesDeleteSelectedProfile": "Supprimer le profil sélectionné",
|
||||
"UserProfilesClose": "Fermer",
|
||||
"ProfileImageSelectionTitle": "Sélection de l'image du profil",
|
||||
"ProfileImageSelectionHeader": "Choisir l'image du profil",
|
||||
"ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware",
|
||||
"ProfileImageSelectionImportImage": "Importer une image",
|
||||
"ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware",
|
||||
"InputDialogTitle": "Fenêtre d'entrée de texte",
|
||||
"InputDialogOk": "OK",
|
||||
"InputDialogCancel": "Annuler",
|
||||
"InputDialogAddNewProfileTitle": "Choisir un nom de profil",
|
||||
"InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil",
|
||||
"InputDialogAddNewProfileSubtext": "(Longueur max.: {0})",
|
||||
"AvatarChoose": "Choisir",
|
||||
"AvatarSetBackgroundColor": "Choisir une couleur de fond",
|
||||
"AvatarClose": "Fermer",
|
||||
"ControllerMotionTitle": "Motion Control Settings",
|
||||
"ControllerRumbleTitle": "Rumble Settings"
|
||||
}
|
@ -1,597 +0,0 @@
|
||||
{
|
||||
"MenuBarFileOpenApplet": "Apri applet",
|
||||
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'editor Mii in modalità standalone",
|
||||
"SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse",
|
||||
"SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:",
|
||||
"SettingsTabSystemMemoryManagerModeSoftware": "Software",
|
||||
"SettingsTabSystemMemoryManagerModeHost": "Host (veloce)",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)",
|
||||
"MenuBarFile": "_File",
|
||||
"MenuBarFileOpenFromFile": "_Carica applicazione da un file",
|
||||
"MenuBarFileOpenUnpacked": "Carica _gioco estratto",
|
||||
"MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx",
|
||||
"MenuBarFileOpenLogsFolder": "Apri cartella dei logs",
|
||||
"MenuBarFileExit": "_Esci",
|
||||
"MenuBarOptions": "Opzioni",
|
||||
"MenuBarOptionsToggleFullscreen": "Schermo intero",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero",
|
||||
"MenuBarOptionsStopEmulation": "Ferma emulazione",
|
||||
"MenuBarOptionsSettings": "_Impostazioni",
|
||||
"MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente",
|
||||
"MenuBarActions": "_Azioni",
|
||||
"MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up",
|
||||
"MenuBarActionsScanAmiibo": "Scansiona un Amiibo",
|
||||
"MenuBarTools": "_Strumenti",
|
||||
"MenuBarToolsInstallFirmware": "Installa firmware",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP",
|
||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella",
|
||||
"MenuBarHelp": "Aiuto",
|
||||
"MenuBarHelpCheckForUpdates": "Controlla aggiornamenti",
|
||||
"MenuBarHelpAbout": "Informazioni",
|
||||
"MenuSearch": "Cerca...",
|
||||
"GameListHeaderFavorite": "Pref",
|
||||
"GameListHeaderIcon": "Icona",
|
||||
"GameListHeaderApplication": "Nome",
|
||||
"GameListHeaderDeveloper": "Sviluppatore",
|
||||
"GameListHeaderVersion": "Versione",
|
||||
"GameListHeaderTimePlayed": "Tempo di gioco",
|
||||
"GameListHeaderLastPlayed": "Giocato l'ultima volta",
|
||||
"GameListHeaderFileExtension": "Estensione",
|
||||
"GameListHeaderFileSize": "Dimensione file",
|
||||
"GameListHeaderPath": "Percorso",
|
||||
"GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente",
|
||||
"GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente",
|
||||
"GameListContextMenuOpenUserDeviceDirectory": "Apri la cartella dispositivo dell'utente",
|
||||
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo",
|
||||
"GameListContextMenuOpenUserBcatDirectory": "Apri la cartella BCAT dell'utente",
|
||||
"GameListContextMenuOpenUserBcatDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione",
|
||||
"GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco",
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco",
|
||||
"GameListContextMenuManageDlc": "Gestici DLC",
|
||||
"GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC",
|
||||
"GameListContextMenuOpenModsDirectory": "Apri cartella delle mod",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione",
|
||||
"GameListContextMenuCacheManagement": "Gestione della cache",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache",
|
||||
"GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione",
|
||||
"GameListContextMenuExtractData": "Estrai dati",
|
||||
"GameListContextMenuExtractDataExeFS": "ExeFS",
|
||||
"GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)",
|
||||
"GameListContextMenuExtractDataRomFS": "RomFS",
|
||||
"GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)",
|
||||
"GameListContextMenuExtractDataLogo": "Logo",
|
||||
"GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)",
|
||||
"StatusBarGamesLoaded": "{0}/{1} Giochi caricati",
|
||||
"StatusBarSystemVersion": "Versione di sistema: {0}",
|
||||
"Settings": "Impostazioni",
|
||||
"SettingsTabGeneral": "Interfaccia utente",
|
||||
"SettingsTabGeneralGeneral": "Generali",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence",
|
||||
"SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio",
|
||||
"SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"",
|
||||
"SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo",
|
||||
"SettingsTabGeneralGameDirectories": "Cartelle dei giochi",
|
||||
"SettingsTabGeneralAdd": "Aggiungi",
|
||||
"SettingsTabGeneralRemove": "Rimuovi",
|
||||
"SettingsTabSystem": "Sistema",
|
||||
"SettingsTabSystemCore": "Core",
|
||||
"SettingsTabSystemSystemRegion": "Regione del sistema:",
|
||||
"SettingsTabSystemSystemRegionJapan": "Giappone",
|
||||
"SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America",
|
||||
"SettingsTabSystemSystemRegionEurope": "Europa",
|
||||
"SettingsTabSystemSystemRegionAustralia": "Australia",
|
||||
"SettingsTabSystemSystemRegionChina": "Cina",
|
||||
"SettingsTabSystemSystemRegionKorea": "Corea",
|
||||
"SettingsTabSystemSystemRegionTaiwan": "Taiwan",
|
||||
"SettingsTabSystemSystemLanguage": "Lingua del sistema:",
|
||||
"SettingsTabSystemSystemLanguageJapanese": "Giapponese",
|
||||
"SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano",
|
||||
"SettingsTabSystemSystemLanguageFrench": "Francese",
|
||||
"SettingsTabSystemSystemLanguageGerman": "Tedesco",
|
||||
"SettingsTabSystemSystemLanguageItalian": "Italiano",
|
||||
"SettingsTabSystemSystemLanguageSpanish": "Spagnolo",
|
||||
"SettingsTabSystemSystemLanguageChinese": "Cinese",
|
||||
"SettingsTabSystemSystemLanguageKorean": "Coreano",
|
||||
"SettingsTabSystemSystemLanguageDutch": "Olandese",
|
||||
"SettingsTabSystemSystemLanguagePortuguese": "Portoghese",
|
||||
"SettingsTabSystemSystemLanguageRussian": "Russo",
|
||||
"SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese",
|
||||
"SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico",
|
||||
"SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese",
|
||||
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano",
|
||||
"SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato",
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale",
|
||||
"SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:",
|
||||
"SettingsTabSystemSystemTime": "Data e ora del sistema:",
|
||||
"SettingsTabSystemEnableVsync": "Attiva VSync",
|
||||
"SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS",
|
||||
"SettingsTabSystemAudioBackend": "Backend audio:",
|
||||
"SettingsTabSystemAudioBackendDummy": "Dummy",
|
||||
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
|
||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||
"SettingsTabSystemHacks": "Hacks",
|
||||
"SettingsTabSystemHacksNote": " (Possono causare instabilità)",
|
||||
"SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB",
|
||||
"SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti",
|
||||
"SettingsTabGraphics": "Grafica",
|
||||
"SettingsTabGraphicsAPI": "API Grafiche",
|
||||
"SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache",
|
||||
"SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:",
|
||||
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
|
||||
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering4x": "4x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering8x": "8x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering16x": "16x",
|
||||
"SettingsTabGraphicsResolutionScale": "Scala della risoluzione:",
|
||||
"SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)",
|
||||
"SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)",
|
||||
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
|
||||
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
|
||||
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
|
||||
"SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:",
|
||||
"SettingsTabGraphicsAspectRatio4x3": "4:3",
|
||||
"SettingsTabGraphicsAspectRatio16x9": "16:9",
|
||||
"SettingsTabGraphicsAspectRatio16x10": "16:10",
|
||||
"SettingsTabGraphicsAspectRatio21x9": "21:9",
|
||||
"SettingsTabGraphicsAspectRatio32x9": "32:9",
|
||||
"SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra",
|
||||
"SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore",
|
||||
"SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:",
|
||||
"SettingsTabLogging": "Log",
|
||||
"SettingsTabLoggingLogging": "Log",
|
||||
"SettingsTabLoggingEnableLoggingToFile": "Salva i log su file",
|
||||
"SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs",
|
||||
"SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs",
|
||||
"SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs",
|
||||
"SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs",
|
||||
"SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs",
|
||||
"SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs",
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:",
|
||||
"SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "Errore",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto",
|
||||
"SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug",
|
||||
"SettingsTabInput": "Input",
|
||||
"SettingsTabInputEnableDockedMode": "Attiva modalità TV",
|
||||
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
||||
"SettingsButtonSave": "Salva",
|
||||
"SettingsButtonClose": "Chiudi",
|
||||
"SettingsButtonOk": "OK",
|
||||
"SettingsButtonCancel": "Cancella",
|
||||
"SettingsButtonApply": "Applica",
|
||||
"ControllerSettingsPlayer": "Giocatore",
|
||||
"ControllerSettingsPlayer1": "Giocatore 1",
|
||||
"ControllerSettingsPlayer2": "Giocatore 2",
|
||||
"ControllerSettingsPlayer3": "Giocatore 3",
|
||||
"ControllerSettingsPlayer4": "Giocatore 4",
|
||||
"ControllerSettingsPlayer5": "Giocatore 5",
|
||||
"ControllerSettingsPlayer6": "Giocatore 6",
|
||||
"ControllerSettingsPlayer7": "Giocatore 7",
|
||||
"ControllerSettingsPlayer8": "Giocatore 8",
|
||||
"ControllerSettingsHandheld": "Portatile",
|
||||
"ControllerSettingsInputDevice": "Dispositivo di input",
|
||||
"ControllerSettingsRefresh": "Ricarica",
|
||||
"ControllerSettingsDeviceDisabled": "Disabilitato",
|
||||
"ControllerSettingsControllerType": "Tipo di controller",
|
||||
"ControllerSettingsControllerTypeHandheld": "Portatile",
|
||||
"ControllerSettingsControllerTypeProController": "Pro Controller",
|
||||
"ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon",
|
||||
"ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro",
|
||||
"ControllerSettingsControllerTypeJoyConRight": "JoyCon destro",
|
||||
"ControllerSettingsProfile": "Profilo",
|
||||
"ControllerSettingsProfileDefault": "Predefinito",
|
||||
"ControllerSettingsLoad": "Carica",
|
||||
"ControllerSettingsAdd": "Aggiungi",
|
||||
"ControllerSettingsRemove": "Rimuovi",
|
||||
"ControllerSettingsButtons": "Pulsanti",
|
||||
"ControllerSettingsButtonA": "A",
|
||||
"ControllerSettingsButtonB": "B",
|
||||
"ControllerSettingsButtonX": "X",
|
||||
"ControllerSettingsButtonY": "Y",
|
||||
"ControllerSettingsButtonPlus": "+",
|
||||
"ControllerSettingsButtonMinus": "-",
|
||||
"ControllerSettingsDPad": "Croce direzionale",
|
||||
"ControllerSettingsDPadUp": "Su",
|
||||
"ControllerSettingsDPadDown": "Giù",
|
||||
"ControllerSettingsDPadLeft": "Sinistra",
|
||||
"ControllerSettingsDPadRight": "Destra",
|
||||
"ControllerSettingsLStick": "Stick sinistro",
|
||||
"ControllerSettingsLStickButton": "Pulsante",
|
||||
"ControllerSettingsLStickUp": "Su",
|
||||
"ControllerSettingsLStickDown": "Giù",
|
||||
"ControllerSettingsLStickLeft": "Sinistra",
|
||||
"ControllerSettingsLStickRight": "Destra",
|
||||
"ControllerSettingsLStickStick": "Stick",
|
||||
"ControllerSettingsLStickInvertXAxis": "Inverti stick X",
|
||||
"ControllerSettingsLStickInvertYAxis": "Inverti stick Y",
|
||||
"ControllerSettingsLStickDeadzone": "Zona morta:",
|
||||
"ControllerSettingsRStick": "Stick destro",
|
||||
"ControllerSettingsRStickButton": "Pulsante",
|
||||
"ControllerSettingsRStickUp": "Su",
|
||||
"ControllerSettingsRStickDown": "Giù",
|
||||
"ControllerSettingsRStickLeft": "Sinistra",
|
||||
"ControllerSettingsRStickRight": "Destra",
|
||||
"ControllerSettingsRStickStick": "Stick",
|
||||
"ControllerSettingsRStickInvertXAxis": "Inverti stick X",
|
||||
"ControllerSettingsRStickInvertYAxis": "Inverti stick Y",
|
||||
"ControllerSettingsRStickDeadzone": "Zona morta:",
|
||||
"ControllerSettingsTriggersLeft": "Grilletto sinistro",
|
||||
"ControllerSettingsTriggersRight": "Grilletto destro",
|
||||
"ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro",
|
||||
"ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro",
|
||||
"ControllerSettingsTriggers": "Grilletti",
|
||||
"ControllerSettingsTriggerL": "L",
|
||||
"ControllerSettingsTriggerR": "R",
|
||||
"ControllerSettingsTriggerZL": "ZL",
|
||||
"ControllerSettingsTriggerZR": "ZR",
|
||||
"ControllerSettingsLeftSL": "SL",
|
||||
"ControllerSettingsLeftSR": "SR",
|
||||
"ControllerSettingsRightSL": "SL",
|
||||
"ControllerSettingsRightSR": "SR",
|
||||
"ControllerSettingsExtraButtonsLeft": "Tasto sinitro",
|
||||
"ControllerSettingsExtraButtonsRight": "Tasto destro",
|
||||
"ControllerSettingsMisc": "Varie",
|
||||
"ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:",
|
||||
"ControllerSettingsMotion": "Movimento",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook",
|
||||
"ControllerSettingsMotionControllerSlot": "Slot del controller:",
|
||||
"ControllerSettingsMotionMirrorInput": "Input specchiato",
|
||||
"ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:",
|
||||
"ControllerSettingsMotionServerHost": "Server:",
|
||||
"ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:",
|
||||
"ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:",
|
||||
"ControllerSettingsSave": "Salva",
|
||||
"ControllerSettingsClose": "Chiudi",
|
||||
"UserProfilesSelectedUserProfile": "Profilo utente selezionato:",
|
||||
"UserProfilesSaveProfileName": "Salva nome del profilo",
|
||||
"UserProfilesChangeProfileImage": "Cambia immagine profilo",
|
||||
"UserProfilesAvailableUserProfiles": "Profili utente disponibili:",
|
||||
"UserProfilesAddNewProfile": "Aggiungi nuovo profilo",
|
||||
"UserProfilesDeleteSelectedProfile": "Elimina il profilo selezionato",
|
||||
"UserProfilesClose": "Chiudi",
|
||||
"ProfileImageSelectionTitle": "Selezione dell'immagine profilo",
|
||||
"ProfileImageSelectionHeader": "Scegli un'immagine profilo",
|
||||
"ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema",
|
||||
"ProfileImageSelectionImportImage": "Importa file immagine",
|
||||
"ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware",
|
||||
"InputDialogTitle": "Input Dialog",
|
||||
"InputDialogOk": "OK",
|
||||
"InputDialogCancel": "Annulla",
|
||||
"InputDialogAddNewProfileTitle": "Scegli il nome profilo",
|
||||
"InputDialogAddNewProfileHeader": "Digita un nome profilo",
|
||||
"InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})",
|
||||
"AvatarChoose": "Scegli",
|
||||
"AvatarSetBackgroundColor": "Imposta colore di sfondo",
|
||||
"AvatarClose": "Chiudi",
|
||||
"ControllerSettingsLoadProfileToolTip": "Carica profilo",
|
||||
"ControllerSettingsAddProfileToolTip": "Aggiungi profilo",
|
||||
"ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo",
|
||||
"ControllerSettingsSaveProfileToolTip": "Salva profilo",
|
||||
"MenuBarFileToolsTakeScreenshot": "Fai uno screenshot",
|
||||
"MenuBarFileToolsHideUi": "Nascondi Ui",
|
||||
"GameListContextMenuToggleFavorite": "Preferito",
|
||||
"GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito",
|
||||
"SettingsTabGeneralTheme": "Tema",
|
||||
"SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato",
|
||||
"SettingsTabGeneralThemeBaseStyle": "Modalità",
|
||||
"SettingsTabGeneralThemeBaseStyleDark": "Scura",
|
||||
"SettingsTabGeneralThemeBaseStyleLight": "Chiara",
|
||||
"SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato",
|
||||
"ButtonBrowse": "Sfoglia",
|
||||
"ControllerSettingsConfigureGeneral": "Configura",
|
||||
"ControllerSettingsRumble": "Vibrazione",
|
||||
"ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte",
|
||||
"ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole",
|
||||
"DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]",
|
||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?",
|
||||
"DialogConfirmationTitle": "Ryujinx - Conferma",
|
||||
"DialogUpdaterTitle": "Ryujinx - Updater",
|
||||
"DialogErrorTitle": "Ryujinx - Errore",
|
||||
"DialogWarningTitle": "Ryujinx - Avviso",
|
||||
"DialogExitTitle": "Ryujinx - Uscita",
|
||||
"DialogErrorMessage": "Ryujinx ha riscontrato un problema",
|
||||
"DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?",
|
||||
"DialogExitSubMessage": "Tutti i dati non salvati andranno persi!",
|
||||
"DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}",
|
||||
"DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}",
|
||||
"FolderDialogExtractTitle": "Scegli una cartella in cui estrarre",
|
||||
"DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...",
|
||||
"DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA",
|
||||
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.",
|
||||
"DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.",
|
||||
"DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.",
|
||||
"DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.",
|
||||
"DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!",
|
||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore nel tentativo di ottenere informazioni da Github Release. Questo può verificarsi se una nuova release è in compilazione su GitHub Actions. Riprova tra qualche minuto.",
|
||||
"DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.",
|
||||
"DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...",
|
||||
"DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...",
|
||||
"DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...",
|
||||
"DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...",
|
||||
"DialogUpdaterCompleteMessage": "Aggiornamento completato!",
|
||||
"DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?",
|
||||
"DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!",
|
||||
"DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)",
|
||||
"DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!",
|
||||
"DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!",
|
||||
"DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!",
|
||||
"DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.",
|
||||
"DialogRestartRequiredMessage": "Riavvio richiesto",
|
||||
"DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.",
|
||||
"DialogThemeRestartSubMessage": "Vuoi riavviare?",
|
||||
"DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})",
|
||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.",
|
||||
"DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato",
|
||||
"DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato",
|
||||
"DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni",
|
||||
"DialogControllerAppletTitle": "Applet del controller",
|
||||
"DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}",
|
||||
"DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}",
|
||||
"DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}",
|
||||
"DialogUserErrorDialogMessage": "{0}: {1}",
|
||||
"DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.",
|
||||
"DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})",
|
||||
"DialogAmiiboApiTitle": "Amiibo API",
|
||||
"DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.",
|
||||
"DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.",
|
||||
"DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.",
|
||||
"DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto",
|
||||
"DialogProfileDeleteProfileTitle": "Eliminazione profilo",
|
||||
"DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?",
|
||||
"DialogWarning": "Avviso",
|
||||
"DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?",
|
||||
"DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}",
|
||||
"DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?",
|
||||
"DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}",
|
||||
"DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore",
|
||||
"DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido",
|
||||
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.",
|
||||
"DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}",
|
||||
"DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.",
|
||||
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.",
|
||||
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?",
|
||||
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...",
|
||||
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.",
|
||||
"DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato",
|
||||
"DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. File errato: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?",
|
||||
"DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.",
|
||||
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?",
|
||||
"DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato",
|
||||
"DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.",
|
||||
"DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!",
|
||||
"DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading",
|
||||
"DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.",
|
||||
"SettingsTabGraphicsFeaturesOptions": "Funzionalità",
|
||||
"SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading",
|
||||
"CommonAuto": "Auto",
|
||||
"CommonOff": "Spento",
|
||||
"CommonOn": "Acceso",
|
||||
"InputDialogYes": "Si",
|
||||
"InputDialogNo": "No",
|
||||
"DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.",
|
||||
"MenuBarOptionsPauseEmulation": "Pausa",
|
||||
"MenuBarOptionsResumeEmulation": "Riprendi",
|
||||
"AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.",
|
||||
"AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.",
|
||||
"AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.",
|
||||
"AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.",
|
||||
"AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.",
|
||||
"AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.",
|
||||
"AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.",
|
||||
"AboutRyujinxAboutTitle": "Informazioni:",
|
||||
"AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.",
|
||||
"AboutRyujinxMaintainersTitle": "Mantenuto da:",
|
||||
"AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.",
|
||||
"AboutRyujinxSupprtersTitle": "Supportato su Patreon da:",
|
||||
"AmiiboSeriesLabel": "Serie Amiibo",
|
||||
"AmiiboCharacterLabel": "Personaggio",
|
||||
"AmiiboScanButtonLabel": "Scannerizza",
|
||||
"AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo",
|
||||
"AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale",
|
||||
"DlcManagerTableHeadingEnabledLabel": "Abilitato",
|
||||
"DlcManagerTableHeadingTitleIdLabel": "Title ID",
|
||||
"DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore",
|
||||
"DlcManagerTableHeadingFullPathLabel": "Percorso completo",
|
||||
"DlcManagerRemoveAllButton": "Rimuovi tutti",
|
||||
"MenuBarOptionsChangeLanguage": "Cambia lingua",
|
||||
"CommonSort": "Ordina",
|
||||
"CommonShowNames": "Mostra nomi",
|
||||
"CommonFavorite": "Preferito",
|
||||
"OrderAscending": "Crescente",
|
||||
"OrderDescending": "Decrescente",
|
||||
"SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti",
|
||||
"ErrorWindowTitle": "Finestra errore",
|
||||
"ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence",
|
||||
"AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista",
|
||||
"AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista",
|
||||
"RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata",
|
||||
"CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI",
|
||||
"CustomThemePathTooltip": "Percorso al tema GUI personalizzato",
|
||||
"CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato",
|
||||
"DockModeToggleTooltip": "Attiva o disabilta modalità TV",
|
||||
"DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)",
|
||||
"DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)",
|
||||
"RegionTooltip": "Cambia regione di sistema",
|
||||
"LanguageTooltip": "Cambia lingua di sistema",
|
||||
"TimezoneTooltip": "Cambia fuso orario di sistema",
|
||||
"TimeTooltip": "Cambia data e ora di sistema",
|
||||
"VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale",
|
||||
"PptcToggleTooltip": "Attiva o disattiva PPTC",
|
||||
"FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco",
|
||||
"AudioBackendTooltip": "Cambia backend audio",
|
||||
"MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.",
|
||||
"MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.",
|
||||
"MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.",
|
||||
"MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.",
|
||||
"DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB",
|
||||
"IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti",
|
||||
"GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading",
|
||||
"GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.",
|
||||
"ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache",
|
||||
"ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili",
|
||||
"ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.",
|
||||
"AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)",
|
||||
"AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.",
|
||||
"ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders",
|
||||
"FileLogTooltip": "Attiva o disattiva il logging su file",
|
||||
"StubLogTooltip": "Attiva messaggi stub log",
|
||||
"InfoLogTooltip": "Attiva messaggi info log",
|
||||
"WarnLogTooltip": "Attiva messaggi warning log",
|
||||
"ErrorLogTooltip": "Attiva messaggi error log",
|
||||
"TraceLogTooltip": "Attiva messaggi trace log",
|
||||
"GuestLogTooltip": "Attiva messaggi guest log",
|
||||
"FileAccessLogTooltip": "Attiva messaggi file access log",
|
||||
"FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3",
|
||||
"DeveloperOptionTooltip": "Usa con attenzione",
|
||||
"OpenGlLogLevel": "Richiede livelli di log appropriati abilitati",
|
||||
"DebugLogTooltip": "Attiva messaggi debug log",
|
||||
"LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare",
|
||||
"LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare",
|
||||
"OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx",
|
||||
"OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log",
|
||||
"ExitTooltip": "Esci da Ryujinx",
|
||||
"OpenSettingsTooltip": "Apri finestra delle impostazioni",
|
||||
"OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente",
|
||||
"StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi",
|
||||
"CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx",
|
||||
"OpenAboutTooltip": "Apri finestra delle informazioni",
|
||||
"GridSize": "Dimensione griglia",
|
||||
"GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia",
|
||||
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano",
|
||||
"AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors",
|
||||
"SettingsTabSystemAudioVolume": "Volume: ",
|
||||
"AudioVolumeTooltip": "Cambia volume audio",
|
||||
"SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access",
|
||||
"EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata",
|
||||
"GameListContextMenuManageCheatToolTip": "Gestisci Cheats",
|
||||
"GameListContextMenuManageCheat": "Gestisci Cheats",
|
||||
"ControllerSettingsStickRange": "Raggio:",
|
||||
"DialogStopEmulationTitle": "Ryujinx - Ferma emulazione",
|
||||
"DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?",
|
||||
"SettingsTabCpu": "CPU",
|
||||
"SettingsTabAudio": "Audio",
|
||||
"SettingsTabNetwork": "Rete",
|
||||
"SettingsTabNetworkConnection": "Connessione di rete",
|
||||
"SettingsTabCpuCache": "Cache CPU",
|
||||
"SettingsTabCpuMemory": "Memoria CPU",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "Updater disabilitato!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella aternativa di atmosphere che contiene le mods dell'applicazione",
|
||||
"ControllerSettingsRotate90": "Ruota in senso orario di 90°",
|
||||
"IconSize": "Dimensioni icona",
|
||||
"IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco",
|
||||
"MenuBarOptionsShowConsole": "Mostra console",
|
||||
"ShaderCachePurgeError" : "Errore nella pulizia della shader cache a {0}: {1}",
|
||||
"UserErrorNoKeys": "Chiavi non trovate",
|
||||
"UserErrorNoFirmware": "Firmware non trovato",
|
||||
"UserErrorFirmwareParsingFailed": "Errori di analisi del firmware",
|
||||
"UserErrorApplicationNotFound": "Applicazione non trovata",
|
||||
"UserErrorUnknown": "Errore sconosciuto",
|
||||
"UserErrorUndefined": "Errore non definito",
|
||||
"UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'",
|
||||
"UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato",
|
||||
"UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.",
|
||||
"UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida al percorso specificato.",
|
||||
"UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!",
|
||||
"UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!",
|
||||
"OpenSetupGuideMessage": "Apri la guida all'installazione",
|
||||
"NoUpdate": "Nessun aggiornamento",
|
||||
"TitleUpdateVersionLabel": "Versione {0} - {1}",
|
||||
"RyujinxInfo": "Ryujinx - Info",
|
||||
"RyujinxConfirm": "Ryujinx - Conferma",
|
||||
"FileDialogAllTypes": "Tutti i tipi",
|
||||
"Never": "Mai",
|
||||
"SwkbdMinCharacters": "Non può avere meno di {0} caratteri",
|
||||
"SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri",
|
||||
"SoftwareKeyboard": "Tastiera software",
|
||||
"DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.",
|
||||
"DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.",
|
||||
"DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n",
|
||||
"UpdaterRenaming": "Rinominazione dei vecchi files...",
|
||||
"UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}",
|
||||
"UpdaterAddingFiles": "Aggiunta nuovi files...",
|
||||
"UpdaterExtracting": "Estrazione aggiornamento...",
|
||||
"UpdaterDownloading": "Download aggiornamento...",
|
||||
"Game": "Gioco",
|
||||
"Docked": "TV",
|
||||
"Handheld": "Portatile",
|
||||
"ConnectionError": "Errore di connessione.",
|
||||
"AboutPageDeveloperListMore": "{0} e altri ancora...",
|
||||
"ApiError": "Errore dell'API.",
|
||||
"LoadingHeading": "Caricamento di {0}",
|
||||
"CompilingPPTC": "Compilazione PTC",
|
||||
"CompilingShaders": "Compilazione Shaders",
|
||||
"AllKeyboards": "Tutte le tastiere",
|
||||
"OpenFileDialogTitle": "Seleziona un file supportato da aprire",
|
||||
"OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto",
|
||||
"AllSupportedFormats": "Tutti i formati supportati",
|
||||
"RyujinxUpdater": "Ryujinx Updater",
|
||||
"SettingsTabHotkeys": "Tasti di scelta rapida",
|
||||
"SettingsTabHotkeysHotkeys": "Tasti di scelta rapida",
|
||||
"SettingsTabHotkeysToggleVsyncHotkey": "VSync:",
|
||||
"SettingsTabHotkeysScreenshotHotkey": "Screenshot:",
|
||||
"SettingsTabHotkeysShowUiHotkey": "Mostra UI:",
|
||||
"SettingsTabHotkeysPauseHotkey": "Metti in pausa:",
|
||||
"SettingsTabHotkeysToggleMuteHotkey": "Muta:",
|
||||
"ControllerMotionTitle": "Impostazioni dei sensori di movimento",
|
||||
"ControllerRumbleTitle": "Impostazioni di vibrazione",
|
||||
"SettingsSelectThemeFileDialogTitle" : "Seleziona file del tema",
|
||||
"SettingsXamlThemeFile" : "File del tema xaml",
|
||||
"SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:",
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:",
|
||||
"AvatarWindowTitle": "Gestisci account - Avatar",
|
||||
"Amiibo": "Amiibo",
|
||||
"Unknown": "Sconosciuto",
|
||||
"Usage": "Utilizzo",
|
||||
"Writable": "Scrivibile",
|
||||
"SelectDlcDialogTitle": "Seleziona file dei DLC",
|
||||
"SelectUpdateDialogTitle": "Seleziona file di aggiornamento",
|
||||
"UserProfileWindowTitle": "Gestisci profili degli utenti",
|
||||
"CheatWindowTitle": "Gestisci cheat dei giochi",
|
||||
"DlcWindowTitle": "Gestisci DLC dei giochi",
|
||||
"UpdateWindowTitle": "Gestisci aggiornamenti dei giochi",
|
||||
"CheatWindowHeading": "Cheat disponibiili per {0} [{1}]",
|
||||
"DlcWindowHeading": "DLC disponibili per {0} [{1}]",
|
||||
"UserProfilesEditProfile": "Modifica selezionati",
|
||||
"Cancel": "Annulla",
|
||||
"Save": "Salva",
|
||||
"Discard": "Scarta",
|
||||
"UserProfilesSetProfileImage": "Imposta immagine profilo",
|
||||
"UserProfileEmptyNameError": "È richiesto un nome",
|
||||
"UserProfileNoImageError": "Dev'essere impostata un'immagine profilo",
|
||||
"GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]",
|
||||
"UserProfilesName": "Name:",
|
||||
"UserProfilesUserId": "User Id:",
|
||||
"SettingsTabGraphicsBackend": "Backend grafica",
|
||||
"SettingsTabGraphicsBackendTooltip": "Backend grafica da usare",
|
||||
"SettingsEnableTextureRecompression": "Abilita Ricompressione Texture",
|
||||
"SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.",
|
||||
"SettingsTabGraphicsPreferredGpu": "GPU preferita",
|
||||
"SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.",
|
||||
"SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx",
|
||||
"SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate",
|
||||
"SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?",
|
||||
"RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?",
|
||||
"SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:",
|
||||
"SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:",
|
||||
"VolumeShort": "Vol"
|
||||
}
|
@ -1,495 +0,0 @@
|
||||
{
|
||||
"MenuBarFileOpenApplet": "애플릿 열기",
|
||||
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기",
|
||||
"SettingsTabInputDirectMouseAccess": "직접 마우스 액세스",
|
||||
"SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드 :",
|
||||
"SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어",
|
||||
"SettingsTabSystemMemoryManagerModeHost": "호스트 (빠른)",
|
||||
"SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트가 확인되지 않음 (가장 빠르고 안전하지 않은)",
|
||||
"MenuBarFile": "_파일",
|
||||
"MenuBarFileOpenFromFile": "_서류철음에서 애플리케이션 로드",
|
||||
"MenuBarFileOpenUnpacked": "_압축을 푼 게임 로드",
|
||||
"MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기",
|
||||
"MenuBarFileOpenLogsFolder": "로그 폴더 열기",
|
||||
"MenuBarFileExit": "_그만두",
|
||||
"MenuBarOptions": "옵션",
|
||||
"MenuBarOptionsToggleFullscreen": "전체 화면 전환",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 열기",
|
||||
"MenuBarOptionsStopEmulation": "에뮬레이션 중지",
|
||||
"MenuBarOptionsSettings": "_조절",
|
||||
"MenuBarOptionsManageUserProfiles": "사용자 프로필 _관리",
|
||||
"MenuBarActions": "_행위",
|
||||
"MenuBarOptionsSimulateWakeUpMessage": "깨우기 명령어 시뮬레이션",
|
||||
"MenuBarActionsScanAmiibo": "Amiibo 스캔",
|
||||
"MenuBarTools": "_도구",
|
||||
"MenuBarToolsInstallFirmware": "펌웨어 설치",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치",
|
||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "디렉토리에서 펌웨어 설치",
|
||||
"MenuBarHelp": "돕다",
|
||||
"MenuBarHelpCheckForUpdates": "업데이트 확인",
|
||||
"MenuBarHelpAbout": "통지",
|
||||
"MenuSearch": "찾다···",
|
||||
"GameListHeaderFavorite": "즐겨찾기",
|
||||
"GameListHeaderIcon": "상",
|
||||
"GameListHeaderApplication": "이름",
|
||||
"GameListHeaderDeveloper": "개발자",
|
||||
"GameListHeaderVersion": "버전",
|
||||
"GameListHeaderTimePlayed": "플레이 시간",
|
||||
"GameListHeaderLastPlayed": "해본 마지막",
|
||||
"GameListHeaderFileExtension": "파일 확장자",
|
||||
"GameListHeaderFileSize": "파일 크기",
|
||||
"GameListHeaderPath": "파일 경로",
|
||||
"GameListContextMenuOpenUserSaveDirectory": "사용자 저장 폴더 열기",
|
||||
"GameListContextMenuOpenUserSaveDirectoryToolTip": "응용 프로그램의 사용자 저장이 포함된 폴더를 엽니다",
|
||||
"GameListContextMenuOpenUserDeviceDirectory": "사용자 장치 폴더 열기",
|
||||
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "응용 프로그램의 장치 저장이 포함된 폴더를 엽니다",
|
||||
"GameListContextMenuOpenUserBcatDirectory": "사용자의 BCAT 폴더 열기",
|
||||
"GameListContextMenuOpenUserBcatDirectoryToolTip": "응용 프로그램의 BCAT 저장이 포함된 폴더를 엽니다",
|
||||
"GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리s",
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기",
|
||||
"GameListContextMenuManageDlc": "DLC 관리",
|
||||
"GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기",
|
||||
"GameListContextMenuOpenModsDirectory": "모드 디렉토리 열기",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "응용 프로그램의 모드가들 포함된 디렉터리를 엽니다",
|
||||
"GameListContextMenuCacheManagement": "캐시 관리",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "PPTC 캐시 제거",
|
||||
"GameListContextMenuCacheManagementPurgePptcToolTip": "응용 프로그램 PPTC 캐시 삭제",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거",
|
||||
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "애플리케이션 셰이더 캐시를 삭제합니다.",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉토리 열기",
|
||||
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용 프로그램 PPTC 캐시가 포함된 디렉터리를 엽니다",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉토리 열기",
|
||||
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용 프로그램 셰이더 캐시가 포함된 디렉터리를 엽니다",
|
||||
"GameListContextMenuExtractData": "데이터 추출",
|
||||
"GameListContextMenuExtractDataExeFS": "ExeFS",
|
||||
"GameListContextMenuExtractDataExeFSToolTip": "애플리케이션의 현재 구성에서 ExeFS 추출 (업데이트 포함)",
|
||||
"GameListContextMenuExtractDataRomFS": "RomFS",
|
||||
"GameListContextMenuExtractDataRomFSToolTip": "애플리케이션의 현재 구성에서 RomFS 추출 (업데이트 포함)",
|
||||
"GameListContextMenuExtractDataLogo": "Logo",
|
||||
"GameListContextMenuExtractDataLogoToolTip": "애플리케이션의 현재 구성에서 로고 섹션 추출 (업데이트 포함)",
|
||||
"StatusBarGamesLoaded": "로드된 {0}/{1}개의 게임",
|
||||
"StatusBarSystemVersion": "시스템 버전 : {0}",
|
||||
"Settings": "조절",
|
||||
"SettingsTabGeneral": "사용자 인터페이스",
|
||||
"SettingsTabGeneralGeneral": "일반",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "Discord Rich Presence 활성화",
|
||||
"SettingsTabGeneralCheckUpdatesOnLaunch": "열 때 업데이트 확인",
|
||||
"SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시",
|
||||
"SettingsTabGeneralHideCursorOnIdle": "유휴 상태에서 커서 숨기기",
|
||||
"SettingsTabGeneralGameDirectories": "게임 디렉토리들",
|
||||
"SettingsTabGeneralAdd": "추가하다",
|
||||
"SettingsTabGeneralRemove": "제거하다",
|
||||
"SettingsTabSystem": "체계",
|
||||
"SettingsTabSystemCore": "핵심",
|
||||
"SettingsTabSystemSystemRegion": "시스템 영역 :",
|
||||
"SettingsTabSystemSystemRegionJapan": "일본",
|
||||
"SettingsTabSystemSystemRegionUSA": "미국",
|
||||
"SettingsTabSystemSystemRegionEurope": "유럽",
|
||||
"SettingsTabSystemSystemRegionAustralia": "호주",
|
||||
"SettingsTabSystemSystemRegionChina": "중국",
|
||||
"SettingsTabSystemSystemRegionKorea": "한국",
|
||||
"SettingsTabSystemSystemRegionTaiwan": "대만",
|
||||
"SettingsTabSystemSystemLanguage": "시스템 언어 :",
|
||||
"SettingsTabSystemSystemLanguageJapanese": "일본어",
|
||||
"SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)",
|
||||
"SettingsTabSystemSystemLanguageFrench": "프랑스어",
|
||||
"SettingsTabSystemSystemLanguageGerman": "독일어",
|
||||
"SettingsTabSystemSystemLanguageItalian": "이탈리아어",
|
||||
"SettingsTabSystemSystemLanguageSpanish": "스페인어",
|
||||
"SettingsTabSystemSystemLanguageChinese": "중국어",
|
||||
"SettingsTabSystemSystemLanguageKorean": "한국어",
|
||||
"SettingsTabSystemSystemLanguageDutch": "네덜란드 어",
|
||||
"SettingsTabSystemSystemLanguagePortuguese": "포르투갈어",
|
||||
"SettingsTabSystemSystemLanguageRussian": "러시아어",
|
||||
"SettingsTabSystemSystemLanguageTaiwanese": "대만어",
|
||||
"SettingsTabSystemSystemLanguageBritishEnglish": "영어(영국)",
|
||||
"SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어(캐나다)",
|
||||
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어(라틴 아메리카)",
|
||||
"SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체",
|
||||
"SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체",
|
||||
"SettingsTabSystemSystemTimeZone": "시스템 시간대 :",
|
||||
"SettingsTabSystemSystemTime": "시스템 시간 :",
|
||||
"SettingsTabSystemEnableVsync": "수직 동기화 사용",
|
||||
"SettingsTabSystemEnablePptc": "PPTC(Profiled Persistent Translation Cache) 활성화",
|
||||
"SettingsTabSystemEnableFsIntegrityChecks": "FS 무결성 검사 활성화",
|
||||
"SettingsTabSystemAudioBackend": "오디오 백엔드 :",
|
||||
"SettingsTabSystemAudioBackendDummy": "Dummy",
|
||||
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
|
||||
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
|
||||
"SettingsTabSystemAudioBackendSDL2": "SDL2",
|
||||
"SettingsTabSystemHacks": "해킹",
|
||||
"SettingsTabSystemHacksNote": " (불안정을 일으킬 수 있음)",
|
||||
"SettingsTabSystemExpandDramSize": "DRAM 크기를 6GiB로 확장",
|
||||
"SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시",
|
||||
"SettingsTabGraphics": "제도법",
|
||||
"SettingsTabGraphicsAPI": "그래픽 API",
|
||||
"SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화",
|
||||
"SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링 :",
|
||||
"SettingsTabGraphicsAnisotropicFilteringAuto": "자동적 인",
|
||||
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering4x": "4x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering8x": "8x",
|
||||
"SettingsTabGraphicsAnisotropicFiltering16x": "16x",
|
||||
"SettingsTabGraphicsResolutionScale": "해상도 스케일 :",
|
||||
"SettingsTabGraphicsResolutionScaleCustom": "사용자 지정(권장하지 않음)",
|
||||
"SettingsTabGraphicsResolutionScaleNative": "기본 (720p/1080p)",
|
||||
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
|
||||
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
|
||||
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
|
||||
"SettingsTabGraphicsAspectRatio": "종횡비 :",
|
||||
"SettingsTabGraphicsAspectRatio4x3": "4:3",
|
||||
"SettingsTabGraphicsAspectRatio16x9": "16:9",
|
||||
"SettingsTabGraphicsAspectRatio16x10": "16:10",
|
||||
"SettingsTabGraphicsAspectRatio21x9": "21:9",
|
||||
"SettingsTabGraphicsAspectRatio32x9": "32:9",
|
||||
"SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기",
|
||||
"SettingsTabGraphicsDeveloperOptions": "개발자 옵션",
|
||||
"SettingsTabGraphicsShaderDumpPath": "그래픽 쉐이더 덤프 경로 :",
|
||||
"SettingsTabLogging": "로깅",
|
||||
"SettingsTabLoggingLogging": "로깅",
|
||||
"SettingsTabLoggingEnableLoggingToFile": "파일에 로깅 활성화",
|
||||
"SettingsTabLoggingEnableStubLogs": "스텁 로그 켜기 ",
|
||||
"SettingsTabLoggingEnableInfoLogs": "정보 로그 켜기",
|
||||
"SettingsTabLoggingEnableWarningLogs": "경고 로그 켜기",
|
||||
"SettingsTabLoggingEnableErrorLogs": "오류 로그 켜기",
|
||||
"SettingsTabLoggingEnableGuestLogs": "게스트 로그 켜기",
|
||||
"SettingsTabLoggingEnableFsAccessLogs": "Fs 액세스 로그 켜기",
|
||||
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 액세스 로그 모드 :",
|
||||
"SettingsTabLoggingDeveloperOptions": "개발자 옵션 (경고 : 성능이 저하됩니다.)",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelNone": "없음",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelError": "오류",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "감속",
|
||||
"SettingsTabLoggingGraphicsBackendLogLevelAll": "모두",
|
||||
"SettingsTabLoggingEnableDebugLogs": "디버그 로그 사용",
|
||||
"SettingsTabInput": "입력",
|
||||
"SettingsTabInputEnableDockedMode": "도킹 모드 활성화",
|
||||
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
||||
"SettingsButtonSave": "구하다",
|
||||
"SettingsButtonClose": "출구",
|
||||
"SettingsButtonOk": "좋아",
|
||||
"SettingsButtonCancel": "취소",
|
||||
"SettingsButtonApply": "적용하다",
|
||||
"ControllerSettingsPlayer": "플레이어",
|
||||
"ControllerSettingsPlayer1": "플레이어 1",
|
||||
"ControllerSettingsPlayer2": "플레이어 2",
|
||||
"ControllerSettingsPlayer3": "플레이어 3",
|
||||
"ControllerSettingsPlayer4": "플레이어 4",
|
||||
"ControllerSettingsPlayer5": "플레이어 5",
|
||||
"ControllerSettingsPlayer6": "플레이어 6",
|
||||
"ControllerSettingsPlayer7": "플레이어 7",
|
||||
"ControllerSettingsPlayer8": "플레이어 8",
|
||||
"ControllerSettingsHandheld": "휴대용",
|
||||
"ControllerSettingsInputDevice": "입력 장치",
|
||||
"ControllerSettingsRefresh": "새로 고치다",
|
||||
"ControllerSettingsDeviceDisabled": "장애가있는",
|
||||
"ControllerSettingsControllerType": "컨트롤러 유형",
|
||||
"ControllerSettingsControllerTypeHandheld": "휴대용",
|
||||
"ControllerSettingsControllerTypeProController": "Pro Controller",
|
||||
"ControllerSettingsControllerTypeJoyConPair": "JoyCon 페어",
|
||||
"ControllerSettingsControllerTypeJoyConLeft": "JoyCon 왼쪽",
|
||||
"ControllerSettingsControllerTypeJoyConRight": "JoyCon 오른쪽",
|
||||
"ControllerSettingsProfile": "프로필",
|
||||
"ControllerSettingsProfileDefault": "기본",
|
||||
"ControllerSettingsLoad": "짐",
|
||||
"ControllerSettingsAdd": "추가하다",
|
||||
"ControllerSettingsRemove": "제거하다",
|
||||
"ControllerSettingsButtons": "버튼",
|
||||
"ControllerSettingsButtonA": "A",
|
||||
"ControllerSettingsButtonB": "B",
|
||||
"ControllerSettingsButtonX": "X",
|
||||
"ControllerSettingsButtonY": "Y",
|
||||
"ControllerSettingsButtonPlus": "+",
|
||||
"ControllerSettingsButtonMinus": "-",
|
||||
"ControllerSettingsDPad": "Directional Pad",
|
||||
"ControllerSettingsDPadUp": "위로",
|
||||
"ControllerSettingsDPadDown": "아래에",
|
||||
"ControllerSettingsDPadLeft": "왼쪽",
|
||||
"ControllerSettingsDPadRight": "오른쪽",
|
||||
"ControllerSettingsLStick": "왼쪽 스틱",
|
||||
"ControllerSettingsLStickButton": "단추",
|
||||
"ControllerSettingsLStickUp": "위로",
|
||||
"ControllerSettingsLStickDown": "아래에",
|
||||
"ControllerSettingsLStickLeft": "왼쪽",
|
||||
"ControllerSettingsLStickRight": "오른쪽",
|
||||
"ControllerSettingsLStickStick": "막대",
|
||||
"ControllerSettingsLStickInvertXAxis": "스틱 X축 반전",
|
||||
"ControllerSettingsLStickInvertYAxis": "스틱 Y축 반전",
|
||||
"ControllerSettingsLStickDeadzone": "데드 존 :",
|
||||
"ControllerSettingsRStick": "오른쪽 스틱",
|
||||
"ControllerSettingsRStickButton": "단추",
|
||||
"ControllerSettingsRStickUp": "위로",
|
||||
"ControllerSettingsRStickDown": "아래에",
|
||||
"ControllerSettingsRStickLeft": "왼쪽",
|
||||
"ControllerSettingsRStickRight": "오른쪽",
|
||||
"ControllerSettingsRStickStick": "Stick",
|
||||
"ControllerSettingsRStickInvertXAxis": "스틱 X축 반전",
|
||||
"ControllerSettingsRStickInvertYAxis": "스틱 Y축 반전",
|
||||
"ControllerSettingsRStickDeadzone": "데드 존 :",
|
||||
"ControllerSettingsTriggersLeft": "왼쪽 트리거",
|
||||
"ControllerSettingsTriggersRight": "오른쪽 트리거",
|
||||
"ControllerSettingsTriggersButtonsLeft": "트리거 버튼 왼쪽",
|
||||
"ControllerSettingsTriggersButtonsRight": "트리거 버튼 오른쪽",
|
||||
"ControllerSettingsTriggers": "방아쇠",
|
||||
"ControllerSettingsTriggerL": "L",
|
||||
"ControllerSettingsTriggerR": "R",
|
||||
"ControllerSettingsTriggerZL": "ZL",
|
||||
"ControllerSettingsTriggerZR": "ZR",
|
||||
"ControllerSettingsLeftSL": "SL",
|
||||
"ControllerSettingsLeftSR": "SR",
|
||||
"ControllerSettingsRightSL": "SL",
|
||||
"ControllerSettingsRightSR": "SR",
|
||||
"ControllerSettingsExtraButtonsLeft": "왼쪽 버튼",
|
||||
"ControllerSettingsExtraButtonsRight": "버튼 오른쪽",
|
||||
"ControllerSettingsMisc": "여러 가지 잡다한",
|
||||
"ControllerSettingsTriggerThreshold": "트리거 임계값 :",
|
||||
"ControllerSettingsMotion": "운동",
|
||||
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용",
|
||||
"ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :",
|
||||
"ControllerSettingsMotionMirrorInput": "미러 입력",
|
||||
"ControllerSettingsMotionRightJoyConSlot": "오른쪽 JoyCon 슬롯 :",
|
||||
"ControllerSettingsMotionServerHost": "서버 호스트 :",
|
||||
"ControllerSettingsMotionGyroSensitivity": "자이로 감도 :",
|
||||
"ControllerSettingsMotionGyroDeadzone": "자이로 데드존 :",
|
||||
"ControllerSettingsSave": "구하다",
|
||||
"ControllerSettingsClose": "출구",
|
||||
"UserProfilesSelectedUserProfile": "선택한 사용자 프로필 :",
|
||||
"UserProfilesSaveProfileName": "프로필 이름 저장",
|
||||
"UserProfilesChangeProfileImage": "프로필 이미지 변경",
|
||||
"UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로필 :",
|
||||
"UserProfilesAddNewProfile": "새 프로필 추가",
|
||||
"UserProfilesDeleteSelectedProfile": "선택한 프로필 삭제",
|
||||
"UserProfilesClose": "출구",
|
||||
"ProfileImageSelectionTitle": "프로필 이미지 선택",
|
||||
"ProfileImageSelectionHeader": "이미지 선택",
|
||||
"ProfileImageSelectionNote": "사용자 정의 프로필 이미지를 사용하거나 시스템 펌웨어에서 하나를 선택할 수 있습니다",
|
||||
"ProfileImageSelectionImportImage": "이미지 파일 가져오기",
|
||||
"ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택",
|
||||
"InputDialogTitle": "입력 대화 상자",
|
||||
"InputDialogOk": "확인",
|
||||
"InputDialogCancel": "취소",
|
||||
"InputDialogAddNewProfileTitle": "프로필 이름 선택",
|
||||
"InputDialogAddNewProfileHeader": "프로필 이름을 입력하세요",
|
||||
"InputDialogAddNewProfileSubtext": "최대 길이 : {0})",
|
||||
"AvatarChoose": "선택하다",
|
||||
"AvatarSetBackgroundColor": "배경색 설정",
|
||||
"AvatarClose": "출구",
|
||||
"ControllerSettingsLoadProfileToolTip": "프로필 로드",
|
||||
"ControllerSettingsAddProfileToolTip": "프로필 추가",
|
||||
"ControllerSettingsRemoveProfileToolTip": "프로필 제거",
|
||||
"ControllerSettingsSaveProfileToolTip": "프로필 저장",
|
||||
"MenuBarFileToolsTakeScreenshot": "스크린 샷을 찍다",
|
||||
"MenuBarFileToolsHideUi": "사용자 인터페이스 숨기기",
|
||||
"GameListContextMenuToggleFavorite": "즐겨찾기 설정",
|
||||
"GameListContextMenuToggleFavoriteToolTip": "이 게임이 즐겨찾기인지 여부를 전환합니다",
|
||||
"SettingsTabGeneralTheme": "테마",
|
||||
"SettingsTabGeneralThemeCustomTheme": "사용자 정의 테마 경로",
|
||||
"SettingsTabGeneralThemeBaseStyle": "기본 스타일",
|
||||
"SettingsTabGeneralThemeBaseStyleDark": "어두운",
|
||||
"SettingsTabGeneralThemeBaseStyleLight": "빛",
|
||||
"SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화",
|
||||
"ButtonBrowse": "검색",
|
||||
"ControllerSettingsConfigureGeneral": "션 구성",
|
||||
"ControllerSettingsRumble": "하인 좌석",
|
||||
"ControllerSettingsRumbleStrongMultiplier": "강력한 럼블 배율기",
|
||||
"ControllerSettingsRumbleWeakMultiplier": "약한 럼블 승수",
|
||||
"DialogMessageSaveNotAvailableMessage": "에 대한 세이브 데이터가 없습니다 {0} [{1:x16}]",
|
||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임의 세이브 데이터를 생성하시겠습니까?",
|
||||
"DialogConfirmationTitle": "Ryujinx - 확인",
|
||||
"DialogUpdaterTitle": "Ryujinx - 업데이터",
|
||||
"DialogErrorTitle": "Ryujinx - 오류",
|
||||
"DialogWarningTitle": "Ryujinx - 경고",
|
||||
"DialogExitTitle": "Ryujinx - 출구",
|
||||
"DialogErrorMessage": "Ryujinx에 오류가 발생했습니다",
|
||||
"DialogExitMessage": "Ryujinx를 종료하시겠습니까?",
|
||||
"DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!",
|
||||
"DialogMessageCreateSaveErrorMessage": "지정된 세이브 데이터를 작성하는 중에 오류가 발생했습니다 : {0}",
|
||||
"DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류가 발생했습니다 : {0}",
|
||||
"FolderDialogExtractTitle": "추출할 폴더 선택",
|
||||
"DialogNcaExtractionMessage": "{1}에서 섹션 {0} 추출···",
|
||||
"DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기",
|
||||
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패. 선택한 파일에 기본 NCA가 없습니다.",
|
||||
"DialogNcaExtractionCheckLogErrorMessage": "추출 실패. 자세한 내용은 로그 파일을 읽으십시오.",
|
||||
"DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.",
|
||||
"DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.",
|
||||
"DialogUpdaterCancelUpdateMessage": "업데이트 취소 중!",
|
||||
"DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!",
|
||||
"DialogUpdaterFailedToGetVersionMessage": "Github 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하십시오.",
|
||||
"DialogUpdaterConvertFailedGithubMessage": "Github Release에서 받은 Ryujinx 버전을 변환하지 못했습니다.",
|
||||
"DialogUpdaterDownloadingMessage": "업데이트 다운로드 중···",
|
||||
"DialogUpdaterExtractionMessage": "업데이트 추출···",
|
||||
"DialogUpdaterRenamingMessage": "업데이트 이름 바꾸기···",
|
||||
"DialogUpdaterAddingFilesMessage": "새 업데이트 추가···",
|
||||
"DialogUpdaterCompleteMessage": "업데이트 완료!",
|
||||
"DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하시겠습니까?",
|
||||
"DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!",
|
||||
"DialogUpdaterArchNotSupportedSubMessage": "(x86 시스템만 지원됩니다!)",
|
||||
"DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!",
|
||||
"DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하십시오!",
|
||||
"DialogUpdaterDirtyBuildMessage": "Ryujinx의 더러운 빌드는 업데이트할 수 없습니다!",
|
||||
"DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하십시오.",
|
||||
"DialogRestartRequiredMessage": "재시작 필요",
|
||||
"DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.",
|
||||
"DialogThemeRestartSubMessage": "다시 시작하시겠습니까?",
|
||||
"DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하시겠습니까? (펌웨어 {0})",
|
||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어를 찾을 수 없지만 Ryujinx는 제공된 게임에서 펌웨어 {0} 설치할 수 있었습니다.\n이제 에뮬레이터가 시작됩니다.",
|
||||
"DialogFirmwareNoFirmwareInstalledMessage": "펌웨어가 설치되지 않았습니다",
|
||||
"DialogFirmwareInstalledMessage": "펌웨어 {0} 설치되었습니다.",
|
||||
"DialogOpenSettingsWindowLabel": "설정 창 열기",
|
||||
"DialogControllerAppletTitle": "컨트롤러 애플릿",
|
||||
"DialogMessageDialogErrorExceptionMessage": "메시지 대화 상자를 표시하는 동안 오류가 발생했습니다 : {0}",
|
||||
"DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류가 발생했습니다 : {0}",
|
||||
"DialogErrorAppletErrorExceptionMessage": "ErrorApplet 대화 상자를 표시하는 동안 오류가 발생했습니다 : {0}",
|
||||
"DialogUserErrorDialogMessage": "{0}: {1}",
|
||||
"DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르십시오",
|
||||
"DialogUserErrorDialogTitle": "Ryuijnx의 오류 ({0})",
|
||||
"DialogAmiiboApiTitle": "Amiibo API",
|
||||
"DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.",
|
||||
"DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 오프라인인지 확인해야 할 수 있습니다.",
|
||||
"DialogProfileInvalidProfileErrorMessage": "프로필 AAA는 현재 입력 구성 시스템과 호환되지 않습니다.",
|
||||
"DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로필을 덮어쓸 수 없습니다",
|
||||
"DialogProfileDeleteProfileTitle": "프로필 삭제",
|
||||
"DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?",
|
||||
"DialogWarning": "경고",
|
||||
"DialogPPTCDeletionMessage": "{0}에\n\n 대한 PPTC 캐시를 삭제하려고 합니다.\n\n계속하시겠습니까?",
|
||||
"DialogPPTCDeletionErrorMessage": "{0} : {1}에서 PPTC 캐시를 제거하는 동안 오류가 발생했습니다.",
|
||||
"DialogShaderDeletionMessage": "{0}에\n\n 대한 셰이더 캐시를 삭제하려고 합니다.\n\n계속하시겠습니까?",
|
||||
"DialogShaderDeletionErrorMessage": "{0} : {1}에서 셰이더 캐시를 제거하는 동안 오류가 발생했습니다.",
|
||||
"DialogRyujinxErrorMessage": "Ryujinx에 오류가 발생했습니다",
|
||||
"DialogInvalidTitleIdErrorMessage": "UI 오류 : 선택한 게임에 유효한 제목 ID가 없습니다",
|
||||
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.",
|
||||
"DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치",
|
||||
"DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}가 설치되려고 합니다",
|
||||
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 설치된 버전 {0}를 대체합니다.",
|
||||
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하시겠습니까?",
|
||||
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치···",
|
||||
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}가 성공적으로 설치되었습니다.",
|
||||
"DialogUserProfileDeletionWarningMessage": "선택한 프로필이 삭제되면 사용 가능한 다른 프로필이 없습니다",
|
||||
"DialogUserProfileDeletionConfirmMessage": "선택한 프로필을 삭제하시겠습니까",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "저장하시겠습니까?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. 오류 파일 : {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "지정한 파일에 선택한 타이틀의 DLC가 없습니다!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "개발자 전용으로 설계된 디버그 로깅을 활성화했습니다.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 디버그 로깅을 비활성화하는 것이 좋습니다. 지금 디버그 로깅을 비활성화하시겠습니까?",
|
||||
"DialogPerformanceCheckShaderDumpEnabledMessage": "Y개발자만 사용하도록 설계된 셰이더 덤핑이 활성화되어 있습니다.",
|
||||
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 셰이더 덤핑을 비활성화하는 것이 좋습니다. 지금 셰이더 덤핑을 비활성화하시겠습니까?",
|
||||
"DialogLoadAppGameAlreadyLoadedMessage": "다른 게임이 이미 로드되었습니다",
|
||||
"DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으십시오.",
|
||||
"DialogUpdateAddUpdateErrorMessage": "파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!",
|
||||
"DialogSettingsBackendThreadingWarningTitle": "경고 - 백엔드 스레딩",
|
||||
"DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후 Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.",
|
||||
"SettingsTabGraphicsFeaturesOptions": "특징",
|
||||
"SettingsTabGraphicsBackendMultithreading": "그래픽 백엔드 멀티스레딩 :",
|
||||
"CommonAuto": "자동적 인",
|
||||
"CommonOff": "끄다",
|
||||
"CommonOn": "켜짐",
|
||||
"InputDialogYes": "네",
|
||||
"InputDialogNo": "아니",
|
||||
"DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 기호가 있습니다. 다시 시도하십시오.",
|
||||
"MenuBarOptionsPauseEmulation": "정지시키다",
|
||||
"MenuBarOptionsResumeEmulation": "재개하다",
|
||||
"AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하십시오.",
|
||||
"AboutDisclaimerMessage": "Ryujinx는 Nintendo™와 제휴하지 않으며,\n어떤 식으로든 또는 그 파트너.",
|
||||
"AboutAmiiboDisclaimerMessage": "AmiiboAPI(www.amiiboapi.com) 사용\nAmiibo 에뮬레이션에서.",
|
||||
"AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.",
|
||||
"AboutGithubUrlTooltipMessage": "클릭하여 기본 브라우저에서 Ryujinx GitHub 페이지를 엽니다.",
|
||||
"AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx Discord 서버 초대를 열려면 클릭하세요.",
|
||||
"AboutTwitterUrlTooltipMessage": "클릭하여 기본 브라우저에서 Ryujinx Twitter 페이지를 엽니다.",
|
||||
"AboutRyujinxAboutTitle": "에 대한 :",
|
||||
"AboutRyujinxAboutContent": "Ryujinx는 Nintendo Switch™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n모든 최신 뉴스는 Twitter 또는 Discord에서 확인하세요.\n기고에 관심이 있는 개발자는 GitHub 또는 Discord에서 자세한 내용을 확인할 수 있습니다.",
|
||||
"AboutRyujinxMaintainersTitle": "유지 관리 :",
|
||||
"AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하십시오.",
|
||||
"AboutRyujinxSupprtersTitle": "Patreon에서 지원 :",
|
||||
"AmiiboSeriesLabel": "Amiibo 시리즈",
|
||||
"AmiiboCharacterLabel": "성격",
|
||||
"AmiiboScanButtonLabel": "스캔",
|
||||
"AmiiboOptionsShowAllLabel": "모든 Amiibo 표시",
|
||||
"AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용",
|
||||
"DlcManagerTableHeadingEnabledLabel": "활성화됨",
|
||||
"DlcManagerTableHeadingTitleIdLabel": "제목 ID",
|
||||
"DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로",
|
||||
"DlcManagerTableHeadingFullPathLabel": "전체 경로",
|
||||
"DlcManagerRemoveAllButton": "모두 제거",
|
||||
"MenuBarOptionsChangeLanguage": "언어 변경",
|
||||
"CommonSort": "정렬",
|
||||
"CommonShowNames": "이름 표시",
|
||||
"CommonFavorite": "가장 좋아하는",
|
||||
"OrderAscending": "오름차순",
|
||||
"OrderDescending": "내림차순",
|
||||
"SettingsTabGraphicsFeatures": "특징ㆍ개선 사항",
|
||||
"ErrorWindowTitle": "오류 창",
|
||||
"ToggleDiscordTooltip": "Discord Rich Presence 활성화 또는 비활성화",
|
||||
"AddGameDirBoxTooltip": "게임 디렉토리를 입력하여 목록에 추가하세요",
|
||||
"AddGameDirTooltip": "목록에 게임 디렉토리 추가",
|
||||
"RemoveGameDirTooltip": "선택한 게임 디렉토리 제거",
|
||||
"CustomThemeCheckTooltip": "GUI에서 사용자 정의 테마 활성화 또는 비활성화",
|
||||
"CustomThemePathTooltip": "사용자 지정 GUI 테마 경로",
|
||||
"CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾기",
|
||||
"DockModeToggleTooltip": "도킹 모드 활성화 또는 비활성화",
|
||||
"DirectKeyboardTooltip": "\"직접 키보드 액세스(HID) 지원\" 활성화 또는 비활성화(텍스트 입력 장치로 키보드에 대한 게임 액세스 제공)",
|
||||
"DirectMouseTooltip": "\"직접 마우스 액세스(HID) 지원\" 활성화 또는 비활성화(포인팅 장치로 마우스에 대한 게임 액세스 제공)",
|
||||
"RegionTooltip": "시스템 지역 변경",
|
||||
"LanguageTooltip": "시스템 언어 변경",
|
||||
"TimezoneTooltip": "시스템 시간대 변경",
|
||||
"TimeTooltip": "시스템 시간 변경",
|
||||
"VSyncToggleTooltip": "수직 동기화 활성화 또는 비활성화",
|
||||
"PptcToggleTooltip": "PPTC 활성화 또는 비활성화",
|
||||
"FsIntegrityToggleTooltip": "게임 콘텐츠 파일에 대한 무결성 검사 활성화",
|
||||
"AudioBackendTooltip": "오디오 백엔드 변경",
|
||||
"MemoryManagerTooltip": "게스트 메모리가 매핑되고 액세스되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 큰 영향을 줍니다.",
|
||||
"MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하십시오. 정확도는 가장 높지만 성능은 가장 느립니다.",
|
||||
"MemoryManagerHostTooltip": "호스트 주소 공간에서 메모리를 직접 매핑합니다. 훨씬 더 빠른 JIT 컴파일 및 실행.",
|
||||
"MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 액세스하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 액세스할 수 있으므로 이 모드로 신뢰할 수 있는 프로그램만 실행하십시오.",
|
||||
"DRamTooltip": "에뮬레이트된 시스템의 메모리 양을 4GiB에서 6GiB로 확장",
|
||||
"IgnoreMissingServicesTooltip": "누락된 서비스 무시 옵션 활성화 또는 비활성화",
|
||||
"GraphicsBackendThreadingTooltip": "그래픽 백엔드 멀티스레딩 활성화",
|
||||
"GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다. 셰이더 컴파일의 런타임 멀티스레딩을 허용하고, 말더듬을 줄이고, 자체 멀티스레딩 지원 없이 드라이버의 성능을 개선합니다. 멀티스레딩이 있는 드라이버에서 약간 다른 최대 성능. 드라이버 내장 멀티스레딩을 올바르게 비활성화하려면 Ryujinx를 다시 시작해야 할 수도 있고 최상의 성능을 얻으려면 수동으로 수행해야 할 수도 있습니다.",
|
||||
"ShaderCacheToggleTooltip": "셰이더 캐시 활성화 또는 비활성화",
|
||||
"ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 배율",
|
||||
"ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 해상도 스케일. 비적분 스케일은 문제나 충돌을 일으킬 가능성이 더 큽니다.",
|
||||
"AnisotropyTooltip": "이방성 필터링 수준(게임에서 요청한 값을 사용하려면 자동으로 설정)",
|
||||
"AspectRatioTooltip": "렌더러 창에 적용된 종횡비.",
|
||||
"ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로",
|
||||
"FileLogTooltip": "디스크의 파일에 대한 로깅 활성화 또는 비활성화",
|
||||
"StubLogTooltip": "스텁 로그 메시지 인쇄 활성화",
|
||||
"InfoLogTooltip": "정보 로그 메시지 인쇄 활성화",
|
||||
"WarnLogTooltip": "경고 로그 메시지 인쇄 활성화",
|
||||
"ErrorLogTooltip": "오류 로그 메시지 인쇄 활성화",
|
||||
"GuestLogTooltip": "게스트 로그 메시지 인쇄 활성화",
|
||||
"FileAccessLogTooltip": "파일 액세스 로그 메시지 인쇄 활성화",
|
||||
"FSAccessLogModeTooltip": "콘솔에 대한 FS 액세스 로그 출력을 활성화합니다. 가능한 모드는 0-3입니다",
|
||||
"DeveloperOptionTooltip": "주의해서 사용",
|
||||
"OpenGlLogLevel": "적절한 로그 수준이 활성화되어 있어야 합니다",
|
||||
"DebugLogTooltip": "디버그 로그 메시지 인쇄 활성화",
|
||||
"LoadApplicationFileTooltip": "로드할 Switch 호환 파일을 선택하려면 파일 선택기를 엽니다.",
|
||||
"LoadApplicationFolderTooltip": "파일 선택기를 열어 로드할 Switch 호환 가능하고 압축을 푼 응용 프로그램을 선택합니다",
|
||||
"OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기",
|
||||
"OpenRyujinxLogsTooltip": "로그가 기록되는 폴더를 엽니다.",
|
||||
"ExitTooltip": "Ryujinx 종료",
|
||||
"OpenSettingsTooltip": "설정 창 열기",
|
||||
"OpenProfileManagerTooltip": "사용자 프로필 관리자 창 열기",
|
||||
"StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아가기",
|
||||
"CheckUpdatesTooltip": "Ryujinx 업데이트 확인",
|
||||
"OpenAboutTooltip": "정보 창 열기",
|
||||
"GridSize": "그리드 크기",
|
||||
"GridSizeTooltip": "그리드 항목의 크기 변경",
|
||||
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어(브라질)",
|
||||
"AboutRyujinxContributorsButtonHeader": "모든 기여자 보기",
|
||||
"SettingsTabSystemAudioVolume": "용량 : ",
|
||||
"AudioVolumeTooltip": "오디오 볼륨 변경",
|
||||
"SettingsTabSystemEnableInternetAccess": "게스트 인터넷 액세스 활성화s",
|
||||
"EnableInternetAccessTooltip": "게스트 인터넷 액세스를 활성화합니다. 활성화된 경우 응용 프로그램은 에뮬레이트된 스위치 콘솔이 인터넷에 연결된 것처럼 작동합니다. 경우에 따라 이 옵션이 비활성화된 경우에도 응용 프로그램이 인터넷에 계속 액세스할 수 있습니다",
|
||||
"GameListContextMenuManageCheatToolTip" : "치트 관리",
|
||||
"GameListContextMenuManageCheat" : "치트 관리",
|
||||
"ControllerSettingsStickRange" : "범위:",
|
||||
"DialogStopEmulationTitle" : "Ryujinx - 에뮬레이션 중지",
|
||||
"DialogStopEmulationMessage": "에뮬레이션을 중지하시겠습니까?",
|
||||
"SettingsTabCpu": "CPU",
|
||||
"SettingsTabAudio": "오디오",
|
||||
"SettingsTabNetwork": "회로망",
|
||||
"SettingsTabNetworkConnection" : "네트워크 연결",
|
||||
"SettingsTabCpuCache" : "CPU 캐시",
|
||||
"SettingsTabCpuMemory" : "CPU 메모리",
|
||||
"ControllerMotionTitle": "Motion Control Settings",
|
||||
"ControllerRumbleTitle": "Rumble Settings"
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
class LocaleManager : BaseModel
|
||||
{
|
||||
private const string DefaultLanguageCode = "en_US";
|
||||
|
||||
private Dictionary<string, string> _localeStrings;
|
||||
private ConcurrentDictionary<string, object[]> _dynamicValues;
|
||||
|
||||
public static LocaleManager Instance { get; } = new LocaleManager();
|
||||
public Dictionary<string, string> LocaleStrings { get => _localeStrings; set => _localeStrings = value; }
|
||||
|
||||
|
||||
public LocaleManager()
|
||||
{
|
||||
_localeStrings = new Dictionary<string, string>();
|
||||
_dynamicValues = new ConcurrentDictionary<string, object[]>();
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value))
|
||||
{
|
||||
localeLanguageCode = ConfigurationState.Instance.Ui.LanguageCode.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Load english first, if the target language translation is incomplete, we default to english.
|
||||
LoadDefaultLanguage();
|
||||
|
||||
if (localeLanguageCode != DefaultLanguageCode)
|
||||
{
|
||||
LoadLanguage(localeLanguageCode);
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_localeStrings.TryGetValue(key, out string value))
|
||||
{
|
||||
if (_dynamicValues.TryGetValue(key, out var dynamicValue))
|
||||
{
|
||||
return string.Format(value, dynamicValue);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
set
|
||||
{
|
||||
_localeStrings[key] = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDynamicValue(string key, params object[] values)
|
||||
{
|
||||
_dynamicValues[key] = values;
|
||||
|
||||
OnPropertyChanged("Item");
|
||||
}
|
||||
|
||||
public void LoadDefaultLanguage()
|
||||
{
|
||||
LoadLanguage(DefaultLanguageCode);
|
||||
}
|
||||
|
||||
public void LoadLanguage(string languageCode)
|
||||
{
|
||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||
|
||||
if (languageJson == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
|
||||
foreach (var item in strings)
|
||||
{
|
||||
this[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ConfigurationState.Instance.Ui.LanguageCode.Value = languageCode;
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helper
|
||||
{
|
||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
static partial class MetalHelper
|
||||
{
|
||||
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
|
||||
|
||||
private struct Selector
|
||||
{
|
||||
public readonly IntPtr NativePtr;
|
||||
|
||||
public unsafe Selector(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
NativePtr = sel_registerName(data);
|
||||
}
|
||||
|
||||
public static implicit operator Selector(string value) => new Selector(value);
|
||||
}
|
||||
|
||||
private static unsafe IntPtr GetClass(string value)
|
||||
{
|
||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
||||
byte* data = stackalloc byte[size];
|
||||
|
||||
fixed (char* pValue = value)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
||||
}
|
||||
|
||||
return objc_getClass(data);
|
||||
}
|
||||
|
||||
private struct NSPoint
|
||||
{
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public NSPoint(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
private struct NSRect
|
||||
{
|
||||
public NSPoint Pos;
|
||||
public NSPoint Size;
|
||||
|
||||
public NSRect(double x, double y, double width, double height)
|
||||
{
|
||||
Pos = new NSPoint(x, y);
|
||||
Size = new NSPoint(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
|
||||
{
|
||||
// Create a new CAMetalLayer.
|
||||
IntPtr layerClass = GetClass("CAMetalLayer");
|
||||
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
|
||||
objc_msgSend(metalLayer, "init");
|
||||
|
||||
// Create a child NSView to render into.
|
||||
IntPtr nsViewClass = GetClass("NSView");
|
||||
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
|
||||
|
||||
// Make its renderer our metal layer.
|
||||
objc_msgSend(child, "setWantsLayer:", (byte)1);
|
||||
objc_msgSend(child, "setLayer:", metalLayer);
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
|
||||
// Ensure the scale factor is up to date.
|
||||
updateBounds = (Rect rect) => {
|
||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||
};
|
||||
|
||||
nsView = child;
|
||||
return metalLayer;
|
||||
}
|
||||
|
||||
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static unsafe partial IntPtr sel_registerName(byte* data);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static unsafe partial IntPtr objc_getClass(byte* data);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
||||
|
||||
[LibraryImport(LibObjCImport)]
|
||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
||||
|
||||
[LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")]
|
||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
||||
}
|
||||
}
|
@ -1,714 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Modules
|
||||
{
|
||||
internal static class Updater
|
||||
{
|
||||
private const string GitHubApiURL = "https://api.github.com";
|
||||
internal static bool Running;
|
||||
|
||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
|
||||
private static readonly int ConnectionCount = 4;
|
||||
|
||||
private static string _buildVer;
|
||||
private static string _platformExt;
|
||||
private static string _buildUrl;
|
||||
private static long _buildSize;
|
||||
|
||||
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
||||
|
||||
public static bool UpdateSuccessful { get; private set; }
|
||||
|
||||
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
|
||||
{
|
||||
if (Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Running = true;
|
||||
mainWindow.CanUpdate = false;
|
||||
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "osx_x64.zip";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_platformExt = "win_x64.zip";
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
_platformExt = "linux_x64.tar.gz";
|
||||
}
|
||||
|
||||
Version newVersion;
|
||||
Version currentVersion;
|
||||
|
||||
try
|
||||
{
|
||||
currentVersion = Version.Parse(Program.Version);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get latest version number from GitHub API
|
||||
try
|
||||
{
|
||||
using (HttpClient jsonClient = ConstructHttpClient())
|
||||
{
|
||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformations.ReleaseChannelOwner}/{ReleaseInformations.ReleaseChannelRepo}/releases/latest";
|
||||
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||
JToken assets = jsonRoot["assets"];
|
||||
|
||||
_buildVer = (string)jsonRoot["name"];
|
||||
|
||||
foreach (JToken asset in assets)
|
||||
{
|
||||
string assetName = (string)asset["name"];
|
||||
string assetState = (string)asset["state"];
|
||||
string downloadURL = (string)asset["browser_download_url"];
|
||||
|
||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||
{
|
||||
_buildUrl = downloadURL;
|
||||
|
||||
if (assetState != "uploaded")
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are availaible.
|
||||
if (_buildUrl == null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
newVersion = Version.Parse(_buildVer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVersion <= currentVersion)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
|
||||
});
|
||||
}
|
||||
|
||||
Running = false;
|
||||
mainWindow.CanUpdate = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch build size information to learn chunk sizes.
|
||||
using (HttpClient buildSizeClient = ConstructHttpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||
|
||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
|
||||
|
||||
_buildSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
|
||||
LocaleManager.Instance["RyujinxUpdaterMessage"],
|
||||
$"{Program.Version} -> {newVersion}");
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
UpdateRyujinx(mainWindow, _buildUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpClient ConstructHttpClient()
|
||||
{
|
||||
HttpClient result = new HttpClient();
|
||||
|
||||
// Required by GitHub to interract with APIs.
|
||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
UpdateSuccessful = false;
|
||||
|
||||
// Empty update dir, although it shouldn't ever have anything inside it
|
||||
if (Directory.Exists(UpdateDir))
|
||||
{
|
||||
Directory.Delete(UpdateDir, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(UpdateDir);
|
||||
|
||||
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
||||
|
||||
var taskDialog = new TaskDialog()
|
||||
{
|
||||
Header = LocaleManager.Instance["RyujinxUpdater"],
|
||||
SubHeader = LocaleManager.Instance["UpdaterDownloading"],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
Buttons = { },
|
||||
ShowProgressBar = true
|
||||
};
|
||||
|
||||
taskDialog.XamlRoot = parent;
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
if (_buildSize >= 0)
|
||||
{
|
||||
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
};
|
||||
|
||||
await taskDialog.ShowAsync(true);
|
||||
|
||||
if (UpdateSuccessful)
|
||||
{
|
||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance["RyujinxUpdater"],
|
||||
LocaleManager.Instance["DialogUpdaterCompleteMessage"],
|
||||
LocaleManager.Instance["DialogUpdaterRestartMessage"]);
|
||||
|
||||
if (shouldRestart)
|
||||
{
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
|
||||
if (!Path.Exists(ryuExe))
|
||||
{
|
||||
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
}
|
||||
|
||||
SetFileExecutable(ryuExe);
|
||||
|
||||
Process.Start(ryuExe, CommandLineState.Arguments);
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long remainderChunk = _buildSize % ConnectionCount;
|
||||
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
|
||||
List<byte[]> list = new List<byte[]>(ConnectionCount);
|
||||
List<WebClient> webClients = new List<WebClient>(ConnectionCount);
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
{
|
||||
list.Add(Array.Empty<byte>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
{
|
||||
#pragma warning disable SYSLIB0014
|
||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||
using (WebClient client = new WebClient())
|
||||
#pragma warning restore SYSLIB0014
|
||||
{
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
||||
}
|
||||
|
||||
client.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
if (args.Cancelled)
|
||||
{
|
||||
webClients[index].Dispose();
|
||||
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
// We do not want to timeout while downloading
|
||||
client.Timeout = TimeSpan.FromDays(1);
|
||||
|
||||
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
||||
{
|
||||
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
|
||||
{
|
||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||
long byteWritten = 0;
|
||||
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readSize = remoteFileStream.Read(buffer);
|
||||
|
||||
if (readSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
byteWritten += readSize;
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetPercentage(double value, double max)
|
||||
{
|
||||
return max == 0 ? 0 : value / max * 100;
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile));
|
||||
worker.Name = "Updater.SingleThreadWorker";
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
private static void SetFileExecutable(string path)
|
||||
{
|
||||
const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
|
||||
UnixFileMode.UserWrite |
|
||||
UnixFileMode.UserRead |
|
||||
UnixFileMode.GroupRead |
|
||||
UnixFileMode.GroupWrite |
|
||||
UnixFileMode.OtherRead |
|
||||
UnixFileMode.OtherWrite;
|
||||
|
||||
if (!OperatingSystem.IsWindows() && File.Exists(path))
|
||||
{
|
||||
File.SetUnixFileMode(path, ExecutableFileMode);
|
||||
}
|
||||
}
|
||||
|
||||
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
{
|
||||
// Extract Update
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterExtracting"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
using (Stream inStream = File.OpenRead(updateFile))
|
||||
using (Stream gzipStream = new GZipInputStream(inStream))
|
||||
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
{
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
TarEntry entry = tarEntry;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream inStream = File.OpenRead(updateFile))
|
||||
using (ZipFile zipFile = new ZipFile(inStream))
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
|
||||
List<string> allFiles = EnumerateFilesToDelete().ToList();
|
||||
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterRenaming"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
{
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
count++;
|
||||
try
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, string.Format(LocaleManager.Instance["UpdaterRenameFailed"], file));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance["UpdaterAddingFiles"];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
Directory.Delete(UpdateDir, true);
|
||||
|
||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
||||
|
||||
UpdateSuccessful = true;
|
||||
|
||||
taskDialog.Hide();
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"],
|
||||
LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterNoInternetMessage"],
|
||||
LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Program.Version.Contains("dirty") || !ReleaseInformations.IsValid())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"],
|
||||
LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
if (showWarnings)
|
||||
{
|
||||
if (ReleaseInformations.IsFlatHubBuild())
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.s
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
{
|
||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
foreach (string dir in WindowsDependencyDirs)
|
||||
{
|
||||
string dirPath = Path.Combine(HomeDir, dir);
|
||||
if (Directory.Exists(dirPath))
|
||||
{
|
||||
files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||
{
|
||||
var total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||
foreach (string directory in Directory.GetDirectories(root))
|
||||
{
|
||||
string dirName = Path.GetFileName(directory);
|
||||
|
||||
if (!Directory.Exists(Path.Combine(dest, dirName)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(dest, dirName));
|
||||
}
|
||||
|
||||
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog);
|
||||
}
|
||||
|
||||
double count = 0;
|
||||
foreach (string file in Directory.GetFiles(root))
|
||||
{
|
||||
count++;
|
||||
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanupUpdate()
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(HomeDir, "*.ryuold", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.GameGridView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenTitleUpdateManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDownloadableContentManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageDlc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenCheatManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenSdModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
Command="{Binding PurgePtcCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding PurgeShaderCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenPtcDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenShaderCacheDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
Command="{Binding ExtractExeFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding ExtractRomFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding ExtractLogo}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxWidth" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Border
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<Panel
|
||||
Grid.Row="1"
|
||||
Height="50"
|
||||
Margin="0 10 0 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</Panel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon
|
||||
Margin="5,5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,83 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibHac.Common;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class GameGridView : UserControl
|
||||
{
|
||||
private ApplicationData _selectedApplication;
|
||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||
{
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||
}
|
||||
|
||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
if (listBox.SelectedItem is ApplicationData selected)
|
||||
{
|
||||
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
var selected = listBox.SelectedItem as ApplicationData;
|
||||
|
||||
_selectedApplication = selected;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationData SelectedApplication => _selectedApplication;
|
||||
|
||||
public GameGridView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||
}
|
||||
|
||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
if (sender is ContextMenu menu)
|
||||
{
|
||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.GameListView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenTitleUpdateManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDownloadableContentManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageDlc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenCheatManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenSdModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
Command="{Binding PurgePtcCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding PurgeShaderCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenPtcDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenShaderCacheDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
Command="{Binding ExtractExeFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding ExtractRomFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding ExtractLogo}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Name="GameListBox"
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxHeight" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||
<Setter Property="MinHeight" Value="100" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Margin="0"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5" >
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Developer}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TimePlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding LastPlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FileSize}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource SystemAccentColor}"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,83 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibHac.Common;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class GameListView : UserControl
|
||||
{
|
||||
private ApplicationData _selectedApplication;
|
||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||
{
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||
}
|
||||
|
||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
if (listBox.SelectedItem is ApplicationData selected)
|
||||
{
|
||||
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
var selected = listBox.SelectedItem as ApplicationData;
|
||||
|
||||
_selectedApplication = selected;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationData SelectedApplication => _selectedApplication;
|
||||
|
||||
public GameListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||
}
|
||||
|
||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
if (sender is ContextMenu menu)
|
||||
{
|
||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.InputDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Width="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLength="{Binding MaxLength}"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding SubMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,57 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class InputDialog : UserControl
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public string Input { get; set; }
|
||||
public string SubMessage { get; set; }
|
||||
|
||||
public uint MaxLength { get; }
|
||||
|
||||
public InputDialog(string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
Message = message;
|
||||
Input = input;
|
||||
SubMessage = subMessage;
|
||||
MaxLength = maxLength;
|
||||
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public InputDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
|
||||
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
|
||||
{
|
||||
UserResult result = UserResult.Cancel;
|
||||
|
||||
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = title,
|
||||
PrimaryButtonText = LocaleManager.Instance["InputDialogOk"],
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance["InputDialogCancel"],
|
||||
Content = content,
|
||||
PrimaryButtonCommand = MiniCommand.Create(() =>
|
||||
{
|
||||
result = UserResult.Ok;
|
||||
input = content.Input;
|
||||
})
|
||||
};
|
||||
await contentDialog.ShowAsync();
|
||||
|
||||
return (result, input);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class NavigationDialogHost : UserControl
|
||||
{
|
||||
public AccountManager AccountManager { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public HorizonClient HorizonClient { get; }
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public NavigationDialogHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||
{
|
||||
AccountManager = accountManager;
|
||||
ContentManager = contentManager;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
HorizonClient = horizonClient;
|
||||
ViewModel = new UserProfileViewModel(this);
|
||||
|
||||
|
||||
if (contentManager.GetCurrentFirmwareVersion() != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
|
||||
});
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void GoBack(object parameter = null)
|
||||
{
|
||||
if (ContentFrame.BackStack.Count > 0)
|
||||
{
|
||||
ContentFrame.GoBack();
|
||||
}
|
||||
|
||||
ViewModel.LoadProfiles();
|
||||
}
|
||||
|
||||
public void Navigate(Type sourcePageType, object parameter)
|
||||
{
|
||||
ContentFrame.Navigate(sourcePageType, parameter);
|
||||
}
|
||||
|
||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||
{
|
||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||
Content = content,
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
contentDialog.Closed += (sender, args) =>
|
||||
{
|
||||
content.ViewModel.Dispose();
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
|
||||
Navigate(typeof(UserSelector), this);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Controls.ProfileImageSelectionDialog"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5,10,5, 5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="70" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
HorizontalAlignment="Center"
|
||||
Grid.Row="1"
|
||||
Text="{locale:Locale ProfileImageSelectionHeader}" />
|
||||
<TextBlock
|
||||
FontWeight="Bold"
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
MaxWidth="400"
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Text="{locale:Locale ProfileImageSelectionNote}" />
|
||||
<StackPanel
|
||||
Margin="5,0"
|
||||
Spacing="10"
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Name="Import"
|
||||
Click="Import_OnClick"
|
||||
Width="200">
|
||||
<TextBlock Text="{locale:Locale ProfileImageSelectionImportImage}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="SelectFirmwareImage"
|
||||
IsEnabled="{Binding FirmwareFound}"
|
||||
Click="SelectFirmwareImage_OnClick"
|
||||
Width="200">
|
||||
<TextBlock Text="{locale:Locale ProfileImageSelectionSelectAvatar}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,127 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class RendererHost : UserControl, IDisposable
|
||||
{
|
||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
||||
private EmbeddedWindow _currentWindow;
|
||||
|
||||
public bool IsVulkan { get; private set; }
|
||||
|
||||
public RendererHost(GraphicsDebugLevel graphicsDebugLevel)
|
||||
{
|
||||
_graphicsDebugLevel = graphicsDebugLevel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public RendererHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void CreateOpenGL()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
_currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel);
|
||||
Initialize();
|
||||
|
||||
IsVulkan = false;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||
Content = _currentWindow;
|
||||
}
|
||||
|
||||
public void CreateVulkan()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
_currentWindow = new VulkanEmbeddedWindow();
|
||||
Initialize();
|
||||
|
||||
IsVulkan = true;
|
||||
}
|
||||
|
||||
public OpenGLContextBase GetContext()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
return openGlEmbeddedWindow.Context;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||
{
|
||||
SizeChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||
{
|
||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void MakeCurrent()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.MakeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeCurrent(SwappableNativeWindowBase window)
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.MakeCurrent(window);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapBuffers()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.SwapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> RendererInitialized;
|
||||
public event Action<object, Size> SizeChanged;
|
||||
public void Dispose()
|
||||
{
|
||||
if (_currentWindow != null)
|
||||
{
|
||||
_currentWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api)
|
||||
{
|
||||
return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow)
|
||||
? vulkanEmbeddedWindow.CreateSurface(instance)
|
||||
: default;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
Height="400"
|
||||
Width="550"
|
||||
x:Class="Ryujinx.Ava.UI.Controls.SaveManager"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center">
|
||||
<Label
|
||||
Content="{locale:Locale CommonSort}"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Name}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Size}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale OrderAscending}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<Label
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Left"
|
||||
Content="{locale:Locale Descending}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10,0, 0, 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label
|
||||
Content="{locale:Locale Search}"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Search}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,5"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox
|
||||
Name="SaveList"
|
||||
Items="{Binding View}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
<Grid HorizontalAlignment="Stretch" Margin="0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Border
|
||||
Height="42"
|
||||
Margin="2"
|
||||
Width="42"
|
||||
Padding="10"
|
||||
IsVisible="{Binding !InGameList}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Help"
|
||||
FontSize="30"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<Image
|
||||
IsVisible="{Binding InGameList}"
|
||||
Margin="2"
|
||||
Width="42"
|
||||
Height="42"
|
||||
Source="{Binding Icon,
|
||||
Converter={StaticResource ByteImage}}" />
|
||||
<TextBlock
|
||||
MaxLines="3"
|
||||
Width="320"
|
||||
Margin="5"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Title}" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
Content="{Binding SizeString}"
|
||||
IsVisible="{Binding SizeAvailable}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Name="OpenLocation"
|
||||
Command="{Binding OpenLocation}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="OpenFolder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Name="Delete"
|
||||
Command="{Binding Delete}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Delete"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,160 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class SaveManager : UserControl
|
||||
{
|
||||
private readonly UserProfile _userProfile;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private int _sortIndex;
|
||||
private int _orderIndex;
|
||||
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||
private string _search;
|
||||
|
||||
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||
|
||||
public ObservableCollection<SaveModel> View
|
||||
{
|
||||
get => _view;
|
||||
set => _view = value;
|
||||
}
|
||||
|
||||
public int SortIndex
|
||||
{
|
||||
get => _sortIndex;
|
||||
set
|
||||
{
|
||||
_sortIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public int OrderIndex
|
||||
{
|
||||
get => _orderIndex;
|
||||
set
|
||||
{
|
||||
_orderIndex = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public SaveManager()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||
{
|
||||
_userProfile = userProfile;
|
||||
_horizonClient = horizonClient;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
InitializeComponent();
|
||||
|
||||
DataContext = this;
|
||||
|
||||
Task.Run(LoadSaves);
|
||||
}
|
||||
|
||||
public void LoadSaves()
|
||||
{
|
||||
Saves.Clear();
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
if (save.ProgramId.Value != 0)
|
||||
{
|
||||
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||
Saves.Add(saveModel);
|
||||
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
|
||||
}
|
||||
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
Saves.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Sort(GetComparer())
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_view.Clear();
|
||||
_view.AddRange(view);
|
||||
}
|
||||
|
||||
private IComparer<SaveModel> GetComparer()
|
||||
{
|
||||
switch (SortIndex)
|
||||
{
|
||||
case 0:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
||||
case 1:
|
||||
return OrderIndex == 0
|
||||
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is SaveModel save)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
MinHeight="400"
|
||||
x:Class="Ryujinx.Ava.UI.Controls.UserRecoverer"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0"
|
||||
Margin="5"
|
||||
Height="30"
|
||||
Width="50"
|
||||
MinWidth="50"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding GoBack}">
|
||||
<ui:SymbolIcon Symbol="Back"/>
|
||||
</Button>
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{locale:Locale UserProfilesRecoverHeading}"/>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding LostProfiles}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding UserId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding Recover}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{locale:Locale Recover}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,44 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class UserRecoverer : UserControl
|
||||
{
|
||||
private UserProfileViewModel _viewModel;
|
||||
private NavigationDialogHost _parent;
|
||||
|
||||
public UserRecoverer()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
switch (arg.NavigationMode)
|
||||
{
|
||||
case NavigationMode.New:
|
||||
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
|
||||
|
||||
_viewModel = args.viewModel;
|
||||
_parent = args.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Controls.UserSelector"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
d:DesignHeight="450"
|
||||
MinWidth="500"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Design.DataContext>
|
||||
<viewModels:UserProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Margin="5"
|
||||
MaxHeight="300"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
DoubleTapped="ProfilesList_DoubleTapped"
|
||||
Items="{Binding Profiles}"
|
||||
SelectionChanged="SelectingItemsControl_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Width="96"
|
||||
Height="96"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Height="30"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Name}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
Width="10"
|
||||
Height="10"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="LimeGreen"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding IsOpened}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding AddUser}"
|
||||
Content="{locale:Locale UserProfilesAddNewProfile}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="0"
|
||||
Margin="2"
|
||||
Grid.Column="1"
|
||||
Command="{Binding EditUser}"
|
||||
Content="{locale:Locale UserProfilesEditProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Content="{locale:Locale UserProfilesManageSaves}"
|
||||
Command="{Binding ManageSaves}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="2"
|
||||
Command="{Binding DeleteUser}"
|
||||
Content="{locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
Command="{Binding RecoverLostAccounts}"
|
||||
Content="{locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,77 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class UserSelector : UserControl
|
||||
{
|
||||
private NavigationDialogHost _parent;
|
||||
public UserProfileViewModel ViewModel { get; set; }
|
||||
|
||||
public UserSelector()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||
{
|
||||
NavigatedTo(e);
|
||||
}, RoutingStrategies.Direct);
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigatedTo(NavigationEventArgs arg)
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
if (arg.NavigationMode == NavigationMode.New)
|
||||
{
|
||||
_parent = (NavigationDialogHost)arg.Parameter;
|
||||
ViewModel = _parent.ViewModel;
|
||||
}
|
||||
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
int selectedIndex = listBox.SelectedIndex;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||
{
|
||||
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
|
||||
|
||||
_parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
|
||||
|
||||
ViewModel.LoadProfiles();
|
||||
|
||||
foreach (UserProfile profile in ViewModel.Profiles)
|
||||
{
|
||||
profile.UpdateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
int selectedIndex = listBox.SelectedIndex;
|
||||
|
||||
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
|
||||
{
|
||||
ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using SPB.Graphics;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
|
||||
{
|
||||
public AvaloniaGlxContext(IntPtr handle)
|
||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
||||
{
|
||||
ContextHandle = handle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using SPB.Graphics;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
|
||||
{
|
||||
public AvaloniaWglContext(IntPtr handle)
|
||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
||||
{
|
||||
ContextHandle = handle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Ryujinx.Ava.UI.Helper;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public class EmbeddedWindow : NativeControlHost
|
||||
{
|
||||
private WindowProc _wndProcDelegate;
|
||||
private string _className;
|
||||
|
||||
protected GLXWindow X11Window { get; set; }
|
||||
protected IntPtr WindowHandle { get; set; }
|
||||
protected IntPtr X11Display { get; set; }
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
public event EventHandler<Size> SizeChanged;
|
||||
|
||||
protected virtual void OnWindowDestroyed() { }
|
||||
protected virtual void OnWindowDestroying()
|
||||
{
|
||||
WindowHandle = IntPtr.Zero;
|
||||
X11Display = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public EmbeddedWindow()
|
||||
{
|
||||
var stateObserverable = this.GetObservable(BoundsProperty);
|
||||
|
||||
stateObserverable.Subscribe(StateChanged);
|
||||
|
||||
this.Initialized += NativeEmbeddedWindow_Initialized;
|
||||
}
|
||||
|
||||
public virtual void OnWindowCreated() { }
|
||||
|
||||
private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
OnWindowCreated();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
WindowCreated?.Invoke(this, WindowHandle);
|
||||
});
|
||||
}
|
||||
|
||||
private void StateChanged(Rect rect)
|
||||
{
|
||||
SizeChanged?.Invoke(this, rect.Size);
|
||||
_updateBoundsCallback?.Invoke(rect);
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return CreateLinux(parent);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return CreateWin32(parent);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return CreateMacOs(parent);
|
||||
}
|
||||
|
||||
return base.CreateNativeControlCore(parent);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
OnWindowDestroying();
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
DestroyLinux();
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
DestroyWin32(control);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
DestroyMacOS();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
}
|
||||
|
||||
OnWindowDestroyed();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||
{
|
||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||
|
||||
return new PlatformHandle(WindowHandle, "X11");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
IPlatformHandle CreateWin32(IPlatformHandle parent)
|
||||
{
|
||||
_className = "NativeWindow-" + Guid.NewGuid();
|
||||
_wndProcDelegate = WndProc;
|
||||
var wndClassEx = new WNDCLASSEX
|
||||
{
|
||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||
hInstance = GetModuleHandle(null),
|
||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CS_OWNDC,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
||||
};
|
||||
|
||||
var atom = RegisterClassEx(ref wndClassEx);
|
||||
|
||||
var handle = CreateWindowEx(
|
||||
0,
|
||||
_className,
|
||||
"NativeWindow",
|
||||
WindowStyles.WS_CHILD,
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
480,
|
||||
parent.Handle,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero);
|
||||
|
||||
WindowHandle = handle;
|
||||
|
||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||
|
||||
return new PlatformHandle(WindowHandle, "HWND");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
||||
var root = VisualRoot as Window;
|
||||
bool isLeft = false;
|
||||
switch (msg)
|
||||
{
|
||||
case WindowsMessages.LBUTTONDOWN:
|
||||
case WindowsMessages.RBUTTONDOWN:
|
||||
isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||
this.RaiseEvent(new PointerPressedEventArgs(
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed),
|
||||
KeyModifiers.None));
|
||||
break;
|
||||
case WindowsMessages.LBUTTONUP:
|
||||
case WindowsMessages.RBUTTONUP:
|
||||
isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||
this.RaiseEvent(new PointerReleasedEventArgs(
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased),
|
||||
KeyModifiers.None,
|
||||
isLeft ? MouseButton.Left : MouseButton.Right));
|
||||
break;
|
||||
case WindowsMessages.MOUSEMOVE:
|
||||
this.RaiseEvent(new PointerEventArgs(
|
||||
PointerMovedEvent,
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||
KeyModifiers.None));
|
||||
break;
|
||||
}
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOs(IPlatformHandle parent)
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
}
|
||||
|
||||
void DestroyLinux()
|
||||
{
|
||||
X11Window?.Dispose();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
void DestroyWin32(IPlatformHandle handle)
|
||||
{
|
||||
DestroyWindow(handle.Handle);
|
||||
UnregisterClass(_className, GetModuleHandle(null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using Avalonia.OpenGL;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class IGlContextExtension
|
||||
{
|
||||
public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context)
|
||||
{
|
||||
var handle = (IntPtr)context.GetType().GetProperty("Handle").GetValue(context);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return new AvaloniaWglContext(handle);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return new AvaloniaGlxContext(handle);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
using Avalonia.Platform;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.Metal;
|
||||
using SPB.Platform.Win32;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public class VulkanEmbeddedWindow : EmbeddedWindow
|
||||
{
|
||||
private NativeWindowBase _window;
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||
{
|
||||
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
|
||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||
|
||||
X11Window.Hide();
|
||||
|
||||
return new PlatformHandle(WindowHandle, "X11");
|
||||
}
|
||||
|
||||
public SurfaceKHR CreateSurface(Instance instance)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_window = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class Amiibo
|
||||
{
|
||||
public struct AmiiboJson
|
||||
{
|
||||
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
|
||||
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
public struct AmiiboApi
|
||||
{
|
||||
[JsonPropertyName("name")] public string Name { get; set; }
|
||||
[JsonPropertyName("head")] public string Head { get; set; }
|
||||
[JsonPropertyName("tail")] public string Tail { get; set; }
|
||||
[JsonPropertyName("image")] public string Image { get; set; }
|
||||
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
|
||||
[JsonPropertyName("character")] public string Character { get; set; }
|
||||
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
|
||||
[JsonPropertyName("type")] public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
|
||||
|
||||
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public string GetId()
|
||||
{
|
||||
return Head + Tail;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is AmiiboApi amiibo)
|
||||
{
|
||||
return amiibo.Head + amiibo.Tail == Head + Tail;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class AmiiboApiGamesSwitch
|
||||
{
|
||||
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
||||
|
||||
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
|
||||
|
||||
[JsonPropertyName("gameName")] public string GameName { get; set; }
|
||||
}
|
||||
|
||||
public class AmiiboApiUsage
|
||||
{
|
||||
[JsonPropertyName("Usage")] public string Usage { get; set; }
|
||||
|
||||
[JsonPropertyName("write")] public bool Write { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class ProfileImageModel
|
||||
{
|
||||
public ProfileImageModel(string name, byte[] data)
|
||||
{
|
||||
Name = name;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
internal class TitleUpdateModel
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool IsNoUpdate { get; }
|
||||
public ApplicationControlProperty Control { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public string Label => IsNoUpdate
|
||||
? LocaleManager.Instance["NoUpdate"]
|
||||
: string.Format(LocaleManager.Instance["TitleUpdateVersionLabel"], Control.DisplayVersionString.ToString(),
|
||||
Path);
|
||||
|
||||
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false)
|
||||
{
|
||||
Control = control;
|
||||
Path = path;
|
||||
IsNoUpdate = isNoUpdate;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,215 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class UserProfileViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private readonly NavigationDialogHost _owner;
|
||||
|
||||
private UserProfile _selectedProfile;
|
||||
private UserProfile _highlightedProfile;
|
||||
|
||||
public UserProfileViewModel()
|
||||
{
|
||||
Profiles = new ObservableCollection<UserProfile>();
|
||||
LostProfiles = new ObservableCollection<UserProfile>();
|
||||
}
|
||||
|
||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||
|
||||
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||
|
||||
public UserProfile SelectedProfile
|
||||
{
|
||||
get => _selectedProfile;
|
||||
set
|
||||
{
|
||||
_selectedProfile = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
|
||||
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHighlightedProfileEditable => _highlightedProfile != null;
|
||||
|
||||
public bool IsHighlightedProfileDeletable => _highlightedProfile != null && _highlightedProfile.UserId != AccountManager.DefaultUserId;
|
||||
|
||||
public UserProfile HighlightedProfile
|
||||
{
|
||||
get => _highlightedProfile;
|
||||
set
|
||||
{
|
||||
_highlightedProfile = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
|
||||
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public void LoadProfiles()
|
||||
{
|
||||
Profiles.Clear();
|
||||
LostProfiles.Clear();
|
||||
|
||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
Profiles.Add(new UserProfile(profile, _owner));
|
||||
}
|
||||
|
||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
||||
|
||||
if (SelectedProfile == null)
|
||||
{
|
||||
SelectedProfile = Profiles.First();
|
||||
|
||||
if (SelectedProfile != null)
|
||||
{
|
||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||
default, saveDataId: default, index: default);
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
HashSet<UserId> lostAccounts = new HashSet<UserId>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||
|
||||
if (readCount == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < readCount; i++)
|
||||
{
|
||||
var save = saveDataInfo[i];
|
||||
var id = new UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
|
||||
{
|
||||
lostAccounts.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(var account in lostAccounts)
|
||||
{
|
||||
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
|
||||
}
|
||||
}
|
||||
|
||||
public void AddUser()
|
||||
{
|
||||
UserProfile userProfile = null;
|
||||
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
||||
}
|
||||
|
||||
public async void ManageSaves()
|
||||
{
|
||||
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
||||
|
||||
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
{
|
||||
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||
Content = manager,
|
||||
Padding = new Thickness(0)
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
|
||||
public void EditUser()
|
||||
{
|
||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
||||
}
|
||||
|
||||
public async void DeleteUser()
|
||||
{
|
||||
if (_highlightedProfile != null)
|
||||
{
|
||||
var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
|
||||
|
||||
if (_highlightedProfile.UserId == lastUserId)
|
||||
{
|
||||
// If we are deleting the currently open profile, then we must open something else before deleting.
|
||||
var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_owner.AccountManager.OpenUser(profile.UserId);
|
||||
}
|
||||
|
||||
var result =
|
||||
await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
|
||||
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
_owner.AccountManager.DeleteUser(_highlightedProfile.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_owner.GoBack();
|
||||
}
|
||||
|
||||
public void RecoverLostAccounts()
|
||||
{
|
||||
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.AboutWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
Width="850"
|
||||
Height="550"
|
||||
MinWidth="500"
|
||||
MinHeight="550"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid
|
||||
Margin="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Height="110"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,20,0,0"
|
||||
FontSize="35"
|
||||
Text="Ryujinx"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
FontSize="16"
|
||||
Text="(REE-YOU-JINX)"
|
||||
TextAlignment="Center" />
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.ryujinx.org/">
|
||||
<TextBlock
|
||||
Text="www.ryujinx.org"
|
||||
TextAlignment="Center"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
Text="{locale:Locale AboutDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="AmiiboLabel"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
PointerPressed="AmiiboLabel_OnPointerPressed"
|
||||
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<StackPanel
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.patreon.com/ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Patreon" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="GitHub" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://discordapp.com/invite/N2FmfVc">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Discord" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://twitter.com/RyujinxEmu">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Twitter" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Margin="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1,0,0,0">
|
||||
<Separator Width="0" />
|
||||
</Border>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxAboutTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{locale:Locale AboutRyujinxAboutContent}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{Binding Developers}" />
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
|
||||
<TextBlock
|
||||
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
|
||||
TextAlignment="Right"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Margin="0,0,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<Border
|
||||
Grid.Row="6"
|
||||
Width="460"
|
||||
Height="200"
|
||||
Margin="20,5"
|
||||
Padding="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1">
|
||||
<TextBlock
|
||||
Name="SupportersTextBlock"
|
||||
VerticalAlignment="Top"
|
||||
Text="{Binding Supporters}"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@ -1,78 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class AboutWindow : StyleableWindow
|
||||
{
|
||||
public AboutWindow()
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["MenuBarHelpAbout"];
|
||||
}
|
||||
|
||||
Version = Program.Version;
|
||||
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
_ = DownloadPatronsJson();
|
||||
}
|
||||
|
||||
public string Supporters { get; set; }
|
||||
public string Version { get; set; }
|
||||
|
||||
public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«");
|
||||
|
||||
private void Button_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
OpenHelper.OpenUrl(button.Tag.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadPatronsJson()
|
||||
{
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
Supporters = LocaleManager.Instance["ConnectionError"];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HttpClient httpClient = new();
|
||||
|
||||
try
|
||||
{
|
||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||
|
||||
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Supporters = LocaleManager.Instance["ApiError"];
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() => SupportersTextBlock.Text = Supporters);
|
||||
}
|
||||
|
||||
private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (sender is TextBlock)
|
||||
{
|
||||
OpenHelper.OpenUrl("https://amiiboapi.com");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.UI.Windows.AvatarWindow"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:AvatarProfileViewModel"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:AvatarProfileViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Height="400"
|
||||
Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" MaxWidth="700" Margin="0" HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Image Margin="5" Height="96" Width="96"
|
||||
Source="{Binding Data, Converter={StaticResource ByteImage}}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<ProgressBar Grid.Row="2" IsIndeterminate="{Binding IsIndeterminate}" Value="{Binding ImagesLoaded}" HorizontalAlignment="Stretch" Margin="5"
|
||||
Maximum="{Binding ImageCount}" Minimum="0" />
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
|
||||
<Button Content="{locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
|
||||
<ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
|
||||
<Button HorizontalAlignment="Right" Content="{locale:Locale Discard}" Click="CloseButton_OnClick"
|
||||
Name="CloseButton"
|
||||
Width="200" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,172 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
Width="800"
|
||||
Height="500"
|
||||
MinWidth="800"
|
||||
MinHeight="500"
|
||||
MaxWidth="800"
|
||||
MaxHeight="500"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid Name="DownloadableContentGrid" Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Name="Heading"
|
||||
Grid.Row="1"
|
||||
MaxWidth="500"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="18"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<DockPanel
|
||||
Grid.Row="2"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="EnableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding EnableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="DisableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding DisableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid
|
||||
Name="DlcDataGrid"
|
||||
MinHeight="200"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="True"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
Items="{Binding _downloadableContents}"
|
||||
SelectionMode="Extended"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<DataGrid.Styles>
|
||||
<Styles>
|
||||
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
</Style>
|
||||
</Styles>
|
||||
<Styles>
|
||||
<Style Selector="DataGridCell:nth-child(1)">
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||
</Style>
|
||||
</Styles>
|
||||
</DataGrid.Styles>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="90">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox
|
||||
Width="50"
|
||||
MinWidth="40"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding Enabled}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
||||
</DataGridTemplateColumn.Header>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn Binding="{Binding ContainerPath}">
|
||||
<DataGridTextColumn.Header>
|
||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
||||
</DataGridTextColumn.Header>
|
||||
</DataGridTextColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="4"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveSelected}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding SaveAndClose}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@ -1,770 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.MainWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
Title="Ryujinx"
|
||||
Width="1280"
|
||||
Height="777"
|
||||
MinWidth="1092"
|
||||
MinHeight="672"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:MainWindowViewModel"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Window.Styles>
|
||||
<Style Selector="TitleBar:fullscreen">
|
||||
<Setter Property="Background" Value="#000000" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
<Design.DataContext>
|
||||
<viewModels:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<Window.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
</Window.Resources>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<helpers:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
|
||||
<StackPanel Grid.Row="0" IsVisible="False">
|
||||
<helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<helpers:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<helpers:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
|
||||
<helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
|
||||
</StackPanel>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel
|
||||
Name="MenuBar"
|
||||
MinHeight="35"
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding ShowMenuAndStatusBar}"
|
||||
Orientation="Vertical">
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Menu
|
||||
Name="Menu"
|
||||
Height="35"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFile}"
|
||||
Header="{locale:Locale MenuBarFileOpenFromFile}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenUnpacked}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
|
||||
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenMiiApplet}"
|
||||
Header="Mii Edit Applet"
|
||||
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenRyujinxFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenLogsFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding CloseWindow}"
|
||||
Header="{locale:Locale MenuBarFileExit}"
|
||||
ToolTip.Tip="{locale:Locale ExitTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ToggleFullscreen}"
|
||||
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
|
||||
InputGesture="F11" />
|
||||
<MenuItem>
|
||||
<MenuItem.Icon>
|
||||
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
|
||||
MinWidth="250">
|
||||
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
|
||||
</CheckBox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
|
||||
MinWidth="250">
|
||||
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
|
||||
</CheckBox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="de_DE"
|
||||
Header="Deutsch" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="en_US"
|
||||
Header="English (US)" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="es_ES"
|
||||
Header="Español (ES)" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="fr_FR"
|
||||
Header="Français" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="it_IT"
|
||||
Header="Italiano" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pt_BR"
|
||||
Header="Português (BR)" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="tr_TR"
|
||||
Header="Türkçe" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="el_GR"
|
||||
Header="Ελληνικά" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pl_PL"
|
||||
Header="Polski" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ru_RU"
|
||||
Header="Русский" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="zh_CN"
|
||||
Header="简体中文" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="zh_TW"
|
||||
Header="繁體中文" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ja_JP"
|
||||
Header="日本語" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ko_KR"
|
||||
Header="한국어" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenSettings}"
|
||||
Header="{locale:Locale MenuBarOptionsSettings}"
|
||||
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ManageProfiles}"
|
||||
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Name="ActionsMenuItem"
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarActions}"
|
||||
IsEnabled="{Binding IsGameRunning}">
|
||||
<MenuItem
|
||||
Click="PauseEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding !IsPaused}"
|
||||
IsVisible="{Binding !IsPaused}" />
|
||||
<MenuItem
|
||||
Click="ResumeEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding IsPaused}"
|
||||
IsVisible="{Binding IsPaused}" />
|
||||
<MenuItem
|
||||
Click="StopEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsStopEmulation}"
|
||||
InputGesture="Escape"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="ScanAmiiboMenuItem"
|
||||
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
|
||||
Command="{ReflectionBinding OpenAmiiboWindow}"
|
||||
Header="{locale:Locale MenuBarActionsScanAmiibo}"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding TakeScreenshot}"
|
||||
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
InputGesture="{Binding ScreenshotKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding HideUi}"
|
||||
Header="{locale:Locale MenuBarFileToolsHideUi}"
|
||||
InputGesture="{Binding ShowUiKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem
|
||||
Name="UpdateMenuItem"
|
||||
Command="{ReflectionBinding CheckForUpdates}"
|
||||
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
|
||||
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenAboutWindow}"
|
||||
Header="{locale:Locale MenuBarHelpAbout}"
|
||||
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<ContentControl
|
||||
Name="MainContent"
|
||||
Grid.Row="1"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="0,0,0,0"
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{Binding ShowContent}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,5"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,0,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetListMode}"
|
||||
IsEnabled="{Binding IsGrid}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{helpers:GlyphValueConverter List}" />
|
||||
</Button>
|
||||
<Button
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,5,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetGridMode}"
|
||||
IsEnabled="{Binding IsList}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{helpers:GlyphValueConverter Grid}" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale IconSize}"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Height="35"
|
||||
Margin="5,-10,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsSnapToTickEnabled="True"
|
||||
Maximum="4"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
|
||||
Value="{Binding GridSizeScale}" />
|
||||
<CheckBox
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding ShowNames, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsGrid}">
|
||||
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
|
||||
</CheckBox>
|
||||
<TextBox
|
||||
Name="SearchBox"
|
||||
MinWidth="200"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
KeyUp="SearchBox_OnKeyUp"
|
||||
Text="{Binding SearchText}"
|
||||
Watermark="{locale:Locale MenuSearch}" />
|
||||
<ui:DropDownButton
|
||||
Width="150"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding SortName}"
|
||||
DockPanel.Dock="Right">
|
||||
<ui:DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale CommonFavorite}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
|
||||
Tag="Favorite" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderApplication}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||
Tag="Title" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||
Tag="Developer" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderTimePlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||
Tag="TotalTimePlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderLastPlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||
Tag="LastPlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileExtension}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||
Tag="FileType" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileSize}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||
Tag="FileSize" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderPath}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||
Tag="Path" />
|
||||
</StackPanel>
|
||||
<Border
|
||||
Width="60"
|
||||
Height="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderAscending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding IsAscending, Mode=OneTime}"
|
||||
Tag="Ascending" />
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderDescending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding !IsAscending, Mode=OneTime}"
|
||||
Tag="Descending" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</ui:DropDownButton.Flyout>
|
||||
</ui:DropDownButton>
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
Text="{locale:Locale CommonSort}" />
|
||||
</DockPanel>
|
||||
<controls:GameListView
|
||||
x:Name="GameList"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsList}" />
|
||||
<controls:GameGridView
|
||||
x:Name="GameGrid"
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsGrid}" />
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Name="LoadingView"
|
||||
ZIndex="1000">
|
||||
<Grid
|
||||
Margin="40"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Width="256"
|
||||
Height="256"
|
||||
Margin="10"
|
||||
Padding="4"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="2"
|
||||
BoxShadow="4 4 32 8 #40000000"
|
||||
CornerRadius="3"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Image
|
||||
Width="256"
|
||||
Height="256"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
|
||||
</Border>
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding LoadHeading}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="10"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
BorderBrush="{Binding ProgressBarBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<ProgressBar
|
||||
Height="10"
|
||||
MinWidth="500"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{Binding ProgressBarBackgroundColor}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
Foreground="{Binding ProgressBarForegroundColor}"
|
||||
IsIndeterminate="{Binding IsLoadingIndeterminate}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Maximum="{Binding ProgressMaximum}"
|
||||
Minimum="0"
|
||||
Value="{Binding ProgressValue}" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
FontSize="18"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding CacheLoadStatus}"
|
||||
TextAlignment="Left" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
Name="StatusBar"
|
||||
Grid.Row="2"
|
||||
Margin="0"
|
||||
MinHeight="22"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
DockPanel.Dock="Bottom"
|
||||
IsVisible="{Binding ShowMenuAndStatusBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
Margin="5"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button
|
||||
Width="25"
|
||||
Height="25"
|
||||
MinWidth="0"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Command="{ReflectionBinding LoadApplications}">
|
||||
<ui:SymbolIcon
|
||||
Width="50"
|
||||
Height="100"
|
||||
Symbol="Refresh" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Name="LoadStatus"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
Text="{locale:Locale StatusBarGamesLoaded}" />
|
||||
<ProgressBar
|
||||
Name="LoadProgressBar"
|
||||
Grid.Column="2"
|
||||
Height="6"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource HighlightColor}"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="0,2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsGameRunning}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="VsyncStatus"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{Binding VsyncColor}"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="VsyncStatus_PointerReleased"
|
||||
Text="VSync"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Name="DockedStatus"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="DockedStatus_PointerReleased"
|
||||
Text="{Binding DockedStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Name="AspectRatioStatus"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="AspectRatioStatus_PointerReleased"
|
||||
Text="{Binding AspectRatioStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<ui:ToggleSplitButton
|
||||
Name="VolumeStatus"
|
||||
Padding="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
BorderThickness="0"
|
||||
Content="{Binding VolumeStatusText}"
|
||||
IsChecked="{Binding VolumeMuted}"
|
||||
IsVisible="{Binding !ShowLoadProgress}">
|
||||
<ui:ToggleSplitButton.Flyout>
|
||||
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<Grid Margin="0">
|
||||
<Slider
|
||||
MaxHeight="40"
|
||||
Width="150"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="0.05"
|
||||
Maximum="1"
|
||||
Minimum="0"
|
||||
SmallChange="0.01"
|
||||
TickFrequency="0.05"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Value="{Binding Volume}" />
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</ui:ToggleSplitButton.Flyout>
|
||||
</ui:ToggleSplitButton>
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GameStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding FifoStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding BackendText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Height="12"
|
||||
Margin="0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GpuNameText}"
|
||||
TextAlignment="Left" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowFirmwareStatus}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="FirmwareStatus"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale StatusBarSystemVersion}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@ -1,721 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Applet;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.Input.SDL2;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class MainWindow : StyleableWindow
|
||||
{
|
||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||
private bool _canUpdate;
|
||||
private bool _isClosing;
|
||||
private bool _isLoading;
|
||||
|
||||
private Control _mainViewContent;
|
||||
|
||||
private UserChannelPersistence _userChannelPersistence;
|
||||
private static bool _deferLoad;
|
||||
private static string _launchPath;
|
||||
private static bool _startFullscreen;
|
||||
private string _currentEmulatedGamePath;
|
||||
internal readonly AvaHostUiHandler UiHandler;
|
||||
private AutoResetEvent _rendererWaitEvent;
|
||||
|
||||
public VirtualFileSystem VirtualFileSystem { get; private set; }
|
||||
public ContentManager ContentManager { get; private set; }
|
||||
public AccountManager AccountManager { get; private set; }
|
||||
|
||||
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
|
||||
|
||||
internal AppHost AppHost { get; private set; }
|
||||
public InputManager InputManager { get; private set; }
|
||||
|
||||
internal RendererHost RendererControl { get; private set; }
|
||||
internal MainWindowViewModel ViewModel { get; private set; }
|
||||
public SettingsWindow SettingsWindow { get; set; }
|
||||
|
||||
public bool CanUpdate
|
||||
{
|
||||
get => _canUpdate;
|
||||
set
|
||||
{
|
||||
_canUpdate = value;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() => UpdateMenuItem.IsEnabled = _canUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShowKeyErrorOnLoad { get; set; }
|
||||
public ApplicationLibrary ApplicationLibrary { get; set; }
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
ViewModel = new MainWindowViewModel(this);
|
||||
|
||||
MainWindowViewModel = ViewModel;
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
|
||||
UiHandler = new AvaHostUiHandler(this);
|
||||
|
||||
Title = $"Ryujinx {Program.Version}";
|
||||
|
||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
|
||||
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
|
||||
Width /= Program.WindowScaleFactor;
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
ViewModel.Initialize();
|
||||
|
||||
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
|
||||
|
||||
LoadGameList();
|
||||
}
|
||||
|
||||
_rendererWaitEvent = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
public void LoadGameList()
|
||||
{
|
||||
if (_isLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
|
||||
ViewModel.LoadApplications();
|
||||
|
||||
_isLoading = false;
|
||||
}
|
||||
|
||||
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
|
||||
{
|
||||
if (ViewModel.ShowMenuAndStatusBar && !ViewModel.ShowLoadProgress)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (args.VSyncEnabled)
|
||||
{
|
||||
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ff2eeac9"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ffff4554"));
|
||||
}
|
||||
|
||||
ViewModel.DockedStatusText = args.DockedMode;
|
||||
ViewModel.AspectRatioStatusText = args.AspectRatio;
|
||||
ViewModel.GameStatusText = args.GameStatus;
|
||||
ViewModel.VolumeStatusText = args.VolumeStatus;
|
||||
ViewModel.FifoStatusText = args.FifoStatus;
|
||||
ViewModel.GpuNameText = args.GpuName;
|
||||
ViewModel.BackendText = args.GpuBackend;
|
||||
|
||||
ViewModel.ShowStatusSeparator = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandleScalingChanged(double scale)
|
||||
{
|
||||
Program.DesktopScaleFactor = scale;
|
||||
base.HandleScalingChanged(scale);
|
||||
}
|
||||
|
||||
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
||||
{
|
||||
if (args.Application != null)
|
||||
{
|
||||
ViewModel.SelectedIcon = args.Application.Icon;
|
||||
|
||||
string path = new FileInfo(args.Application.Path).FullName;
|
||||
|
||||
LoadApplication(path);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
public async Task PerformanceCheck()
|
||||
{
|
||||
if (ConfigurationState.Instance.Logger.EnableTrace.Value)
|
||||
{
|
||||
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"];
|
||||
string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"];
|
||||
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
|
||||
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
|
||||
LocaleManager.Instance["RyujinxConfirm"]);
|
||||
|
||||
if (result != UserResult.Yes)
|
||||
{
|
||||
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
|
||||
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
|
||||
{
|
||||
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"];
|
||||
string secondaryMessage =
|
||||
LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
|
||||
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
|
||||
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
|
||||
LocaleManager.Instance["RyujinxConfirm"]);
|
||||
|
||||
if (result != UserResult.Yes)
|
||||
{
|
||||
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
|
||||
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
|
||||
{
|
||||
_deferLoad = true;
|
||||
_launchPath = launchPathArg;
|
||||
_startFullscreen = startFullscreenArg;
|
||||
}
|
||||
|
||||
#pragma warning disable CS1998
|
||||
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
|
||||
#pragma warning restore CS1998
|
||||
{
|
||||
if (AppHost != null)
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"],
|
||||
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"],
|
||||
LocaleManager.Instance["InputDialogOk"],
|
||||
"",
|
||||
LocaleManager.Instance["RyujinxInfo"]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if RELEASE
|
||||
await PerformanceCheck();
|
||||
#endif
|
||||
|
||||
Logger.RestartTime();
|
||||
|
||||
if (ViewModel.SelectedIcon == null)
|
||||
{
|
||||
ViewModel.SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
|
||||
}
|
||||
|
||||
PrepareLoadScreen();
|
||||
|
||||
_mainViewContent = MainContent.Content as Control;
|
||||
|
||||
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||
{
|
||||
RendererControl.CreateOpenGL();
|
||||
}
|
||||
else
|
||||
{
|
||||
RendererControl.CreateVulkan();
|
||||
}
|
||||
|
||||
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
if (!await AppHost.LoadGuestApplication())
|
||||
{
|
||||
AppHost.DisposeContext();
|
||||
AppHost = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CanUpdate = false;
|
||||
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
|
||||
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
|
||||
|
||||
SwitchToGameControl(startFullscreen);
|
||||
|
||||
_currentEmulatedGamePath = path;
|
||||
|
||||
Thread gameThread = new(InitializeGame)
|
||||
{
|
||||
Name = "GUI.WindowThread"
|
||||
};
|
||||
gameThread.Start();
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeGame()
|
||||
{
|
||||
RendererControl.RendererInitialized += GlRenderer_Created;
|
||||
|
||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||
AppHost.AppExit += AppHost_AppExit;
|
||||
|
||||
_rendererWaitEvent.WaitOne();
|
||||
|
||||
AppHost?.Start();
|
||||
|
||||
AppHost.DisposeContext();
|
||||
}
|
||||
|
||||
|
||||
private void HandleRelaunch()
|
||||
{
|
||||
if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart)
|
||||
{
|
||||
_userChannelPersistence.ShouldRestart = false;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
LoadApplication(_currentEmulatedGamePath);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, clear state.
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
_currentEmulatedGamePath = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SwitchToGameControl(bool startFullscreen = false)
|
||||
{
|
||||
ViewModel.ShowLoadProgress = false;
|
||||
ViewModel.ShowContent = true;
|
||||
ViewModel.IsLoadingIndeterminate = false;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
MainContent.Content = RendererControl;
|
||||
|
||||
if (startFullscreen && WindowState != WindowState.FullScreen)
|
||||
{
|
||||
ViewModel.ToggleFullscreen();
|
||||
}
|
||||
|
||||
RendererControl.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowLoading(bool startFullscreen = false)
|
||||
{
|
||||
ViewModel.ShowContent = false;
|
||||
ViewModel.ShowLoadProgress = true;
|
||||
ViewModel.IsLoadingIndeterminate = true;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (startFullscreen && WindowState != WindowState.FullScreen)
|
||||
{
|
||||
ViewModel.ToggleFullscreen();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void GlRenderer_Created(object sender, EventArgs e)
|
||||
{
|
||||
ShowLoading();
|
||||
|
||||
_rendererWaitEvent.Set();
|
||||
}
|
||||
|
||||
private void AppHost_AppExit(object sender, EventArgs e)
|
||||
{
|
||||
if (_isClosing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ViewModel.IsGameRunning = false;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
ViewModel.ShowMenuAndStatusBar = true;
|
||||
ViewModel.ShowContent = true;
|
||||
ViewModel.ShowLoadProgress = false;
|
||||
ViewModel.IsLoadingIndeterminate = false;
|
||||
CanUpdate = true;
|
||||
Cursor = Cursor.Default;
|
||||
|
||||
if (MainContent.Content != _mainViewContent)
|
||||
{
|
||||
MainContent.Content = _mainViewContent;
|
||||
}
|
||||
|
||||
AppHost = null;
|
||||
|
||||
HandleRelaunch();
|
||||
});
|
||||
|
||||
RendererControl.RendererInitialized -= GlRenderer_Created;
|
||||
RendererControl = null;
|
||||
|
||||
ViewModel.SelectedIcon = null;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version}";
|
||||
});
|
||||
}
|
||||
|
||||
public void Sort_Checked(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is RadioButton button)
|
||||
{
|
||||
var sort = Enum.Parse<ApplicationSort>(button.Tag.ToString());
|
||||
ViewModel.Sort(sort);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandleWindowStateChanged(WindowState state)
|
||||
{
|
||||
WindowState = state;
|
||||
|
||||
if (state != WindowState.Minimized)
|
||||
{
|
||||
Renderer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Order_Checked(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is RadioButton button)
|
||||
{
|
||||
var tag = button.Tag.ToString();
|
||||
ViewModel.Sort(tag != "Descending");
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
VirtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||
LibHacHorizonManager = new LibHacHorizonManager();
|
||||
ContentManager = new ContentManager(VirtualFileSystem);
|
||||
|
||||
LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
|
||||
LibHacHorizonManager.InitializeArpServer();
|
||||
LibHacHorizonManager.InitializeBcatServer();
|
||||
LibHacHorizonManager.InitializeSystemClients();
|
||||
|
||||
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
|
||||
|
||||
// Save data created before we supported extra data in directory save data will not work properly if
|
||||
// given empty extra data. Luckily some of that extra data can be created using the data from the
|
||||
// save data indexer, which should be enough to check access permissions for user saves.
|
||||
// Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
|
||||
// Consider removing this at some point in the future when we don't need to worry about old saves.
|
||||
VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient);
|
||||
|
||||
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
||||
|
||||
VirtualFileSystem.ReloadKeySet();
|
||||
|
||||
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
|
||||
|
||||
RefreshFirmwareStatus();
|
||||
}
|
||||
|
||||
protected void CheckLaunchState()
|
||||
{
|
||||
if (ShowKeyErrorOnLoad)
|
||||
{
|
||||
ShowKeyErrorOnLoad = false;
|
||||
|
||||
Dispatcher.UIThread.Post(async () => await
|
||||
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
|
||||
}
|
||||
|
||||
if (_deferLoad)
|
||||
{
|
||||
_deferLoad = false;
|
||||
|
||||
LoadApplication(_launchPath, _startFullscreen);
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
|
||||
{
|
||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshFirmwareStatus()
|
||||
{
|
||||
SystemVersion version = null;
|
||||
try
|
||||
{
|
||||
version = ContentManager.GetCurrentFirmwareVersion();
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
bool hasApplet = false;
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion",
|
||||
version.VersionString);
|
||||
|
||||
hasApplet = version.Major > 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
LocaleManager.Instance.UpdateDynamicValue("StatusBarSystemVersion", "0.0");
|
||||
}
|
||||
|
||||
ViewModel.IsAppletMenuActive = hasApplet;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
VolumeStatus.Click += VolumeStatus_CheckedChanged;
|
||||
|
||||
GameGrid.ApplicationOpened += Application_Opened;
|
||||
|
||||
GameGrid.DataContext = ViewModel;
|
||||
|
||||
GameList.ApplicationOpened += Application_Opened;
|
||||
|
||||
GameList.DataContext = ViewModel;
|
||||
|
||||
LoadHotKeys();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
|
||||
CheckLaunchState();
|
||||
}
|
||||
|
||||
public static void UpdateGraphicsConfig()
|
||||
{
|
||||
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
|
||||
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
||||
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
||||
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
||||
}
|
||||
|
||||
public void LoadHotKeys()
|
||||
{
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
||||
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
||||
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
||||
}
|
||||
|
||||
public static void SaveConfig()
|
||||
{
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
|
||||
public void UpdateGameMetadata(string titleId)
|
||||
{
|
||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
|
||||
{
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void PrepareLoadScreen()
|
||||
{
|
||||
using MemoryStream stream = new MemoryStream(ViewModel.SelectedIcon);
|
||||
using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
|
||||
|
||||
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
|
||||
|
||||
const int ColorDivisor = 4;
|
||||
|
||||
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
|
||||
Color progressBgColor = Color.FromRgb(
|
||||
(byte)(dominantColor.R / ColorDivisor),
|
||||
(byte)(dominantColor.G / ColorDivisor),
|
||||
(byte)(dominantColor.B / ColorDivisor));
|
||||
|
||||
ViewModel.ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
|
||||
ViewModel.ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
|
||||
}
|
||||
|
||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
ViewModel.SearchText = SearchBox.Text;
|
||||
}
|
||||
|
||||
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (AppHost != null)
|
||||
{
|
||||
await AppHost.ShowExitPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
AppHost?.Pause();
|
||||
});
|
||||
}
|
||||
|
||||
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
AppHost?.Resume();
|
||||
});
|
||||
}
|
||||
|
||||
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
{
|
||||
ViewModel.IsAmiiboRequested = AppHost.Device.System.SearchingForAmiibo(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
AppHost.Device.EnableDeviceVsync = !AppHost.Device.EnableDeviceVsync;
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {AppHost.Device.EnableDeviceVsync}");
|
||||
}
|
||||
|
||||
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
|
||||
}
|
||||
|
||||
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
|
||||
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
|
||||
}
|
||||
|
||||
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
|
||||
{
|
||||
var volumeSplitButton = sender as ToggleSplitButton;
|
||||
if (ViewModel.IsGameRunning)
|
||||
{
|
||||
if (!volumeSplitButton.IsChecked)
|
||||
{
|
||||
AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
AppHost.Device.SetVolume(0);
|
||||
}
|
||||
|
||||
ViewModel.Volume = AppHost.Device.GetVolume();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
if (!_isClosing && AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
ConfirmExit();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_isClosing = true;
|
||||
|
||||
if (AppHost != null)
|
||||
{
|
||||
AppHost.AppExit -= AppHost_AppExit;
|
||||
AppHost.AppExit += (sender, e) =>
|
||||
{
|
||||
AppHost = null;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
MainContent = null;
|
||||
|
||||
Close();
|
||||
});
|
||||
};
|
||||
AppHost?.Stop();
|
||||
|
||||
e.Cancel = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationLibrary.CancelLoading();
|
||||
InputManager.Dispose();
|
||||
Program.Exit();
|
||||
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
private void ConfirmExit()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
_isClosing = await ContentDialogHelper.CreateExitDialog();
|
||||
|
||||
if (_isClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,980 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.SettingsWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
Width="1100"
|
||||
Height="768"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="950"
|
||||
MinWidth="800"
|
||||
MinHeight="480"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:SettingsViewModel"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<Window.Resources>
|
||||
<helpers:KeyValueConverter x:Key="Key" />
|
||||
</Window.Resources>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Grid.Row="1"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False"/>
|
||||
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
||||
<ScrollViewer Name="UiPage"
|
||||
Margin="0,0,2,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGeneral}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}"
|
||||
Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<ListBox
|
||||
Name="GameList"
|
||||
MinHeight="250"
|
||||
Items="{Binding GameDirectories}" />
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
Name="PathBox"
|
||||
Margin="0"
|
||||
ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}"
|
||||
VerticalAlignment="Stretch" />
|
||||
<Button
|
||||
Name="AddButton"
|
||||
Grid.Column="1"
|
||||
MinWidth="90"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip.Tip="{locale:Locale AddGameDirTooltip}"
|
||||
Click="AddButton_OnClick">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
Grid.Column="2"
|
||||
MinWidth="90"
|
||||
Margin="10,0,0,0"
|
||||
ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}"
|
||||
Click="RemoveButton_OnClick">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralTheme}" />
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox IsChecked="{Binding EnableCustomTheme}"
|
||||
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
|
||||
</CheckBox>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Grid.Row="1"
|
||||
Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
|
||||
ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
|
||||
<TextBox Margin="0,10,0,0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Text="{Binding CustomThemePath}" />
|
||||
<Button Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="10,10,0,0"
|
||||
Command="{ReflectionBinding BrowseTheme}"
|
||||
ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
|
||||
Content="{locale:Locale ButtonBrowse}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Grid.Row="2"
|
||||
Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
|
||||
<ComboBox VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
MinWidth="100"
|
||||
SelectedIndex="{Binding BaseStyleIndex}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Name="InputPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Padding="0,0,2,0"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel Margin="4" Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
|
||||
IsChecked="{Binding EnableDockedMode}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
|
||||
</CheckBox>
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
|
||||
IsChecked="{Binding EnableKeyboard}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
|
||||
IsChecked="{Binding EnableMouse}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0,0,0,0" MinHeight="600" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Name="HotkeysPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel Margin="10,5" Orientation="Vertical" Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeUpHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.VolumeUp, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysVolumeDownHotkey}" Width="230" />
|
||||
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
|
||||
<TextBlock
|
||||
Text="{Binding KeyboardHotkeys.VolumeDown, Mode=TwoWay, Converter={StaticResource Key}}"
|
||||
TextAlignment="Center" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer Name="SystemPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemRegion}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding Region}"
|
||||
ToolTip.Tip="{locale:Locale RegionTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding Language}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
|
||||
Width="250" />
|
||||
<AutoCompleteBox
|
||||
Name="TimeZoneBox"
|
||||
Width="350"
|
||||
MaxDropDownHeight="500"
|
||||
FilterMode="Contains"
|
||||
Items="{Binding TimeZones}"
|
||||
SelectionChanged="TimeZoneBox_OnSelectionChanged"
|
||||
Text="{Binding Path=TimeZone, Mode=OneWay}"
|
||||
TextChanged="TimeZoneBox_OnTextChanged"
|
||||
ValueMemberBinding="{ReflectionBinding TzMultiBinding}"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTime}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="250"/>
|
||||
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="350" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
|
||||
<TimePicker
|
||||
VerticalAlignment="Center"
|
||||
ClockIdentifier="24HourClock"
|
||||
SelectedTime="{Binding TimeOffset}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}" />
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableVsync}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
|
||||
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
|
||||
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemHacks}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding ExpandDramSize}"
|
||||
ToolTip.Tip="{locale:Locale DRamTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
|
||||
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer
|
||||
Name="CpuPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuCache}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnablePptc}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
|
||||
ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabCpuMemory}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemMemoryManagerMode}"
|
||||
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding MemoryMode}"
|
||||
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<ComboBoxItem
|
||||
ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem
|
||||
ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem
|
||||
ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer
|
||||
Name="GraphicsPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsAPI}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsBackend}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
|
||||
SelectedIndex="{Binding GraphicsBackendIndex}">
|
||||
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
||||
<TextBlock Text="Vulkan" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
||||
<TextBlock Text="OpenGL" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" IsVisible="{Binding IsVulkanSelected}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsPreferredGpu}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale SettingsTabGraphicsPreferredGpuTooltip}"
|
||||
SelectedIndex="{Binding PreferredGpuIndex}"
|
||||
Items="{Binding AvailableGpus}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsFeatures}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableShaderCache}"
|
||||
ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableTextureRecompression}"
|
||||
ToolTip.Tip="{locale:Locale SettingsEnableTextureRecompressionTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsEnableTextureRecompression}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableMacroHLE}"
|
||||
ToolTip.Tip="{locale:Locale SettingsEnableMacroHLETooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsEnableMacroHLE}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsResolutionScale}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding ResolutionScale}"
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<ui:NumberBox
|
||||
Margin="10,0,0,0"
|
||||
ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}"
|
||||
MinWidth="150"
|
||||
SmallChange="0.1"
|
||||
LargeChange="1"
|
||||
SimpleNumberFormat="F2"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
IsVisible="{Binding IsCustomResolutionScaleActive}"
|
||||
Maximum="100"
|
||||
Minimum="0.1"
|
||||
Value="{Binding CustomResolutionScale}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding MaxAnisotropy}"
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale AnisotropyTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale AspectRatioTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsAspectRatio}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding AspectRatio}"
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
|
||||
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale CommonAuto}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale CommonOff}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale CommonOn}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}"
|
||||
Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}"
|
||||
Width="250" />
|
||||
<TextBox Text="{Binding ShaderDumpPath}"
|
||||
Width="350"
|
||||
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer
|
||||
Name="AudioPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabAudio}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemAudioBackend}"
|
||||
ToolTip.Tip="{locale:Locale AudioBackendTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding AudioBackend}"
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemAudioVolume}"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Width="250" />
|
||||
<ui:NumberBox Value="{Binding Volume}"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Width="350"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
SimpleNumberFormat="F0"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
<Slider Value="{Binding Volume}"
|
||||
Margin="250,0,0,0"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
SmallChange="5"
|
||||
TickFrequency="5"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="10"
|
||||
Width="350" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer
|
||||
Name="NetworkPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabNetworkConnection}" />
|
||||
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
|
||||
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer
|
||||
Name="LoggingPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingLogging}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableFileLog}"
|
||||
ToolTip.Tip="{locale:Locale FileLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableStub}"
|
||||
ToolTip.Tip="{locale:Locale StubLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableInfo}"
|
||||
ToolTip.Tip="{locale:Locale InfoLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableWarn}"
|
||||
ToolTip.Tip="{locale:Locale WarnLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableError}"
|
||||
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableTrace}"
|
||||
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableGuest}"
|
||||
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableDebug}"
|
||||
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
|
||||
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
|
||||
Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}"
|
||||
Width="285" />
|
||||
<ui:NumberBox
|
||||
Maximum="3"
|
||||
Minimum="0"
|
||||
Width="150"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
SmallChange="1"
|
||||
LargeChange="1"
|
||||
Value="{Binding FsGlobalAccessLogMode}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevel}"
|
||||
ToolTip.Tip="{locale:Locale OpenGlLogLevel}"
|
||||
Width="285" />
|
||||
<ComboBox SelectedIndex="{Binding OpenglDebugLevel}"
|
||||
Width="150"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale OpenGlLogLevel}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelError}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelPerformance}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelAll}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
<ui:NavigationView Grid.Row="1"
|
||||
IsSettingsVisible="False"
|
||||
Name="NavPanel"
|
||||
IsBackEnabled="False"
|
||||
PaneDisplayMode="Left"
|
||||
Margin="2,10,10,0"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
OpenPaneLength="200">
|
||||
<ui:NavigationView.MenuItems>
|
||||
<ui:NavigationViewItem IsSelected="True"
|
||||
Content="{locale:Locale SettingsTabGeneral}"
|
||||
Tag="UiPage"
|
||||
Icon="New" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabInput}"
|
||||
Tag="InputPage"
|
||||
Icon="Games" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabHotkeys}"
|
||||
Tag="HotkeysPage"
|
||||
Icon="Keyboard" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabSystem}"
|
||||
Tag="SystemPage"
|
||||
Icon="Settings" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabCpu}"
|
||||
Tag="CpuPage">
|
||||
<ui:NavigationViewItem.Icon>
|
||||
<ui:FontIcon FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
|
||||
Glyph="{helpers:GlyphValueConverter Chip}" />
|
||||
</ui:NavigationViewItem.Icon>
|
||||
</ui:NavigationViewItem>
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabGraphics}"
|
||||
Tag="GraphicsPage"
|
||||
Icon="Image" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabAudio}"
|
||||
Icon="Audio"
|
||||
Tag="AudioPage" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabNetwork}"
|
||||
Tag="NetworkPage"
|
||||
Icon="Globe" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabLogging}"
|
||||
Tag="LoggingPage"
|
||||
Icon="Document" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
</ui:NavigationView>
|
||||
<ReversibleStackPanel
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
ReverseOrder="{ReflectionBinding IsMacOS}">
|
||||
<Button
|
||||
HotKey="Enter"
|
||||
Classes="accent"
|
||||
Content="{locale:Locale SettingsButtonOk}"
|
||||
Command="{ReflectionBinding OkButton}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
Content="{locale:Locale SettingsButtonCancel}"
|
||||
Command="{ReflectionBinding CancelButton}" />
|
||||
<Button
|
||||
Content="{locale:Locale SettingsButtonApply}"
|
||||
Command="{ReflectionBinding ApplyButton}" />
|
||||
</ReversibleStackPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@ -1,213 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class SettingsWindow : StyleableWindow
|
||||
{
|
||||
private ButtonKeyAssigner _currentAssigner;
|
||||
|
||||
internal SettingsViewModel ViewModel { get; set; }
|
||||
|
||||
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
|
||||
|
||||
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
|
||||
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()).Trim());
|
||||
MultiBinding tzMultiBinding = new() { Converter = converter };
|
||||
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
|
||||
tzMultiBinding.Bindings.Add(new Binding("Location"));
|
||||
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
|
||||
|
||||
TimeZoneBox.ValueMemberBinding = tzMultiBinding;
|
||||
}
|
||||
|
||||
public SettingsWindow()
|
||||
{
|
||||
ViewModel = new SettingsViewModel();
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
Pages.Children.Clear();
|
||||
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
|
||||
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
|
||||
}
|
||||
|
||||
private void Button_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleButton button)
|
||||
{
|
||||
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentAssigner == null && (bool)button.IsChecked)
|
||||
{
|
||||
_currentAssigner = new ButtonKeyAssigner(button);
|
||||
|
||||
FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
|
||||
|
||||
PointerPressed += MouseClick;
|
||||
|
||||
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
|
||||
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
||||
|
||||
_currentAssigner.GetInputAndAssign(assigner);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_currentAssigner != null)
|
||||
{
|
||||
ToggleButton oldButton = _currentAssigner.ToggledButton;
|
||||
|
||||
_currentAssigner.Cancel();
|
||||
_currentAssigner = null;
|
||||
|
||||
button.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Button_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_currentAssigner?.Cancel();
|
||||
_currentAssigner = null;
|
||||
}
|
||||
|
||||
private void MouseClick(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
bool shouldUnbind = false;
|
||||
|
||||
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
|
||||
{
|
||||
shouldUnbind = true;
|
||||
}
|
||||
|
||||
_currentAssigner?.Cancel(shouldUnbind);
|
||||
|
||||
PointerPressed -= MouseClick;
|
||||
}
|
||||
|
||||
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.SelectedItem is NavigationViewItem navitem)
|
||||
{
|
||||
NavPanel.Content = navitem.Tag.ToString() switch
|
||||
{
|
||||
"UiPage" => UiPage,
|
||||
"InputPage" => InputPage,
|
||||
"HotkeysPage" => HotkeysPage,
|
||||
"SystemPage" => SystemPage,
|
||||
"CpuPage" => CpuPage,
|
||||
"GraphicsPage" => GraphicsPage,
|
||||
"AudioPage" => AudioPage,
|
||||
"NetworkPage" => NetworkPage,
|
||||
"LoggingPage" => LoggingPage,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string path = PathBox.Text;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
|
||||
{
|
||||
ViewModel.GameDirectories.Add(path);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = await new OpenFolderDialog().ShowAsync(this);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
ViewModel.GameDirectories.Add(path);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int oldIndex = GameList.SelectedIndex;
|
||||
|
||||
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
|
||||
{
|
||||
ViewModel.GameDirectories.Remove(path);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
|
||||
if (GameList.ItemCount > 0)
|
||||
{
|
||||
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.AddedItems != null && e.AddedItems.Count > 0)
|
||||
{
|
||||
if (e.AddedItems[0] is TimeZone timeZone)
|
||||
{
|
||||
e.Handled = true;
|
||||
|
||||
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is AutoCompleteBox box)
|
||||
{
|
||||
if (box.SelectedItem != null && box.SelectedItem is TimeZone timeZone)
|
||||
{
|
||||
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
ControllerSettings.Dispose();
|
||||
|
||||
_currentAssigner?.Cancel();
|
||||
_currentAssigner = null;
|
||||
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
Width="600"
|
||||
Height="400"
|
||||
MinWidth="600"
|
||||
MinHeight="400"
|
||||
MaxWidth="600"
|
||||
MaxHeight="400"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Grid Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Name="Heading"
|
||||
Grid.Row="1"
|
||||
MaxWidth="500"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="18"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding _titleUpdates}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RadioButton
|
||||
Padding="8,0"
|
||||
VerticalContentAlignment="Center"
|
||||
GroupName="Update"
|
||||
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
||||
<Label
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding Label}"
|
||||
FontSize="12" />
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveSelected}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Save}">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
@ -1,271 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class TitleUpdateWindow : StyleableWindow
|
||||
{
|
||||
private readonly string _titleUpdateJsonPath;
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
|
||||
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
public TitleUpdateWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||
}
|
||||
|
||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
}
|
||||
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||
|
||||
LoadUpdates();
|
||||
PrintHeading();
|
||||
}
|
||||
|
||||
private void PrintHeading()
|
||||
{
|
||||
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
||||
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
if (_titleUpdateWindowData.Selected == "")
|
||||
{
|
||||
_titleUpdates[0].IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
||||
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
|
||||
|
||||
foreach (TitleUpdateModel update in enabled)
|
||||
{
|
||||
update.IsEnabled = false;
|
||||
}
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
selected.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
|
||||
foreach (var update in _titleUpdates)
|
||||
{
|
||||
update.IsEnabled = false;
|
||||
}
|
||||
|
||||
_titleUpdates.Last().IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
||||
{
|
||||
if (removeSelectedOnly)
|
||||
{
|
||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
||||
}
|
||||
|
||||
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
||||
|
||||
SortUpdates();
|
||||
PrintHeading();
|
||||
}
|
||||
|
||||
public void RemoveSelected()
|
||||
{
|
||||
RemoveUpdates(true);
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
RemoveUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
|
||||
AllowMultiple = true
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
string[] files = await dialog.ShowAsync(this);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
PrintHeading();
|
||||
}
|
||||
|
||||
private void SortUpdates()
|
||||
{
|
||||
var list = _titleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
_titleUpdates.Clear();
|
||||
_titleUpdates.AddRange(list);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in _titleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
if (update.IsEnabled)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
}
|
||||
|
||||
if (Owner is MainWindow window)
|
||||
{
|
||||
window.ViewModel.LoadApplications();
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public enum BackendThreading
|
||||
{
|
||||
Auto,
|
||||
Off,
|
||||
On
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Vulkan,
|
||||
OpenGl
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
public enum MotionInputBackendType : byte
|
||||
{
|
||||
Invalid,
|
||||
GamepadDriver,
|
||||
CemuHook
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
public enum StickInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public enum InputBackendType
|
||||
{
|
||||
Invalid,
|
||||
WindowKeyboard,
|
||||
GamepadSDL2,
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public enum MemoryManagerMode : byte
|
||||
{
|
||||
SoftwarePageTable,
|
||||
HostMapped,
|
||||
HostMappedUnsafe
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DefaultLogFormatter : ILogFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public string Format(LogEventArgs args)
|
||||
{
|
||||
StringBuilder sb = _stringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
sb.Clear();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
|
||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||
|
||||
if (args.ThreadName != null)
|
||||
{
|
||||
sb.Append(args.ThreadName);
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
sb.Append(args.Message);
|
||||
|
||||
if (args.Data != null)
|
||||
{
|
||||
PropertyInfo[] props = args.Data.GetType().GetProperties();
|
||||
|
||||
sb.Append(" {");
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Array array = (Array)prop.GetValue(args.Data);
|
||||
foreach (var item in array)
|
||||
{
|
||||
sb.Append(item.ToString());
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(prop.GetValue(args.Data));
|
||||
}
|
||||
|
||||
sb.Append(" ; ");
|
||||
}
|
||||
|
||||
// We remove the final ';' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public class JsonHelper
|
||||
{
|
||||
public static JsonNamingPolicy SnakeCase { get; }
|
||||
|
||||
private class SnakeCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
public override string ConvertName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < name.Length; i++)
|
||||
{
|
||||
char c = name[i];
|
||||
|
||||
if (char.IsUpper(c))
|
||||
{
|
||||
if (i == 0 || char.IsUpper(name[i - 1]))
|
||||
{
|
||||
builder.Append(char.ToLowerInvariant(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("_");
|
||||
builder.Append(char.ToLowerInvariant(c));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
static JsonHelper()
|
||||
{
|
||||
SnakeCase = new SnakeCaseNamingPolicy();
|
||||
}
|
||||
|
||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false)
|
||||
{
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
DictionaryKeyPolicy = SnakeCase,
|
||||
PropertyNamingPolicy = SnakeCase,
|
||||
WriteIndented = prettyPrint,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new JsonInputConfigConverter());
|
||||
options.Converters.Add(new JsonMotionConfigControllerConverter());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(Stream stream)
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions());
|
||||
}
|
||||
}
|
||||
|
||||
public static T DeserializeFromFile<T>(string path)
|
||||
{
|
||||
return Deserialize<T>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(string json)
|
||||
{
|
||||
return JsonSerializer.Deserialize<T>(json, GetDefaultSerializerOptions());
|
||||
}
|
||||
|
||||
public static void Serialize<TValue>(Stream stream, TValue obj, bool prettyPrint = false)
|
||||
{
|
||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(SerializeToUtf8Bytes(obj, prettyPrint));
|
||||
}
|
||||
}
|
||||
|
||||
public static string Serialize<TValue>(TValue obj, bool prettyPrint = false)
|
||||
{
|
||||
return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint));
|
||||
}
|
||||
|
||||
public static byte[] SerializeToUtf8Bytes<T>(T obj, bool prettyPrint = false)
|
||||
{
|
||||
return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static class StreamUtils
|
||||
{
|
||||
public static byte[] StreamToBytes(Stream input)
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
input.CopyTo(stream);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu.Tracking
|
||||
{
|
||||
public class CpuMultiRegionHandle : IMultiRegionHandle
|
||||
{
|
||||
private readonly MultiRegionHandle _impl;
|
||||
|
||||
public bool Dirty => _impl.Dirty;
|
||||
|
||||
internal CpuMultiRegionHandle(MultiRegionHandle impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public void Dispose() => _impl.Dispose();
|
||||
public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
|
||||
public IEnumerable<IRegionHandle> GetHandles() => _impl.GetHandles();
|
||||
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
|
||||
public void RegisterAction(ulong address, ulong size, RegionSignal action) => _impl.RegisterAction(address, size, action);
|
||||
public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action) => _impl.RegisterPreciseAction(address, size, action);
|
||||
public void SignalWrite() => _impl.SignalWrite();
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Tracking
|
||||
{
|
||||
public class CpuRegionHandle : IRegionHandle
|
||||
{
|
||||
private readonly RegionHandle _impl;
|
||||
|
||||
public bool Dirty => _impl.Dirty;
|
||||
public bool Unmapped => _impl.Unmapped;
|
||||
public ulong Address => _impl.Address;
|
||||
public ulong Size => _impl.Size;
|
||||
public ulong EndAddress => _impl.EndAddress;
|
||||
|
||||
internal CpuRegionHandle(RegionHandle impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public void Dispose() => _impl.Dispose();
|
||||
public bool DirtyOrVolatile() => _impl.DirtyOrVolatile();
|
||||
public void ForceDirty() => _impl.ForceDirty();
|
||||
public IRegionHandle GetHandle() => _impl;
|
||||
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
|
||||
public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
|
||||
public void RegisterDirtyEvent(Action action) => _impl.RegisterDirtyEvent(action);
|
||||
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
||||
|
||||
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.Tracking
|
||||
{
|
||||
public class CpuSmartMultiRegionHandle : IMultiRegionHandle
|
||||
{
|
||||
private readonly SmartMultiRegionHandle _impl;
|
||||
|
||||
public bool Dirty => _impl.Dirty;
|
||||
|
||||
internal CpuSmartMultiRegionHandle(SmartMultiRegionHandle impl)
|
||||
{
|
||||
_impl = impl;
|
||||
}
|
||||
|
||||
public void Dispose() => _impl.Dispose();
|
||||
public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
|
||||
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
|
||||
public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
|
||||
public void QueryModified(Action<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
|
||||
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
|
||||
public void SignalWrite() => _impl.SignalWrite();
|
||||
}
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Program;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
{
|
||||
static class CommandHelper
|
||||
{
|
||||
private delegate void CommandDelegate(Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer);
|
||||
|
||||
private static int _totalCommands = (int)Enum.GetValues<CommandType>().Max() + 1;
|
||||
private static CommandDelegate[] _lookup = new CommandDelegate[_totalCommands];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ref T GetCommand<T>(Span<byte> memory)
|
||||
{
|
||||
return ref Unsafe.As<byte, T>(ref MemoryMarshal.GetReference(memory));
|
||||
}
|
||||
|
||||
public static int GetMaxCommandSize()
|
||||
{
|
||||
Assembly assembly = typeof(CommandHelper).Assembly;
|
||||
|
||||
IEnumerable<Type> commands = assembly.GetTypes().Where(type => typeof(IGALCommand).IsAssignableFrom(type) && type.IsValueType);
|
||||
|
||||
int maxSize = commands.Max(command =>
|
||||
{
|
||||
MethodInfo method = typeof(Unsafe).GetMethod(nameof(Unsafe.SizeOf));
|
||||
MethodInfo generic = method.MakeGenericMethod(command);
|
||||
int size = (int)generic.Invoke(null, null);
|
||||
|
||||
return size;
|
||||
});
|
||||
|
||||
InitLookup();
|
||||
|
||||
return maxSize + 1; // 1 byte reserved for command size.
|
||||
}
|
||||
|
||||
private static void InitLookup()
|
||||
{
|
||||
_lookup[(int)CommandType.Action] = (memory, threaded, renderer) =>
|
||||
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateBuffer] = (memory, threaded, renderer) =>
|
||||
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateProgram] = (memory, threaded, renderer) =>
|
||||
CreateProgramCommand.Run(ref GetCommand<CreateProgramCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateSampler] = (memory, threaded, renderer) =>
|
||||
CreateSamplerCommand.Run(ref GetCommand<CreateSamplerCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateSync] = (memory, threaded, renderer) =>
|
||||
CreateSyncCommand.Run(ref GetCommand<CreateSyncCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateTexture] = (memory, threaded, renderer) =>
|
||||
CreateTextureCommand.Run(ref GetCommand<CreateTextureCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.GetCapabilities] = (memory, threaded, renderer) =>
|
||||
GetCapabilitiesCommand.Run(ref GetCommand<GetCapabilitiesCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.PreFrame] = (memory, threaded, renderer) =>
|
||||
PreFrameCommand.Run(ref GetCommand<PreFrameCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ReportCounter] = (memory, threaded, renderer) =>
|
||||
ReportCounterCommand.Run(ref GetCommand<ReportCounterCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ResetCounter] = (memory, threaded, renderer) =>
|
||||
ResetCounterCommand.Run(ref GetCommand<ResetCounterCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.UpdateCounters] = (memory, threaded, renderer) =>
|
||||
UpdateCountersCommand.Run(ref GetCommand<UpdateCountersCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.BufferDispose] = (memory, threaded, renderer) =>
|
||||
BufferDisposeCommand.Run(ref GetCommand<BufferDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BufferGetData] = (memory, threaded, renderer) =>
|
||||
BufferGetDataCommand.Run(ref GetCommand<BufferGetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BufferSetData] = (memory, threaded, renderer) =>
|
||||
BufferSetDataCommand.Run(ref GetCommand<BufferSetDataCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.CounterEventDispose] = (memory, threaded, renderer) =>
|
||||
CounterEventDisposeCommand.Run(ref GetCommand<CounterEventDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CounterEventFlush] = (memory, threaded, renderer) =>
|
||||
CounterEventFlushCommand.Run(ref GetCommand<CounterEventFlushCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.ProgramDispose] = (memory, threaded, renderer) =>
|
||||
ProgramDisposeCommand.Run(ref GetCommand<ProgramDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ProgramGetBinary] = (memory, threaded, renderer) =>
|
||||
ProgramGetBinaryCommand.Run(ref GetCommand<ProgramGetBinaryCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ProgramCheckLink] = (memory, threaded, renderer) =>
|
||||
ProgramCheckLinkCommand.Run(ref GetCommand<ProgramCheckLinkCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.SamplerDispose] = (memory, threaded, renderer) =>
|
||||
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.TextureCopyTo] = (memory, threaded, renderer) =>
|
||||
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToScaled] = (memory, threaded, renderer) =>
|
||||
TextureCopyToScaledCommand.Run(ref GetCommand<TextureCopyToScaledCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToSlice] = (memory, threaded, renderer) =>
|
||||
TextureCopyToSliceCommand.Run(ref GetCommand<TextureCopyToSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCreateView] = (memory, threaded, renderer) =>
|
||||
TextureCreateViewCommand.Run(ref GetCommand<TextureCreateViewCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureGetData] = (memory, threaded, renderer) =>
|
||||
TextureGetDataCommand.Run(ref GetCommand<TextureGetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureGetDataSlice] = (memory, threaded, renderer) =>
|
||||
TextureGetDataSliceCommand.Run(ref GetCommand<TextureGetDataSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureRelease] = (memory, threaded, renderer) =>
|
||||
TextureReleaseCommand.Run(ref GetCommand<TextureReleaseCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetData] = (memory, threaded, renderer) =>
|
||||
TextureSetDataCommand.Run(ref GetCommand<TextureSetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetDataSlice] = (memory, threaded, renderer) =>
|
||||
TextureSetDataSliceCommand.Run(ref GetCommand<TextureSetDataSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetDataSliceRegion] = (memory, threaded, renderer) =>
|
||||
TextureSetDataSliceRegionCommand.Run(ref GetCommand<TextureSetDataSliceRegionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetStorage] = (memory, threaded, renderer) =>
|
||||
TextureSetStorageCommand.Run(ref GetCommand<TextureSetStorageCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.WindowPresent] = (memory, threaded, renderer) =>
|
||||
WindowPresentCommand.Run(ref GetCommand<WindowPresentCommand>(memory), threaded, renderer);
|
||||
|
||||
_lookup[(int)CommandType.Barrier] = (memory, threaded, renderer) =>
|
||||
BarrierCommand.Run(ref GetCommand<BarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BeginTransformFeedback] = (memory, threaded, renderer) =>
|
||||
BeginTransformFeedbackCommand.Run(ref GetCommand<BeginTransformFeedbackCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearBuffer] = (memory, threaded, renderer) =>
|
||||
ClearBufferCommand.Run(ref GetCommand<ClearBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearRenderTargetColor] = (memory, threaded, renderer) =>
|
||||
ClearRenderTargetColorCommand.Run(ref GetCommand<ClearRenderTargetColorCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearRenderTargetDepthStencil] = (memory, threaded, renderer) =>
|
||||
ClearRenderTargetDepthStencilCommand.Run(ref GetCommand<ClearRenderTargetDepthStencilCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CommandBufferBarrier] = (memory, threaded, renderer) =>
|
||||
CommandBufferBarrierCommand.Run(ref GetCommand<CommandBufferBarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CopyBuffer] = (memory, threaded, renderer) =>
|
||||
CopyBufferCommand.Run(ref GetCommand<CopyBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DispatchCompute] = (memory, threaded, renderer) =>
|
||||
DispatchComputeCommand.Run(ref GetCommand<DispatchComputeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.Draw] = (memory, threaded, renderer) =>
|
||||
DrawCommand.Run(ref GetCommand<DrawCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexed] = (memory, threaded, renderer) =>
|
||||
DrawIndexedCommand.Run(ref GetCommand<DrawIndexedCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexedIndirect] = (memory, threaded, renderer) =>
|
||||
DrawIndexedIndirectCommand.Run(ref GetCommand<DrawIndexedIndirectCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexedIndirectCount] = (memory, threaded, renderer) =>
|
||||
DrawIndexedIndirectCountCommand.Run(ref GetCommand<DrawIndexedIndirectCountCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndirect] = (memory, threaded, renderer) =>
|
||||
DrawIndirectCommand.Run(ref GetCommand<DrawIndirectCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndirectCount] = (memory, threaded, renderer) =>
|
||||
DrawIndirectCountCommand.Run(ref GetCommand<DrawIndirectCountCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawTexture] = (memory, threaded, renderer) =>
|
||||
DrawTextureCommand.Run(ref GetCommand<DrawTextureCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.EndHostConditionalRendering] = (memory, threaded, renderer) =>
|
||||
EndHostConditionalRenderingCommand.Run(renderer);
|
||||
_lookup[(int)CommandType.EndTransformFeedback] = (memory, threaded, renderer) =>
|
||||
EndTransformFeedbackCommand.Run(ref GetCommand<EndTransformFeedbackCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetAlphaTest] = (memory, threaded, renderer) =>
|
||||
SetAlphaTestCommand.Run(ref GetCommand<SetAlphaTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetBlendState] = (memory, threaded, renderer) =>
|
||||
SetBlendStateCommand.Run(ref GetCommand<SetBlendStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthBias] = (memory, threaded, renderer) =>
|
||||
SetDepthBiasCommand.Run(ref GetCommand<SetDepthBiasCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthClamp] = (memory, threaded, renderer) =>
|
||||
SetDepthClampCommand.Run(ref GetCommand<SetDepthClampCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthMode] = (memory, threaded, renderer) =>
|
||||
SetDepthModeCommand.Run(ref GetCommand<SetDepthModeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthTest] = (memory, threaded, renderer) =>
|
||||
SetDepthTestCommand.Run(ref GetCommand<SetDepthTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetFaceCulling] = (memory, threaded, renderer) =>
|
||||
SetFaceCullingCommand.Run(ref GetCommand<SetFaceCullingCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetFrontFace] = (memory, threaded, renderer) =>
|
||||
SetFrontFaceCommand.Run(ref GetCommand<SetFrontFaceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetStorageBuffers] = (memory, threaded, renderer) =>
|
||||
SetStorageBuffersCommand.Run(ref GetCommand<SetStorageBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTransformFeedbackBuffers] = (memory, threaded, renderer) =>
|
||||
SetTransformFeedbackBuffersCommand.Run(ref GetCommand<SetTransformFeedbackBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetUniformBuffers] = (memory, threaded, renderer) =>
|
||||
SetUniformBuffersCommand.Run(ref GetCommand<SetUniformBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetImage] = (memory, threaded, renderer) =>
|
||||
SetImageCommand.Run(ref GetCommand<SetImageCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetIndexBuffer] = (memory, threaded, renderer) =>
|
||||
SetIndexBufferCommand.Run(ref GetCommand<SetIndexBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetLineParameters] = (memory, threaded, renderer) =>
|
||||
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetLogicOpState] = (memory, threaded, renderer) =>
|
||||
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetMultisampleState] = (memory, threaded, renderer) =>
|
||||
SetMultisampleStateCommand.Run(ref GetCommand<SetMultisampleStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPatchParameters] = (memory, threaded, renderer) =>
|
||||
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPointParameters] = (memory, threaded, renderer) =>
|
||||
SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPolygonMode] = (memory, threaded, renderer) =>
|
||||
SetPolygonModeCommand.Run(ref GetCommand<SetPolygonModeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPrimitiveRestart] = (memory, threaded, renderer) =>
|
||||
SetPrimitiveRestartCommand.Run(ref GetCommand<SetPrimitiveRestartCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPrimitiveTopology] = (memory, threaded, renderer) =>
|
||||
SetPrimitiveTopologyCommand.Run(ref GetCommand<SetPrimitiveTopologyCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetProgram] = (memory, threaded, renderer) =>
|
||||
SetProgramCommand.Run(ref GetCommand<SetProgramCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRasterizerDiscard] = (memory, threaded, renderer) =>
|
||||
SetRasterizerDiscardCommand.Run(ref GetCommand<SetRasterizerDiscardCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargetColorMasks] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetColorMasksCommand.Run(ref GetCommand<SetRenderTargetColorMasksCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargetScale] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargets] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetScissor] = (memory, threaded, renderer) =>
|
||||
SetScissorsCommand.Run(ref GetCommand<SetScissorsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetStencilTest] = (memory, threaded, renderer) =>
|
||||
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTextureAndSampler] = (memory, threaded, renderer) =>
|
||||
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetUserClipDistance] = (memory, threaded, renderer) =>
|
||||
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetVertexAttribs] = (memory, threaded, renderer) =>
|
||||
SetVertexAttribsCommand.Run(ref GetCommand<SetVertexAttribsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetVertexBuffers] = (memory, threaded, renderer) =>
|
||||
SetVertexBuffersCommand.Run(ref GetCommand<SetVertexBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetViewports] = (memory, threaded, renderer) =>
|
||||
SetViewportsCommand.Run(ref GetCommand<SetViewportsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureBarrier] = (memory, threaded, renderer) =>
|
||||
TextureBarrierCommand.Run(ref GetCommand<TextureBarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureBarrierTiled] = (memory, threaded, renderer) =>
|
||||
TextureBarrierTiledCommand.Run(ref GetCommand<TextureBarrierTiledCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TryHostConditionalRendering] = (memory, threaded, renderer) =>
|
||||
TryHostConditionalRenderingCommand.Run(ref GetCommand<TryHostConditionalRenderingCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TryHostConditionalRenderingFlush] = (memory, threaded, renderer) =>
|
||||
TryHostConditionalRenderingFlushCommand.Run(ref GetCommand<TryHostConditionalRenderingFlushCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.UpdateRenderScale] = (memory, threaded, renderer) =>
|
||||
UpdateRenderScaleCommand.Run(ref GetCommand<UpdateRenderScaleCommand>(memory), threaded, renderer);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RunCommand(Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
_lookup[memory[memory.Length - 1]](memory, threaded, renderer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
interface IGALCommand
|
||||
{
|
||||
CommandType CommandType { get; }
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Model
|
||||
{
|
||||
unsafe struct PinnedSpan<T> where T : unmanaged
|
||||
{
|
||||
private void* _ptr;
|
||||
private int _size;
|
||||
|
||||
public PinnedSpan(ReadOnlySpan<T> span)
|
||||
{
|
||||
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
|
||||
_size = span.Length;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> Get()
|
||||
{
|
||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// A texture cache that automatically removes older textures that are not used for some time.
|
||||
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
||||
/// old ones at the bottom of the list are deleted.
|
||||
/// </summary>
|
||||
class AutoDeleteCache : IEnumerable<Texture>
|
||||
{
|
||||
private const int MaxCapacity = 2048;
|
||||
|
||||
private readonly LinkedList<Texture> _textures;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the automatic deletion cache.
|
||||
/// </summary>
|
||||
public AutoDeleteCache()
|
||||
{
|
||||
_textures = new LinkedList<Texture>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new texture to the cache, even if the texture added is already on the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this method is only recommended if you know that the texture is not yet on the cache,
|
||||
/// otherwise it would store the same texture more than once.
|
||||
/// </remarks>
|
||||
/// <param name="texture">The texture to be added to the cache</param>
|
||||
public void Add(Texture texture)
|
||||
{
|
||||
texture.IncrementReferenceCount();
|
||||
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
|
||||
if (_textures.Count > MaxCapacity)
|
||||
{
|
||||
Texture oldestTexture = _textures.First.Value;
|
||||
|
||||
if (!oldestTexture.CheckModified(false))
|
||||
{
|
||||
// The texture must be flushed if it falls out of the auto delete cache.
|
||||
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||
// as it is expected that other overlapping textures exist that have more up-to-date contents.
|
||||
|
||||
oldestTexture.Group.SynchronizeDependents(oldestTexture);
|
||||
oldestTexture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.RemoveFirst();
|
||||
|
||||
oldestTexture.DecrementReferenceCount();
|
||||
|
||||
oldestTexture.CacheNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new texture to the cache, or just moves it to the top of the list if the
|
||||
/// texture is already on the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Moving the texture to the top of the list prevents it from being deleted,
|
||||
/// as the textures on the bottom of the list are deleted when new ones are added.
|
||||
/// </remarks>
|
||||
/// <param name="texture">The texture to be added, or moved to the top</param>
|
||||
public void Lift(Texture texture)
|
||||
{
|
||||
if (texture.CacheNode != null)
|
||||
{
|
||||
if (texture.CacheNode != _textures.Last)
|
||||
{
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(texture);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(Texture texture, bool flush)
|
||||
{
|
||||
if (texture.CacheNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove our reference to this texture.
|
||||
if (flush)
|
||||
{
|
||||
texture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
texture.CacheNode = null;
|
||||
|
||||
return texture.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
public IEnumerator<Texture> GetEnumerator()
|
||||
{
|
||||
return _textures.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _textures.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
public class UnmapEventArgs
|
||||
{
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public UnmapEventArgs(ulong address, ulong size)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Graphics" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,214 +0,0 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
class Window : IWindow, IDisposable
|
||||
{
|
||||
private const int TextureCount = 3;
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
private int _copyFramebufferHandle;
|
||||
|
||||
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
||||
|
||||
internal bool ScreenCaptureRequested { get; set; }
|
||||
|
||||
public Window(OpenGLRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||||
{
|
||||
GL.Disable(EnableCap.FramebufferSrgb);
|
||||
|
||||
(int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers();
|
||||
|
||||
CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback);
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
|
||||
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
// Restore unpack alignment to 4, as performance overlays such as RTSS may change this to load their resources.
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
}
|
||||
|
||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
||||
|
||||
public void SetSize(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
||||
|
||||
TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.ReadFramebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
viewConverted.Handle,
|
||||
0);
|
||||
|
||||
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
|
||||
|
||||
GL.Disable(EnableCap.RasterizerDiscard);
|
||||
GL.Disable(IndexedEnableCap.ScissorTest, 0);
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
int srcX0, srcX1, srcY0, srcY1;
|
||||
float scale = view.ScaleFactor;
|
||||
|
||||
if (crop.Left == 0 && crop.Right == 0)
|
||||
{
|
||||
srcX0 = 0;
|
||||
srcX1 = (int)(view.Width / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
srcX0 = crop.Left;
|
||||
srcX1 = crop.Right;
|
||||
}
|
||||
|
||||
if (crop.Top == 0 && crop.Bottom == 0)
|
||||
{
|
||||
srcY0 = 0;
|
||||
srcY1 = (int)(view.Height / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
srcY0 = crop.Top;
|
||||
srcY1 = crop.Bottom;
|
||||
}
|
||||
|
||||
if (scale != 1f)
|
||||
{
|
||||
srcX0 = (int)(srcX0 * scale);
|
||||
srcY0 = (int)(srcY0 * scale);
|
||||
srcX1 = (int)Math.Ceiling(srcX1 * scale);
|
||||
srcY1 = (int)Math.Ceiling(srcY1 * scale);
|
||||
}
|
||||
|
||||
float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
|
||||
float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
|
||||
|
||||
int dstWidth = (int)(_width * ratioX);
|
||||
int dstHeight = (int)(_height * ratioY);
|
||||
|
||||
int dstPaddingX = (_width - dstWidth) / 2;
|
||||
int dstPaddingY = (_height - dstHeight) / 2;
|
||||
|
||||
int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
|
||||
int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
|
||||
|
||||
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
||||
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
||||
|
||||
if (ScreenCaptureRequested)
|
||||
{
|
||||
CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgr(), crop.FlipX, crop.FlipY);
|
||||
|
||||
ScreenCaptureRequested = false;
|
||||
}
|
||||
|
||||
GL.BlitFramebuffer(
|
||||
srcX0,
|
||||
srcY0,
|
||||
srcX1,
|
||||
srcY1,
|
||||
dstX0,
|
||||
dstY0,
|
||||
dstX1,
|
||||
dstY1,
|
||||
ClearBufferMask.ColorBufferBit,
|
||||
BlitFramebufferFilter.Linear);
|
||||
|
||||
// Remove Alpha channel
|
||||
GL.ColorMask(false, false, false, true);
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
||||
{
|
||||
((Pipeline)_renderer.Pipeline).RestoreComponentMask(i);
|
||||
}
|
||||
|
||||
// Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
|
||||
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
|
||||
GL.Viewport(0, 0, _width, _height);
|
||||
|
||||
swapBuffersCallback();
|
||||
|
||||
((Pipeline)_renderer.Pipeline).RestoreClipControl();
|
||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||
((Pipeline)_renderer.Pipeline).RestoreRasterizerDiscard();
|
||||
((Pipeline)_renderer.Pipeline).RestoreViewport0();
|
||||
|
||||
if (viewConverted != view)
|
||||
{
|
||||
viewConverted.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetCopyFramebufferHandleLazy()
|
||||
{
|
||||
int handle = _copyFramebufferHandle;
|
||||
|
||||
if (handle == 0)
|
||||
{
|
||||
handle = GL.GenFramebuffer();
|
||||
|
||||
_copyFramebufferHandle = handle;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void InitializeBackgroundContext(IOpenGLContext baseContext)
|
||||
{
|
||||
BackgroundContext = new BackgroundContextWorker(baseContext);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
|
||||
{
|
||||
long size = Math.Abs(4 * width * height);
|
||||
byte[] bitmap = new byte[size];
|
||||
|
||||
GL.ReadPixels(x, y, width, height, isBgra ? PixelFormat.Bgra : PixelFormat.Rgba, PixelType.UnsignedByte, bitmap);
|
||||
|
||||
_renderer.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BackgroundContext.Dispose();
|
||||
|
||||
if (_copyFramebufferHandle != 0)
|
||||
{
|
||||
GL.DeleteFramebuffer(_copyFramebufferHandle);
|
||||
|
||||
_copyFramebufferHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,495 +0,0 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.StructuredIr.InstructionInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
class OperandManager
|
||||
{
|
||||
private static readonly string[] StagePrefixes = new string[] { "cp", "vp", "tcp", "tep", "gp", "fp" };
|
||||
|
||||
private readonly struct BuiltInAttribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public AggregateType Type { get; }
|
||||
|
||||
public BuiltInAttribute(string name, AggregateType type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<int, BuiltInAttribute> _builtInAttributes = new Dictionary<int, BuiltInAttribute>()
|
||||
{
|
||||
{ AttributeConsts.Layer, new BuiltInAttribute("gl_Layer", AggregateType.S32) },
|
||||
{ AttributeConsts.PointSize, new BuiltInAttribute("gl_PointSize", AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionX, new BuiltInAttribute("gl_Position.x", AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionY, new BuiltInAttribute("gl_Position.y", AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionZ, new BuiltInAttribute("gl_Position.z", AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionW, new BuiltInAttribute("gl_Position.w", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance0, new BuiltInAttribute("gl_ClipDistance[0]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance1, new BuiltInAttribute("gl_ClipDistance[1]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance2, new BuiltInAttribute("gl_ClipDistance[2]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance3, new BuiltInAttribute("gl_ClipDistance[3]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance4, new BuiltInAttribute("gl_ClipDistance[4]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance5, new BuiltInAttribute("gl_ClipDistance[5]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance6, new BuiltInAttribute("gl_ClipDistance[6]", AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance7, new BuiltInAttribute("gl_ClipDistance[7]", AggregateType.FP32) },
|
||||
{ AttributeConsts.PointCoordX, new BuiltInAttribute("gl_PointCoord.x", AggregateType.FP32) },
|
||||
{ AttributeConsts.PointCoordY, new BuiltInAttribute("gl_PointCoord.y", AggregateType.FP32) },
|
||||
{ AttributeConsts.TessCoordX, new BuiltInAttribute("gl_TessCoord.x", AggregateType.FP32) },
|
||||
{ AttributeConsts.TessCoordY, new BuiltInAttribute("gl_TessCoord.y", AggregateType.FP32) },
|
||||
{ AttributeConsts.InstanceId, new BuiltInAttribute("gl_InstanceID", AggregateType.S32) },
|
||||
{ AttributeConsts.VertexId, new BuiltInAttribute("gl_VertexID", AggregateType.S32) },
|
||||
{ AttributeConsts.BaseInstance, new BuiltInAttribute("gl_BaseInstanceARB", AggregateType.S32) },
|
||||
{ AttributeConsts.BaseVertex, new BuiltInAttribute("gl_BaseVertexARB", AggregateType.S32) },
|
||||
{ AttributeConsts.InstanceIndex, new BuiltInAttribute("gl_InstanceIndex", AggregateType.S32) },
|
||||
{ AttributeConsts.VertexIndex, new BuiltInAttribute("gl_VertexIndex", AggregateType.S32) },
|
||||
{ AttributeConsts.DrawIndex, new BuiltInAttribute("gl_DrawIDARB", AggregateType.S32) },
|
||||
{ AttributeConsts.FrontFacing, new BuiltInAttribute("gl_FrontFacing", AggregateType.Bool) },
|
||||
|
||||
// Special.
|
||||
{ AttributeConsts.FragmentOutputDepth, new BuiltInAttribute("gl_FragDepth", AggregateType.FP32) },
|
||||
{ AttributeConsts.ThreadKill, new BuiltInAttribute("gl_HelperInvocation", AggregateType.Bool) },
|
||||
{ AttributeConsts.ThreadIdX, new BuiltInAttribute("gl_LocalInvocationID.x", AggregateType.U32) },
|
||||
{ AttributeConsts.ThreadIdY, new BuiltInAttribute("gl_LocalInvocationID.y", AggregateType.U32) },
|
||||
{ AttributeConsts.ThreadIdZ, new BuiltInAttribute("gl_LocalInvocationID.z", AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdX, new BuiltInAttribute("gl_WorkGroupID.x", AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdY, new BuiltInAttribute("gl_WorkGroupID.y", AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdZ, new BuiltInAttribute("gl_WorkGroupID.z", AggregateType.U32) },
|
||||
{ AttributeConsts.LaneId, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
{ AttributeConsts.InvocationId, new BuiltInAttribute("gl_InvocationID", AggregateType.S32) },
|
||||
{ AttributeConsts.PrimitiveId, new BuiltInAttribute("gl_PrimitiveID", AggregateType.S32) },
|
||||
{ AttributeConsts.PatchVerticesIn, new BuiltInAttribute("gl_PatchVerticesIn", AggregateType.S32) },
|
||||
{ AttributeConsts.EqMask, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
{ AttributeConsts.GeMask, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
{ AttributeConsts.GtMask, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
{ AttributeConsts.LeMask, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
{ AttributeConsts.LtMask, new BuiltInAttribute(null, AggregateType.U32) },
|
||||
|
||||
// Support uniforms.
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 0, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[0]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 4, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[1]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 8, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[2]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 12, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[3]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 16, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[4]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 20, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[5]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 24, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[6]", AggregateType.Bool) },
|
||||
{ AttributeConsts.FragmentOutputIsBgraBase + 28, new BuiltInAttribute($"{DefaultNames.SupportBlockIsBgraName}[7]", AggregateType.Bool) },
|
||||
|
||||
{ AttributeConsts.SupportBlockViewInverseX, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.x", AggregateType.FP32) },
|
||||
{ AttributeConsts.SupportBlockViewInverseY, new BuiltInAttribute($"{DefaultNames.SupportBlockViewportInverse}.y", AggregateType.FP32) }
|
||||
};
|
||||
|
||||
private Dictionary<AstOperand, string> _locals;
|
||||
|
||||
public OperandManager()
|
||||
{
|
||||
_locals = new Dictionary<AstOperand, string>();
|
||||
}
|
||||
|
||||
public string DeclareLocal(AstOperand operand)
|
||||
{
|
||||
string name = $"{DefaultNames.LocalNamePrefix}_{_locals.Count}";
|
||||
|
||||
_locals.Add(operand, name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public string GetExpression(CodeGenContext context, AstOperand operand)
|
||||
{
|
||||
return operand.Type switch
|
||||
{
|
||||
OperandType.Argument => GetArgumentName(operand.Value),
|
||||
OperandType.Attribute => GetAttributeName(context, operand.Value, perPatch: false),
|
||||
OperandType.AttributePerPatch => GetAttributeName(context, operand.Value, perPatch: true),
|
||||
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
|
||||
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
|
||||
OperandType.LocalVariable => _locals[operand],
|
||||
OperandType.Undefined => DefaultNames.UndefinedName,
|
||||
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetConstantBufferName(AstOperand operand, ShaderConfig config)
|
||||
{
|
||||
return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing));
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
|
||||
{
|
||||
return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}";
|
||||
}
|
||||
|
||||
private static string GetVec4Indexed(string vectorName, string indexExpr, bool indexElement)
|
||||
{
|
||||
if (indexElement)
|
||||
{
|
||||
return $"{vectorName}[{indexExpr}]";
|
||||
}
|
||||
|
||||
string result = $"{vectorName}.x";
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
result = $"(({indexExpr}) == {i}) ? ({vectorName}.{GetSwizzleMask(i)}) : ({result})";
|
||||
}
|
||||
return $"({result})";
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(int slot, string offsetExpr, ShaderStage stage, bool cbIndexable, bool indexElement)
|
||||
{
|
||||
return GetVec4Indexed(GetUbName(stage, slot, cbIndexable) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(string slotExpr, string offsetExpr, ShaderStage stage, bool indexElement)
|
||||
{
|
||||
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
|
||||
}
|
||||
|
||||
public static string GetOutAttributeName(CodeGenContext context, int value, bool perPatch)
|
||||
{
|
||||
return GetAttributeName(context, value, perPatch, isOutAttr: true);
|
||||
}
|
||||
|
||||
public static string GetAttributeName(CodeGenContext context, int value, bool perPatch, bool isOutAttr = false, string indexExpr = "0")
|
||||
{
|
||||
ShaderConfig config = context.Config;
|
||||
|
||||
if ((value & AttributeConsts.LoadOutputMask) != 0)
|
||||
{
|
||||
isOutAttr = true;
|
||||
}
|
||||
|
||||
value &= AttributeConsts.Mask & ~3;
|
||||
char swzMask = GetSwizzleMask((value >> 2) & 3);
|
||||
|
||||
if (perPatch)
|
||||
{
|
||||
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
|
||||
{
|
||||
value -= AttributeConsts.UserAttributePerPatchBase;
|
||||
|
||||
return $"{DefaultNames.PerPatchAttributePrefix}{(value >> 4)}.{swzMask}";
|
||||
}
|
||||
else if (value < AttributeConsts.UserAttributePerPatchBase)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
AttributeConsts.TessLevelOuter0 => "gl_TessLevelOuter[0]",
|
||||
AttributeConsts.TessLevelOuter1 => "gl_TessLevelOuter[1]",
|
||||
AttributeConsts.TessLevelOuter2 => "gl_TessLevelOuter[2]",
|
||||
AttributeConsts.TessLevelOuter3 => "gl_TessLevelOuter[3]",
|
||||
AttributeConsts.TessLevelInner0 => "gl_TessLevelInner[0]",
|
||||
AttributeConsts.TessLevelInner1 => "gl_TessLevelInner[1]",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
int attrOffset = value;
|
||||
value -= AttributeConsts.UserAttributeBase;
|
||||
|
||||
string prefix = isOutAttr
|
||||
? DefaultNames.OAttributePrefix
|
||||
: DefaultNames.IAttributePrefix;
|
||||
|
||||
bool indexable = config.UsedFeatures.HasFlag(isOutAttr ? FeatureFlags.OaIndexing : FeatureFlags.IaIndexing);
|
||||
|
||||
if (indexable)
|
||||
{
|
||||
string name = prefix;
|
||||
|
||||
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
|
||||
{
|
||||
name += $"[{indexExpr}]";
|
||||
}
|
||||
|
||||
return name + $"[{(value >> 4)}]." + swzMask;
|
||||
}
|
||||
else if (config.TransformFeedbackEnabled &&
|
||||
((config.LastInVertexPipeline && isOutAttr) ||
|
||||
(config.Stage == ShaderStage.Fragment && !isOutAttr)))
|
||||
{
|
||||
int components = config.LastInPipeline ? context.Info.GetTransformFeedbackOutputComponents(attrOffset) : 1;
|
||||
string name = components > 1 ? $"{prefix}{(value >> 4)}" : $"{prefix}{(value >> 4)}_{swzMask}";
|
||||
|
||||
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
|
||||
{
|
||||
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
|
||||
}
|
||||
|
||||
return components > 1 ? name + '.' + swzMask : name;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = $"{prefix}{(value >> 4)}";
|
||||
|
||||
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr))
|
||||
{
|
||||
name += isOutAttr ? "[gl_InvocationID]" : $"[{indexExpr}]";
|
||||
}
|
||||
|
||||
return name + '.' + swzMask;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
value -= AttributeConsts.FragmentOutputColorBase;
|
||||
|
||||
return $"{DefaultNames.OAttributePrefix}{(value >> 4)}.{swzMask}";
|
||||
}
|
||||
else if (_builtInAttributes.TryGetValue(value, out BuiltInAttribute builtInAttr))
|
||||
{
|
||||
string subgroupMask = value switch
|
||||
{
|
||||
AttributeConsts.EqMask => "Eq",
|
||||
AttributeConsts.GeMask => "Ge",
|
||||
AttributeConsts.GtMask => "Gt",
|
||||
AttributeConsts.LeMask => "Le",
|
||||
AttributeConsts.LtMask => "Lt",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (subgroupMask != null)
|
||||
{
|
||||
return config.GpuAccessor.QueryHostSupportsShaderBallot()
|
||||
? $"unpackUint2x32(gl_SubGroup{subgroupMask}MaskARB).x"
|
||||
: $"gl_Subgroup{subgroupMask}Mask.x";
|
||||
}
|
||||
else if (value == AttributeConsts.LaneId)
|
||||
{
|
||||
return config.GpuAccessor.QueryHostSupportsShaderBallot()
|
||||
? "gl_SubGroupInvocationARB"
|
||||
: "gl_SubgroupInvocationID";
|
||||
}
|
||||
|
||||
if (config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
// TODO: There must be a better way to handle this...
|
||||
switch (value)
|
||||
{
|
||||
case AttributeConsts.PositionX: return $"(gl_FragCoord.x / {DefaultNames.SupportBlockRenderScaleName}[0])";
|
||||
case AttributeConsts.PositionY: return $"(gl_FragCoord.y / {DefaultNames.SupportBlockRenderScaleName}[0])";
|
||||
case AttributeConsts.PositionZ: return "gl_FragCoord.z";
|
||||
case AttributeConsts.PositionW: return "gl_FragCoord.w";
|
||||
|
||||
case AttributeConsts.FrontFacing:
|
||||
if (config.GpuAccessor.QueryHostHasFrontFacingBug())
|
||||
{
|
||||
// This is required for Intel on Windows, gl_FrontFacing sometimes returns incorrect
|
||||
// (flipped) values. Doing this seems to fix it.
|
||||
return "(-floatBitsToInt(float(gl_FrontFacing)) < 0)";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string name = builtInAttr.Name;
|
||||
|
||||
if (AttributeInfo.IsArrayAttributeGlsl(config.Stage, isOutAttr) && AttributeInfo.IsArrayBuiltIn(value))
|
||||
{
|
||||
name = isOutAttr ? $"gl_out[gl_InvocationID].{name}" : $"gl_in[{indexExpr}].{name}";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Warn about unknown built-in attribute.
|
||||
|
||||
return isOutAttr ? "// bad_attr0x" + value.ToString("X") : "0.0";
|
||||
}
|
||||
|
||||
public static string GetAttributeName(string attrExpr, ShaderConfig config, bool isOutAttr = false, string indexExpr = "0")
|
||||
{
|
||||
string name = isOutAttr
|
||||
? DefaultNames.OAttributePrefix
|
||||
: DefaultNames.IAttributePrefix;
|
||||
|
||||
if (config.Stage == ShaderStage.Geometry && !isOutAttr)
|
||||
{
|
||||
name += $"[{indexExpr}]";
|
||||
}
|
||||
|
||||
return $"{name}[{attrExpr} >> 2][{attrExpr} & 3]";
|
||||
}
|
||||
|
||||
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
|
||||
{
|
||||
if (cbIndexable)
|
||||
{
|
||||
return GetUbName(stage, NumberFormatter.FormatInt(slot, AggregateType.S32));
|
||||
}
|
||||
|
||||
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}{slot}_{DefaultNames.UniformNameSuffix}";
|
||||
}
|
||||
|
||||
private static string GetUbName(ShaderStage stage, string slotExpr)
|
||||
{
|
||||
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}[{slotExpr}].{DefaultNames.DataName}";
|
||||
}
|
||||
|
||||
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
|
||||
{
|
||||
return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
|
||||
}
|
||||
|
||||
public static string GetSamplerName(ShaderStage stage, int cbufSlot, int handle, bool indexed, string indexExpr)
|
||||
{
|
||||
string suffix = cbufSlot < 0 ? $"_tcb_{handle:X}" : $"_cb{cbufSlot}_{handle:X}";
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
suffix += $"a[{indexExpr}]";
|
||||
}
|
||||
|
||||
return GetShaderStagePrefix(stage) + "_" + DefaultNames.SamplerNamePrefix + suffix;
|
||||
}
|
||||
|
||||
public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
|
||||
{
|
||||
return GetImageName(stage, texOp.CbufSlot, texOp.Handle, texOp.Format, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
|
||||
}
|
||||
|
||||
public static string GetImageName(
|
||||
ShaderStage stage,
|
||||
int cbufSlot,
|
||||
int handle,
|
||||
TextureFormat format,
|
||||
bool indexed,
|
||||
string indexExpr)
|
||||
{
|
||||
string suffix = cbufSlot < 0
|
||||
? $"_tcb_{handle:X}_{format.ToGlslFormat()}"
|
||||
: $"_cb{cbufSlot}_{handle:X}_{format.ToGlslFormat()}";
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
suffix += $"a[{indexExpr}]";
|
||||
}
|
||||
|
||||
return GetShaderStagePrefix(stage) + "_" + DefaultNames.ImageNamePrefix + suffix;
|
||||
}
|
||||
|
||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||
{
|
||||
int index = (int)stage;
|
||||
|
||||
if ((uint)index >= StagePrefixes.Length)
|
||||
{
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
return StagePrefixes[index];
|
||||
}
|
||||
|
||||
private static char GetSwizzleMask(int value)
|
||||
{
|
||||
return "xyzw"[value];
|
||||
}
|
||||
|
||||
public static string GetArgumentName(int argIndex)
|
||||
{
|
||||
return $"{DefaultNames.ArgumentNamePrefix}{argIndex}";
|
||||
}
|
||||
|
||||
public static AggregateType GetNodeDestType(CodeGenContext context, IAstNode node, bool isAsgDest = false)
|
||||
{
|
||||
if (node is AstOperation operation)
|
||||
{
|
||||
if (operation.Inst == Instruction.LoadAttribute)
|
||||
{
|
||||
// Load attribute basically just returns the attribute value.
|
||||
// Some built-in attributes may have different types, so we need
|
||||
// to return the type based on the attribute that is being read.
|
||||
if (operation.GetSource(0) is AstOperand operand && operand.Type == OperandType.Constant)
|
||||
{
|
||||
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
|
||||
{
|
||||
return builtInAttr.Type;
|
||||
}
|
||||
}
|
||||
|
||||
return OperandInfo.GetVarType(OperandType.Attribute);
|
||||
}
|
||||
else if (operation.Inst == Instruction.Call)
|
||||
{
|
||||
AstOperand funcId = (AstOperand)operation.GetSource(0);
|
||||
|
||||
Debug.Assert(funcId.Type == OperandType.Constant);
|
||||
|
||||
return context.GetFunction(funcId.Value).ReturnType;
|
||||
}
|
||||
else if (operation.Inst == Instruction.VectorExtract)
|
||||
{
|
||||
return GetNodeDestType(context, operation.GetSource(0)) & ~AggregateType.ElementCountMask;
|
||||
}
|
||||
else if (operation is AstTextureOperation texOp)
|
||||
{
|
||||
if (texOp.Inst == Instruction.ImageLoad ||
|
||||
texOp.Inst == Instruction.ImageStore ||
|
||||
texOp.Inst == Instruction.ImageAtomic)
|
||||
{
|
||||
return texOp.GetVectorType(texOp.Format.GetComponentType());
|
||||
}
|
||||
else if (texOp.Inst == Instruction.TextureSample)
|
||||
{
|
||||
return texOp.GetVectorType(GetDestVarType(operation.Inst));
|
||||
}
|
||||
}
|
||||
|
||||
return GetDestVarType(operation.Inst);
|
||||
}
|
||||
else if (node is AstOperand operand)
|
||||
{
|
||||
if (operand.Type == OperandType.Argument)
|
||||
{
|
||||
int argIndex = operand.Value;
|
||||
|
||||
return context.CurrentFunction.GetArgumentType(argIndex);
|
||||
}
|
||||
|
||||
return GetOperandVarType(context, operand, isAsgDest);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Invalid node type \"{node?.GetType().Name ?? "null"}\".");
|
||||
}
|
||||
}
|
||||
|
||||
private static AggregateType GetOperandVarType(CodeGenContext context, AstOperand operand, bool isAsgDest = false)
|
||||
{
|
||||
if (operand.Type == OperandType.Attribute)
|
||||
{
|
||||
if (_builtInAttributes.TryGetValue(operand.Value & ~3, out BuiltInAttribute builtInAttr))
|
||||
{
|
||||
return builtInAttr.Type;
|
||||
}
|
||||
else if (context.Config.Stage == ShaderStage.Vertex && !isAsgDest &&
|
||||
operand.Value >= AttributeConsts.UserAttributeBase &&
|
||||
operand.Value < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
int location = (operand.Value - AttributeConsts.UserAttributeBase) / 16;
|
||||
|
||||
AttributeType type = context.Config.GpuAccessor.QueryAttributeType(location);
|
||||
|
||||
return type.ToAggregateType();
|
||||
}
|
||||
}
|
||||
|
||||
return OperandInfo.GetVarType(operand);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Shader.Decoders
|
||||
{
|
||||
static class BitfieldExtensions
|
||||
{
|
||||
public static bool Extract(this int value, int lsb)
|
||||
{
|
||||
return ((value >> lsb) & 1) != 0;
|
||||
}
|
||||
|
||||
public static int Extract(this int value, int lsb, int length)
|
||||
{
|
||||
return (value >> lsb) & (int)(uint.MaxValue >> (32 - length));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
{
|
||||
enum OperandType
|
||||
{
|
||||
Argument,
|
||||
Attribute,
|
||||
AttributePerPatch,
|
||||
Constant,
|
||||
ConstantBuffer,
|
||||
Label,
|
||||
LocalVariable,
|
||||
Register,
|
||||
Undefined
|
||||
}
|
||||
|
||||
static class OperandTypeExtensions
|
||||
{
|
||||
public static bool IsAttribute(this OperandType type)
|
||||
{
|
||||
return type == OperandType.Attribute || type == OperandType.AttributePerPatch;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
readonly struct TransformFeedbackOutput
|
||||
{
|
||||
public readonly bool Valid;
|
||||
public readonly int Buffer;
|
||||
public readonly int Offset;
|
||||
public readonly int Stride;
|
||||
|
||||
public TransformFeedbackOutput(int buffer, int offset, int stride)
|
||||
{
|
||||
Valid = true;
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
Stride = stride;
|
||||
}
|
||||
}
|
||||
|
||||
class StructuredProgramInfo
|
||||
{
|
||||
public List<StructuredFunction> Functions { get; }
|
||||
|
||||
public HashSet<int> Inputs { get; }
|
||||
public HashSet<int> Outputs { get; }
|
||||
public HashSet<int> InputsPerPatch { get; }
|
||||
public HashSet<int> OutputsPerPatch { get; }
|
||||
|
||||
public HelperFunctionsMask HelperFunctionsMask { get; set; }
|
||||
|
||||
public TransformFeedbackOutput[] TransformFeedbackOutputs { get; }
|
||||
|
||||
public StructuredProgramInfo()
|
||||
{
|
||||
Functions = new List<StructuredFunction>();
|
||||
|
||||
Inputs = new HashSet<int>();
|
||||
Outputs = new HashSet<int>();
|
||||
InputsPerPatch = new HashSet<int>();
|
||||
OutputsPerPatch = new HashSet<int>();
|
||||
|
||||
TransformFeedbackOutputs = new TransformFeedbackOutput[0xc0];
|
||||
}
|
||||
|
||||
public TransformFeedbackOutput GetTransformFeedbackOutput(int attr)
|
||||
{
|
||||
int index = attr / 4;
|
||||
return TransformFeedbackOutputs[index];
|
||||
}
|
||||
|
||||
public int GetTransformFeedbackOutputComponents(int attr)
|
||||
{
|
||||
int index = attr / 4;
|
||||
int baseIndex = index & ~3;
|
||||
|
||||
int count = 1;
|
||||
|
||||
for (; count < 4; count++)
|
||||
{
|
||||
ref var prev = ref TransformFeedbackOutputs[baseIndex + count - 1];
|
||||
ref var curr = ref TransformFeedbackOutputs[baseIndex + count];
|
||||
|
||||
int prevOffset = prev.Offset;
|
||||
int currOffset = curr.Offset;
|
||||
|
||||
if (!prev.Valid || !curr.Valid || prevOffset + 4 != currOffset)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (baseIndex + count <= index)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class AttributeConsts
|
||||
{
|
||||
public const int TessLevelOuter0 = 0x000;
|
||||
public const int TessLevelOuter1 = 0x004;
|
||||
public const int TessLevelOuter2 = 0x008;
|
||||
public const int TessLevelOuter3 = 0x00c;
|
||||
public const int TessLevelInner0 = 0x010;
|
||||
public const int TessLevelInner1 = 0x014;
|
||||
public const int PrimitiveId = 0x060;
|
||||
public const int Layer = 0x064;
|
||||
public const int ViewportIndex = 0x068;
|
||||
public const int PointSize = 0x06c;
|
||||
public const int PositionX = 0x070;
|
||||
public const int PositionY = 0x074;
|
||||
public const int PositionZ = 0x078;
|
||||
public const int PositionW = 0x07c;
|
||||
public const int FrontColorDiffuseR = 0x280;
|
||||
public const int FrontColorDiffuseG = 0x284;
|
||||
public const int FrontColorDiffuseB = 0x288;
|
||||
public const int FrontColorDiffuseA = 0x28c;
|
||||
public const int FrontColorSpecularR = 0x290;
|
||||
public const int FrontColorSpecularG = 0x294;
|
||||
public const int FrontColorSpecularB = 0x298;
|
||||
public const int FrontColorSpecularA = 0x29c;
|
||||
public const int BackColorDiffuseR = 0x2a0;
|
||||
public const int BackColorDiffuseG = 0x2a4;
|
||||
public const int BackColorDiffuseB = 0x2a8;
|
||||
public const int BackColorDiffuseA = 0x2ac;
|
||||
public const int BackColorSpecularR = 0x2b0;
|
||||
public const int BackColorSpecularG = 0x2b4;
|
||||
public const int BackColorSpecularB = 0x2b8;
|
||||
public const int BackColorSpecularA = 0x2bc;
|
||||
public const int ClipDistance0 = 0x2c0;
|
||||
public const int ClipDistance1 = 0x2c4;
|
||||
public const int ClipDistance2 = 0x2c8;
|
||||
public const int ClipDistance3 = 0x2cc;
|
||||
public const int ClipDistance4 = 0x2d0;
|
||||
public const int ClipDistance5 = 0x2d4;
|
||||
public const int ClipDistance6 = 0x2d8;
|
||||
public const int ClipDistance7 = 0x2dc;
|
||||
public const int PointCoordX = 0x2e0;
|
||||
public const int PointCoordY = 0x2e4;
|
||||
public const int TessCoordX = 0x2f0;
|
||||
public const int TessCoordY = 0x2f4;
|
||||
public const int InstanceId = 0x2f8;
|
||||
public const int VertexId = 0x2fc;
|
||||
public const int TexCoordCount = 10;
|
||||
public const int TexCoordBase = 0x300;
|
||||
public const int TexCoordEnd = TexCoordBase + TexCoordCount * 16;
|
||||
public const int FrontFacing = 0x3fc;
|
||||
|
||||
public const int UserAttributesCount = 32;
|
||||
public const int UserAttributeBase = 0x80;
|
||||
public const int UserAttributeEnd = UserAttributeBase + UserAttributesCount * 16;
|
||||
|
||||
public const int UserAttributePerPatchBase = 0x18;
|
||||
public const int UserAttributePerPatchEnd = 0x200;
|
||||
|
||||
public const int LoadOutputMask = 1 << 30;
|
||||
public const int Mask = 0x3fffffff;
|
||||
|
||||
|
||||
// Note: Those attributes are used internally by the translator
|
||||
// only, they don't exist on Maxwell.
|
||||
public const int SpecialMask = 0xf << 24;
|
||||
public const int FragmentOutputDepth = 0x1000000;
|
||||
public const int FragmentOutputColorBase = 0x1000010;
|
||||
public const int FragmentOutputColorEnd = FragmentOutputColorBase + 8 * 16;
|
||||
|
||||
public const int FragmentOutputIsBgraBase = 0x1000100;
|
||||
public const int FragmentOutputIsBgraEnd = FragmentOutputIsBgraBase + 8 * 4;
|
||||
|
||||
public const int SupportBlockViewInverseX = 0x1000200;
|
||||
public const int SupportBlockViewInverseY = 0x1000204;
|
||||
|
||||
public const int ThreadIdX = 0x2000000;
|
||||
public const int ThreadIdY = 0x2000004;
|
||||
public const int ThreadIdZ = 0x2000008;
|
||||
|
||||
public const int CtaIdX = 0x2000010;
|
||||
public const int CtaIdY = 0x2000014;
|
||||
public const int CtaIdZ = 0x2000018;
|
||||
|
||||
public const int LaneId = 0x2000020;
|
||||
|
||||
public const int InvocationId = 0x2000024;
|
||||
public const int PatchVerticesIn = 0x2000028;
|
||||
|
||||
public const int EqMask = 0x2000030;
|
||||
public const int GeMask = 0x2000034;
|
||||
public const int GtMask = 0x2000038;
|
||||
public const int LeMask = 0x200003c;
|
||||
public const int LtMask = 0x2000040;
|
||||
|
||||
public const int ThreadKill = 0x2000044;
|
||||
|
||||
public const int BaseInstance = 0x2000050;
|
||||
public const int BaseVertex = 0x2000054;
|
||||
public const int InstanceIndex = 0x2000058;
|
||||
public const int VertexIndex = 0x200005c;
|
||||
public const int DrawIndex = 0x2000060;
|
||||
}
|
||||
}
|
@ -1,202 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
readonly struct AttributeInfo
|
||||
{
|
||||
private static readonly Dictionary<int, AttributeInfo> _builtInAttributes = new Dictionary<int, AttributeInfo>()
|
||||
{
|
||||
{ AttributeConsts.Layer, new AttributeInfo(AttributeConsts.Layer, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.ViewportIndex, new AttributeInfo(AttributeConsts.ViewportIndex, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.PointSize, new AttributeInfo(AttributeConsts.PointSize, 0, 1, AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionX, new AttributeInfo(AttributeConsts.PositionX, 0, 4, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionY, new AttributeInfo(AttributeConsts.PositionX, 1, 4, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionZ, new AttributeInfo(AttributeConsts.PositionX, 2, 4, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.PositionW, new AttributeInfo(AttributeConsts.PositionX, 3, 4, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance0, new AttributeInfo(AttributeConsts.ClipDistance0, 0, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance1, new AttributeInfo(AttributeConsts.ClipDistance0, 1, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance2, new AttributeInfo(AttributeConsts.ClipDistance0, 2, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance3, new AttributeInfo(AttributeConsts.ClipDistance0, 3, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance4, new AttributeInfo(AttributeConsts.ClipDistance0, 4, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance5, new AttributeInfo(AttributeConsts.ClipDistance0, 5, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance6, new AttributeInfo(AttributeConsts.ClipDistance0, 6, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.ClipDistance7, new AttributeInfo(AttributeConsts.ClipDistance0, 7, 8, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.PointCoordX, new AttributeInfo(AttributeConsts.PointCoordX, 0, 2, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.PointCoordY, new AttributeInfo(AttributeConsts.PointCoordX, 1, 2, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessCoordX, new AttributeInfo(AttributeConsts.TessCoordX, 0, 3, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessCoordY, new AttributeInfo(AttributeConsts.TessCoordX, 1, 3, AggregateType.Vector4 | AggregateType.FP32) },
|
||||
{ AttributeConsts.InstanceId, new AttributeInfo(AttributeConsts.InstanceId, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.VertexId, new AttributeInfo(AttributeConsts.VertexId, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.BaseInstance, new AttributeInfo(AttributeConsts.BaseInstance, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.BaseVertex, new AttributeInfo(AttributeConsts.BaseVertex, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.InstanceIndex, new AttributeInfo(AttributeConsts.InstanceIndex, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.VertexIndex, new AttributeInfo(AttributeConsts.VertexIndex, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.DrawIndex, new AttributeInfo(AttributeConsts.DrawIndex, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.FrontFacing, new AttributeInfo(AttributeConsts.FrontFacing, 0, 1, AggregateType.Bool) },
|
||||
|
||||
// Special.
|
||||
{ AttributeConsts.FragmentOutputDepth, new AttributeInfo(AttributeConsts.FragmentOutputDepth, 0, 1, AggregateType.FP32) },
|
||||
{ AttributeConsts.ThreadKill, new AttributeInfo(AttributeConsts.ThreadKill, 0, 1, AggregateType.Bool) },
|
||||
{ AttributeConsts.ThreadIdX, new AttributeInfo(AttributeConsts.ThreadIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.ThreadIdY, new AttributeInfo(AttributeConsts.ThreadIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.ThreadIdZ, new AttributeInfo(AttributeConsts.ThreadIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdX, new AttributeInfo(AttributeConsts.CtaIdX, 0, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdY, new AttributeInfo(AttributeConsts.CtaIdX, 1, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.CtaIdZ, new AttributeInfo(AttributeConsts.CtaIdX, 2, 3, AggregateType.Vector3 | AggregateType.U32) },
|
||||
{ AttributeConsts.LaneId, new AttributeInfo(AttributeConsts.LaneId, 0, 1, AggregateType.U32) },
|
||||
{ AttributeConsts.InvocationId, new AttributeInfo(AttributeConsts.InvocationId, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.PrimitiveId, new AttributeInfo(AttributeConsts.PrimitiveId, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.PatchVerticesIn, new AttributeInfo(AttributeConsts.PatchVerticesIn, 0, 1, AggregateType.S32) },
|
||||
{ AttributeConsts.EqMask, new AttributeInfo(AttributeConsts.EqMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
|
||||
{ AttributeConsts.GeMask, new AttributeInfo(AttributeConsts.GeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
|
||||
{ AttributeConsts.GtMask, new AttributeInfo(AttributeConsts.GtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
|
||||
{ AttributeConsts.LeMask, new AttributeInfo(AttributeConsts.LeMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
|
||||
{ AttributeConsts.LtMask, new AttributeInfo(AttributeConsts.LtMask, 0, 4, AggregateType.Vector4 | AggregateType.U32) },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<int, AttributeInfo> _builtInAttributesPerPatch = new Dictionary<int, AttributeInfo>()
|
||||
{
|
||||
{ AttributeConsts.TessLevelOuter0, new AttributeInfo(AttributeConsts.TessLevelOuter0, 0, 4, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessLevelOuter1, new AttributeInfo(AttributeConsts.TessLevelOuter0, 1, 4, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessLevelOuter2, new AttributeInfo(AttributeConsts.TessLevelOuter0, 2, 4, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessLevelOuter3, new AttributeInfo(AttributeConsts.TessLevelOuter0, 3, 4, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessLevelInner0, new AttributeInfo(AttributeConsts.TessLevelInner0, 0, 2, AggregateType.Array | AggregateType.FP32) },
|
||||
{ AttributeConsts.TessLevelInner1, new AttributeInfo(AttributeConsts.TessLevelInner0, 1, 2, AggregateType.Array | AggregateType.FP32) },
|
||||
};
|
||||
|
||||
public int BaseValue { get; }
|
||||
public int Value { get; }
|
||||
public int Length { get; }
|
||||
public AggregateType Type { get; }
|
||||
public bool IsBuiltin { get; }
|
||||
public bool IsValid => Type != AggregateType.Invalid;
|
||||
|
||||
public AttributeInfo(int baseValue, int index, int length, AggregateType type, bool isBuiltin = true)
|
||||
{
|
||||
BaseValue = baseValue;
|
||||
Value = baseValue + index * 4;
|
||||
Length = length;
|
||||
Type = type;
|
||||
IsBuiltin = isBuiltin;
|
||||
}
|
||||
|
||||
public int GetInnermostIndex()
|
||||
{
|
||||
return (Value - BaseValue) / 4;
|
||||
}
|
||||
|
||||
public static bool Validate(ShaderConfig config, int value, bool isOutAttr, bool perPatch)
|
||||
{
|
||||
return perPatch ? ValidatePerPatch(config, value, isOutAttr) : Validate(config, value, isOutAttr);
|
||||
}
|
||||
|
||||
public static bool Validate(ShaderConfig config, int value, bool isOutAttr)
|
||||
{
|
||||
if (value == AttributeConsts.ViewportIndex && !config.GpuAccessor.QueryHostSupportsViewportIndex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return From(config, value, isOutAttr).IsValid;
|
||||
}
|
||||
|
||||
public static bool ValidatePerPatch(ShaderConfig config, int value, bool isOutAttr)
|
||||
{
|
||||
return FromPatch(config, value, isOutAttr).IsValid;
|
||||
}
|
||||
|
||||
public static AttributeInfo From(ShaderConfig config, int value, bool isOutAttr)
|
||||
{
|
||||
value &= ~3;
|
||||
|
||||
if (value >= AttributeConsts.UserAttributeBase && value < AttributeConsts.UserAttributeEnd)
|
||||
{
|
||||
int location = (value - AttributeConsts.UserAttributeBase) / 16;
|
||||
|
||||
AggregateType elemType;
|
||||
|
||||
if (config.Stage == ShaderStage.Vertex && !isOutAttr)
|
||||
{
|
||||
elemType = config.GpuAccessor.QueryAttributeType(location).ToAggregateType();
|
||||
}
|
||||
else
|
||||
{
|
||||
elemType = AggregateType.FP32;
|
||||
}
|
||||
|
||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | elemType, false);
|
||||
}
|
||||
else if (value >= AttributeConsts.FragmentOutputColorBase && value < AttributeConsts.FragmentOutputColorEnd)
|
||||
{
|
||||
return new AttributeInfo(value & ~0xf, (value >> 2) & 3, 4, AggregateType.Vector4 | AggregateType.FP32, false);
|
||||
}
|
||||
else if (value == AttributeConsts.SupportBlockViewInverseX || value == AttributeConsts.SupportBlockViewInverseY)
|
||||
{
|
||||
return new AttributeInfo(value, 0, 1, AggregateType.FP32);
|
||||
}
|
||||
else if (_builtInAttributes.TryGetValue(value, out AttributeInfo info))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
|
||||
}
|
||||
|
||||
public static AttributeInfo FromPatch(ShaderConfig config, int value, bool isOutAttr)
|
||||
{
|
||||
value &= ~3;
|
||||
|
||||
if (value >= AttributeConsts.UserAttributePerPatchBase && value < AttributeConsts.UserAttributePerPatchEnd)
|
||||
{
|
||||
int offset = (value - AttributeConsts.UserAttributePerPatchBase) & 0xf;
|
||||
return new AttributeInfo(value - offset, offset >> 2, 4, AggregateType.Vector4 | AggregateType.FP32, false);
|
||||
}
|
||||
else if (_builtInAttributesPerPatch.TryGetValue(value, out AttributeInfo info))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
return new AttributeInfo(value, 0, 0, AggregateType.Invalid);
|
||||
}
|
||||
|
||||
public static bool IsArrayBuiltIn(int attr)
|
||||
{
|
||||
if (attr <= AttributeConsts.TessLevelInner1 ||
|
||||
attr == AttributeConsts.TessCoordX ||
|
||||
attr == AttributeConsts.TessCoordY)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (attr & AttributeConsts.SpecialMask) == 0;
|
||||
}
|
||||
|
||||
public static bool IsArrayAttributeGlsl(ShaderStage stage, bool isOutAttr)
|
||||
{
|
||||
if (isOutAttr)
|
||||
{
|
||||
return stage == ShaderStage.TessellationControl;
|
||||
}
|
||||
else
|
||||
{
|
||||
return stage == ShaderStage.TessellationControl ||
|
||||
stage == ShaderStage.TessellationEvaluation ||
|
||||
stage == ShaderStage.Geometry;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsArrayAttributeSpirv(ShaderStage stage, bool isOutAttr)
|
||||
{
|
||||
if (isOutAttr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return stage == ShaderStage.TessellationControl ||
|
||||
stage == ShaderStage.TessellationEvaluation ||
|
||||
stage == ShaderStage.Geometry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
static class BitfieldExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Extract(this int value, int lsb)
|
||||
{
|
||||
return ((value >> (lsb & 0x1f)) & 1) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Extract(this int value, int lsb, int length)
|
||||
{
|
||||
return (value >> (lsb & 0x1f)) & (int)(uint.MaxValue >> (32 - length));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Extract(this long value, int lsb)
|
||||
{
|
||||
return ((int)(value >> (lsb & 0x3f)) & 1) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int Extract(this long value, int lsb, int length)
|
||||
{
|
||||
return (int)(value >> (lsb & 0x3f)) & (int)(uint.MaxValue >> (32 - length));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ExtractSx(this long value, int lsb, int length)
|
||||
{
|
||||
int shift = lsb & 0x3f;
|
||||
|
||||
return (int)((value << (64 - (shift + length))) >> (64 - length));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct BlendingSlotStruct
|
||||
{
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
|
||||
public int AlphaK1 => _word0.Extract(0, 10);
|
||||
public int AlphaK2 => _word0.Extract(16, 10);
|
||||
public int SrcFactCMatchSelect => _word0.Extract(32, 3);
|
||||
public int DstFactCMatchSelect => _word0.Extract(36, 3);
|
||||
public int SrcFactAMatchSelect => _word0.Extract(40, 3);
|
||||
public int DstFactAMatchSelect => _word0.Extract(44, 3);
|
||||
public int OverrideR => _word1.Extract(66, 10);
|
||||
public int OverrideG => _word1.Extract(76, 10);
|
||||
public int OverrideB => _word1.Extract(86, 10);
|
||||
public int OverrideA => _word1.Extract(96, 10);
|
||||
public bool UseOverrideR => _word1.Extract(108);
|
||||
public bool UseOverrideG => _word1.Extract(109);
|
||||
public bool UseOverrideB => _word1.Extract(110);
|
||||
public bool UseOverrideA => _word1.Extract(111);
|
||||
public bool MaskR => _word1.Extract(112);
|
||||
public bool MaskG => _word1.Extract(113);
|
||||
public bool MaskB => _word1.Extract(114);
|
||||
public bool MaskA => _word1.Extract(115);
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct ClearRectStruct
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public int ClearRect0Left => _word0.Extract(0, 14);
|
||||
public int ClearRect0Right => _word0.Extract(16, 14);
|
||||
public int ClearRect0Top => _word0.Extract(32, 14);
|
||||
public int ClearRect0Bottom => _word0.Extract(48, 14);
|
||||
public int ClearRect1Left => _word1.Extract(64, 14);
|
||||
public int ClearRect1Right => _word1.Extract(80, 14);
|
||||
public int ClearRect1Top => _word1.Extract(96, 14);
|
||||
public int ClearRect1Bottom => _word1.Extract(112, 14);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct LumaKeyStruct
|
||||
{
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
|
||||
public int LumaCoeff0 => _word0.Extract(0, 20);
|
||||
public int LumaCoeff1 => _word0.Extract(20, 20);
|
||||
public int LumaCoeff2 => _word0.Extract(40, 20);
|
||||
public int LumaRShift => _word0.Extract(60, 4);
|
||||
public int LumaCoeff3 => _word1.Extract(64, 20);
|
||||
public int LumaKeyLower => _word1.Extract(84, 10);
|
||||
public int LumaKeyUpper => _word1.Extract(94, 10);
|
||||
public bool LumaKeyEnabled => _word1.Extract(104);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct MatrixStruct
|
||||
{
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
private long _word2;
|
||||
private long _word3;
|
||||
|
||||
public int MatrixCoeff00 => _word0.ExtractSx(0, 20);
|
||||
public int MatrixCoeff10 => _word0.ExtractSx(20, 20);
|
||||
public int MatrixCoeff20 => _word0.ExtractSx(40, 20);
|
||||
public int MatrixRShift => _word0.Extract(60, 4);
|
||||
public int MatrixCoeff01 => _word1.ExtractSx(64, 20);
|
||||
public int MatrixCoeff11 => _word1.ExtractSx(84, 20);
|
||||
public int MatrixCoeff21 => _word1.ExtractSx(104, 20);
|
||||
public bool MatrixEnable => _word1.Extract(127);
|
||||
public int MatrixCoeff02 => _word2.ExtractSx(128, 20);
|
||||
public int MatrixCoeff12 => _word2.ExtractSx(148, 20);
|
||||
public int MatrixCoeff22 => _word2.ExtractSx(168, 20);
|
||||
public int MatrixCoeff03 => _word3.ExtractSx(192, 20);
|
||||
public int MatrixCoeff13 => _word3.ExtractSx(212, 20);
|
||||
public int MatrixCoeff23 => _word3.ExtractSx(232, 20);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct OutputConfig
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public int AlphaFillMode => _word0.Extract(0, 3);
|
||||
public int AlphaFillSlot => _word0.Extract(3, 3);
|
||||
public int BackgroundAlpha => _word0.Extract(6, 10);
|
||||
public int BackgroundR => _word0.Extract(16, 10);
|
||||
public int BackgroundG => _word0.Extract(26, 10);
|
||||
public int BackgroundB => _word0.Extract(36, 10);
|
||||
public int RegammaMode => _word0.Extract(46, 2);
|
||||
public bool OutputFlipX => _word0.Extract(48);
|
||||
public bool OutputFlipY => _word0.Extract(49);
|
||||
public bool OutputTranspose => _word0.Extract(50);
|
||||
public int TargetRectLeft => _word1.Extract(64, 14);
|
||||
public int TargetRectRight => _word1.Extract(80, 14);
|
||||
public int TargetRectTop => _word1.Extract(96, 14);
|
||||
public int TargetRectBottom => _word1.Extract(112, 14);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct OutputSurfaceConfig
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public PixelFormat OutPixelFormat => (PixelFormat)_word0.Extract(0, 7);
|
||||
public int OutChromaLocHoriz => _word0.Extract(7, 2);
|
||||
public int OutChromaLocVert => _word0.Extract(9, 2);
|
||||
public int OutBlkKind => _word0.Extract(11, 4);
|
||||
public int OutBlkHeight => _word0.Extract(15, 4);
|
||||
public int OutSurfaceWidth => _word0.Extract(32, 14);
|
||||
public int OutSurfaceHeight => _word0.Extract(46, 14);
|
||||
public int OutLumaWidth => _word1.Extract(64, 14);
|
||||
public int OutLumaHeight => _word1.Extract(78, 14);
|
||||
public int OutChromaWidth => _word1.Extract(96, 14);
|
||||
public int OutChromaHeight => _word1.Extract(110, 14);
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct PipeConfig
|
||||
{
|
||||
#pragma warning disable CS0169, CS0649
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
#pragma warning restore CS0169, CS0649
|
||||
|
||||
public int DownsampleHoriz => _word0.Extract(0, 11);
|
||||
public int DownsampleVert => _word0.Extract(16, 11);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct SlotConfig
|
||||
{
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
private long _word2;
|
||||
private long _word3;
|
||||
private long _word4;
|
||||
private long _word5;
|
||||
private long _word6;
|
||||
private long _word7;
|
||||
|
||||
public bool SlotEnable => _word0.Extract(0);
|
||||
public bool DeNoise => _word0.Extract(1);
|
||||
public bool AdvancedDenoise => _word0.Extract(2);
|
||||
public bool CadenceDetect => _word0.Extract(3);
|
||||
public bool MotionMap => _word0.Extract(4);
|
||||
public bool MMapCombine => _word0.Extract(5);
|
||||
public bool IsEven => _word0.Extract(6);
|
||||
public bool ChromaEven => _word0.Extract(7);
|
||||
public bool CurrentFieldEnable => _word0.Extract(8);
|
||||
public bool PrevFieldEnable => _word0.Extract(9);
|
||||
public bool NextFieldEnable => _word0.Extract(10);
|
||||
public bool NextNrFieldEnable => _word0.Extract(11);
|
||||
public bool CurMotionFieldEnable => _word0.Extract(12);
|
||||
public bool PrevMotionFieldEnable => _word0.Extract(13);
|
||||
public bool PpMotionFieldEnable => _word0.Extract(14);
|
||||
public bool CombMotionFieldEnable => _word0.Extract(15);
|
||||
public FrameFormat FrameFormat => (FrameFormat)_word0.Extract(16, 4);
|
||||
public int FilterLengthY => _word0.Extract(20, 2);
|
||||
public int FilterLengthX => _word0.Extract(22, 2);
|
||||
public int Panoramic => _word0.Extract(24, 12);
|
||||
public int DetailFltClamp => _word0.Extract(58, 6);
|
||||
public int FilterNoise => _word1.Extract(64, 10);
|
||||
public int FilterDetail => _word1.Extract(74, 10);
|
||||
public int ChromaNoise => _word1.Extract(84, 10);
|
||||
public int ChromaDetail => _word1.Extract(94, 10);
|
||||
public DeinterlaceMode DeinterlaceMode => (DeinterlaceMode)_word1.Extract(104, 4);
|
||||
public int MotionAccumWeight => _word1.Extract(108, 3);
|
||||
public int NoiseIir => _word1.Extract(111, 11);
|
||||
public int LightLevel => _word1.Extract(122, 4);
|
||||
public int SoftClampLow => _word2.Extract(128, 10);
|
||||
public int SoftClampHigh => _word2.Extract(138, 10);
|
||||
public int PlanarAlpha => _word2.Extract(160, 10);
|
||||
public bool ConstantAlpha => _word2.Extract(170);
|
||||
public int StereoInterleave => _word2.Extract(171, 3);
|
||||
public bool ClipEnabled => _word2.Extract(174);
|
||||
public int ClearRectMask => _word2.Extract(175, 8);
|
||||
public int DegammaMode => _word2.Extract(183, 2);
|
||||
public bool DecompressEnable => _word2.Extract(186);
|
||||
public int DecompressCtbCount => _word3.Extract(192, 8);
|
||||
public int DecompressZbcColor => _word3.Extract(200, 32);
|
||||
public int SourceRectLeft => _word4.Extract(256, 30);
|
||||
public int SourceRectRight => _word4.Extract(288, 30);
|
||||
public int SourceRectTop => _word5.Extract(320, 30);
|
||||
public int SourceRectBottom => _word5.Extract(352, 30);
|
||||
public int DstRectLeft => _word6.Extract(384, 14);
|
||||
public int DstRectRight => _word6.Extract(400, 14);
|
||||
public int DstRectTop => _word6.Extract(416, 14);
|
||||
public int DstRectBottom => _word6.Extract(432, 14);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
struct SlotSurfaceConfig
|
||||
{
|
||||
private long _word0;
|
||||
private long _word1;
|
||||
|
||||
public PixelFormat SlotPixelFormat => (PixelFormat)_word0.Extract(0, 7);
|
||||
public int SlotChromaLocHoriz => _word0.Extract(7, 2);
|
||||
public int SlotChromaLocVert => _word0.Extract(9, 2);
|
||||
public int SlotBlkKind => _word0.Extract(11, 4);
|
||||
public int SlotBlkHeight => _word0.Extract(15, 4);
|
||||
public int SlotCacheWidth => _word0.Extract(19, 3);
|
||||
public int SlotSurfaceWidth => _word0.Extract(32, 14);
|
||||
public int SlotSurfaceHeight => _word0.Extract(46, 14);
|
||||
public int SlotLumaWidth => _word1.Extract(64, 14);
|
||||
public int SlotLumaHeight => _word1.Extract(78, 14);
|
||||
public int SlotChromaWidth => _word1.Extract(96, 14);
|
||||
public int SlotChromaHeight => _word1.Extract(110, 14);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class AutoFlushCounter
|
||||
{
|
||||
// How often to flush on framebuffer change.
|
||||
private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000;
|
||||
|
||||
private const int MinDrawCountForFlush = 10;
|
||||
private const int InitialQueryCountForFlush = 32;
|
||||
|
||||
private long _lastFlush;
|
||||
private ulong _lastDrawCount;
|
||||
private bool _hasPendingQuery;
|
||||
private int _queryCount;
|
||||
|
||||
public void RegisterFlush(ulong drawCount)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
_lastDrawCount = drawCount;
|
||||
|
||||
_hasPendingQuery = false;
|
||||
}
|
||||
|
||||
public bool RegisterPendingQuery()
|
||||
{
|
||||
_hasPendingQuery = true;
|
||||
|
||||
// Interrupt render passes to flush queries, so that early results arrive sooner.
|
||||
if (++_queryCount == InitialQueryCountForFlush)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ShouldFlushQuery()
|
||||
{
|
||||
return _hasPendingQuery;
|
||||
}
|
||||
|
||||
public bool ShouldFlush(ulong drawCount)
|
||||
{
|
||||
_queryCount = 0;
|
||||
|
||||
if (_hasPendingQuery)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
long draws = (long)(drawCount - _lastDrawCount);
|
||||
|
||||
if (draws < MinDrawCountForFlush)
|
||||
{
|
||||
if (draws == 0)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
long flushTimeout = FramebufferFlushTimer;
|
||||
|
||||
long now = Stopwatch.GetTimestamp();
|
||||
|
||||
return now > _lastFlush + flushTimeout;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,968 +0,0 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Ryujinx.Graphics.Vulkan.Shaders;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class HelperShader : IDisposable
|
||||
{
|
||||
private const int UniformBufferAlignment = 256;
|
||||
|
||||
private readonly PipelineHelperShader _pipeline;
|
||||
private readonly ISampler _samplerLinear;
|
||||
private readonly ISampler _samplerNearest;
|
||||
private readonly IProgram _programColorBlit;
|
||||
private readonly IProgram _programColorBlitClearAlpha;
|
||||
private readonly IProgram _programColorClear;
|
||||
private readonly IProgram _programStrideChange;
|
||||
private readonly IProgram _programConvertIndexBuffer;
|
||||
private readonly IProgram _programConvertIndirectData;
|
||||
private readonly IProgram _programColorCopyToNonMs;
|
||||
private readonly IProgram _programColorDrawToMs;
|
||||
|
||||
public HelperShader(VulkanRenderer gd, Device device)
|
||||
{
|
||||
_pipeline = new PipelineHelperShader(gd, device);
|
||||
_pipeline.Initialize();
|
||||
|
||||
_samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
_samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
|
||||
|
||||
var colorBlitVertexBindings = new ShaderBindings(
|
||||
new[] { 1 },
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
var colorBlitFragmentBindings = new ShaderBindings(
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
new[] { 0 },
|
||||
Array.Empty<int>());
|
||||
|
||||
_programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
_programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, colorBlitFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var colorClearFragmentBindings = new ShaderBindings(
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
_programColorClear = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, colorBlitVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, colorClearFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var strideChangeBindings = new ShaderBindings(
|
||||
new[] { 0 },
|
||||
new[] { 1, 2 },
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, strideChangeBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var colorCopyToNonMsBindings = new ShaderBindings(
|
||||
new[] { 0 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 0 },
|
||||
new[] { 0 });
|
||||
|
||||
_programColorCopyToNonMs = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorCopyToNonMsComputeShaderSource, colorCopyToNonMsBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var colorDrawToMsVertexBindings = new ShaderBindings(
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
var colorDrawToMsFragmentBindings = new ShaderBindings(
|
||||
new[] { 0 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 0 },
|
||||
Array.Empty<int>());
|
||||
|
||||
_programColorDrawToMs = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ColorDrawToMsVertexShaderSource, colorDrawToMsVertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv),
|
||||
new ShaderSource(ShaderBinaries.ColorDrawToMsFragmentShaderSource, colorDrawToMsFragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var convertIndexBufferBindings = new ShaderBindings(
|
||||
new[] { 0 },
|
||||
new[] { 1, 2 },
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ConvertIndexBufferShaderSource, convertIndexBufferBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
});
|
||||
|
||||
var convertIndirectDataBindings = new ShaderBindings(
|
||||
new[] { 0 },
|
||||
new[] { 1, 2, 3 },
|
||||
Array.Empty<int>(),
|
||||
Array.Empty<int>());
|
||||
|
||||
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(ShaderBinaries.ConvertIndirectDataShaderSource, convertIndirectDataBindings, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
});
|
||||
}
|
||||
|
||||
public void Blit(
|
||||
VulkanRenderer gd,
|
||||
TextureView src,
|
||||
Auto<DisposableImageView> dst,
|
||||
int dstWidth,
|
||||
int dstHeight,
|
||||
VkFormat dstFormat,
|
||||
Extents2D srcRegion,
|
||||
Extents2D dstRegion,
|
||||
bool linearFilter,
|
||||
bool clearAlpha = false)
|
||||
{
|
||||
gd.FlushAllCommands();
|
||||
|
||||
using var cbs = gd.CommandBufferPool.Rent();
|
||||
|
||||
Blit(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion, linearFilter, clearAlpha);
|
||||
}
|
||||
|
||||
public void Blit(
|
||||
VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
TextureView src,
|
||||
Auto<DisposableImageView> dst,
|
||||
int dstWidth,
|
||||
int dstHeight,
|
||||
VkFormat dstFormat,
|
||||
Extents2D srcRegion,
|
||||
Extents2D dstRegion,
|
||||
bool linearFilter,
|
||||
bool clearAlpha = false)
|
||||
{
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
const int RegionBufferSize = 16;
|
||||
|
||||
var sampler = linearFilter ? _samplerLinear : _samplerNearest;
|
||||
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
|
||||
|
||||
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
|
||||
|
||||
region[0] = (float)srcRegion.X1 / src.Width;
|
||||
region[1] = (float)srcRegion.X2 / src.Width;
|
||||
region[2] = (float)srcRegion.Y1 / src.Height;
|
||||
region[3] = (float)srcRegion.Y2 / src.Height;
|
||||
|
||||
if (dstRegion.X1 > dstRegion.X2)
|
||||
{
|
||||
(region[0], region[1]) = (region[1], region[0]);
|
||||
}
|
||||
|
||||
if (dstRegion.Y1 > dstRegion.Y2)
|
||||
{
|
||||
(region[2], region[3]) = (region[3], region[2]);
|
||||
}
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
|
||||
var rect = new Rectangle<float>(
|
||||
MathF.Min(dstRegion.X1, dstRegion.X2),
|
||||
MathF.Min(dstRegion.Y1, dstRegion.Y2),
|
||||
MathF.Abs(dstRegion.X2 - dstRegion.X1),
|
||||
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
rect,
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
|
||||
|
||||
_pipeline.SetProgram(clearAlpha ? _programColorBlitClearAlpha : _programColorBlit);
|
||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
||||
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
||||
_pipeline.SetScissors(scissors);
|
||||
|
||||
if (clearAlpha)
|
||||
{
|
||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||
}
|
||||
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
||||
_pipeline.Draw(4, 1, 0, 0);
|
||||
_pipeline.Finish(gd, cbs);
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
}
|
||||
|
||||
public void Clear(
|
||||
VulkanRenderer gd,
|
||||
Auto<DisposableImageView> dst,
|
||||
ReadOnlySpan<float> clearColor,
|
||||
uint componentMask,
|
||||
int dstWidth,
|
||||
int dstHeight,
|
||||
VkFormat dstFormat,
|
||||
Rectangle<int> scissor)
|
||||
{
|
||||
const int ClearColorBufferSize = 16;
|
||||
|
||||
gd.FlushAllCommands();
|
||||
|
||||
using var cbs = gd.CommandBufferPool.Rent();
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
new Rectangle<float>(0, 0, dstWidth, dstHeight),
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
scissors[0] = scissor;
|
||||
|
||||
_pipeline.SetProgram(_programColorClear);
|
||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
||||
_pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
||||
_pipeline.Draw(4, 1, 0, 0);
|
||||
_pipeline.Finish();
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
}
|
||||
|
||||
public void DrawTexture(
|
||||
VulkanRenderer gd,
|
||||
PipelineBase pipeline,
|
||||
TextureView src,
|
||||
ISampler srcSampler,
|
||||
Extents2DF srcRegion,
|
||||
Extents2DF dstRegion)
|
||||
{
|
||||
const int RegionBufferSize = 16;
|
||||
|
||||
pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
|
||||
|
||||
Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
|
||||
|
||||
region[0] = srcRegion.X1 / src.Width;
|
||||
region[1] = srcRegion.X2 / src.Width;
|
||||
region[2] = srcRegion.Y1 / src.Height;
|
||||
region[3] = srcRegion.Y2 / src.Height;
|
||||
|
||||
if (dstRegion.X1 > dstRegion.X2)
|
||||
{
|
||||
(region[0], region[1]) = (region[1], region[0]);
|
||||
}
|
||||
|
||||
if (dstRegion.Y1 > dstRegion.Y2)
|
||||
{
|
||||
(region[2], region[3]) = (region[3], region[2]);
|
||||
}
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||
|
||||
pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
|
||||
var rect = new Rectangle<float>(
|
||||
MathF.Min(dstRegion.X1, dstRegion.X2),
|
||||
MathF.Min(dstRegion.Y1, dstRegion.Y2),
|
||||
MathF.Abs(dstRegion.X2 - dstRegion.X1),
|
||||
MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
rect,
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
pipeline.SetProgram(_programColorBlit);
|
||||
pipeline.SetViewports(viewports, false);
|
||||
pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
||||
pipeline.Draw(4, 1, 0, 0);
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
}
|
||||
|
||||
public unsafe void ConvertI8ToI16(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
|
||||
{
|
||||
ChangeStride(gd, cbs, src, dst, srcOffset, size, 1, 2);
|
||||
}
|
||||
|
||||
public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size, int stride, int newStride)
|
||||
{
|
||||
bool supportsUint8 = gd.Capabilities.SupportsShaderInt8;
|
||||
|
||||
int elems = size / stride;
|
||||
int newSize = elems * newStride;
|
||||
|
||||
var srcBufferAuto = src.GetBuffer();
|
||||
var dstBufferAuto = dst.GetBuffer();
|
||||
|
||||
var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value;
|
||||
var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value;
|
||||
|
||||
var access = supportsUint8 ? AccessFlags.ShaderWriteBit : AccessFlags.TransferWriteBit;
|
||||
var stage = supportsUint8 ? PipelineStageFlags.ComputeShaderBit : PipelineStageFlags.TransferBit;
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
access,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
stage,
|
||||
0,
|
||||
newSize);
|
||||
|
||||
if (supportsUint8)
|
||||
{
|
||||
const int ParamsBufferSize = 16;
|
||||
|
||||
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||
|
||||
shaderParams[0] = stride;
|
||||
shaderParams[1] = newStride;
|
||||
shaderParams[2] = size;
|
||||
shaderParams[3] = srcOffset;
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
||||
|
||||
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
|
||||
|
||||
sbRanges[0] = srcBufferAuto;
|
||||
sbRanges[1] = dstBufferAuto;
|
||||
|
||||
_pipeline.SetStorageBuffers(1, sbRanges);
|
||||
|
||||
_pipeline.SetProgram(_programStrideChange);
|
||||
_pipeline.DispatchCompute(1, 1, 1);
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
|
||||
_pipeline.Finish(gd, cbs);
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
||||
|
||||
var bufferCopy = new BufferCopy[elems];
|
||||
|
||||
for (ulong i = 0; i < (ulong)elems; i++)
|
||||
{
|
||||
bufferCopy[i] = new BufferCopy((ulong)srcOffset + i * (ulong)stride, i * (ulong)newStride, (ulong)stride);
|
||||
}
|
||||
|
||||
fixed (BufferCopy* pBufferCopy = bufferCopy)
|
||||
{
|
||||
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)elems, pBufferCopy);
|
||||
}
|
||||
}
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
access,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
stage,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
newSize);
|
||||
}
|
||||
|
||||
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
BufferHolder src,
|
||||
BufferHolder dst,
|
||||
IndexBufferPattern pattern,
|
||||
int indexSize,
|
||||
int srcOffset,
|
||||
int indexCount)
|
||||
{
|
||||
// TODO: Support conversion with primitive restart enabled.
|
||||
// TODO: Convert with a compute shader?
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
int outputIndexSize = 4;
|
||||
|
||||
var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
|
||||
var dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
||||
|
||||
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
||||
|
||||
var bufferCopy = new List<BufferCopy>();
|
||||
int outputOffset = 0;
|
||||
|
||||
// Try to merge copies of adjacent indices to reduce copy count.
|
||||
int sequenceStart = 0;
|
||||
int sequenceLength = 0;
|
||||
|
||||
foreach (var index in pattern.GetIndexMapping(indexCount))
|
||||
{
|
||||
if (sequenceLength > 0)
|
||||
{
|
||||
if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
|
||||
{
|
||||
sequenceLength++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Commit the copy so far.
|
||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
||||
outputOffset += outputIndexSize * sequenceLength;
|
||||
}
|
||||
|
||||
sequenceStart = index;
|
||||
sequenceLength = 1;
|
||||
}
|
||||
|
||||
if (sequenceLength > 0)
|
||||
{
|
||||
// Commit final pending copy.
|
||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
||||
}
|
||||
|
||||
var bufferCopyArray = bufferCopy.ToArray();
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
fixed (BufferCopy* pBufferCopy = bufferCopyArray)
|
||||
{
|
||||
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
|
||||
}
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
}
|
||||
|
||||
public void CopyMSToNonMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth)
|
||||
{
|
||||
const int ParamsBufferSize = 16;
|
||||
|
||||
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||
|
||||
int samples = src.Info.Samples;
|
||||
|
||||
// X and Y are the expected texture samples.
|
||||
// Z and W are the actual texture samples used.
|
||||
// They may differ if the GPU does not support the samples count requested and we had to use a lower amount.
|
||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
src.GetImage().Get(cbs).Value,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.ShaderReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
src.FirstLayer + srcLayer,
|
||||
src.FirstLevel,
|
||||
depth,
|
||||
1);
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
_pipeline.SetProgram(_programColorCopyToNonMs);
|
||||
|
||||
var format = GetFormat(src.Info.BytesPerPixel);
|
||||
|
||||
int dispatchX = (dst.Info.Width + 31) / 32;
|
||||
int dispatchY = (dst.Info.Height + 31) / 32;
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
||||
|
||||
if (src.Info.Target == Target.Texture2DMultisampleArray ||
|
||||
dst.Info.Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
var srcView = Create2DLayerView(src, srcLayer + z, format);
|
||||
var dstView = Create2DLayerView(dst, dstLayer + z);
|
||||
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
|
||||
_pipeline.SetImage(0, dstView, format);
|
||||
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
srcView.Release();
|
||||
dstView.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var srcView = Create2DLayerView(src, srcLayer, format);
|
||||
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 0, srcView, null);
|
||||
_pipeline.SetImage(0, dst, format);
|
||||
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
srcView.Release();
|
||||
}
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
|
||||
_pipeline.Finish(gd, cbs);
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
dst.GetImage().Get(cbs).Value,
|
||||
AccessFlags.ShaderWriteBit,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
dst.FirstLayer + dstLayer,
|
||||
dst.FirstLevel,
|
||||
depth,
|
||||
1);
|
||||
}
|
||||
|
||||
public void CopyNonMSToMS(VulkanRenderer gd, CommandBufferScoped cbs, TextureView src, TextureView dst, int srcLayer, int dstLayer, int depth)
|
||||
{
|
||||
const int ParamsBufferSize = 16;
|
||||
|
||||
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||
|
||||
int samples = dst.Info.Samples;
|
||||
|
||||
// X and Y are the expected texture samples.
|
||||
// Z and W are the actual texture samples used.
|
||||
// They may differ if the GPU does not support the samples count requested and we had to use a lower amount.
|
||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||
|
||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
||||
|
||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
src.GetImage().Get(cbs).Value,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
AccessFlags.ShaderReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
src.FirstLayer + srcLayer,
|
||||
src.FirstLevel,
|
||||
depth,
|
||||
1);
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
_pipeline.SetProgram(_programColorDrawToMs);
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
|
||||
var rect = new Rectangle<float>(0, 0, dst.Width, dst.Height);
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
rect,
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, dst.Width, dst.Height);
|
||||
|
||||
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
|
||||
|
||||
var format = GetFormat(src.Info.BytesPerPixel);
|
||||
var vkFormat = FormatTable.GetFormat(format);
|
||||
|
||||
if (src.Info.Target == Target.Texture2DMultisampleArray ||
|
||||
dst.Info.Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
var srcView = Create2DLayerView(src, srcLayer + z, format);
|
||||
var dstView = Create2DLayerView(dst, dstLayer + z);
|
||||
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, srcView, null);
|
||||
_pipeline.SetRenderTarget(
|
||||
((TextureView)dstView).GetView(format).GetImageViewForAttachment(),
|
||||
(uint)dst.Width,
|
||||
(uint)dst.Height,
|
||||
(uint)samples,
|
||||
false,
|
||||
vkFormat);
|
||||
|
||||
_pipeline.Draw(4, 1, 0, 0);
|
||||
|
||||
srcView.Release();
|
||||
dstView.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var srcView = Create2DLayerView(src, srcLayer, format);
|
||||
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, srcView, null);
|
||||
_pipeline.SetRenderTarget(
|
||||
dst.GetView(format).GetImageViewForAttachment(),
|
||||
(uint)dst.Width,
|
||||
(uint)dst.Height,
|
||||
(uint)samples,
|
||||
false,
|
||||
vkFormat);
|
||||
|
||||
_pipeline.Draw(4, 1, 0, 0);
|
||||
|
||||
srcView.Release();
|
||||
}
|
||||
|
||||
gd.BufferManager.Delete(bufferHandle);
|
||||
|
||||
_pipeline.Finish(gd, cbs);
|
||||
|
||||
TextureView.InsertImageBarrier(
|
||||
gd.Api,
|
||||
cbs.CommandBuffer,
|
||||
dst.GetImage().Get(cbs).Value,
|
||||
AccessFlags.ColorAttachmentWriteBit,
|
||||
TextureStorage.DefaultAccessMask,
|
||||
PipelineStageFlags.FragmentShaderBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
ImageAspectFlags.ColorBit,
|
||||
dst.FirstLayer + dstLayer,
|
||||
dst.FirstLevel,
|
||||
depth,
|
||||
1);
|
||||
}
|
||||
|
||||
private static (int, int) GetSampleCountXYLog2(int samples)
|
||||
{
|
||||
int samplesInXLog2 = 0;
|
||||
int samplesInYLog2 = 0;
|
||||
|
||||
switch (samples)
|
||||
{
|
||||
case 2: // 2x1
|
||||
samplesInXLog2 = 1;
|
||||
break;
|
||||
case 4: // 2x2
|
||||
samplesInXLog2 = 1;
|
||||
samplesInYLog2 = 1;
|
||||
break;
|
||||
case 8: // 4x2
|
||||
samplesInXLog2 = 2;
|
||||
samplesInYLog2 = 1;
|
||||
break;
|
||||
case 16: // 4x4
|
||||
samplesInXLog2 = 2;
|
||||
samplesInYLog2 = 2;
|
||||
break;
|
||||
case 32: // 8x4
|
||||
samplesInXLog2 = 3;
|
||||
samplesInYLog2 = 2;
|
||||
break;
|
||||
case 64: // 8x8
|
||||
samplesInXLog2 = 3;
|
||||
samplesInYLog2 = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
return (samplesInXLog2, samplesInYLog2);
|
||||
}
|
||||
|
||||
private static ITexture Create2DLayerView(TextureView from, int layer, GAL.Format? format = null)
|
||||
{
|
||||
var target = from.Info.Target switch
|
||||
{
|
||||
Target.Texture1DArray => Target.Texture1D,
|
||||
Target.Texture2DArray => Target.Texture2D,
|
||||
Target.Texture2DMultisampleArray => Target.Texture2DMultisample,
|
||||
_ => from.Info.Target
|
||||
};
|
||||
|
||||
var info = new TextureCreateInfo(
|
||||
from.Info.Width,
|
||||
from.Info.Height,
|
||||
from.Info.Depth,
|
||||
1,
|
||||
from.Info.Samples,
|
||||
from.Info.BlockWidth,
|
||||
from.Info.BlockHeight,
|
||||
from.Info.BytesPerPixel,
|
||||
format ?? from.Info.Format,
|
||||
from.Info.DepthStencilMode,
|
||||
target,
|
||||
from.Info.SwizzleR,
|
||||
from.Info.SwizzleG,
|
||||
from.Info.SwizzleB,
|
||||
from.Info.SwizzleA);
|
||||
|
||||
return from.CreateView(info, layer, 0);
|
||||
}
|
||||
|
||||
private static GAL.Format GetFormat(int bytesPerPixel)
|
||||
{
|
||||
return bytesPerPixel switch
|
||||
{
|
||||
1 => GAL.Format.R8Uint,
|
||||
2 => GAL.Format.R16Uint,
|
||||
4 => GAL.Format.R32Uint,
|
||||
8 => GAL.Format.R32G32Uint,
|
||||
16 => GAL.Format.R32G32B32A32Uint,
|
||||
_ => throw new ArgumentException($"Invalid bytes per pixel {bytesPerPixel}.")
|
||||
};
|
||||
}
|
||||
|
||||
public void ConvertIndexBufferIndirect(
|
||||
VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
BufferHolder srcIndirectBuffer,
|
||||
BufferHolder dstIndirectBuffer,
|
||||
BufferRange drawCountBuffer,
|
||||
BufferHolder srcIndexBuffer,
|
||||
BufferHolder dstIndexBuffer,
|
||||
IndexBufferPattern pattern,
|
||||
int indexSize,
|
||||
int srcIndexBufferOffset,
|
||||
int srcIndexBufferSize,
|
||||
int srcIndirectBufferOffset,
|
||||
bool hasDrawCount,
|
||||
int maxDrawCount,
|
||||
int indirectDataStride)
|
||||
{
|
||||
// TODO: Support conversion with primitive restart enabled.
|
||||
|
||||
BufferRange drawCountBufferAligned = new BufferRange(
|
||||
drawCountBuffer.Handle,
|
||||
drawCountBuffer.Offset & ~(UniformBufferAlignment - 1),
|
||||
UniformBufferAlignment);
|
||||
|
||||
int indirectDataSize = maxDrawCount * indirectDataStride;
|
||||
|
||||
int indexCount = srcIndexBufferSize / indexSize;
|
||||
int primitivesCount = pattern.GetPrimitiveCount(indexCount);
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
int outputIndexSize = 4;
|
||||
|
||||
var srcBuffer = srcIndexBuffer.GetBuffer().Get(cbs, srcIndexBufferOffset, indexCount * indexSize).Value;
|
||||
var dstBuffer = dstIndexBuffer.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
||||
|
||||
const int ParamsBufferSize = 24 * sizeof(int);
|
||||
const int ParamsIndirectDispatchOffset = 16 * sizeof(int);
|
||||
const int ParamsIndirectDispatchSize = 3 * sizeof(int);
|
||||
|
||||
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||
|
||||
shaderParams[8] = pattern.PrimitiveVertices;
|
||||
shaderParams[9] = pattern.PrimitiveVerticesOut;
|
||||
shaderParams[10] = indexSize;
|
||||
shaderParams[11] = outputIndexSize;
|
||||
shaderParams[12] = pattern.BaseIndex;
|
||||
shaderParams[13] = pattern.IndexStride;
|
||||
shaderParams[14] = srcIndexBufferOffset;
|
||||
shaderParams[15] = primitivesCount;
|
||||
shaderParams[16] = 1;
|
||||
shaderParams[17] = 1;
|
||||
shaderParams[18] = 1;
|
||||
shaderParams[19] = hasDrawCount ? 1 : 0;
|
||||
shaderParams[20] = maxDrawCount;
|
||||
shaderParams[21] = (drawCountBuffer.Offset & (UniformBufferAlignment - 1)) / 4;
|
||||
shaderParams[22] = indirectDataStride / 4;
|
||||
shaderParams[23] = srcIndirectBufferOffset / 4;
|
||||
|
||||
pattern.OffsetIndex.CopyTo(shaderParams.Slice(0, pattern.OffsetIndex.Length));
|
||||
|
||||
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false, out var patternBuffer);
|
||||
var patternBufferAuto = patternBuffer.GetBuffer();
|
||||
|
||||
gd.BufferManager.SetData<int>(patternBufferHandle, 0, shaderParams);
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
srcIndirectBuffer.GetBuffer().Get(cbs, srcIndirectBufferOffset, indirectDataSize).Value,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.ShaderReadBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
srcIndirectBufferOffset,
|
||||
indirectDataSize);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
|
||||
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
|
||||
|
||||
_pipeline.SetProgram(_programConvertIndirectData);
|
||||
_pipeline.DispatchCompute(1, 1, 1);
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
patternBufferAuto.Get(cbs, ParamsIndirectDispatchOffset, ParamsIndirectDispatchSize).Value,
|
||||
AccessFlags.ShaderWriteBit,
|
||||
AccessFlags.IndirectCommandReadBit,
|
||||
PipelineStageFlags.ComputeShaderBit,
|
||||
PipelineStageFlags.DrawIndirectBit,
|
||||
ParamsIndirectDispatchOffset,
|
||||
ParamsIndirectDispatchSize);
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) });
|
||||
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
|
||||
|
||||
_pipeline.SetProgram(_programConvertIndexBuffer);
|
||||
_pipeline.DispatchComputeIndirect(patternBufferAuto, ParamsIndirectDispatchOffset);
|
||||
|
||||
BufferHolder.InsertBufferBarrier(
|
||||
gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
AccessFlags.TransferWriteBit,
|
||||
BufferHolder.DefaultAccessFlags,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
convertedCount * outputIndexSize);
|
||||
|
||||
gd.BufferManager.Delete(patternBufferHandle);
|
||||
|
||||
_pipeline.Finish(gd, cbs);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_programColorBlitClearAlpha.Dispose();
|
||||
_programColorBlit.Dispose();
|
||||
_programColorClear.Dispose();
|
||||
_programStrideChange.Dispose();
|
||||
_programConvertIndexBuffer.Dispose();
|
||||
_programConvertIndirectData.Dispose();
|
||||
_programColorCopyToNonMs.Dispose();
|
||||
_programColorDrawToMs.Dispose();
|
||||
_samplerNearest.Dispose();
|
||||
_samplerLinear.Dispose();
|
||||
_pipeline.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,635 +0,0 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
public unsafe static class VulkanInitialization
|
||||
{
|
||||
private const uint InvalidIndex = uint.MaxValue;
|
||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||
private const int QueuesCount = 2;
|
||||
|
||||
public static string[] DesirableExtensions { get; } = new string[]
|
||||
{
|
||||
ExtConditionalRendering.ExtensionName,
|
||||
ExtExtendedDynamicState.ExtensionName,
|
||||
ExtTransformFeedback.ExtensionName,
|
||||
KhrDrawIndirectCount.ExtensionName,
|
||||
KhrPushDescriptor.ExtensionName,
|
||||
"VK_EXT_custom_border_color",
|
||||
"VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
|
||||
"VK_EXT_fragment_shader_interlock",
|
||||
"VK_EXT_index_type_uint8",
|
||||
"VK_EXT_robustness2",
|
||||
"VK_KHR_shader_float16_int8",
|
||||
"VK_EXT_shader_subgroup_ballot",
|
||||
"VK_EXT_subgroup_size_control",
|
||||
"VK_NV_geometry_shader_passthrough"
|
||||
};
|
||||
|
||||
public static string[] RequiredExtensions { get; } = new string[]
|
||||
{
|
||||
KhrSwapchain.ExtensionName
|
||||
};
|
||||
|
||||
private static string[] _excludedMessages = new string[]
|
||||
{
|
||||
// NOTE: Done on purpose right now.
|
||||
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
||||
// TODO: Figure out if fixable
|
||||
"VUID-vkCmdDrawIndexed-None-04584",
|
||||
// TODO: Might be worth looking into making this happy to possibly optimize copies.
|
||||
"UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
|
||||
// TODO: Fix this, it's causing too much noise right now.
|
||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||
};
|
||||
|
||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
{
|
||||
var enabledLayers = new List<string>();
|
||||
|
||||
void AddAvailableLayer(string layerName)
|
||||
{
|
||||
uint layerPropertiesCount;
|
||||
|
||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
|
||||
|
||||
LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount];
|
||||
|
||||
fixed (LayerProperties* pLayerProperties = layerProperties)
|
||||
{
|
||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < layerPropertiesCount; i++)
|
||||
{
|
||||
string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
|
||||
|
||||
if (currentLayerName == layerName)
|
||||
{
|
||||
enabledLayers.Add(layerName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
|
||||
}
|
||||
|
||||
if (logLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
||||
}
|
||||
|
||||
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||
|
||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
var applicationInfo = new ApplicationInfo
|
||||
{
|
||||
PApplicationName = (byte*)appName,
|
||||
ApplicationVersion = 1,
|
||||
PEngineName = (byte*)appName,
|
||||
EngineVersion = 1,
|
||||
ApiVersion = Vk.Version12.Value
|
||||
};
|
||||
|
||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||
IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count];
|
||||
|
||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||
{
|
||||
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < enabledLayers.Count; i++)
|
||||
{
|
||||
ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]);
|
||||
}
|
||||
|
||||
var instanceCreateInfo = new InstanceCreateInfo
|
||||
{
|
||||
SType = StructureType.InstanceCreateInfo,
|
||||
PApplicationInfo = &applicationInfo,
|
||||
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
|
||||
PpEnabledLayerNames = (byte**)ppEnabledLayers,
|
||||
EnabledExtensionCount = (uint)enabledExtensions.Length,
|
||||
EnabledLayerCount = (uint)enabledLayers.Count
|
||||
};
|
||||
|
||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
||||
|
||||
Marshal.FreeHGlobal(appName);
|
||||
|
||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(ppEnabledExtensions[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < enabledLayers.Count; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||
}
|
||||
|
||||
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private unsafe static uint DebugMessenger(
|
||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* pUserData)
|
||||
{
|
||||
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||
|
||||
foreach (string excludedMessagePart in _excludedMessages)
|
||||
{
|
||||
if (msg.Contains(excludedMessagePart))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||
//throw new Exception(msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
uint physicalDeviceCount;
|
||||
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
||||
|
||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||
|
||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
||||
{
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
||||
}
|
||||
|
||||
// First we try to pick the the user preferred GPU.
|
||||
for (int i = 0; i < physicalDevices.Length; i++)
|
||||
{
|
||||
if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
|
||||
{
|
||||
return physicalDevices[i];
|
||||
}
|
||||
}
|
||||
|
||||
// If we fail to do that, just use the first compatible GPU.
|
||||
for (int i = 0; i < physicalDevices.Length; i++)
|
||||
{
|
||||
if (IsSuitableDevice(api, physicalDevices[i], surface))
|
||||
{
|
||||
return physicalDevices[i];
|
||||
}
|
||||
}
|
||||
|
||||
throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
|
||||
}
|
||||
|
||||
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
|
||||
{
|
||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||
|
||||
var applicationInfo = new ApplicationInfo
|
||||
{
|
||||
PApplicationName = (byte*)appName,
|
||||
ApplicationVersion = 1,
|
||||
PEngineName = (byte*)appName,
|
||||
EngineVersion = 1,
|
||||
ApiVersion = Vk.Version12.Value
|
||||
};
|
||||
|
||||
var instanceCreateInfo = new InstanceCreateInfo
|
||||
{
|
||||
SType = StructureType.InstanceCreateInfo,
|
||||
PApplicationInfo = &applicationInfo,
|
||||
PpEnabledExtensionNames = null,
|
||||
PpEnabledLayerNames = null,
|
||||
EnabledExtensionCount = 0,
|
||||
EnabledLayerCount = 0
|
||||
};
|
||||
|
||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
||||
|
||||
Marshal.FreeHGlobal(appName);
|
||||
|
||||
uint physicalDeviceCount;
|
||||
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
||||
|
||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||
|
||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
||||
{
|
||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
||||
}
|
||||
|
||||
DeviceInfo[] devices = new DeviceInfo[physicalDevices.Length];
|
||||
|
||||
for (int i = 0; i < physicalDevices.Length; i++)
|
||||
{
|
||||
var physicalDevice = physicalDevices[i];
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
|
||||
devices[i] = new DeviceInfo(
|
||||
StringFromIdPair(properties.VendorID, properties.DeviceID),
|
||||
VendorUtils.GetNameFromId(properties.VendorID),
|
||||
Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName),
|
||||
properties.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||
}
|
||||
|
||||
api.DestroyInstance(instance, null);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
public static string StringFromIdPair(uint vendorId, uint deviceId)
|
||||
{
|
||||
return $"0x{vendorId:X}_0x{deviceId:X}";
|
||||
}
|
||||
|
||||
private static bool IsPreferredAndSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
|
||||
{
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
|
||||
if (StringFromIdPair(properties.VendorID, properties.DeviceID) != preferredGpuId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsSuitableDevice(api, physicalDevice, surface);
|
||||
}
|
||||
|
||||
private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface)
|
||||
{
|
||||
int extensionMatches = 0;
|
||||
uint propertiesCount;
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||
{
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < propertiesCount; i++)
|
||||
{
|
||||
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
||||
|
||||
if (RequiredExtensions.Contains(extensionName))
|
||||
{
|
||||
extensionMatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||
}
|
||||
|
||||
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||
{
|
||||
const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
|
||||
|
||||
var khrSurface = new KhrSurface(api.Context);
|
||||
|
||||
uint propertiesCount;
|
||||
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
|
||||
|
||||
QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount];
|
||||
|
||||
fixed (QueueFamilyProperties* pProperties = properties)
|
||||
{
|
||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
|
||||
}
|
||||
|
||||
for (uint index = 0; index < propertiesCount; index++)
|
||||
{
|
||||
var queueFlags = properties[index].QueueFlags;
|
||||
|
||||
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
|
||||
|
||||
if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
||||
{
|
||||
queueCount = properties[index].QueueCount;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
queueCount = 0;
|
||||
return InvalidIndex;
|
||||
}
|
||||
|
||||
public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount)
|
||||
{
|
||||
if (queueCount > QueuesCount)
|
||||
{
|
||||
queueCount = QueuesCount;
|
||||
}
|
||||
|
||||
float* queuePriorities = stackalloc float[(int)queueCount];
|
||||
|
||||
for (int i = 0; i < queueCount; i++)
|
||||
{
|
||||
queuePriorities[i] = 1f;
|
||||
}
|
||||
|
||||
var queueCreateInfo = new DeviceQueueCreateInfo()
|
||||
{
|
||||
SType = StructureType.DeviceQueueCreateInfo,
|
||||
QueueFamilyIndex = queueFamilyIndex,
|
||||
QueueCount = queueCount,
|
||||
PQueuePriorities = queuePriorities
|
||||
};
|
||||
|
||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||
bool useRobustBufferAccess = VendorUtils.FromId(properties.VendorID) == Vendor.Nvidia;
|
||||
|
||||
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFeatures2
|
||||
};
|
||||
|
||||
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColorSupported = new PhysicalDeviceCustomBorderColorFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt
|
||||
};
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
|
||||
{
|
||||
features2.PNext = &featuresCustomBorderColorSupported;
|
||||
}
|
||||
|
||||
api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
|
||||
|
||||
var supportedFeatures = features2.Features;
|
||||
|
||||
var features = new PhysicalDeviceFeatures()
|
||||
{
|
||||
DepthBiasClamp = true,
|
||||
DepthClamp = true,
|
||||
DualSrcBlend = true,
|
||||
FragmentStoresAndAtomics = true,
|
||||
GeometryShader = supportedFeatures.GeometryShader,
|
||||
ImageCubeArray = true,
|
||||
IndependentBlend = true,
|
||||
LogicOp = supportedFeatures.LogicOp,
|
||||
MultiViewport = true,
|
||||
PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
|
||||
SamplerAnisotropy = true,
|
||||
ShaderClipDistance = true,
|
||||
ShaderFloat64 = supportedFeatures.ShaderFloat64,
|
||||
ShaderImageGatherExtended = true,
|
||||
ShaderStorageImageMultisample = supportedFeatures.ShaderStorageImageMultisample,
|
||||
// ShaderStorageImageReadWithoutFormat = true,
|
||||
// ShaderStorageImageWriteWithoutFormat = true,
|
||||
TessellationShader = true,
|
||||
VertexPipelineStoresAndAtomics = true,
|
||||
RobustBufferAccess = useRobustBufferAccess
|
||||
};
|
||||
|
||||
void* pExtendedFeatures = null;
|
||||
|
||||
var featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
TransformFeedback = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresTransformFeedback;
|
||||
|
||||
var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
NullDescriptor = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresRobustness2;
|
||||
|
||||
var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
ExtendedDynamicState = supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresExtendedDynamicState;
|
||||
|
||||
var featuresVk11 = new PhysicalDeviceVulkan11Features()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan11Features,
|
||||
PNext = pExtendedFeatures,
|
||||
ShaderDrawParameters = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresVk11;
|
||||
|
||||
var featuresVk12 = new PhysicalDeviceVulkan12Features()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = pExtendedFeatures,
|
||||
DescriptorIndexing = supportedExtensions.Contains("VK_EXT_descriptor_indexing"),
|
||||
DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
||||
UniformBufferStandardLayout = supportedExtensions.Contains("VK_KHR_uniform_buffer_standard_layout")
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresVk12;
|
||||
|
||||
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_index_type_uint8"))
|
||||
{
|
||||
featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
IndexTypeUint8 = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresIndexU8;
|
||||
}
|
||||
|
||||
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"))
|
||||
{
|
||||
featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
FragmentShaderPixelInterlock = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresFragmentShaderInterlock;
|
||||
}
|
||||
|
||||
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_subgroup_size_control"))
|
||||
{
|
||||
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
SubgroupSizeControl = true
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresSubgroupSizeControl;
|
||||
}
|
||||
|
||||
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
|
||||
|
||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
||||
featuresCustomBorderColorSupported.CustomBorderColors &&
|
||||
featuresCustomBorderColorSupported.CustomBorderColorWithoutFormat)
|
||||
{
|
||||
featuresCustomBorderColor = new PhysicalDeviceCustomBorderColorFeaturesEXT()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceCustomBorderColorFeaturesExt,
|
||||
PNext = pExtendedFeatures,
|
||||
CustomBorderColors = true,
|
||||
CustomBorderColorWithoutFormat = true,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresCustomBorderColor;
|
||||
}
|
||||
|
||||
var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
|
||||
|
||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||
|
||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||
{
|
||||
ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
|
||||
}
|
||||
|
||||
var deviceCreateInfo = new DeviceCreateInfo()
|
||||
{
|
||||
SType = StructureType.DeviceCreateInfo,
|
||||
PNext = pExtendedFeatures,
|
||||
QueueCreateInfoCount = 1,
|
||||
PQueueCreateInfos = &queueCreateInfo,
|
||||
PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
|
||||
EnabledExtensionCount = (uint)enabledExtensions.Length,
|
||||
PEnabledFeatures = &features
|
||||
};
|
||||
|
||||
api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||
{
|
||||
Marshal.FreeHGlobal(ppEnabledExtensions[i]);
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice)
|
||||
{
|
||||
uint propertiesCount;
|
||||
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
||||
|
||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||
|
||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
||||
{
|
||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
||||
}
|
||||
|
||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
||||
}
|
||||
|
||||
internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
||||
{
|
||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||
}
|
||||
|
||||
internal unsafe static void CreateDebugMessenger(
|
||||
Vk api,
|
||||
GraphicsDebugLevel logLevel,
|
||||
Instance instance,
|
||||
out ExtDebugUtils debugUtils,
|
||||
out DebugUtilsMessengerEXT debugUtilsMessenger)
|
||||
{
|
||||
debugUtils = default;
|
||||
|
||||
if (logLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
if (!api.TryGetInstanceExtension(instance, out debugUtils))
|
||||
{
|
||||
debugUtilsMessenger = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var filterLogType = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
var filterLogSeverity = logLevel switch
|
||||
{
|
||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
||||
};
|
||||
|
||||
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageType = filterLogType,
|
||||
MessageSeverity = filterLogSeverity,
|
||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
|
||||
};
|
||||
|
||||
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
|
||||
}
|
||||
else
|
||||
{
|
||||
debugUtilsMessenger = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,903 +0,0 @@
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using LibHac;
|
||||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using static Ryujinx.HLE.HOS.ModLoader;
|
||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
using JsonHelper = Common.Utilities.JsonHelper;
|
||||
|
||||
public class ApplicationLoader
|
||||
{
|
||||
// Binaries from exefs are loaded into mem in this order. Do not change.
|
||||
internal static readonly string[] ExeFsPrefixes =
|
||||
{
|
||||
"rtld",
|
||||
"main",
|
||||
"subsdk0",
|
||||
"subsdk1",
|
||||
"subsdk2",
|
||||
"subsdk3",
|
||||
"subsdk4",
|
||||
"subsdk5",
|
||||
"subsdk6",
|
||||
"subsdk7",
|
||||
"subsdk8",
|
||||
"subsdk9",
|
||||
"sdk"
|
||||
};
|
||||
|
||||
private readonly Switch _device;
|
||||
private string _titleName;
|
||||
private string _displayVersion;
|
||||
private BlitStruct<ApplicationControlProperty> _controlData;
|
||||
|
||||
public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
|
||||
public string TitleName => _titleName;
|
||||
public string DisplayVersion => _displayVersion;
|
||||
|
||||
public ulong TitleId { get; private set; }
|
||||
public bool TitleIs64Bit { get; private set; }
|
||||
|
||||
public string TitleIdText => TitleId.ToString("x16");
|
||||
|
||||
public ApplicationLoader(Switch device)
|
||||
{
|
||||
_device = device;
|
||||
_controlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||
}
|
||||
|
||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||
{
|
||||
if (romFsFile != null)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.LoadRomFs(romFsFile);
|
||||
}
|
||||
|
||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
new[] { TitleId },
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (TitleId != 0)
|
||||
{
|
||||
EnsureSaveData(new ApplicationId(TitleId));
|
||||
}
|
||||
|
||||
LoadExeFs(codeFs, metaData);
|
||||
}
|
||||
|
||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
||||
{
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
|
||||
{
|
||||
Nca patchNca = null;
|
||||
Nca controlNca = null;
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||
|
||||
if (ncaProgramIndex != programIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||
{
|
||||
controlNca = nca;
|
||||
}
|
||||
}
|
||||
|
||||
return (patchNca, controlNca);
|
||||
}
|
||||
|
||||
public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
|
||||
{
|
||||
updatePath = null;
|
||||
|
||||
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
||||
{
|
||||
// Clear the program index part.
|
||||
titleIdBase &= 0xFFFFFFFFFFFFFFF0;
|
||||
|
||||
// Load update informations if existing.
|
||||
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
||||
|
||||
if (File.Exists(titleUpdateMetadataPath))
|
||||
{
|
||||
updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
|
||||
|
||||
if (File.Exists(updatePath))
|
||||
{
|
||||
FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
public void LoadXci(string xciFile)
|
||||
{
|
||||
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
||||
Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
if (!xci.HasPartition(XciPartitionType.Secure))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(securePartition).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
}
|
||||
|
||||
public void LoadNsp(string nspFile)
|
||||
{
|
||||
FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
Nca mainNca;
|
||||
Nca patchNca;
|
||||
Nca controlNca;
|
||||
|
||||
try
|
||||
{
|
||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
|
||||
|
||||
RegisterProgramMapInfo(nsp).ThrowIfFailure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainNca != null)
|
||||
{
|
||||
_device.Configuration.ContentManager.ClearAocData();
|
||||
_device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
LoadNca(mainNca, patchNca, controlNca);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
||||
LoadExeFs(nsp, null, isHomebrew: true);
|
||||
}
|
||||
|
||||
public void LoadNca(string ncaFile)
|
||||
{
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
LoadNca(nca, null, null);
|
||||
}
|
||||
|
||||
public void LoadServiceNca(string ncaFile)
|
||||
{
|
||||
// Disable PPTC here as it does not support multiple processes running.
|
||||
// TODO: This should be eventually removed and it should stop using global state and
|
||||
// instead manage the cache per process.
|
||||
Ptc.Close();
|
||||
PtcProfiler.Stop();
|
||||
|
||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
||||
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = codeFs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
|
||||
|
||||
string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
|
||||
bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
|
||||
|
||||
string programName = Encoding.ASCII.GetString(npdm.Meta.Value.ProgramName).TrimEnd('\0');
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
||||
{
|
||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
IStorage dataStorage = null;
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
(Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
if (updateControlNca != null)
|
||||
{
|
||||
controlNca = updateControlNca;
|
||||
}
|
||||
|
||||
// Load program 0 control NCA as we are going to need it for display version.
|
||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||
|
||||
// Load Aoc
|
||||
string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||
|
||||
if (File.Exists(titleAocMetadataPath))
|
||||
{
|
||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
|
||||
|
||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
||||
{
|
||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||
{
|
||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
||||
{
|
||||
_device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (patchNca == null)
|
||||
{
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
||||
{
|
||||
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MetaLoader metaData = ReadNpdm(codeFs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
||||
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
||||
|
||||
if (controlNca != null)
|
||||
{
|
||||
ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref _displayVersion);
|
||||
}
|
||||
else
|
||||
{
|
||||
ControlData.ByteSpan.Clear();
|
||||
}
|
||||
|
||||
// NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
|
||||
// BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
|
||||
if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
|
||||
{
|
||||
string dummyTitleName = "";
|
||||
BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
|
||||
|
||||
ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref _displayVersion);
|
||||
}
|
||||
|
||||
if (dataStorage == null)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
||||
}
|
||||
else
|
||||
{
|
||||
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read));
|
||||
}
|
||||
|
||||
// Don't create save data for system programs.
|
||||
if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
|
||||
{
|
||||
// Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
|
||||
// We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
|
||||
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
||||
}
|
||||
|
||||
LoadExeFs(codeFs, metaData);
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||
}
|
||||
|
||||
// Sets TitleId, so be sure to call before using it
|
||||
private MetaLoader ReadNpdm(IFileSystem fs)
|
||||
{
|
||||
using var npdmFile = new UniqueRef<IFile>();
|
||||
|
||||
Result result = fs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||
|
||||
MetaLoader metaData;
|
||||
|
||||
if (ResultFs.PathNotFound.Includes(result))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
|
||||
|
||||
metaData = GetDefaultNpdm();
|
||||
}
|
||||
else
|
||||
{
|
||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
||||
|
||||
var npdmBuffer = new byte[fileSize];
|
||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
||||
|
||||
metaData = new MetaLoader();
|
||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
||||
}
|
||||
|
||||
metaData.GetNpdm(out var npdm).ThrowIfFailure();
|
||||
|
||||
TitleId = npdm.Aci.Value.ProgramId.Value;
|
||||
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
||||
{
|
||||
using var controlFile = new UniqueRef<IFile>();
|
||||
|
||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
||||
Result result = controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read);
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
||||
|
||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
||||
{
|
||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
controlData.ByteSpan.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null, bool isHomebrew = false)
|
||||
{
|
||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||
{
|
||||
metaData = null; // TODO: Check if we should retain old npdm.
|
||||
}
|
||||
|
||||
metaData ??= ReadNpdm(codeFs);
|
||||
|
||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
||||
|
||||
for (int i = 0; i < nsos.Length; i++)
|
||||
{
|
||||
string name = ExeFsPrefixes[i];
|
||||
|
||||
if (!codeFs.FileExists($"/{name}"))
|
||||
{
|
||||
continue; // File doesn't exist, skip.
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref(), $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
||||
}
|
||||
|
||||
// ExeFs file replacements.
|
||||
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
|
||||
|
||||
// Collect the nsos, ignoring ones that aren't used.
|
||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
||||
|
||||
// Take the npdm from mods if present.
|
||||
if (modLoadResult.Npdm != null)
|
||||
{
|
||||
metaData = modLoadResult.Npdm;
|
||||
}
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
bool usePtc = _device.System.EnablePtc;
|
||||
|
||||
// Don't use PPTC if ExeFs files have been replaced.
|
||||
usePtc &= !modLoadResult.Modified;
|
||||
|
||||
if (_device.System.EnablePtc && !usePtc)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
|
||||
}
|
||||
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
|
||||
|
||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
|
||||
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: programs);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
|
||||
}
|
||||
|
||||
public void LoadProgram(string filePath)
|
||||
{
|
||||
MetaLoader metaData = GetDefaultNpdm();
|
||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
||||
ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: true);
|
||||
|
||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||
|
||||
IExecutable executable;
|
||||
|
||||
if (isNro)
|
||||
{
|
||||
FileStream input = new FileStream(filePath, FileMode.Open);
|
||||
NroExecutable obj = new NroExecutable(input.AsStorage());
|
||||
|
||||
executable = obj;
|
||||
|
||||
// Homebrew NRO can actually have some data after the actual NRO.
|
||||
if (input.Length > obj.FileSize)
|
||||
{
|
||||
input.Position = obj.FileSize;
|
||||
|
||||
BinaryReader reader = new BinaryReader(input);
|
||||
|
||||
uint asetMagic = reader.ReadUInt32();
|
||||
if (asetMagic == 0x54455341)
|
||||
{
|
||||
uint asetVersion = reader.ReadUInt32();
|
||||
if (asetVersion == 0)
|
||||
{
|
||||
ulong iconOffset = reader.ReadUInt64();
|
||||
ulong iconSize = reader.ReadUInt64();
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
ulong romfsOffset = reader.ReadUInt64();
|
||||
ulong romfsSize = reader.ReadUInt64();
|
||||
|
||||
if (romfsSize != 0)
|
||||
{
|
||||
_device.Configuration.VirtualFileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
|
||||
}
|
||||
|
||||
if (nacpSize != 0)
|
||||
{
|
||||
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
|
||||
|
||||
reader.Read(ControlData.ByteSpan);
|
||||
|
||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
||||
|
||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
||||
{
|
||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
||||
}
|
||||
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
||||
}
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
programInfo.ProgramId = 0000000000000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
|
||||
}
|
||||
|
||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
||||
|
||||
_titleName = programInfo.Name;
|
||||
TitleId = programInfo.ProgramId;
|
||||
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
|
||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
||||
|
||||
// Explicitly null titleid to disable the shader cache.
|
||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
||||
_device.Gpu.HostInitalized.Set();
|
||||
|
||||
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: executable);
|
||||
|
||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
|
||||
}
|
||||
|
||||
private MetaLoader GetDefaultNpdm()
|
||||
{
|
||||
Assembly asm = Assembly.GetCallingAssembly();
|
||||
|
||||
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
||||
{
|
||||
var npdmBuffer = new byte[npdmStream.Length];
|
||||
npdmStream.Read(npdmBuffer);
|
||||
|
||||
var metaLoader = new MetaLoader();
|
||||
metaLoader.Load(npdmBuffer).ThrowIfFailure();
|
||||
|
||||
return metaLoader;
|
||||
}
|
||||
}
|
||||
|
||||
private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
|
||||
{
|
||||
ulong mainProgramId = 0;
|
||||
Span<bool> hasIndex = stackalloc bool[0x10];
|
||||
|
||||
fileSystem.ImportTickets(pfs);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong currentProgramId = nca.Header.TitleId;
|
||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
||||
|
||||
if (mainProgramId == 0 && currentMainProgramId != 0)
|
||||
{
|
||||
mainProgramId = currentMainProgramId;
|
||||
}
|
||||
|
||||
if (mainProgramId != currentMainProgramId)
|
||||
{
|
||||
// As far as I know there aren't any multi-application game cards containing multi-program applications,
|
||||
// so because multi-application game cards are the only way we should run into multiple applications
|
||||
// we'll just return that there's a single program.
|
||||
return (mainProgramId, 1);
|
||||
}
|
||||
|
||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
||||
}
|
||||
|
||||
int programCount = 0;
|
||||
|
||||
for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
|
||||
{
|
||||
programCount++;
|
||||
}
|
||||
|
||||
return (mainProgramId, programCount);
|
||||
}
|
||||
|
||||
private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
|
||||
{
|
||||
(ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
|
||||
|
||||
if (programCount <= 0)
|
||||
return Result.Success;
|
||||
|
||||
Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
|
||||
|
||||
for (int i = 0; i < programCount; i++)
|
||||
{
|
||||
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
|
||||
mapInfo[i].MainProgramId = new ApplicationId(applicationId);
|
||||
mapInfo[i].ProgramIndex = (byte)i;
|
||||
}
|
||||
|
||||
return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
|
||||
}
|
||||
|
||||
private Result EnsureSaveData(ApplicationId applicationId)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
|
||||
|
||||
Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
|
||||
|
||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
||||
|
||||
if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan))
|
||||
{
|
||||
// If the current application doesn't have a loaded control property, create a dummy one
|
||||
// and set the savedata sizes so a user savedata will be created.
|
||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
||||
|
||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
control.SaveDataOwnerId = applicationId.Value;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
||||
|
||||
if (resultCode.IsFailure())
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.Cpu.Jit;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS
|
||||
{
|
||||
class ArmProcessContextFactory : IProcessContextFactory
|
||||
{
|
||||
private readonly ICpuEngine _cpuEngine;
|
||||
private readonly GpuContext _gpu;
|
||||
|
||||
public ArmProcessContextFactory(ICpuEngine cpuEngine, GpuContext gpu)
|
||||
{
|
||||
_cpuEngine = cpuEngine;
|
||||
_gpu = gpu;
|
||||
}
|
||||
|
||||
public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
|
||||
{
|
||||
MemoryManagerMode mode = context.Device.Configuration.MemoryManagerMode;
|
||||
|
||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
||||
{
|
||||
mode = MemoryManagerMode.SoftwarePageTable;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MemoryManagerMode.SoftwarePageTable:
|
||||
var memoryManager = new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
|
||||
return new ArmProcessContext<MemoryManager>(pid, _cpuEngine, _gpu, memoryManager, for64Bit);
|
||||
|
||||
case MemoryManagerMode.HostMapped:
|
||||
case MemoryManagerMode.HostMappedUnsafe:
|
||||
bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
|
||||
var memoryManagerHostMapped = new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler);
|
||||
return new ArmProcessContext<MemoryManagerHostMapped>(pid, _cpuEngine, _gpu, memoryManagerHostMapped, for64Bit);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user