mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 05:28:56 +02:00
Compare commits
24 commits
8db6b82bdf
...
7e88092998
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e88092998 | ||
|
|
8d99782267 | ||
|
|
93f69afe37 | ||
|
|
f23af26096 | ||
|
|
2649b82ff5 | ||
|
|
e7717708f1 | ||
|
|
ab77cb7537 | ||
|
|
94dcf09617 | ||
|
|
4a065abff2 | ||
|
|
dbd0c65df0 | ||
|
|
94cc8727e8 | ||
|
|
2089d508a2 | ||
|
|
d001c9bcba | ||
|
|
74a0456a5f | ||
|
|
3f1f3db6d8 | ||
|
|
7f97290516 | ||
|
|
a751088abf | ||
|
|
cfab359439 | ||
|
|
56e207210c | ||
|
|
a89c8ba5fc | ||
|
|
70620eb635 | ||
|
|
9e16918f3c | ||
|
|
0d80fccbcd | ||
|
|
feaedd02f7 |
95 changed files with 1742 additions and 2281 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -63,4 +63,3 @@ artifacts
|
|||
*.AppImage*
|
||||
/install*
|
||||
vulkansdk*.exe
|
||||
*.tar.zst
|
||||
|
|
|
|||
|
|
@ -61,10 +61,6 @@ See the [sign-up instructions](docs/SIGNUP.md) for information on registration.
|
|||
|
||||
Alternatively, if you wish to add translations, go to the [Eden project on Transifex](https://app.transifex.com/edenemu/eden-emulator) and review [the translations README](./dist/languages).
|
||||
|
||||
## Documentation
|
||||
|
||||
We have a user manual! See our [User Handbook](./docs/user/README.md).
|
||||
|
||||
## Building
|
||||
|
||||
See the [General Build Guide](docs/Build.md)
|
||||
|
|
@ -73,9 +69,7 @@ For information on provided development tooling, see the [Tools directory](./too
|
|||
|
||||
## Download
|
||||
|
||||
You can download the latest releases from [here](https://git.eden-emu.dev/eden-emu/eden/releases).
|
||||
|
||||
Save us some bandwidth! We have [mirrors available](./docs/user/ThirdParty.md#mirrors) as well.
|
||||
You can download the latest releases from [here](https://github.com/eden-emulator/Releases/releases).
|
||||
|
||||
## Support
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# Eden Build Documentation
|
||||
|
||||
Are you just a casual user? Take a look at our [User Handbook](./user) then!
|
||||
|
||||
This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more.
|
||||
|
||||
- **[General Build Instructions](Build.md)**
|
||||
|
|
@ -13,6 +11,7 @@ This contains documentation created by developers. This contains build instructi
|
|||
- **[CPM - CMake Package Manager](./CPMUtil)**
|
||||
- **[Platform-Specific Caveats](Caveats.md)**
|
||||
- **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)**
|
||||
- **[User Handbook](./user)**
|
||||
- **[Dynarmic](./dynarmic)**
|
||||
- **[Cross compilation](./CrossCompile.md)**
|
||||
- **[Driver Bugs](./DriverBugs.md)**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
# User Handbook - Configuring Steam ROM Manager
|
||||
|
||||
## Importing Eden into Steam with Steam Rom Manager
|
||||
# Importing Eden into Steam with Steam Rom Manager
|
||||
|
||||
Use this when you want to import the Eden AppImage into your Steam Library along with artwork using *Steam ROM Manager.*
|
||||
|
||||
|
|
@ -8,7 +6,7 @@ Use this when you want to import the Eden AppImage into your Steam Library along
|
|||
|
||||
---
|
||||
|
||||
#### Pre-Requisites
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden set up and configured
|
||||
- Internet Connection
|
||||
|
|
@ -16,9 +14,9 @@ Use this when you want to import the Eden AppImage into your Steam Library along
|
|||
|
||||
---
|
||||
|
||||
### Steps
|
||||
## Steps
|
||||
|
||||
#### Initial Setup
|
||||
### Initial Setup
|
||||
|
||||
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
|
||||
|
||||
|
|
@ -26,14 +24,14 @@ Use this when you want to import the Eden AppImage into your Steam Library along
|
|||
|
||||
---
|
||||
|
||||
#### Manual Installation
|
||||
### Manual Installation
|
||||
|
||||
1. Open the *Discover Store* and search for *Steam ROM Manager.*
|
||||
2. Select the **Install** button to install the program.
|
||||
|
||||
---
|
||||
|
||||
#### Installing Through *EmuDeck*
|
||||
### Installing Through *EmuDeck*
|
||||
|
||||
<aside>
|
||||
|
||||
|
|
@ -47,9 +45,9 @@ Use this when you want to import the Eden AppImage into your Steam Library along
|
|||
|
||||
---
|
||||
|
||||
#### Adding Eden into *Steam ROM Manager*
|
||||
### Adding Eden into *Steam ROM Manager*
|
||||
|
||||
#### EmuDeck Users
|
||||
### EmuDeck Users
|
||||
|
||||
EmuDeck will automatically create an *Emulators - Emulators* parser for ***Steam ROM Manager*** that uses shell scripts to launch them. We will follow this convention.
|
||||
|
||||
|
|
@ -89,7 +87,7 @@ EmuDeck will automatically create an *Emulators - Emulators* parser for ***Steam
|
|||
|
||||
---
|
||||
|
||||
#### Non-EmuDeck Users
|
||||
### Non-EmuDeck Users
|
||||
|
||||
We will need to create a new parser for the Emulators. Unlike with the EmuDeck model, we will have the parser look for AppImages.
|
||||
|
||||
|
|
@ -128,7 +126,7 @@ We will need to create a new parser for the Emulators. Unlike with the EmuDeck
|
|||
|
||||
---
|
||||
|
||||
#### Adding Eden to Steam
|
||||
### Adding Eden to Steam
|
||||
|
||||
Now that we have the parser or shell script created, we can actually add it to Steam.
|
||||
|
||||
|
|
@ -139,7 +137,7 @@ Now that we have the parser or shell script created, we can actually add it to S
|
|||
|
||||
---
|
||||
|
||||
#### Correcting a Mismatch
|
||||
### Correcting a Mismatch
|
||||
|
||||
If the emulator is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
|
||||
|
||||
|
|
@ -149,7 +147,7 @@ Now that we have the parser or shell script created, we can actually add it to S
|
|||
|
||||
---
|
||||
|
||||
#### Excluding Matches
|
||||
### Excluding Matches
|
||||
|
||||
You may want to tell Steam ROM Manager to ignore some files that it finds in the directory. This is how you do so.
|
||||
|
||||
|
|
@ -161,105 +159,4 @@ Now that we have the parser or shell script created, we can actually add it to S
|
|||
|
||||
5. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
|
||||
6. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
|
||||
7. Try to launch the Emulator from Steam and ensure everything is working. You are now good to go.
|
||||
|
||||
## Importing Games into Steam with Steam Rom Manager
|
||||
|
||||
Use this when you want to import your games inside Eden into Steam to launch with artwork from Steam Game Mode without needing to launch Eden first.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Importing-Games-into-Steam-with-Steam-Rom-Manager-2b757c2edaf680d7a491c92b138f1fcc) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
#### Pre-Requisites
|
||||
|
||||
- Steam Deck Set up and Configured
|
||||
- Eden set up and Configured
|
||||
- Internet Access
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
|
||||
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
|
||||
|
||||
1. Install ***Steam ROM Manager***, there are 2 ways you can accomplish this, either manually or through [*EmuDeck*](https://www.emudeck.com/#downloads).
|
||||
|
||||
---
|
||||
|
||||
#### Manual Installation
|
||||
|
||||
1. Open the *Discover Store* and search for *Steam ROM Manager.*
|
||||
2. Select the **Install** button to install the program.
|
||||
|
||||
---
|
||||
|
||||
#### Installing Through *EmuDeck*
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: This assumes you have already set up EmuDeck, if not - just run through the guided installation and select *Steam ROM Manager* as one of the options.
|
||||
|
||||
</aside>
|
||||
|
||||
1. Open **EmuDeck**, then navigate to *Manage Emulators.*
|
||||
2. Scroll down to the bottom of the page to the *Manage your Tools & Frontends* section. Click **Steam ROM Manager**.
|
||||
|
||||
3. Click the **Install** button on the right hand side to install it.
|
||||
|
||||
---
|
||||
|
||||
2. Open the Start Menu and Launch ***Steam ROM Manager***
|
||||
|
||||
1. The program will now launch and show you a window with parsers.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: Your layout may look different depending on how you installed *Steam ROM Manager*. You may need to go to **Settings → Theme** and change it to *Classic* to follow along.
|
||||
|
||||
</aside>
|
||||
|
||||
2. Switch off all Parsers by hitting the *Toggle Parsers* switch.
|
||||
3. Scroll down the list on the left-hand side and look for a parser called *Nintendo Switch - Eden* and switch it on. This parser may not exist depending on how you installed *Steam ROM Manager* (EmuDeck creates it for you). Follow these steps to create it if it is missing.
|
||||
|
||||
---
|
||||
#### Creating the Eden Parser
|
||||
|
||||
1. Select Create Parser and in the *Community Presets* option look for **Nintendo Switch - Yuzu**.
|
||||
2. Change the **Parser title** from *Nintendo Switch - Yuzu* to *Nintendo Switch - Eden.*
|
||||
3. Hit the **Browse** option under the *ROMs directory* section. Select the directory containing your Switch ROMs.
|
||||
4. Under *Steam collections*, you can add a Steam category name. This just organizes the games under a common category in your Steam Library, this is optional but recommended.
|
||||
5. Scroll down slightly to the **Executable Configuration → Executable**, select **Browse** and select the Eden AppImage.
|
||||
6. Leave everything else the same and hit **Save** to save the parser.
|
||||
---
|
||||
|
||||
4. Click the Eden parser to view the options on the right, select **Test** at the bottom of the screen to ensure that *Steam ROM Manager* detects your games correctly.
|
||||
1. *Steam ROM Manager* will start to scan the specified ROMs directory and match them to games. Look over the results to ensure they are accurate. If you do not see any entries - check your parsers ROMs directory field.
|
||||
1. When you are happy with the results, click the **Add Games** → **Parse** to start the actual Parsing.
|
||||
1. The program will now identify the games and pull artwork from [*SteamGridDB*](https://www.steamgriddb.com/).
|
||||
2. Review the game matches and ensure everything is there.
|
||||
|
||||
---
|
||||
|
||||
#### Correcting a Mismatch
|
||||
|
||||
If the game is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
|
||||
|
||||
1. Hover over the game card and click the magnifying glass icon.
|
||||
2. Search for the game on the *Search SteamGridDB* section and scroll through the results, selecting the one you want.
|
||||
3. Ensure the *Name* and *Game ID* update in the **Per-App Exceptions** and press **Save and close**. The game should now update.
|
||||
|
||||
---
|
||||
|
||||
#### Excluding Matches
|
||||
|
||||
You may want to tell Steam ROM Manager to ignore some files (updates/DLC/etc.) that it finds in the directory. This is how you do so.
|
||||
|
||||
1. Hit the **Exclude Games** button in the bottom right.
|
||||
2. Deselect the game you want to exclude, the poster artwork should go dim and the **Number Excluded** number should increment up. Repeat with any other exclusions you want to add.
|
||||
3. Hit **Save Excludes** when you are happy with your selections.
|
||||
---
|
||||
3. When you are happy with the results, select **Save to Steam** to save the results.
|
||||
1. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
|
||||
2. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
|
||||
3. Try to launch a game and ensure everything is working. You are now good to go.
|
||||
7. Try to launch the Emulator from Steam and ensure everything is working. You are now good to go.
|
||||
100
docs/user/AddGamesToSRM.md
Normal file
100
docs/user/AddGamesToSRM.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# Importing Games into Steam with Steam Rom Manager
|
||||
|
||||
Use this when you want to import your games inside Eden into Steam to launch with artwork from Steam Game Mode without needing to launch Eden first.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Importing-Games-into-Steam-with-Steam-Rom-Manager-2b757c2edaf680d7a491c92b138f1fcc) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Steam Deck Set up and Configured
|
||||
- Eden set up and Configured
|
||||
- Internet Access
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
1. Press the **STEAM** button and then go to *Power → Switch to Desktop* to enter the Desktop mode.
|
||||
|
||||
1. Install ***Steam ROM Manager***, there are 2 ways you can accomplish this, either manually or through [*EmuDeck*](https://www.emudeck.com/#downloads).
|
||||
|
||||
---
|
||||
|
||||
### Manual Installation
|
||||
|
||||
1. Open the *Discover Store* and search for *Steam ROM Manager.*
|
||||
2. Select the **Install** button to install the program.
|
||||
|
||||
---
|
||||
|
||||
### Installing Through *EmuDeck*
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: This assumes you have already set up EmuDeck, if not - just run through the guided installation and select *Steam ROM Manager* as one of the options.
|
||||
|
||||
</aside>
|
||||
|
||||
1. Open **EmuDeck**, then navigate to *Manage Emulators.*
|
||||
2. Scroll down to the bottom of the page to the *Manage your Tools & Frontends* section. Click **Steam ROM Manager**.
|
||||
|
||||
3. Click the **Install** button on the right hand side to install it.
|
||||
|
||||
---
|
||||
|
||||
2. Open the Start Menu and Launch ***Steam ROM Manager***
|
||||
|
||||
1. The program will now launch and show you a window with parsers.
|
||||
|
||||
<aside>
|
||||
|
||||
***TIP***: Your layout may look different depending on how you installed *Steam ROM Manager*. You may need to go to **Settings → Theme** and change it to *Classic* to follow along.
|
||||
|
||||
</aside>
|
||||
|
||||
2. Switch off all Parsers by hitting the *Toggle Parsers* switch.
|
||||
3. Scroll down the list on the left-hand side and look for a parser called *Nintendo Switch - Eden* and switch it on. This parser may not exist depending on how you installed *Steam ROM Manager* (EmuDeck creates it for you). Follow these steps to create it if it is missing.
|
||||
|
||||
---
|
||||
### Creating the Eden Parser
|
||||
|
||||
1. Select Create Parser and in the *Community Presets* option look for **Nintendo Switch - Yuzu**.
|
||||
2. Change the **Parser title** from *Nintendo Switch - Yuzu* to *Nintendo Switch - Eden.*
|
||||
3. Hit the **Browse** option under the *ROMs directory* section. Select the directory containing your Switch ROMs.
|
||||
4. Under *Steam collections*, you can add a Steam category name. This just organizes the games under a common category in your Steam Library, this is optional but recommended.
|
||||
5. Scroll down slightly to the **Executable Configuration → Executable**, select **Browse** and select the Eden AppImage.
|
||||
6. Leave everything else the same and hit **Save** to save the parser.
|
||||
---
|
||||
|
||||
4. Click the Eden parser to view the options on the right, select **Test** at the bottom of the screen to ensure that *Steam ROM Manager* detects your games correctly.
|
||||
1. *Steam ROM Manager* will start to scan the specified ROMs directory and match them to games. Look over the results to ensure they are accurate. If you do not see any entries - check your parsers ROMs directory field.
|
||||
1. When you are happy with the results, click the **Add Games** → **Parse** to start the actual Parsing.
|
||||
1. The program will now identify the games and pull artwork from [*SteamGridDB*](https://www.steamgriddb.com/).
|
||||
2. Review the game matches and ensure everything is there.
|
||||
|
||||
---
|
||||
|
||||
### Correcting a Mismatch
|
||||
|
||||
If the game is not identified correctly, you may need to tell *Steam ROM Manager* what the game is manually.
|
||||
|
||||
1. Hover over the game card and click the magnifying glass icon.
|
||||
2. Search for the game on the *Search SteamGridDB* section and scroll through the results, selecting the one you want.
|
||||
3. Ensure the *Name* and *Game ID* update in the **Per-App Exceptions** and press **Save and close**. The game should now update.
|
||||
|
||||
---
|
||||
|
||||
### Excluding Matches
|
||||
|
||||
You may want to tell Steam ROM Manager to ignore some files (updates/DLC/etc.) that it finds in the directory. This is how you do so.
|
||||
|
||||
1. Hit the **Exclude Games** button in the bottom right.
|
||||
2. Deselect the game you want to exclude, the poster artwork should go dim and the **Number Excluded** number should increment up. Repeat with any other exclusions you want to add.
|
||||
3. Hit **Save Excludes** when you are happy with your selections.
|
||||
---
|
||||
3. When you are happy with the results, select **Save to Steam** to save the results.
|
||||
1. The program will now start writing the entries into the Steam Library. You should get pop up notifications of the progress, but you can monitor the progress by selecting the **Log** on the left-hand side if needed.
|
||||
2. Restart Steam to have the changes take effect. Check your library to ensure that your games are there, in a category if you defined one in the parser.
|
||||
3. Try to launch a game and ensure everything is working. You are now good to go.
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# User Handbook - Custom Firmware (CFW)
|
||||
|
||||
At the moment of writing, we do not support CFW such as Atmosphere, due to:
|
||||
|
||||
- Lacking the required LLE emulation capabilities to properly emulate the full firmware.
|
||||
- Lack of implementation on some of the key internals.
|
||||
- Nobody has bothered to do it (PRs always welcome!)
|
||||
|
||||
We do however, maintain HLE compatibility with the former mentioned CFW, applications that require Atmosphere to run will run fine in the emulator without any adjustments.
|
||||
|
||||
If they don't run - then that's a bug!
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
# User Handbook - Graphics
|
||||
|
||||
Graphical enhancements and visual quality improvments. This doesn't cover texture mods.
|
||||
|
||||
## Visual Enhancements
|
||||
|
||||
### Anti-aliasing
|
||||
|
|
@ -91,7 +89,7 @@ The OpenGL backend would invoke behaviour that would result in swarst/LLVMpipe w
|
|||
|
||||
### HaikuOS compatibility
|
||||
|
||||
HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purposes `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through.
|
||||
HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (required by the emulator). This leads to HaikuOS being one of the few computer platforms where Vulkan is the only available option for users. If OpenGL is desired, Mesa has to be built manually from source. For debugging purpouses `lavapipe` is recommended over the GPU driver; there is in-kernel support for NVIDIA cards through.
|
||||
|
||||
### Fixes for Windows 10 and above having "Device loss"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
# User Handbook - Installing Mods
|
||||
|
||||
## General Notes
|
||||
|
||||
**Note:** When installing a mod, always read the mod's installation instructions.
|
||||
|
||||
This is especially important if a mod uses a framework such as **ARCropolis**, **Skyline**, or **Atmosphere plugins**. In those cases, follow the framework's instructions instead of using Eden's normal mod folder.
|
||||
|
||||
For example, **Super Smash Bros. Ultimate** uses such a framework. See the related section below for details.
|
||||
|
||||
---
|
||||
|
||||
# Installing Mods for Most Games
|
||||
|
||||
1. Right click a game in the game list.
|
||||
2. Click **"Open Mod Data Location"**.
|
||||
3. Extract the mod into that folder.
|
||||
|
||||
Each mod should be placed inside **its own subfolder**.
|
||||
|
||||
---
|
||||
|
||||
# Enabling or Disabling Mods
|
||||
|
||||
1. Right click the game in the game list.
|
||||
2. Click **Configure Game**.
|
||||
3. In the **Add-Ons** tab, enable or disable mods, updates, and DLC by ticking or unticking their boxes.
|
||||
|
||||
---
|
||||
|
||||
# Important Note About SD Card Paths
|
||||
|
||||
Some mods are designed for real Nintendo Switch consoles and refer to the **SD card root**.
|
||||
|
||||
The emulated SD card is located at:
|
||||
|
||||
```
|
||||
%AppData%\eden\sdmc
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
Switch instruction: sd:/ultimate/mods
|
||||
Eden equivalent: sdmc/ultimate/mods
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Framework-Based Mods (Super Smash Bros. Ultimate)
|
||||
|
||||
Some games require external mod frameworks instead of the built-in mod loader.
|
||||
|
||||
The most common example is **Super Smash Bros. Ultimate**.
|
||||
|
||||
These mods are installed directly to the **emulated SD card**, not the normal Eden mod folder.
|
||||
|
||||
---
|
||||
|
||||
# Installing the ARCropolis Modding Framework
|
||||
|
||||
**Note:** Some mod packs bundle ARCropolis with their installer (for example, Smash Ult-S).
|
||||
|
||||
---
|
||||
|
||||
## 1. Download ARCropolis
|
||||
|
||||
Download the latest release:
|
||||
|
||||
https://github.com/Raytwo/ARCropolis/releases/
|
||||
|
||||
---
|
||||
|
||||
## 2. Install ARCropolis
|
||||
|
||||
Extract the **`atmosphere`** folder into:
|
||||
|
||||
```
|
||||
%AppData%\eden\sdmc
|
||||
```
|
||||
|
||||
This is the **emulated SD card directory**.
|
||||
|
||||
Verify installation by checking that the following file exists:
|
||||
|
||||
```
|
||||
sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins\libarcropolis.nro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Download Skyline
|
||||
|
||||
Download the latest Skyline release:
|
||||
|
||||
https://github.com/skyline-dev/skyline/releases
|
||||
|
||||
Skyline used to be bundled with ARCropolis but is now distributed separately to avoid compatibility issues caused by outdated bundled versions.
|
||||
|
||||
---
|
||||
|
||||
## 4. Install Skyline
|
||||
|
||||
Extract the **`exefs`** folder into:
|
||||
|
||||
```
|
||||
sdmc\atmosphere\contents\01006A800016E000
|
||||
```
|
||||
|
||||
The `exefs` folder should be **next to the `romfs` folder**.
|
||||
|
||||
Verify installation by checking that the following file exists:
|
||||
|
||||
```
|
||||
%AppData%\eden\sdmc\atmosphere\contents\01006A800016E000\exefs\subsdk9
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Launch the Game Once
|
||||
|
||||
Start the game and make sure you see the **ARCropolis version text on the title screen**.
|
||||
|
||||
This will also create the folders required for installing mods.
|
||||
|
||||
---
|
||||
|
||||
## 6. Install Smash Ultimate Mods
|
||||
|
||||
Install mods inside:
|
||||
|
||||
```
|
||||
sdmc\ultimate\mods
|
||||
```
|
||||
|
||||
Each mod must be placed inside **its own subfolder**.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
sdmc\ultimate\mods\ExampleMod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## ARCropolis text does not appear on startup
|
||||
|
||||
Check the following:
|
||||
|
||||
- `libarcropolis.nro` exists in:
|
||||
|
||||
```
|
||||
sdmc\atmosphere\contents\01006A800016E000\romfs\skyline\plugins
|
||||
```
|
||||
|
||||
- `subsdk9` exists in:
|
||||
|
||||
```
|
||||
sdmc\atmosphere\contents\01006A800016E000\exefs
|
||||
```
|
||||
|
||||
- Files were extracted to:
|
||||
|
||||
```
|
||||
%AppData%\eden\sdmc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mods are not loading
|
||||
|
||||
Make sure mods are installed inside:
|
||||
|
||||
```
|
||||
sdmc\ultimate\mods
|
||||
```
|
||||
|
||||
Each mod must have its **own subfolder**.
|
||||
|
||||
Correct example:
|
||||
|
||||
```
|
||||
sdmc\ultimate\mods\ExampleMod
|
||||
```
|
||||
|
||||
Incorrect example:
|
||||
|
||||
```
|
||||
sdmc\ultimate\mods\ExampleMod\ExampleMod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installing mods in the wrong folder
|
||||
|
||||
ARCropolis mods **do not go in Eden's normal mod folder**.
|
||||
|
||||
Do **not** install Smash mods here:
|
||||
|
||||
```
|
||||
user\load\01006A800016E000
|
||||
```
|
||||
|
||||
That folder is only used for traditional **RomFS mods**, not ARCropolis.
|
||||
|
|
@ -4,14 +4,10 @@ The "FAQ".
|
|||
|
||||
This handbook is primarily aimed at the end-user - baking useful knowledge for enhancing their emulation experience.
|
||||
|
||||
A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md).
|
||||
|
||||
## Basics
|
||||
|
||||
- **[The Basics](Basics.md)**
|
||||
- **[Quickstart](./QuickStart.md)**
|
||||
- **[Settings](./Settings.md)**
|
||||
- **[Installing Mods](./Mods.md)**
|
||||
- **[Run On macOS](./RunOnMacOS.md)**
|
||||
- **[Audio](Audio.md)**
|
||||
- **[Graphics](Graphics.md)**
|
||||
|
|
@ -21,29 +17,22 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/
|
|||
- **[Using Amiibo](./UsingAmiibo.md)**
|
||||
- **[Using Cheats](./UsingCheats.md)**
|
||||
- **[Importing Saves](./ImportingSaves.md)**
|
||||
- **[Add Eden to Steam ROM Manager](./AddEdenToSRM.md)**
|
||||
- **[Add Games to Steam ROM Manager](./AddGamesToSRM.md)**
|
||||
- **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)**
|
||||
- **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)**
|
||||
- **[Controller Profiles](./ControllerProfiles.md)**
|
||||
- **[Alter Date & Time](./AlterDateTime.md)**
|
||||
|
||||
## 3rd-party Integration
|
||||
|
||||
- **[Configuring Steam ROM Manager](./SteamROM.md)**
|
||||
- **[Server hosting](ServerHosting.md)**
|
||||
- **[Syncthing Guide](./SyncthingGuide.md)**
|
||||
- **[Third Party](./ThirdParty.md)**
|
||||
- **[Obtainium](./ThirdParty.md#configuring-obtainium)**
|
||||
- **[ES-DE](./ThirdParty.md#configuring-es-de)**
|
||||
- **[Mirrors](./ThirdParty.md#mirrors)**
|
||||
|
||||
## Advanced
|
||||
|
||||
- **[Custom Firmware](./CFW.md)**
|
||||
- **[How To Access Logs](./HowToAccessLogs.md)**
|
||||
- **[Gyro Controls](./GyroControls.md)**
|
||||
- **[Platforms and Architectures](Architectures.md)**
|
||||
- **[Server hosting](ServerHosting.md)**
|
||||
- **[Command Line](CommandLine.md)**
|
||||
- **[Native Application Development](Native.md)**
|
||||
- **[Adding Boolean Settings Toggles](AddingBooleanToggles.md)**
|
||||
- **[Adding Debug Knobs](./AddingDebugKnobs.md)**
|
||||
- **[Syncthing Guide](./SyncthingGuide.md)**
|
||||
- **[Testing](Testing.md)**
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
# User Handbook - Settings
|
||||
|
||||
As the emulator continues to grow, so does the number of settings that come and go.
|
||||
|
||||
Most of the development adds new settings that enhance performance/compatibility, only to be removed later in newer versions due to newfound discoveries or because they were "a hacky workaround".
|
||||
|
||||
As such, this guide will NOT mention those kind of settings, we'd rather mention settings which have a long shelf time (i.e won't get removed in future releases) and are likely to be unchanged.
|
||||
|
||||
Some of the options are self explainatory, and they do exactly what they say they do (i.e "Pause when not in focus"); such options will be also skipped due to triviality.
|
||||
|
||||
## Foreword
|
||||
|
||||
Before touching the settings, please see the game boots with stock options. We try our best to ensure users can boot any game using the default settings. If they don't work, then you may try fiddling with options - but please, first use stock options.
|
||||
|
||||
## General
|
||||
|
||||
- `General/Force X11 as Graphics Backend`: Wayland on *NIX has prominent issues that are unlikely to be resolved; the kind that are "not our fault, it's Wayland issue", this "temporary" hack forces X11 as the backend, regardless of the desktop manager's default.
|
||||
- `General/Enable Gamemode`: This only does anything when you have Feral Interactive's Gamemode library installed somewhere, if you do, this will help boost FPS by telling the OS to explicitly prioritize *this* application for "gaming" - only for *NIX systems.
|
||||
- `Hotkeys`: Deceptively to remove a hotkey you must right click and a menu will appear to remove that specific hotkey.
|
||||
- `UI/Language`: Changes language *of the interface* NOT the emulated program!
|
||||
- `Debug/Enable Auto Stub`: May help to "fix" some games by just lying and saying that everything they do returns "success" instead of outright crashing for any function/service that is NOT implemented.
|
||||
- `Debug/Show log in console`: Does as said, note that the program may need to be reopened (Windows) for changes to take effect.
|
||||
- `Debug/Flush log output`: Classically, every write to the log is "buffered", that is, changes aren't written to the disk UNTIL the program has decided it is time to write, until then it keeps data in a buffer which resides on RAM. If the program crashes, the OS will automatically discard said buffer (any RAM associated with a dead process is automatically discarded/reused for some other purpose); this means critical data may not be logged to the disk on time, which may lead to missing log lines. Use this if you're wanting to remove that factor when debugging, sometimes a hard crash may "eat" some of the log lines IF this option isn't enabled.
|
||||
- `Debug/Disable Macro HLE:` The emulator has HLE emulation of macro programs for Maxwell, this means that some details are purpousefully skipped; this option forces all macro programs to be ran without skipping anything.
|
||||
|
||||
## System
|
||||
|
||||
- `System/RNG Seed`: Set to 0 (and uncheck) to disable ASLR systemwide (this makes mods like CTGP to stop working); by default it enables ASLR to replicate console behaviour.
|
||||
- `Network/Enable Airplane Mode`: Enable this if a game is crashing before loading AND the logs mention anything related to "web" or "internet" services.
|
||||
|
||||
## CPU
|
||||
|
||||
- `CPU/Virtual table bouncing`: Some games have the tendency to crash on loading due to an indirect bad jump (Pokemon ZA being the worst offender); this option lies to the game and tells it to just pretend it never executed a given function. This is fine for most casual users, but developers of switch applications **must** disable this. This temporary "hack" should hopefully be gone in 6-7 months from now on.
|
||||
- `Fastmem`, aka. `CPU/Enable Host MMU`: Enables "fastmem"; a detailed description of fastmem can be found [here](../dynarmic/Design.md#fast-memory-fastmem).
|
||||
- `CPU/Unsafe FMA`: Enables deliberate innacurate FMA behaviour which may affect how FMA returns any given operation - this may introduce tiny floating point errors which can cascade in sensitive code (i.e FFmpeg).
|
||||
- `CPU/Faster FRSQRTE and FRECPE`: Introduces accuracy errors on square root and reciprocals in exchange for less checks - this introduces inaccuracies with some cases but it's mostly safe.
|
||||
- `CPU/Faster ASIMD Instructions`: Skips rounding mode checks for ARM ASIMD instructions - this means some code dpeending on these rounding modes may misbehave.
|
||||
- `CPU/Disable address space checks`: Before each memory access, the emulator checks the address is in range, if not it faults; this option makes it so the emulator skips the check entirely (which may be expensive for a myriad of reasons). However at the same time this allows the guest program to "break out" of the emulation context by writing to arbitrary addresses.
|
||||
- `CPU/Ignore global monitor`: This relies on a quirk present on x86 to avoid the ARM global monitor emulation, this may increase performance in mutex-heavy contexts (i.e games waiting for next frames or such); but also can cause deadlocks and fun to debug issues.
|
||||
|
||||
It is important to note the majority of precision-reducing instructions do not benefit cases where they are not used, which means the performance gains will vary per game.
|
||||
|
||||
# Graphics
|
||||
|
||||
See also [an extended breakdown of some options](./Graphics.md).
|
||||
|
||||
- `Extras/Extended Dynamic State` and `Extras/Vertex Input Dynamic State`: These Vulkan extensions essentially allow you to reuse the same pipeline but just change the state between calls (so called "dynamic state"); the "extended" levels signifies how much state can be placed on this "dynamic" range, for example the amount of depth culling to use can be placed on the dynamic state, avoiding costly reloads and flushes. While this by itself is a fine option, SOME vendors (notably PowerVR and Mali) have problems with anything related to EDS3. EDS3 contains EDS2, and EDS2 contains EDS1. Essentially this means more extended data the driver has to keep track of, at the benefit of avoiding costly flushes.
|
||||
- `Advanced/Use persistent cache`: This saves compiled shaders onto the disk, independent of any driver's own disk saved shaders (yes, some drivers, notably NVIDIA, save a secondary shader cache onto disk) - disable this only if you're debugging or working on the GPU backend. This option is meant to massively help to reduce shader stutters (after playing for one session that compiles them).
|
||||
- `Advanced/Use Vulkan pipeline cache`: This is NOT the same as `Use persistent cache`; it's a separate flag that tells the Vulkan backend to create pipeline caches, which are a detail that can be used to massively improve performance and remove pipeline creation overhead. This is a Vulkan feature.
|
||||
|
||||
## Controls
|
||||
|
||||
Most of the controls should work out of the box. If not, please use a joystick calibrator to ensure it's not an issue with your own controller, for example:
|
||||
- https://github.com/dkosmari/calibrate-joystick
|
||||
|
|
@ -7,62 +7,3 @@ While most of the links mentioned in this guide are relatively "safe"; we urge u
|
|||
- [Nightly Eden builds](https://github.com/pflyly/eden-nightly)
|
||||
- [NixOS Eden Flake](https://github.com/Grantimatter/eden-flake)
|
||||
- [ES-DE Frontend Support](https://github.com/GlazedBelmont/es-de-android-custom-systems)
|
||||
|
||||
## Mirrors
|
||||
|
||||
The main origin repository is always at https://git.eden-emu.dev/eden-emu/eden.
|
||||
|
||||
- https://github.com/eden-emulator/mirror
|
||||
- https://git.crueter.xyz/mirror/eden
|
||||
- https://collective.taymaerz.de/eden/eden
|
||||
|
||||
Other mirrors obviously exist on the internet, but we can't guarantee their reliability and/or availability.
|
||||
|
||||
If you're someone wanting to make a mirror, simply setup forgejo and automatically mirror from the origin repository. Or you could mirror a mirror to save us bandwidth... your choice!
|
||||
|
||||
## Configuring Obtainium
|
||||
|
||||
Very nice handy app, here's a quick rundown how to configure:
|
||||
|
||||
1. Copy the URL: https://git.eden-emu.dev/eden-emu/eden/ (or one of your favourite mirrors)
|
||||
2. Open Obtainium and tap `Add App`.
|
||||
3. Paste the URL into the `App Source URL` field.
|
||||
4. Override Source: Look for the `Override Source` dropdown menu and select `Forgejo (Codeberg)`.
|
||||
5. Click `Add:` Obtainium should now be able to parse the releases and find the APK files.
|
||||
|
||||
Note: Even though the site isn't Codeberg, it uses the same Forgejo/Gitea backend, and this setting tells Obtainium how to read the release data.
|
||||
|
||||
## Configuring ES-DE
|
||||
|
||||
### Method 1
|
||||
|
||||
1. Download ZIP from [here](https://github.com/GlazedBelmont/es-de-android-custom-systems)
|
||||
2. Unzip the file and extract `es_systems.xml` and `es_find_rules.xml` to `\Odin2\Internal shared storage\ES-DE\custom_systems`.
|
||||
3. Press `Start -> Other Settings -> Alternative Emulators` and set it to Eden (Standalone).
|
||||
|
||||
### Method 2
|
||||
|
||||
1. Navigate to `\Odin2\Internal shared storage\ES-DE\custom_systems`.
|
||||
2. Add this to your `es_find_rules.xml`:
|
||||
|
||||
```xml
|
||||
<!-- Standard aka. normal release -->
|
||||
<emulator name="EDEN">
|
||||
<rule type="androidpackage">
|
||||
<entry>dev.eden.eden_emulator/org.yuzu.yuzu_emu.activities.EmulationActivity</entry>
|
||||
</rule>
|
||||
</emulator>
|
||||
|
||||
<!-- Optimized -->
|
||||
<emulator name="EDEN">
|
||||
<rule type="androidpackage">
|
||||
<entry>com.miHoYo.Yuanshen/org.yuzu.yuzu_emu.activities.EmulationActivity</entry>
|
||||
</rule>
|
||||
</emulator>
|
||||
```
|
||||
|
||||
3. Add this line of text to your `es_systems.xml` underneath where the rest of your switch system entries are:
|
||||
|
||||
```xml
|
||||
<command label="Eden (Standalone)">%EMULATOR_EDEN% %ACTION%=android.nfc.action.TECH_DISCOVERED %DATA%=%ROMPROVIDER%</command>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pkgs.mkShellNoCC {
|
|||
# libraries
|
||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||
enet libopus vulkan-headers vulkan-utility-libraries
|
||||
spirv-tools spirv-headers vulkan-loader unzip
|
||||
spirv-tools spirv-headers vulkan-loader unzip mbedtls
|
||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||
libusb1 cubeb
|
||||
# eden
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ android {
|
|||
listOf(
|
||||
"-DENABLE_QT=0", // Don't use QT
|
||||
"-DENABLE_WEB_SERVICE=1", // Enable web service
|
||||
"-DENABLE_OPENSSL=ON",
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DYUZU_USE_CPM=ON",
|
||||
"-DCPMUTIL_FORCE_BUNDLED=ON",
|
||||
|
|
|
|||
|
|
@ -152,10 +152,6 @@ object NativeLibrary {
|
|||
|
||||
external fun surfaceDestroyed()
|
||||
|
||||
external fun getAppletCaptureBuffer(): ByteArray
|
||||
external fun getAppletCaptureWidth(): Int
|
||||
external fun getAppletCaptureHeight(): Int
|
||||
|
||||
/**
|
||||
* Unpauses emulation from a paused state.
|
||||
*/
|
||||
|
|
@ -607,12 +603,6 @@ object NativeLibrary {
|
|||
*/
|
||||
external fun addFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
|
||||
* game-folder external-content mounting.
|
||||
*/
|
||||
external fun addGameFolderFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Clears all files added to the manual filesystem provider in our EmulationSession instance
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
|
|
@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
|||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
nfcReader.stopScanning()
|
||||
stopMotionSensorListener()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -339,10 +339,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
|||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
|
||||
return
|
||||
}
|
||||
|
||||
val rotation = this.display?.rotation
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
flipMotionOrientation = true
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.fetcher
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
|
||||
|
|
@ -16,20 +15,8 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
|
|||
state: RecyclerView.State
|
||||
) {
|
||||
outRect.bottom = spacing
|
||||
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
|
||||
if (position == 0) {
|
||||
if (parent.getChildAdapterPosition(view) == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
|
||||
// If the item is in the first row, but NOT in first column add top spacing as well
|
||||
val layoutManager = parent.layoutManager
|
||||
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||
MAX_ANISOTROPY("max_anisotropy"),
|
||||
THEME("theme"),
|
||||
THEME_MODE("theme_mode"),
|
||||
STATIC_THEME_COLOR("static_theme_color"),
|
||||
APP_LANGUAGE("app_language"),
|
||||
OVERLAY_SCALE("control_scale"),
|
||||
OVERLAY_OPACITY("control_opacity"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
|
|
@ -68,9 +68,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
|
||||
clearDialogState()
|
||||
when (item) {
|
||||
when (val item = settingsViewModel.clickedItem) {
|
||||
is AnalogInputSetting -> {
|
||||
val stickParam = NativeInput.getStickParam(
|
||||
item.playerIndex,
|
||||
|
|
@ -109,17 +107,12 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
}
|
||||
|
||||
else -> {
|
||||
item.setting.reset()
|
||||
settingsViewModel.clickedItem!!.setting.reset()
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
|
||||
clearDialogState()
|
||||
}
|
||||
.setOnCancelListener {
|
||||
clearDialogState()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +186,27 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
updateButtonState(isValid)
|
||||
}
|
||||
|
||||
/*
|
||||
* xbzk: these two events, along with attachRepeat feature,
|
||||
* were causing spinbox buttons to respond twice per press
|
||||
* cutting these out to retain accelerated press functionality
|
||||
* TODO: clean this out later if no issues arise
|
||||
*
|
||||
spinboxBinding.buttonDecrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current - 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
|
||||
spinboxBinding.buttonIncrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current + 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
*/
|
||||
|
||||
fun attachRepeat(button: View, delta: Int) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
var runnable: Runnable? = null
|
||||
|
|
@ -425,13 +439,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
|
||||
private fun closeDialog() {
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
clearDialogState()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun clearDialogState() {
|
||||
settingsViewModel.clickedItem = null
|
||||
settingsViewModel.setSliderProgress(-1f)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
||||
|
|
|
|||
|
|
@ -1066,10 +1066,7 @@ class SettingsFragmentPresenter(
|
|||
IntSetting.THEME.getValueAsString()
|
||||
|
||||
override val defaultValue: Int = IntSetting.THEME.defaultValue
|
||||
override fun reset() {
|
||||
IntSetting.THEME.setInt(defaultValue)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
override fun reset() = IntSetting.THEME.setInt(defaultValue)
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.app_settings))
|
||||
|
|
@ -1127,24 +1124,23 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||
YuzuApplication.appContext
|
||||
)
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal)
|
||||
|
||||
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
override fun setInt(value: Int) {
|
||||
IntSetting.STATIC_THEME_COLOR.setInt(value)
|
||||
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, value) }
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
|
||||
override val key: String = IntSetting.STATIC_THEME_COLOR.key
|
||||
override val key: String = Settings.PREF_STATIC_THEME_COLOR
|
||||
override val isRuntimeModifiable: Boolean = true
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
IntSetting.STATIC_THEME_COLOR.getValueAsString(needsGlobal)
|
||||
|
||||
override val defaultValue: Any = IntSetting.STATIC_THEME_COLOR.defaultValue
|
||||
|
||||
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString()
|
||||
override val defaultValue: Any = 0
|
||||
override fun reset() {
|
||||
IntSetting.STATIC_THEME_COLOR.reset()
|
||||
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, 0) }
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.BatteryManager
|
||||
import android.os.BatteryManager.*
|
||||
|
|
@ -98,7 +97,6 @@ import org.yuzu.yuzu_emu.utils.collect
|
|||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.or
|
||||
|
|
@ -143,7 +141,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
private var wasInputOverlayAutoHidden = false
|
||||
private var overlayTouchActive = false
|
||||
private var pausedFrameBitmap: Bitmap? = null
|
||||
|
||||
var shouldUseCustom = false
|
||||
private var isQuickSettingsMenuOpen = false
|
||||
|
|
@ -706,12 +703,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
||||
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
||||
|
||||
binding.pausedIcon.setOnClickListener {
|
||||
if (this::emulationState.isInitialized && emulationState.isPaused) {
|
||||
resumeEmulationFromUi()
|
||||
}
|
||||
}
|
||||
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
|
||||
val lockMode = IntSetting.LOCK_DRAWER.getInt()
|
||||
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
||||
|
|
@ -737,9 +728,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
when (it.itemId) {
|
||||
R.id.menu_pause_emulation -> {
|
||||
if (emulationState.isPaused) {
|
||||
resumeEmulationFromUi()
|
||||
emulationState.run(false)
|
||||
updatePauseMenuEntry(false)
|
||||
} else {
|
||||
pauseEmulationAndCaptureFrame()
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
}
|
||||
binding.inGameMenu.requestFocus()
|
||||
true
|
||||
|
|
@ -833,7 +826,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
R.id.menu_exit -> {
|
||||
clearPausedFrame()
|
||||
emulationState.stop()
|
||||
NativeConfig.reloadGlobalConfig()
|
||||
emulationViewModel.setIsEmulationStopping(true)
|
||||
|
|
@ -1205,71 +1197,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private fun pauseEmulationAndCaptureFrame() {
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
capturePausedFrameFromCore()
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
private fun capturePausedFrameFromCore() {
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
val frameData = NativeLibrary.getAppletCaptureBuffer()
|
||||
val width = NativeLibrary.getAppletCaptureWidth()
|
||||
val height = NativeLibrary.getAppletCaptureHeight()
|
||||
if (frameData.isEmpty() || width <= 0 || height <= 0) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Paused frame capture returned empty/invalid data. " +
|
||||
"size=${frameData.size}, width=$width, height=$height"
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val expectedSize = width * height * 4
|
||||
if (frameData.size < expectedSize) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Paused frame buffer smaller than expected. " +
|
||||
"size=${frameData.size}, expected=$expectedSize"
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize))
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
pausedFrameBitmap?.recycle()
|
||||
pausedFrameBitmap = bitmap
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePausedFrameVisibility() {
|
||||
val b = _binding ?: return
|
||||
val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused
|
||||
b.pausedIcon.setVisible(showPausedUi)
|
||||
|
||||
val bitmap = if (showPausedUi) pausedFrameBitmap else null
|
||||
b.pausedFrameImage.setImageBitmap(bitmap)
|
||||
b.pausedFrameImage.setVisible(bitmap != null)
|
||||
}
|
||||
|
||||
private fun resumeEmulationFromUi() {
|
||||
clearPausedFrame()
|
||||
emulationState.resume()
|
||||
updatePauseMenuEntry(emulationState.isPaused)
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
private fun clearPausedFrame() {
|
||||
val b = _binding
|
||||
b?.pausedFrameImage?.setVisible(false)
|
||||
b?.pausedFrameImage?.setImageDrawable(null)
|
||||
pausedFrameBitmap?.recycle()
|
||||
pausedFrameBitmap = null
|
||||
}
|
||||
|
||||
private fun handleLoadAmiiboSelection(): Boolean {
|
||||
val binding = _binding ?: return true
|
||||
|
||||
|
|
@ -1363,9 +1290,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
override fun onPause() {
|
||||
if (this::emulationState.isInitialized) {
|
||||
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
||||
pauseEmulationAndCaptureFrame()
|
||||
} else {
|
||||
updatePausedFrameVisibility()
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
}
|
||||
}
|
||||
super.onPause()
|
||||
|
|
@ -1375,7 +1301,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
super.onDestroyView()
|
||||
amiiboLoadJob?.cancel()
|
||||
amiiboLoadJob = null
|
||||
clearPausedFrame()
|
||||
_binding?.surfaceInputOverlay?.touchEventListener = null
|
||||
_binding = null
|
||||
isAmiiboPickerOpen = false
|
||||
|
|
@ -1396,7 +1321,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
b.inGameMenu.post {
|
||||
if (!this::emulationState.isInitialized || _binding == null) return@post
|
||||
updatePauseMenuEntry(emulationState.isPaused)
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1836,7 +1760,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
// Only update surface reference, don't trigger state changes
|
||||
emulationState.updateSurfaceReference(holder.surface)
|
||||
}
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
|
|
@ -2167,29 +2090,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun resume() {
|
||||
if (state != State.PAUSED) {
|
||||
Log.warning("[EmulationFragment] Resume called while emulation is not paused.")
|
||||
return
|
||||
}
|
||||
if (!emulationCanStart.invoke()) {
|
||||
Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.")
|
||||
return
|
||||
}
|
||||
val currentSurface = surface
|
||||
if (currentSurface == null || !currentSurface.isValid) {
|
||||
Log.debug("[EmulationFragment] Resume requested with invalid surface.")
|
||||
return
|
||||
}
|
||||
|
||||
NativeLibrary.surfaceChanged(currentSurface)
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.unpauseEmulation()
|
||||
NativeLibrary.playTimeManagerStart()
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun changeProgram(programIndex: Int) {
|
||||
emulationThread.join()
|
||||
|
|
@ -2211,7 +2111,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
@Synchronized
|
||||
fun updateSurface() {
|
||||
if (surface != null && state == State.RUNNING) {
|
||||
if (surface != null) {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
}
|
||||
}
|
||||
|
|
@ -2227,20 +2127,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
@Synchronized
|
||||
fun clearSurface() {
|
||||
if (surface == null) {
|
||||
Log.debug("[EmulationFragment] clearSurface called, but surface already null.")
|
||||
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
|
||||
} else {
|
||||
if (state == State.RUNNING) {
|
||||
pause()
|
||||
}
|
||||
NativeLibrary.surfaceDestroyed()
|
||||
surface = null
|
||||
Log.debug("[EmulationFragment] Surface destroyed.")
|
||||
when (state) {
|
||||
State.PAUSED -> Log.debug(
|
||||
State.RUNNING -> {
|
||||
state = State.PAUSED
|
||||
}
|
||||
|
||||
State.PAUSED -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation paused."
|
||||
)
|
||||
|
||||
else -> Log.debug(
|
||||
else -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation stopped."
|
||||
)
|
||||
}
|
||||
|
|
@ -2248,35 +2148,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
private fun runWithValidSurface(programIndex: Int = 0) {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
if (!emulationCanStart.invoke()) {
|
||||
return
|
||||
}
|
||||
val currentSurface = surface
|
||||
if (currentSurface == null || !currentSurface.isValid) {
|
||||
Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.")
|
||||
return
|
||||
}
|
||||
|
||||
when (state) {
|
||||
State.STOPPED -> {
|
||||
NativeLibrary.surfaceChanged(currentSurface)
|
||||
emulationThread = Thread({
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||
NativeLibrary.run(gamePath, programIndex, true)
|
||||
}, "NativeEmulation")
|
||||
emulationThread.start()
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
State.PAUSED -> {
|
||||
Log.debug(
|
||||
"[EmulationFragment] Surface restored while emulation paused; " +
|
||||
"waiting for explicit resume."
|
||||
)
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.unpauseEmulation()
|
||||
NativeLibrary.playTimeManagerStart()
|
||||
}
|
||||
|
||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||
}
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
private enum class State {
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ class AddonViewModel : ViewModel() {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if there are multiple update versions
|
||||
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
|
||||
val hasMultipleUpdates = updates.size > 1
|
||||
|
||||
NativeConfig.setDisabledAddons(
|
||||
game!!.programId,
|
||||
_patchList.value.mapNotNull {
|
||||
|
|
@ -136,7 +140,7 @@ class AddonViewModel : ViewModel() {
|
|||
if (PatchType.from(it.type) == PatchType.Update) {
|
||||
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
|
||||
it.name
|
||||
} else if (it.numericVersion != 0L) {
|
||||
} else if (hasMultipleUpdates) {
|
||||
"Update@${it.numericVersion}"
|
||||
} else {
|
||||
it.name
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay
|
||||
|
|
@ -20,6 +20,7 @@ import android.os.Looper
|
|||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.WindowInsets
|
||||
|
|
@ -41,10 +42,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||
|
||||
/**
|
||||
* Draws the interactive input overlay on top of the
|
||||
* emulation rendering surface.
|
||||
* [SurfaceView] that is rendering emulation.
|
||||
*/
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
View(context, attrs),
|
||||
SurfaceView(context, attrs),
|
||||
OnTouchListener {
|
||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
|
|
|
|||
|
|
@ -424,9 +424,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
)
|
||||
|
||||
val uriString = result.toString()
|
||||
val folder = gamesViewModel.folders.value.firstOrNull {
|
||||
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
|
||||
}
|
||||
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
|
||||
if (folder != null) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
|
|
|
|||
|
|
@ -61,12 +61,6 @@ object DirectoryInitialization {
|
|||
saveConfig = true
|
||||
}
|
||||
|
||||
val staticThemeColor = preferences.migratePreference<Int>(Settings.PREF_STATIC_THEME_COLOR)
|
||||
if (staticThemeColor != null) {
|
||||
IntSetting.STATIC_THEME_COLOR.setInt(staticThemeColor)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val blackBackgrounds =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
|
||||
if (blackBackgrounds != null) {
|
||||
|
|
|
|||
|
|
@ -51,24 +51,11 @@ object GameHelper {
|
|||
|
||||
// Scan External Content directories and register all NSP/XCI files
|
||||
val externalContentDirs = NativeConfig.getExternalContentDirs()
|
||||
val uniqueExternalContentDirs = linkedSetOf<String>()
|
||||
externalContentDirs.forEach { externalDir ->
|
||||
if (externalDir.isNotEmpty()) {
|
||||
uniqueExternalContentDirs.add(externalDir)
|
||||
}
|
||||
}
|
||||
|
||||
val mountedContainerUris = mutableSetOf<String>()
|
||||
for (externalDir in uniqueExternalContentDirs) {
|
||||
for (externalDir in externalContentDirs) {
|
||||
if (externalDir.isNotEmpty()) {
|
||||
val externalDirUri = externalDir.toUri()
|
||||
if (FileUtil.isTreeUriValid(externalDirUri)) {
|
||||
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
|
||||
val containerUri = it.uri.toString()
|
||||
if (mountedContainerUris.add(containerUri)) {
|
||||
NativeLibrary.addFileToFilesystemProvider(containerUri)
|
||||
}
|
||||
}
|
||||
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,13 +65,10 @@ object GameHelper {
|
|||
val gameDirUri = gameDir.uriString.toUri()
|
||||
val isValid = FileUtil.isTreeUriValid(gameDirUri)
|
||||
if (isValid) {
|
||||
val scanDepth = if (gameDir.deepScan) 3 else 1
|
||||
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(gameDirUri),
|
||||
scanDepth,
|
||||
mountedContainerUris
|
||||
if (gameDir.deepScan) 3 else 1
|
||||
)
|
||||
} else {
|
||||
badDirs.add(index)
|
||||
|
|
@ -119,10 +103,9 @@ object GameHelper {
|
|||
// be done better imo.
|
||||
private val externalContentExtensions = setOf("nsp", "xci")
|
||||
|
||||
private fun scanContentContainersRecursive(
|
||||
private fun scanExternalContentRecursive(
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int,
|
||||
onContainerFound: (MinimalDocumentFile) -> Unit
|
||||
depth: Int
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
|
|
@ -130,15 +113,14 @@ object GameHelper {
|
|||
|
||||
files.forEach {
|
||||
if (it.isDirectory) {
|
||||
scanContentContainersRecursive(
|
||||
scanExternalContentRecursive(
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1,
|
||||
onContainerFound
|
||||
depth - 1
|
||||
)
|
||||
} else {
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
if (externalContentExtensions.contains(extension)) {
|
||||
onContainerFound(it)
|
||||
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -147,8 +129,7 @@ object GameHelper {
|
|||
private fun addGamesRecursive(
|
||||
games: MutableList<Game>,
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int,
|
||||
mountedContainerUris: MutableSet<String>
|
||||
depth: Int
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
|
|
@ -159,20 +140,11 @@ object GameHelper {
|
|||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1,
|
||||
mountedContainerUris
|
||||
depth - 1
|
||||
)
|
||||
} else {
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
val filePath = it.uri.toString()
|
||||
|
||||
if (externalContentExtensions.contains(extension) &&
|
||||
mountedContainerUris.add(filePath)) {
|
||||
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
||||
}
|
||||
|
||||
if (Game.extensions.contains(extension)) {
|
||||
val game = getGame(it.uri, true, false)
|
||||
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
|
||||
val game = getGame(it.uri, true)
|
||||
if (game != null) {
|
||||
games.add(game)
|
||||
}
|
||||
|
|
@ -181,20 +153,14 @@ object GameHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun getGame(
|
||||
uri: Uri,
|
||||
addedToLibrary: Boolean,
|
||||
registerFilesystemProvider: Boolean = true
|
||||
): Game? {
|
||||
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
|
||||
val filePath = uri.toString()
|
||||
if (!GameMetadata.getIsValid(filePath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (registerFilesystemProvider) {
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
}
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
|
||||
var name = GameMetadata.getTitle(filePath)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
|
@ -80,14 +80,16 @@ object PathUtil {
|
|||
}
|
||||
}
|
||||
|
||||
// This really shouldn't be necessary, but the Android API seemingly
|
||||
// doesn't have a way of doing this?
|
||||
// Apparently, on certain devices the mount location can vary, so add
|
||||
// extra cases here if we discover any new ones.
|
||||
fun getRemovableStoragePath(idString: String): String? {
|
||||
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||
var pathFile: File
|
||||
|
||||
for (mountPath in possibleMountPaths) {
|
||||
val pathFile = File(mountPath);
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
pathFile = File("/mnt/media_rw/$idString");
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
|
@ -52,7 +52,7 @@ object ThemeHelper {
|
|||
}
|
||||
|
||||
private fun getSelectedStaticThemeColor(): Int {
|
||||
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
|
||||
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
val themes = arrayOf(
|
||||
R.style.Theme_Eden_Main,
|
||||
R.style.Theme_Yuzu_Main_Violet,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
|
|
@ -11,7 +11,8 @@ import android.graphics.*
|
|||
import android.util.AttributeSet
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
class GradientBorderCardView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
|
@ -43,7 +44,12 @@ class GradientBorderCardView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
private fun updateThemeState() {
|
||||
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val themeIndex = try {
|
||||
prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
} catch (e: Exception) {
|
||||
0 // Default to Eden theme if error
|
||||
}
|
||||
isEdenTheme = themeIndex == 0
|
||||
invalidate()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
|
|
@ -27,7 +27,10 @@ if (ARCHITECTURE_arm64)
|
|||
target_link_libraries(yuzu-android PRIVATE adrenotools)
|
||||
endif()
|
||||
|
||||
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
|
||||
if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE)
|
||||
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
|
||||
endif()
|
||||
|
||||
if (ENABLE_UPDATE_CHECKER)
|
||||
target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -33,12 +33,6 @@ void AndroidConfig::ReadAndroidValues() {
|
|||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
|
||||
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
|
||||
std::string("ext_content_from_game_dirs"),
|
||||
std::make_optional(
|
||||
Settings::values.ext_content_from_game_dirs.GetDefault()));
|
||||
EndGroup();
|
||||
ReadOverlayValues();
|
||||
}
|
||||
ReadDriverValues();
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ namespace AndroidSettings {
|
|||
|
||||
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
|
||||
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
|
||||
Settings::Setting<s32> static_theme_color{linkage, 5, "static_theme_color", Settings::Category::Android};
|
||||
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
|
||||
Settings::Category::Android};
|
||||
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -17,14 +14,6 @@
|
|||
#include "jni/native.h"
|
||||
|
||||
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
if (!surface) {
|
||||
LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface");
|
||||
m_window_width = 0;
|
||||
m_window_height = 0;
|
||||
window_info.render_surface = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_window_width = ANativeWindow_getWidth(surface);
|
||||
m_window_height = ANativeWindow_getHeight(surface);
|
||||
|
||||
|
|
|
|||
|
|
@ -96,11 +96,6 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
|
|||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
|
|
|
|||
|
|
@ -89,8 +89,6 @@
|
|||
#include "jni/native.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/capture.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
|
|
@ -217,8 +215,107 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_manual_provider->AddEntriesFromContainer(file)) {
|
||||
return;
|
||||
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
|
||||
|
||||
if (extension == "nsp") {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Meta) {
|
||||
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
|
||||
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto section0 = meta_nca->GetSubdirectories();
|
||||
if (!section0.empty()) {
|
||||
for (const auto& meta_file : section0[0]->GetFiles()) {
|
||||
if (meta_file->GetExtension() == "cnmt") {
|
||||
FileSys::CNMT cnmt(meta_file);
|
||||
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Control &&
|
||||
title_type == FileSys::TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = FileSys::ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type == FileSys::TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
std::string version_string;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
version_string = str_it->second;
|
||||
}
|
||||
|
||||
m_manual_provider->AddEntryWithVersion(
|
||||
title_type, content_type, title_id, version, version_string,
|
||||
nca->GetBaseFile());
|
||||
|
||||
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
|
||||
title_id, version, version_string);
|
||||
} else {
|
||||
// Use regular AddEntry for non-updates
|
||||
m_manual_provider->AddEntry(title_type, content_type, title_id,
|
||||
nca->GetBaseFile());
|
||||
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XCI files
|
||||
if (extension == "xci") {
|
||||
FileSys::XCI xci{file};
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp) {
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
|
|
@ -240,13 +337,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
|||
}
|
||||
}
|
||||
|
||||
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
|
||||
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
|
||||
return;
|
||||
}
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeSystem(bool reload) {
|
||||
if (!reload) {
|
||||
// Initialize logging system
|
||||
|
|
@ -690,10 +780,9 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i
|
|||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||
if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) {
|
||||
ANativeWindow_release(native_window);
|
||||
}
|
||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||
|
|
@ -880,40 +969,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
|||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
|
||||
using namespace VideoCore::Capture;
|
||||
|
||||
if (!EmulationSession::GetInstance().IsRunning()) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer();
|
||||
if (tiled.size() < TiledSize) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
std::vector<u8> linear(LinearWidth * LinearHeight * BytesPerPixel);
|
||||
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight,
|
||||
LinearDepth, BlockHeight, BlockDepth);
|
||||
|
||||
auto buffer = env->NewByteArray(static_cast<jsize>(linear.size()));
|
||||
if (!buffer) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(linear.size()),
|
||||
reinterpret_cast<const jbyte*>(linear.data()));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jint>(VideoCore::Capture::LinearWidth);
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jint>(VideoCore::Capture::LinearHeight);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
||||
jboolean reload) {
|
||||
// Initialize the emulated system.
|
||||
|
|
@ -1517,12 +1572,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
|
|||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
|
||||
JNIEnv* env, jobject jobj, jstring jpath) {
|
||||
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
|
||||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
||||
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -49,7 +46,6 @@ public:
|
|||
const Core::PerfStatsResults& PerfStats();
|
||||
int ShadersBuilding();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
void SetAppletId(int applet_id);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#E6FFFFFF" />
|
||||
</shape>
|
||||
|
|
@ -108,22 +108,6 @@
|
|||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/paused_frame_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/paused_frame_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/input_container"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -158,18 +142,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/paused_icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_white"
|
||||
android:contentDescription="@string/emulation_unpause"
|
||||
android:padding="14dp"
|
||||
android:src="@drawable/ic_play"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/black" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/show_stats_overlay_text"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
|
|
|
|||
|
|
@ -1222,12 +1222,12 @@
|
|||
|
||||
<!-- Static Themes -->
|
||||
<string name="static_theme_color">Theme Color</string>
|
||||
<string name="eden_theme">Eden</string>
|
||||
<string name="eden_theme">Eden (Default)</string>
|
||||
<string name="violet">Violet</string>
|
||||
<string name="blue">Blue</string>
|
||||
<string name="cyan">Cyan</string>
|
||||
<string name="red">Red</string>
|
||||
<string name="green">Green (Default)</string>
|
||||
<string name="green">Green</string>
|
||||
<string name="yellow">Yellow</string>
|
||||
<string name="orange">Orange</string>
|
||||
<string name="pink">Pink</string>
|
||||
|
|
|
|||
|
|
@ -756,8 +756,6 @@ struct Values {
|
|||
Category::DataStorage};
|
||||
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
||||
Category::DataStorage};
|
||||
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
|
||||
Category::DataStorage};
|
||||
std::vector<std::string> external_content_dirs;
|
||||
|
||||
// Debugging
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ struct CipherContext {
|
|||
static inline const std::string GetCipherName(Mode mode, u32 key_size) {
|
||||
std::string cipher;
|
||||
std::size_t effective_bits = key_size * 8;
|
||||
|
||||
switch (mode) {
|
||||
case Mode::CTR:
|
||||
cipher = "CTR";
|
||||
|
|
@ -52,6 +53,7 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) {
|
|||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return fmt::format("AES-{}-{}", effective_bits, cipher);
|
||||
};
|
||||
|
||||
|
|
@ -85,7 +87,8 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) {
|
|||
|
||||
// TODO: WHY TEMPLATE???????
|
||||
template <typename Key, std::size_t KeySize>
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_unique<CipherContext>()) {
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
|
||||
: ctx(std::make_unique<CipherContext>()) {
|
||||
|
||||
ctx->encryption_context = EVP_CIPHER_CTX_new();
|
||||
ctx->decryption_context = EVP_CIPHER_CTX_new();
|
||||
|
|
@ -96,7 +99,9 @@ Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_u
|
|||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
ASSERT(ctx->encryption_context && ctx->decryption_context && ctx->cipher && "OpenSSL cipher context failed init!");
|
||||
ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher,
|
||||
"OpenSSL cipher context failed init!");
|
||||
|
||||
// now init ciphers
|
||||
ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL));
|
||||
ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL));
|
||||
|
|
@ -160,7 +165,8 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
|
|||
template <typename Key, std::size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* dest,
|
||||
std::size_t sector_id, std::size_t sector_size, Op op) {
|
||||
ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size.");
|
||||
ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
|
||||
|
||||
for (std::size_t i = 0; i < size; i += sector_size) {
|
||||
SetIV(CalculateNintendoTweak(sector_id++));
|
||||
Transcode(src + i, sector_size, dest + i, op);
|
||||
|
|
@ -171,7 +177,8 @@ template <typename Key, std::size_t KeySize>
|
|||
void AESCipher<Key, KeySize>::SetIV(std::span<const u8> data) {
|
||||
const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1);
|
||||
const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1);
|
||||
ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts");
|
||||
|
||||
ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts");
|
||||
}
|
||||
|
||||
template class AESCipher<Key128>;
|
||||
|
|
|
|||
4
src/core/crypto/sha_util.cpp
Normal file
4
src/core/crypto/sha_util.cpp
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace Crypto {} // namespace Crypto
|
||||
19
src/core/crypto/sha_util.h
Normal file
19
src/core/crypto/sha_util.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "key_manager.h"
|
||||
#include "mbedtls/cipher.h"
|
||||
|
||||
namespace Crypto {
|
||||
typedef std::array<u8, 0x20> SHA256Hash;
|
||||
|
||||
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
|
||||
if (len != 0x40)
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Crypto
|
||||
|
|
@ -117,12 +117,6 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
|||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
|
||||
const std::string disabled_key = fmt::format("Update@{}", version);
|
||||
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
|
|
@ -161,7 +155,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
|
|
@ -180,7 +175,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
|
|
@ -584,7 +580,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
|||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
|
|
@ -603,7 +600,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
|||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
|
|
@ -706,8 +704,9 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
|
|
@ -733,8 +732,9 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
|
|
@ -771,8 +771,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
|
||||
|
||||
for (const auto& [slot, entry] : all_updates) {
|
||||
if (slot == ContentProviderUnionSlot::External ||
|
||||
slot == ContentProviderUnionSlot::FrontendManual) {
|
||||
if (slot == ContentProviderUnionSlot::External) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,206 +104,6 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
|
|||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
|
||||
type = Loader::IdentifyFile(file);
|
||||
if (type == Loader::FileType::Unknown) {
|
||||
type = Loader::GuessFromFilename(file->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::NSP) {
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::XCI) {
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_partition;
|
||||
}
|
||||
|
||||
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
|
||||
// As a last resort, probe both container parsers directly.
|
||||
{
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
return nsp;
|
||||
}
|
||||
}
|
||||
{
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition != nullptr) {
|
||||
return secure_partition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
|
||||
std::optional<u64> base_program_id, Callback&& on_entry) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
if (ncas.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<u64, u32> versions;
|
||||
std::map<u64, std::string> version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
if (!nca) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
for (const auto& inner_file : subdirs[0]->GetFiles()) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
|
||||
const auto romfs = nca->GetRomFS();
|
||||
if (!romfs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (!extracted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (!nacp_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const NACP nacp(nacp_file);
|
||||
auto version_string = nacp.GetVersionString();
|
||||
if (!version_string.empty()) {
|
||||
version_strings[title_id] = std::move(version_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool added_entries = false;
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
|
||||
if (!entry_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 version = 0;
|
||||
std::string version_string;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
|
||||
version = version_it->second;
|
||||
}
|
||||
|
||||
if (const auto version_str_it = version_strings.find(title_id);
|
||||
version_str_it != version_strings.end()) {
|
||||
version_string = version_str_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
|
||||
added_entries = true;
|
||||
}
|
||||
}
|
||||
|
||||
return added_entries;
|
||||
}
|
||||
|
||||
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
|
||||
u64 title_id, u32 version,
|
||||
const std::string& version_string,
|
||||
ContentRecordType content_type, const VirtualFile& file) {
|
||||
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
|
||||
[title_id, version](const ExternalUpdateEntry& entry) {
|
||||
return entry.title_id == title_id && entry.version == version;
|
||||
});
|
||||
|
||||
if (it == multi_version_entries.end()) {
|
||||
ExternalUpdateEntry update_entry;
|
||||
update_entry.title_id = title_id;
|
||||
update_entry.version = version;
|
||||
update_entry.version_string = version_string;
|
||||
update_entry.files[static_cast<std::size_t>(content_type)] = file;
|
||||
multi_version_entries.push_back(std::move(update_entry));
|
||||
return;
|
||||
}
|
||||
|
||||
it->files[static_cast<std::size_t>(content_type)] = file;
|
||||
if (it->version_string.empty() && !version_string.empty()) {
|
||||
it->version_string = version_string;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EntryMap, typename VersionMap>
|
||||
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
|
||||
VersionMap& versions,
|
||||
std::vector<ExternalUpdateEntry>& multi_version_entries) {
|
||||
return ForEachContainerEntry(
|
||||
nsp, true, std::nullopt,
|
||||
[&entries, &versions,
|
||||
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& file, u32 version,
|
||||
const std::string& version_string) {
|
||||
entries[{title_id, content_type, title_type}] = file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
versions[title_id] = version;
|
||||
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
|
||||
content_type, file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
|
|
@ -1208,26 +1008,6 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
|
|||
}
|
||||
}
|
||||
|
||||
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
|
||||
std::optional<u64> base_program_id) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ForEachContainerEntry(
|
||||
nsp, only_content, base_program_id,
|
||||
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
|
||||
if (title_type == TitleType::Update) {
|
||||
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
|
||||
entry_file);
|
||||
} else {
|
||||
AddEntry(title_type, content_type, title_id, entry_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ManualContentProvider::ClearAllEntries() {
|
||||
entries.clear();
|
||||
multi_version_entries.clear();
|
||||
|
|
@ -1311,6 +1091,14 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
|
||||
: load_dirs(std::move(load_directories)) {
|
||||
ExternalContentProvider::Refresh();
|
||||
|
|
@ -1371,22 +1159,247 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
|
|||
}
|
||||
|
||||
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
|
||||
if (!nsp) {
|
||||
auto nsp = NSP(file);
|
||||
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
|
||||
const auto ncas = nsp.GetNCAs();
|
||||
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
nsp_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
|
||||
if (!nsp) {
|
||||
auto xci = XCI(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ncas = nsp->GetNCAs();
|
||||
|
||||
std::map<u64, u32> xci_versions;
|
||||
std::map<u64, std::string> xci_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
xci_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
xci_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = xci_versions.find(title_id);
|
||||
if (ver_it != xci_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = xci_version_strings.find(title_id);
|
||||
if (str_it != xci_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
|
|
@ -1478,4 +1491,12 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 1;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
|
@ -263,8 +262,6 @@ public:
|
|||
VirtualFile file);
|
||||
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
u32 version, const std::string& version_string, VirtualFile file);
|
||||
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
|
||||
std::optional<u64> base_program_id = std::nullopt);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
|
|
@ -279,6 +276,7 @@ public:
|
|||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
|
|
@ -305,6 +303,7 @@ public:
|
|||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
void ScanDirectory(const VirtualDir& dir);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -28,10 +28,8 @@ public:
|
|||
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
|
||||
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
|
||||
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
|
||||
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
|
||||
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
|
||||
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -9,15 +9,11 @@
|
|||
#include <ostream>
|
||||
#include <string>
|
||||
#include <concepts>
|
||||
#include <algorithm>
|
||||
#include "common/concepts.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/kip.h"
|
||||
|
|
@ -41,49 +37,6 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
|
||||
u64 program_id = 0,
|
||||
std::size_t program_index = 0) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::NSP) {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
|
||||
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::XCI) {
|
||||
FileSys::XCI xci{file, program_id, program_index};
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_nsp = xci.GetSecurePartitionNSP();
|
||||
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_nsp;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
|
||||
const auto& nca_map = title_entry.second;
|
||||
return nca_map.find(
|
||||
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
|
||||
nca_map.end();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
|
|
@ -109,27 +62,6 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
|||
}
|
||||
}
|
||||
|
||||
bool IsContainerType(FileType type) {
|
||||
return type == FileType::NSP || type == FileType::XCI;
|
||||
}
|
||||
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
|
||||
std::size_t program_index) {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == FileType::Unknown) {
|
||||
type = IdentifyFile(file);
|
||||
}
|
||||
|
||||
if (!IsContainerType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
|
||||
}
|
||||
|
||||
FileType GuessFromFilename(const std::string& name) {
|
||||
if (name == "main")
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -49,29 +46,12 @@ enum class FileType {
|
|||
};
|
||||
|
||||
/**
|
||||
* Identifies the type of a supported file/container based on its structure.
|
||||
* Identifies the type of a bootable file based on the magic value in its header.
|
||||
* @param file open file
|
||||
* @return FileType of file
|
||||
*/
|
||||
FileType IdentifyFile(FileSys::VirtualFile file);
|
||||
|
||||
/**
|
||||
* Returns whether the file type represents a container format that can bundle multiple titles
|
||||
* (currently NSP/XCI).
|
||||
*/
|
||||
bool IsContainerType(FileType type);
|
||||
|
||||
/**
|
||||
* Returns whether a container file is bootable as a game (has Application/Program content).
|
||||
*
|
||||
* @param file open file
|
||||
* @param type optional file type; if Unknown it is auto-detected.
|
||||
* @param program_id optional program id hint for multi-program containers.
|
||||
* @param program_index optional program index hint for multi-program containers.
|
||||
*/
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
|
||||
u64 program_id = 0, std::size_t program_index = 0);
|
||||
|
||||
/**
|
||||
* Guess the type of a bootable file from its name
|
||||
* @param name String name of bootable file
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -58,30 +55,19 @@ AppLoader_NSP::~AppLoader_NSP() = default;
|
|||
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
|
||||
const FileSys::NSP nsp(nsp_file);
|
||||
|
||||
if (nsp.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Non-extracted NSPs can legitimately contain only update/DLC content.
|
||||
// Identify the container format itself; bootability is validated by Load().
|
||||
if (!nsp.GetNCAs().empty()) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
|
||||
for (const auto& entry : nsp.GetFiles()) {
|
||||
if (entry == nullptr) {
|
||||
continue;
|
||||
if (nsp.GetStatus() == ResultStatus::Success) {
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
const auto& name = entry->GetName();
|
||||
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
|
||||
// Non-Extracted Type case
|
||||
const auto program_id = nsp.GetProgramTitleID();
|
||||
if (!nsp.IsExtractedType() &&
|
||||
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(
|
||||
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -47,13 +44,10 @@ AppLoader_XCI::~AppLoader_XCI() = default;
|
|||
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
|
||||
const FileSys::XCI xci(xci_file);
|
||||
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
|
||||
// Bootability is handled by AppLoader_XCI::Load().
|
||||
if (xci.GetSecurePartitionNSP() != nullptr) {
|
||||
if (xci.GetStatus() == ResultStatus::Success &&
|
||||
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
|
||||
FileType::NCA) {
|
||||
return FileType::XCI;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -56,7 +53,6 @@ public:
|
|||
enum class PlayReportType {
|
||||
Old,
|
||||
Old2,
|
||||
Old3,
|
||||
New,
|
||||
System,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -425,9 +425,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
"their resolution, details and supported controllers and depending on this setting.\n"
|
||||
"Setting to Handheld can help improve performance for low end systems."));
|
||||
INSERT(Settings, current_user, QString(), QString());
|
||||
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
|
||||
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
|
||||
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
|
||||
|
||||
// Controls
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -94,17 +91,6 @@ void AlphaTest(EmitContext& ctx) {
|
|||
} // Anonymous namespace
|
||||
|
||||
void EmitPrologue(EmitContext& ctx) {
|
||||
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
|
||||
// Initialize dual-source blending outputs - prevents MoltenVK crash.
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
|
||||
for (u32 i = 0; i < 2; ++i) {
|
||||
if (Sirit::ValidId(ctx.frag_color[i])) {
|
||||
ctx.OpStore(ctx.frag_color[i], default_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.stage == Stage::VertexB) {
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
|
|
|
|||
|
|
@ -1670,22 +1670,13 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
|||
break;
|
||||
case Stage::Fragment:
|
||||
for (u32 index = 0; index < 8; ++index) {
|
||||
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
|
||||
if (!need_dual_source && !info.stores_frag_color[index] &&
|
||||
!profile.need_declared_frag_colors) {
|
||||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
continue;
|
||||
}
|
||||
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
|
||||
frag_color[index] = DefineOutput(*this, type, std::nullopt);
|
||||
// Correct mapping for dual-source blending
|
||||
if (runtime_info.dual_source_blend && index <= 1) {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, 0u);
|
||||
Decorate(frag_color[index], spv::Decoration::Index, index);
|
||||
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
|
||||
} else {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
if (info.stores_frag_depth) {
|
||||
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
|
||||
|
|
|
|||
|
|
@ -110,9 +110,6 @@ struct RuntimeInfo {
|
|||
|
||||
/// Output types for each color attachment
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
|
||||
/// Dual source blending
|
||||
bool dual_source_blend{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
|
|
|||
|
|
@ -14,11 +14,8 @@
|
|||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
|
|
@ -97,10 +94,10 @@ static constexpr Binding NULL_BINDING{
|
|||
|
||||
template <typename Buffer>
|
||||
struct HostBindings {
|
||||
boost::container::static_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
u32 min_index{NUM_VERTEX_BUFFERS};
|
||||
u32 max_index{0};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -22,12 +19,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
|
|||
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
|
||||
ASSERT(memory_manager);
|
||||
program_id = program_id_;
|
||||
dma_pusher.emplace(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d.emplace(system, *memory_manager);
|
||||
fermi_2d.emplace(*memory_manager);
|
||||
kepler_compute.emplace(system, *memory_manager);
|
||||
maxwell_dma.emplace(system, *memory_manager);
|
||||
kepler_memory.emplace(system, *memory_manager);
|
||||
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
|
||||
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
|
||||
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -9,12 +6,6 @@
|
|||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/kepler_memory.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/maxwell_dma.h"
|
||||
#include "video_core/dma_pusher.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
|
|
@ -27,34 +18,49 @@ class RasterizerInterface;
|
|||
namespace Tegra {
|
||||
|
||||
class GPU;
|
||||
|
||||
namespace Engines {
|
||||
class Puller;
|
||||
class Fermi2D;
|
||||
class Maxwell3D;
|
||||
class MaxwellDMA;
|
||||
class KeplerCompute;
|
||||
class KeplerMemory;
|
||||
} // namespace Engines
|
||||
|
||||
class MemoryManager;
|
||||
class DmaPusher;
|
||||
|
||||
namespace Control {
|
||||
|
||||
struct ChannelState {
|
||||
explicit ChannelState(s32 bind_id);
|
||||
ChannelState(const ChannelState& state) = delete;
|
||||
ChannelState& operator=(const ChannelState&) = delete;
|
||||
ChannelState(ChannelState&& other) noexcept = default;
|
||||
ChannelState& operator=(ChannelState&& other) noexcept = default;
|
||||
|
||||
void Init(Core::System& system, GPU& gpu, u64 program_id);
|
||||
|
||||
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||
|
||||
/// 3D engine
|
||||
std::optional<Engines::Maxwell3D> maxwell_3d;
|
||||
/// 2D engine
|
||||
std::optional<Engines::Fermi2D> fermi_2d;
|
||||
/// Compute engine
|
||||
std::optional<Engines::KeplerCompute> kepler_compute;
|
||||
/// DMA engine
|
||||
std::optional<Engines::MaxwellDMA> maxwell_dma;
|
||||
/// Inline memory engine
|
||||
std::optional<Engines::KeplerMemory> kepler_memory;
|
||||
/// NV01 Timer
|
||||
std::optional<Engines::KeplerMemory> nv01_timer;
|
||||
std::optional<DmaPusher> dma_pusher;
|
||||
std::shared_ptr<MemoryManager> memory_manager;
|
||||
|
||||
s32 bind_id = -1;
|
||||
u64 program_id = 0;
|
||||
/// 3D engine
|
||||
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
|
||||
/// 2D engine
|
||||
std::unique_ptr<Engines::Fermi2D> fermi_2d;
|
||||
/// Compute engine
|
||||
std::unique_ptr<Engines::KeplerCompute> kepler_compute;
|
||||
/// DMA engine
|
||||
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
|
||||
/// Inline memory engine
|
||||
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
|
||||
|
||||
std::shared_ptr<MemoryManager> memory_manager;
|
||||
|
||||
std::unique_ptr<DmaPusher> dma_pusher;
|
||||
|
||||
bool initialized{};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -15,7 +15,6 @@
|
|||
namespace Tegra::Engines {
|
||||
|
||||
enum class EngineTypes : u32 {
|
||||
Nv01Timer,
|
||||
KeplerCompute,
|
||||
Maxwell3D,
|
||||
Fermi2D,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -26,15 +26,8 @@ namespace Tegra::Engines {
|
|||
constexpr u32 MacroRegistersStart = 0xE00;
|
||||
|
||||
Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_)
|
||||
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_}
|
||||
, memory_manager{memory_manager_}
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
, macro_engine(bool(Settings::values.disable_macro_jit))
|
||||
#else
|
||||
, macro_engine(true)
|
||||
#endif
|
||||
, upload_state{memory_manager, regs.upload}
|
||||
{
|
||||
: draw_manager{std::make_unique<DrawManager>(this)}, system{system_},
|
||||
memory_manager{memory_manager_}, macro_engine{GetMacroEngine(*this)}, upload_state{memory_manager, regs.upload} {
|
||||
dirty.flags.flip();
|
||||
InitializeRegisterDefaults();
|
||||
execution_mask.reset();
|
||||
|
|
@ -335,9 +328,9 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
|||
shadow_state.shadow_ram_control = static_cast<Regs::ShadowRamControl>(nonshadow_argument);
|
||||
return;
|
||||
case MAXWELL3D_REG_INDEX(load_mme.instruction_ptr):
|
||||
return macro_engine.ClearCode(regs.load_mme.instruction_ptr);
|
||||
return macro_engine->ClearCode(regs.load_mme.instruction_ptr);
|
||||
case MAXWELL3D_REG_INDEX(load_mme.instruction):
|
||||
return macro_engine.AddCode(regs.load_mme.instruction_ptr, argument);
|
||||
return macro_engine->AddCode(regs.load_mme.instruction_ptr, argument);
|
||||
case MAXWELL3D_REG_INDEX(load_mme.start_address):
|
||||
return ProcessMacroBind(argument);
|
||||
case MAXWELL3D_REG_INDEX(falcon[4]):
|
||||
|
|
@ -405,7 +398,7 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
|
|||
((method - MacroRegistersStart) >> 1) % static_cast<u32>(macro_positions.size());
|
||||
|
||||
// Execute the current macro.
|
||||
macro_engine.Execute(*this, macro_positions[entry], parameters);
|
||||
macro_engine->Execute(macro_positions[entry], parameters);
|
||||
|
||||
draw_manager->DrawDeferred();
|
||||
}
|
||||
|
|
@ -471,7 +464,7 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
|||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroUpload(u32 data) {
|
||||
macro_engine.AddCode(regs.load_mme.instruction_ptr++, data);
|
||||
macro_engine->AddCode(regs.load_mme.instruction_ptr++, data);
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessMacroBind(u32 data) {
|
||||
|
|
|
|||
|
|
@ -2258,7 +2258,7 @@ public:
|
|||
/// Returns whether the vertex array specified by index is supposed to be
|
||||
/// accessed per instance or not.
|
||||
bool IsInstancingEnabled(std::size_t index) const {
|
||||
return bool(is_instanced[index]); //FUCK YOU MSVC
|
||||
return is_instanced[index];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -3203,7 +3203,7 @@ private:
|
|||
std::vector<u32> macro_params;
|
||||
|
||||
/// Interpreter for the macro codes uploaded to the GPU.
|
||||
MacroEngine macro_engine;
|
||||
std::optional<MacroEngine> macro_engine;
|
||||
|
||||
Upload::State upload_state;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/engines/engine_interface.h"
|
||||
#include "video_core/engines/engine_upload.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines {
|
||||
class Nv01Timer final : public EngineInterface {
|
||||
public:
|
||||
explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager)
|
||||
: system{system_}
|
||||
{}
|
||||
~Nv01Timer() override;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
void CallMethod(u32 method, u32 method_argument, bool is_last_call) override {
|
||||
LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call);
|
||||
}
|
||||
|
||||
/// Write multiple values to the register identified by method.
|
||||
void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override {
|
||||
LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending);
|
||||
}
|
||||
|
||||
struct Regs {
|
||||
// No fucking idea
|
||||
INSERT_PADDING_BYTES_NOINIT(0x48);
|
||||
} regs{};
|
||||
private:
|
||||
void ConsumeSinkImpl() override {}
|
||||
Core::System& system;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -37,22 +34,24 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
|
|||
bound_engines[method_call.subchannel] = engine_id;
|
||||
switch (engine_id) {
|
||||
case EngineID::FERMI_TWOD_A:
|
||||
dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D);
|
||||
dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel,
|
||||
EngineTypes::Fermi2D);
|
||||
break;
|
||||
case EngineID::MAXWELL_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D);
|
||||
dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel,
|
||||
EngineTypes::Maxwell3D);
|
||||
break;
|
||||
case EngineID::KEPLER_COMPUTE_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute);
|
||||
dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel,
|
||||
EngineTypes::KeplerCompute);
|
||||
break;
|
||||
case EngineID::MAXWELL_DMA_COPY_A:
|
||||
dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA);
|
||||
dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel,
|
||||
EngineTypes::MaxwellDMA);
|
||||
break;
|
||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory);
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer);
|
||||
dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel,
|
||||
EngineTypes::KeplerMemory);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
|
||||
|
|
@ -210,22 +209,24 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {
|
|||
|
||||
switch (engine) {
|
||||
case EngineID::FERMI_TWOD_A:
|
||||
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::MAXWELL_B:
|
||||
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::KEPLER_COMPUTE_B:
|
||||
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::MAXWELL_DMA_COPY_A:
|
||||
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall());
|
||||
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument,
|
||||
method_call.IsLastCall());
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
|
|
@ -254,9 +255,6 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s
|
|||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||
channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||
break;
|
||||
case EngineID::NV01_TIMER:
|
||||
channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -23,7 +20,6 @@ class MemoryManager;
|
|||
class DmaPusher;
|
||||
|
||||
enum class EngineID {
|
||||
NV01_TIMER = 0x0004,
|
||||
FERMI_TWOD_A = 0x902D, // 2D Engine
|
||||
MAXWELL_B = 0xB197, // 3D Engine
|
||||
KEPLER_COMPUTE_B = 0xB1C0,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,10 +7,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
|
|
@ -100,142 +98,62 @@ union MethodAddress {
|
|||
|
||||
} // namespace Macro
|
||||
|
||||
struct HLEMacro {
|
||||
};
|
||||
/// @note: these macros have two versions, a normal and extended version, with the extended version
|
||||
/// also assigning the base vertex/instance.
|
||||
struct HLE_DrawArraysIndirect final {
|
||||
HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
bool extended;
|
||||
};
|
||||
/// @note: these macros have two versions, a normal and extended version, with the extended version
|
||||
/// also assigning the base vertex/instance.
|
||||
struct HLE_DrawIndexedIndirect final {
|
||||
explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
bool extended;
|
||||
};
|
||||
struct HLE_MultiLayerClear final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_MultiDrawIndexedIndirectCount final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
};
|
||||
struct HLE_DrawIndirectByteCount final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
void Fallback(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters);
|
||||
};
|
||||
struct HLE_C713C83D8F63CCF3 final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_D7333D26E0A93EDE final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_BindShader final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_SetRasterBoundingBox final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct HLE_ClearConstBuffer final {
|
||||
HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
size_t base_size;
|
||||
};
|
||||
struct HLE_ClearMemory final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
std::vector<u32> zero_memory;
|
||||
};
|
||||
struct HLE_TransformFeedbackSetup final {
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, [[maybe_unused]] u32 method);
|
||||
};
|
||||
struct MacroInterpreterImpl final {
|
||||
MacroInterpreterImpl() {}
|
||||
MacroInterpreterImpl(std::span<const u32> code_) : code{code_} {}
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> params, u32 method);
|
||||
void Reset();
|
||||
bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot);
|
||||
u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b);
|
||||
void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result);
|
||||
bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const;
|
||||
Macro::Opcode GetOpcode() const;
|
||||
u32 GetRegister(u32 register_id) const;
|
||||
void SetRegister(u32 register_id, u32 value);
|
||||
/// Sets the method address to use for the next Send instruction.
|
||||
[[nodiscard]] inline void SetMethodAddress(u32 address) noexcept {
|
||||
method_address.raw = address;
|
||||
}
|
||||
void Send(Engines::Maxwell3D& maxwell3d, u32 value);
|
||||
u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const;
|
||||
u32 FetchParameter();
|
||||
/// General purpose macro registers.
|
||||
std::array<u32, Macro::NUM_MACRO_REGISTERS> registers = {};
|
||||
/// Input parameters of the current macro.
|
||||
std::vector<u32> parameters;
|
||||
std::span<const u32> code;
|
||||
/// Program counter to execute at after the delay slot is executed.
|
||||
std::optional<u32> delayed_pc;
|
||||
/// Method address to use for the next Send instruction.
|
||||
Macro::MethodAddress method_address = {};
|
||||
/// Current program counter
|
||||
u32 pc{};
|
||||
/// Index of the next parameter that will be fetched by the 'parm' instruction.
|
||||
u32 next_parameter_index = 0;
|
||||
bool carry_flag = false;
|
||||
};
|
||||
struct DynamicCachedMacro {
|
||||
virtual ~DynamicCachedMacro() = default;
|
||||
class CachedMacro {
|
||||
public:
|
||||
CachedMacro(Engines::Maxwell3D& maxwell3d_)
|
||||
: maxwell3d{maxwell3d_}
|
||||
{}
|
||||
virtual ~CachedMacro() = default;
|
||||
/// Executes the macro code with the specified input parameters.
|
||||
/// @param parameters The parameters of the macro
|
||||
/// @param method The method to execute
|
||||
virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span<const u32> parameters, u32 method) = 0;
|
||||
virtual void Execute(const std::vector<u32>& parameters, u32 method) = 0;
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
};
|
||||
|
||||
using AnyCachedMacro = std::variant<
|
||||
std::monostate,
|
||||
HLEMacro,
|
||||
HLE_DrawArraysIndirect,
|
||||
HLE_DrawIndexedIndirect,
|
||||
HLE_MultiDrawIndexedIndirectCount,
|
||||
HLE_MultiLayerClear,
|
||||
HLE_C713C83D8F63CCF3,
|
||||
HLE_D7333D26E0A93EDE,
|
||||
HLE_BindShader,
|
||||
HLE_SetRasterBoundingBox,
|
||||
HLE_ClearConstBuffer,
|
||||
HLE_ClearMemory,
|
||||
HLE_TransformFeedbackSetup,
|
||||
HLE_DrawIndirectByteCount,
|
||||
MacroInterpreterImpl,
|
||||
// Used for JIT x86 macro
|
||||
std::unique_ptr<DynamicCachedMacro>
|
||||
>;
|
||||
class HLEMacro {
|
||||
public:
|
||||
explicit HLEMacro(Engines::Maxwell3D& maxwell3d_);
|
||||
~HLEMacro();
|
||||
// Allocates and returns a cached macro if the hash matches a known function.
|
||||
// Returns nullptr otherwise.
|
||||
[[nodiscard]] std::unique_ptr<CachedMacro> GetHLEProgram(u64 hash) const;
|
||||
private:
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
};
|
||||
|
||||
class MacroEngine {
|
||||
public:
|
||||
explicit MacroEngine(Engines::Maxwell3D& maxwell3d, bool is_interpreted);
|
||||
~MacroEngine();
|
||||
|
||||
struct MacroEngine {
|
||||
MacroEngine(bool is_interpreted_) noexcept : is_interpreted{is_interpreted_} {}
|
||||
// Store the uploaded macro code to compile them when they're called.
|
||||
inline void AddCode(u32 method, u32 data) noexcept {
|
||||
uploaded_macro_code[method].push_back(data);
|
||||
}
|
||||
void AddCode(u32 method, u32 data);
|
||||
|
||||
// Clear the code associated with a method.
|
||||
inline void ClearCode(u32 method) noexcept {
|
||||
macro_cache.erase(method);
|
||||
uploaded_macro_code.erase(method);
|
||||
}
|
||||
void ClearCode(u32 method);
|
||||
|
||||
// Compiles the macro if its not in the cache, and executes the compiled macro
|
||||
void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span<const u32> parameters);
|
||||
AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span<const u32> code);
|
||||
void Execute(u32 method, const std::vector<u32>& parameters);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CachedMacro> Compile(const std::vector<u32>& code);
|
||||
|
||||
private:
|
||||
struct CacheInfo {
|
||||
AnyCachedMacro program;
|
||||
std::unique_ptr<CachedMacro> lle_program{};
|
||||
std::unique_ptr<CachedMacro> hle_program{};
|
||||
u64 hash{};
|
||||
bool has_hle_program{};
|
||||
};
|
||||
|
||||
ankerl::unordered_dense::map<u32, CacheInfo> macro_cache;
|
||||
ankerl::unordered_dense::map<u32, std::vector<u32>> uploaded_macro_code;
|
||||
std::optional<HLEMacro> hle_macros;
|
||||
Engines::Maxwell3D& maxwell3d;
|
||||
bool is_interpreted;
|
||||
};
|
||||
|
||||
std::optional<MacroEngine> GetMacroEngine(Engines::Maxwell3D& maxwell3d);
|
||||
|
||||
} // namespace Tegra
|
||||
|
|
|
|||
|
|
@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
|
|||
VkShaderModule frag_shader = *convert_float_to_depth_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
|
|||
VkShaderModule frag_shader = *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
|
|||
}
|
||||
const std::array stages = MakeStages(*full_screen_vert, *module);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
@ -1135,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
|||
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -64,8 +61,7 @@ public:
|
|||
.pDescriptorUpdateEntries = entries.data(),
|
||||
.templateType = type,
|
||||
.descriptorSetLayout = descriptor_set_layout,
|
||||
.pipelineBindPoint =
|
||||
is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineLayout = pipeline_layout,
|
||||
.set = 0,
|
||||
});
|
||||
|
|
@ -126,7 +122,7 @@ private:
|
|||
});
|
||||
++binding;
|
||||
num_descriptors += descriptors[i].count;
|
||||
offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count;
|
||||
offset += sizeof(DescriptorUpdateEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include <ranges>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
|
@ -630,8 +629,8 @@ vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qc
|
|||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
|
||||
.minFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
|
||||
.magFilter = VK_FILTER_CUBIC_EXT,
|
||||
.minFilter = VK_FILTER_CUBIC_EXT,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
|
|
|
|||
|
|
@ -137,8 +137,14 @@ try
|
|||
memory_allocator,
|
||||
scheduler,
|
||||
swapchain,
|
||||
#ifdef ANDROID
|
||||
surface)
|
||||
, blit_swapchain(device_memory,
|
||||
,
|
||||
#else
|
||||
*surface)
|
||||
,
|
||||
#endif
|
||||
blit_swapchain(device_memory,
|
||||
device,
|
||||
memory_allocator,
|
||||
present_manager,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
|
|
@ -22,8 +22,7 @@ BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const
|
|||
MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
|
||||
Scheduler& scheduler_, const PresentFilters& filters_)
|
||||
: device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
|
||||
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_},
|
||||
image_count{1}, image_index{0},
|
||||
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
|
||||
swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
|
||||
|
||||
BlitScreen::~BlitScreen() = default;
|
||||
|
|
@ -88,49 +87,57 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
|
|||
bool resource_update_required = false;
|
||||
bool presentation_recreate_required = false;
|
||||
|
||||
// Recreate dynamic resources if the adapting filter changed
|
||||
if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
|
||||
resource_update_required = true;
|
||||
}
|
||||
|
||||
if (image_count != current_swapchain_image_count) {
|
||||
// Recreate dynamic resources if the image count changed
|
||||
const size_t old_swapchain_image_count =
|
||||
std::exchange(image_count, current_swapchain_image_count);
|
||||
if (old_swapchain_image_count != current_swapchain_image_count) {
|
||||
resource_update_required = true;
|
||||
image_count = current_swapchain_image_count;
|
||||
}
|
||||
|
||||
if (swapchain_view_format != current_swapchain_view_format ||
|
||||
// Recreate the presentation frame if the format or dimensions of the window changed
|
||||
const VkFormat old_swapchain_view_format =
|
||||
std::exchange(swapchain_view_format, current_swapchain_view_format);
|
||||
if (old_swapchain_view_format != current_swapchain_view_format ||
|
||||
layout.width != frame->width || layout.height != frame->height) {
|
||||
resource_update_required = true;
|
||||
presentation_recreate_required = true;
|
||||
swapchain_view_format = current_swapchain_view_format;
|
||||
}
|
||||
|
||||
// If we have a pending resource update, perform it
|
||||
if (resource_update_required) {
|
||||
// Wait for idle to ensure no resources are in use
|
||||
WaitIdle();
|
||||
|
||||
// Update window adapt pass
|
||||
SetWindowAdaptPass();
|
||||
|
||||
// Update frame format if needed
|
||||
if (presentation_recreate_required) {
|
||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||
window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
image_index = 0;
|
||||
}
|
||||
|
||||
// Add additional layers if needed
|
||||
const VkExtent2D window_size{
|
||||
.width = layout.screen.GetWidth(),
|
||||
.height = layout.screen.GetHeight(),
|
||||
};
|
||||
|
||||
if (layers.size() != framebuffers.size()) {
|
||||
layers.clear();
|
||||
for (size_t i = 0; i < framebuffers.size(); ++i) {
|
||||
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
|
||||
window_size, window_adapt->GetDescriptorSetLayout(), filters);
|
||||
}
|
||||
while (layers.size() < framebuffers.size()) {
|
||||
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
|
||||
window_size, window_adapt->GetDescriptorSetLayout(), filters);
|
||||
}
|
||||
|
||||
// Perform the draw
|
||||
window_adapt->Draw(rasterizer, scheduler, image_index, layers, framebuffers, layout, frame);
|
||||
|
||||
// Advance to next image
|
||||
if (++image_index >= image_count) {
|
||||
image_index = 0;
|
||||
}
|
||||
|
|
@ -139,20 +146,16 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
|
|||
vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& layout,
|
||||
VkImageView image_view,
|
||||
VkFormat current_view_format) {
|
||||
bool format_updated = swapchain_view_format != current_view_format;
|
||||
swapchain_view_format = current_view_format;
|
||||
|
||||
const bool format_updated =
|
||||
std::exchange(swapchain_view_format, current_view_format) != current_view_format;
|
||||
if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
|
||||
WaitIdle();
|
||||
SetWindowAdaptPass();
|
||||
image_index = 0;
|
||||
}
|
||||
|
||||
const VkExtent2D extent{
|
||||
.width = layout.width,
|
||||
.height = layout.height,
|
||||
};
|
||||
|
||||
return CreateFramebuffer(image_view, extent, window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
|
||||
|
|
@ -109,14 +108,6 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
|
|||
// Null buffer not supported, adjust offset and size
|
||||
offset = 0;
|
||||
size = 0;
|
||||
} else {
|
||||
// Align offset down to minTexelBufferOffsetAlignment
|
||||
const u32 alignment = static_cast<u32>(device->GetMinTexelBufferOffsetAlignment());
|
||||
if (alignment > 1) {
|
||||
const u32 aligned_offset = offset & ~(alignment - 1);
|
||||
size += offset - aligned_offset;
|
||||
offset = aligned_offset;
|
||||
}
|
||||
}
|
||||
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
|
||||
return offset == view.offset && size == view.size && format == view.format;
|
||||
|
|
@ -584,18 +575,18 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
|
|||
}
|
||||
|
||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
|
||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
boost::container::small_vector<VkBuffer, 32> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); ++index) {
|
||||
auto handle = bindings.buffers[index]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = VK_WHOLE_SIZE;
|
||||
bindings.offsets[index] = 0;
|
||||
bindings.sizes[index] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
buffer_handles.push_back(handle);
|
||||
}
|
||||
const u32 device_max = device.GetMaxVertexInputBindings();
|
||||
const u32 min_binding = (std::min)(bindings.min_index, device_max);
|
||||
|
|
@ -605,12 +596,19 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
|
|||
return;
|
||||
}
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data(), bindings_.strides.data());
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers2EXT(bindings_.min_index, binding_count, buffer_handles_.data(),
|
||||
bindings_.offsets.data(), bindings_.sizes.data(),
|
||||
bindings_.strides.data());
|
||||
});
|
||||
} else {
|
||||
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles), binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(), bindings_.offsets.data());
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles),
|
||||
binding_count](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers(bindings_.min_index, binding_count, buffer_handles_.data(),
|
||||
bindings_.offsets.data());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -641,21 +639,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<
|
|||
// Already logged in the rasterizer
|
||||
return;
|
||||
}
|
||||
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
|
||||
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
|
||||
auto handle = bindings.buffers[i]->Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
bindings.offsets[i] = 0;
|
||||
bindings.sizes[i] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
}
|
||||
buffer_handles[i] = handle;
|
||||
boost::container::small_vector<VkBuffer, 4> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); ++index) {
|
||||
buffer_handles.push_back(bindings.buffers[index]->Handle());
|
||||
}
|
||||
scheduler.Record([bindings_ = std::move(bindings), buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, u32(buffer_handles_.size()), buffer_handles_.data(), bindings_.offsets.data(), bindings_.sizes.data());
|
||||
scheduler.Record([bindings_ = std::move(bindings),
|
||||
buffer_handles_ = std::move(buffer_handles)](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(0, static_cast<u32>(buffer_handles_.size()),
|
||||
buffer_handles_.data(), bindings_.offsets.data(),
|
||||
bindings_.sizes.data());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
|||
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
|
||||
};
|
||||
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
pipeline = device.GetLogical().CreateComputePipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
|||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}
|
||||
|
|
@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
|||
.codeSize = static_cast<u32>(code.size_bytes()),
|
||||
.pCode = code.data(),
|
||||
});
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
|
@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
|||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,14 +50,11 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
|||
DescriptorLayoutBuilder builder{device};
|
||||
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(false);
|
||||
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
|
||||
descriptor_update_template =
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
}
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false);
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
|
|
@ -67,24 +64,26 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
|||
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
pipeline = device.GetLogical().CreateComputePipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
*pipeline_cache);
|
||||
|
||||
// Log compute pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
|
|
@ -242,16 +241,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
|||
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
|
||||
rescaling_data.data());
|
||||
}
|
||||
if (uses_push_descriptor) {
|
||||
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
|
||||
0, descriptor_data);
|
||||
} else {
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
}
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -58,7 +55,6 @@ private:
|
|||
|
||||
vk::ShaderModule spv_module;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
bool uses_push_descriptor{false};
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorUpdateTemplate descriptor_update_template;
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
|||
buffer_cache.BindHostStageBuffers(stage);
|
||||
PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
|
||||
samplers_it, views_it);
|
||||
const auto& info{stage_infos[stage]};
|
||||
const auto& info{stage_infos[0]};
|
||||
if (info.uses_render_area) {
|
||||
render_area.uses_render_area = true;
|
||||
render_area.words = {static_cast<float>(regs.surface_clip.width),
|
||||
|
|
@ -946,27 +946,29 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
|||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log graphics pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
|
|
|
|||
|
|
@ -121,8 +121,10 @@ VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuff
|
|||
}
|
||||
}
|
||||
|
||||
static constexpr VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
};
|
||||
|
||||
VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
|
||||
vk::CommandBuffer& upload_cmdbuf,
|
||||
|
|
@ -141,7 +143,7 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
|
|||
const VkSemaphore* p_wait_sems =
|
||||
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
|
||||
const VkPipelineStageFlags* p_wait_masks =
|
||||
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
|
||||
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
|
||||
const VkSemaphore* p_signal_sems =
|
||||
(num_signal_semaphores > 0) ? signal_semaphores.data() : nullptr;
|
||||
const u64 wait_zero = 0; // dummy for binary wait
|
||||
|
|
@ -178,7 +180,7 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
|
|||
const VkSemaphore* p_wait_sems =
|
||||
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
|
||||
const VkPipelineStageFlags* p_wait_masks =
|
||||
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
|
||||
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
|
||||
const VkSemaphore* p_signal_sems =
|
||||
(num_signal_semaphores > 0) ? &signal_semaphore : nullptr;
|
||||
const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
|
||||
|
|
|
|||
|
|
@ -237,38 +237,11 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
|||
}
|
||||
info.convert_depth_mode = gl_ndc;
|
||||
break;
|
||||
case Shader::Stage::Fragment: {
|
||||
case Shader::Stage::Fragment:
|
||||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
|
||||
|
||||
// Check for dual source blending
|
||||
const auto& blend0 = key.state.attachments[0];
|
||||
if (blend0.enable != 0) {
|
||||
using F = Maxwell::Blend::Factor;
|
||||
const auto src_rgb = blend0.SourceRGBFactor();
|
||||
const auto dst_rgb = blend0.DestRGBFactor();
|
||||
const auto src_a = blend0.SourceAlphaFactor();
|
||||
const auto dst_a = blend0.DestAlphaFactor();
|
||||
info.dual_source_blend =
|
||||
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
|
||||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
|
||||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
|
||||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
|
||||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
|
||||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
|
||||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
|
||||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
|
||||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
|
||||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
|
||||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
|
||||
}
|
||||
|
||||
if (device.IsMoltenVK()) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
|
|
@ -285,7 +258,6 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -101,14 +101,22 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
|||
MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_,
|
||||
Swapchain& swapchain_,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface_)
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_)
|
||||
#endif
|
||||
: instance{instance_}
|
||||
, render_window{render_window_}
|
||||
, device{device_}
|
||||
, memory_allocator{memory_allocator_}
|
||||
, scheduler{scheduler_}
|
||||
, swapchain{swapchain_}
|
||||
#ifdef ANDROID
|
||||
, surface{surface_}
|
||||
#else
|
||||
, surface_handle{surface_handle_}
|
||||
#endif
|
||||
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
|
||||
, use_present_thread{Settings::values.async_presentation.GetValue()}
|
||||
{
|
||||
|
|
@ -291,7 +299,11 @@ void PresentManager::PresentThread(std::stop_token token) {
|
|||
}
|
||||
|
||||
void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||
#ifndef ANDROID
|
||||
swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer
|
||||
#else
|
||||
swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer
|
||||
#endif
|
||||
SetImageCount();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -15,6 +15,8 @@
|
|||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
} // namespace Core::Frontend
|
||||
|
|
@ -44,7 +46,11 @@ public:
|
|||
MemoryAllocator& memory_allocator,
|
||||
Scheduler& scheduler,
|
||||
Swapchain& swapchain,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface);
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle);
|
||||
#endif
|
||||
~PresentManager();
|
||||
|
||||
/// Returns the last used presentation frame
|
||||
|
|
@ -78,7 +84,11 @@ private:
|
|||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
Swapchain& swapchain;
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
vk::CommandPool cmdpool;
|
||||
std::vector<Frame> frames;
|
||||
boost::container::deque<Frame*> present_queue;
|
||||
|
|
|
|||
|
|
@ -1280,7 +1280,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() {
|
|||
PauseHostConditionalRendering();
|
||||
impl->hcr_is_set = false;
|
||||
impl->is_hcr_running = false;
|
||||
impl->hcr_buffer = VkBuffer{};
|
||||
impl->hcr_buffer = nullptr;
|
||||
impl->hcr_offset = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
|
@ -38,7 +35,7 @@ public:
|
|||
~QueryCacheRuntime();
|
||||
|
||||
template <typename SyncValuesType>
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
|
||||
|
||||
void Barriers(bool is_prebarrier);
|
||||
|
||||
|
|
|
|||
|
|
@ -270,8 +270,8 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
|||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
};
|
||||
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, WRITE_BARRIER);
|
||||
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
|
||||
upload_cmdbuf.End();
|
||||
cmdbuf.End();
|
||||
|
||||
|
|
@ -372,12 +372,18 @@ void Scheduler::EndRenderPass()
|
|||
};
|
||||
}
|
||||
cmdbuf.EndRenderPass();
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
vk::Span(barriers.data(), num_images) // Batched image barriers
|
||||
);
|
||||
});
|
||||
|
||||
state.renderpass = VkRenderPass{};
|
||||
state.renderpass = nullptr;
|
||||
num_renderpass_images = 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
|
|
@ -44,10 +44,10 @@ public:
|
|||
~Scheduler();
|
||||
|
||||
/// Sends the current execution context to the GPU.
|
||||
u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
|
||||
/// Sends the current execution context to the GPU and waits for it to complete.
|
||||
void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
|
||||
/// Waits for the worker thread to finish executing everything. After this function returns it's
|
||||
/// safe to touch worker resources.
|
||||
|
|
@ -114,36 +114,29 @@ public:
|
|||
|
||||
/// Waits for the given GPU tick, optionally pacing frames.
|
||||
void Wait(u64 tick, double target_fps = 0.0) {
|
||||
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) {
|
||||
start_time = now;
|
||||
frame_counter = 0;
|
||||
current_target_fps = target_fps;
|
||||
}
|
||||
frame_counter++;
|
||||
std::chrono::duration<double> frame_interval(1.0 / current_target_fps);
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time > now) {
|
||||
std::this_thread::sleep_until(target_time);
|
||||
} else {
|
||||
start_time = now;
|
||||
frame_counter = 0;
|
||||
}
|
||||
}
|
||||
if (tick > 0) {
|
||||
if (tick >= master_semaphore->CurrentTick()) {
|
||||
Flush();
|
||||
}
|
||||
master_semaphore->Wait(tick);
|
||||
}
|
||||
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (last_target_fps != target_fps) {
|
||||
frame_interval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps));
|
||||
max_frame_count = static_cast<int>(0.1 * target_fps);
|
||||
last_target_fps = target_fps;
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
} else if (frame_counter > max_frame_count) {
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the master timeline semaphore.
|
||||
|
|
@ -237,8 +230,8 @@ private:
|
|||
};
|
||||
|
||||
struct State {
|
||||
VkRenderPass renderpass{};
|
||||
VkFramebuffer framebuffer{};
|
||||
VkRenderPass renderpass = nullptr;
|
||||
VkFramebuffer framebuffer = nullptr;
|
||||
VkExtent2D render_area = {0, 0};
|
||||
GraphicsPipeline* graphics_pipeline = nullptr;
|
||||
bool is_rescaling = false;
|
||||
|
|
@ -288,11 +281,9 @@ private:
|
|||
std::condition_variable_any event_cv;
|
||||
std::jthread worker_thread;
|
||||
|
||||
std::chrono::steady_clock::duration frame_interval{};
|
||||
std::chrono::steady_clock::time_point start_time{};
|
||||
double last_target_fps{};
|
||||
u64 max_frame_count{};
|
||||
u64 frame_counter{};
|
||||
double current_target_fps{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
|||
|
|
@ -109,22 +109,38 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap
|
|||
} // Anonymous namespace
|
||||
|
||||
Swapchain::Swapchain(
|
||||
VkSurfaceKHR_T* surface_,
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface_,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_,
|
||||
#endif
|
||||
const Device& device_,
|
||||
Scheduler& scheduler_,
|
||||
u32 width_,
|
||||
u32 height_)
|
||||
#ifdef ANDROID
|
||||
: surface(surface_)
|
||||
#else
|
||||
: surface_handle{surface_handle_}
|
||||
#endif
|
||||
, device{device_}
|
||||
, scheduler{scheduler_}
|
||||
{
|
||||
#ifdef ANDROID
|
||||
Create(surface, width_, height_);
|
||||
#else
|
||||
Create(surface_handle, width_, height_);
|
||||
#endif
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() = default;
|
||||
|
||||
void Swapchain::Create(
|
||||
VkSurfaceKHR_T* surface_,
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface_,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_,
|
||||
#endif
|
||||
u32 width_,
|
||||
u32 height_)
|
||||
{
|
||||
|
|
@ -132,10 +148,18 @@ void Swapchain::Create(
|
|||
is_suboptimal = false;
|
||||
width = width_;
|
||||
height = height_;
|
||||
#ifdef ANDROID
|
||||
surface = surface_;
|
||||
#else
|
||||
surface_handle = surface_handle_;
|
||||
#endif
|
||||
|
||||
const auto physical_device = device.GetPhysical();
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))};
|
||||
#ifdef ANDROID
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)};
|
||||
#else
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface_handle)};
|
||||
#endif
|
||||
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -230,8 +254,14 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
|
|||
|
||||
void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
const auto physical_device{device.GetPhysical()};
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface));
|
||||
|
||||
#ifdef ANDROID
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface);
|
||||
#else
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(surface_handle)};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface_handle);
|
||||
#endif
|
||||
|
||||
has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
!= present_modes.end();
|
||||
|
|
@ -260,7 +290,11 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.surface = VkSurfaceKHR(surface),
|
||||
#ifdef ANDROID
|
||||
.surface = surface,
|
||||
#else
|
||||
.surface = surface_handle,
|
||||
#endif
|
||||
.minImageCount = requested_image_count,
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
|
|
@ -279,7 +313,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
.compositeAlpha = alpha_flags,
|
||||
.presentMode = present_mode,
|
||||
.clipped = VK_FALSE,
|
||||
.oldSwapchain = VkSwapchainKHR{},
|
||||
.oldSwapchain = nullptr,
|
||||
};
|
||||
const u32 graphics_family{device.GetGraphicsFamily()};
|
||||
const u32 present_family{device.GetPresentFamily()};
|
||||
|
|
@ -311,7 +345,11 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
|
||||
}
|
||||
// Request the size again to reduce the possibility of a TOCTOU race condition.
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface));
|
||||
#ifdef ANDROID
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface);
|
||||
#else
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface_handle);
|
||||
#endif
|
||||
swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height);
|
||||
// Don't add code within this and the swapchain creation.
|
||||
swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
|
@ -23,7 +25,11 @@ class Scheduler;
|
|||
class Swapchain {
|
||||
public:
|
||||
explicit Swapchain(
|
||||
VkSurfaceKHR_T* surface,
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle,
|
||||
#endif
|
||||
const Device& device,
|
||||
Scheduler& scheduler,
|
||||
u32 width,
|
||||
|
|
@ -32,7 +38,11 @@ public:
|
|||
|
||||
/// Creates (or recreates) the swapchain with a given size.
|
||||
void Create(
|
||||
VkSurfaceKHR_T* surface,
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle,
|
||||
#endif
|
||||
u32 width,
|
||||
u32 height);
|
||||
|
||||
|
|
@ -118,7 +128,11 @@ private:
|
|||
|
||||
bool NeedsPresentModeUpdate() const;
|
||||
|
||||
VkSurfaceKHR_T* surface;
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
|
||||
const Device& device;
|
||||
Scheduler& scheduler;
|
||||
|
|
|
|||
|
|
@ -2500,7 +2500,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
|||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -40,6 +40,3 @@
|
|||
#undef False
|
||||
#undef None
|
||||
#undef True
|
||||
|
||||
// "Catch-all" handle for both Android and.. the rest of platforms
|
||||
struct VkSurfaceKHR_T;
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
: instance{instance_}, dld{dld_}, physical{physical_},
|
||||
format_properties(GetFormatProperties(physical)) {
|
||||
// Get suitability and device properties.
|
||||
const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
|
||||
const bool is_suitable = GetSuitability(surface != nullptr);
|
||||
|
||||
const VkDriverId driver_id = properties.driver.driverID;
|
||||
|
||||
|
|
@ -557,12 +557,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
}
|
||||
|
||||
if (is_nvidia) {
|
||||
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
||||
const auto arch = GetNvidiaArch();
|
||||
if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
|
||||
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
}
|
||||
|
||||
if (nv_major_version >= 510) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"NVIDIA Drivers >= 510 do not support MSAA->MSAA image blits. "
|
||||
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
|
||||
cant_blit_msaa = true;
|
||||
}
|
||||
|
||||
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
|
||||
// Use hardware depth/stencil blits instead when available
|
||||
if (!extensions.shader_stencil_export) {
|
||||
|
|
@ -586,20 +594,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
if (is_amd_driver) {
|
||||
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
|
||||
sets_per_pool = 96;
|
||||
|
||||
// Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken.
|
||||
if (!features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
|
||||
has_broken_cube_compatibility = true;
|
||||
}
|
||||
|
||||
// AMD drivers (2026+) have broken float16 math on DKCR
|
||||
if (features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD drivers (2026+) have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_qualcomm) {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
|
||||
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
|
||||
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
|
||||
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
|
||||
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
|
||||
|
||||
// Define extensions which must be supported.
|
||||
|
|
@ -318,11 +317,6 @@ public:
|
|||
return properties.properties.limits.minStorageBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns texel buffer offset alignment requirement.
|
||||
VkDeviceSize GetMinTexelBufferOffsetAlignment() const {
|
||||
return properties.properties.limits.minTexelBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns the maximum range for storage buffers.
|
||||
VkDeviceSize GetMaxStorageBufferRange() const {
|
||||
return properties.properties.limits.maxStorageBufferRange;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface(
|
|||
const vk::Instance& instance,
|
||||
[[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) {
|
||||
[[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
|
||||
VkSurfaceKHR unsafe_surface = VkSurfaceKHR{};
|
||||
VkSurfaceKHR unsafe_surface = nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
|
||||
|
|
|
|||
|
|
@ -404,13 +404,13 @@ public:
|
|||
|
||||
/// Construct a handle transferring the ownership from another handle.
|
||||
Handle(Handle&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {}
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {}
|
||||
|
||||
/// Assign the current handle transferring the ownership from another handle.
|
||||
/// Destroys any previously held object.
|
||||
Handle& operator=(Handle&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, Type{});
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
owner = rhs.owner;
|
||||
dld = rhs.dld;
|
||||
return *this;
|
||||
|
|
@ -424,7 +424,7 @@ public:
|
|||
/// Destroys any held object.
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = Type{};
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
/// Returns the address of the held object.
|
||||
|
|
@ -440,7 +440,7 @@ public:
|
|||
|
||||
/// Returns true when there's a held object.
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != Type{};
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
|
|
@ -455,7 +455,7 @@ public:
|
|||
#endif
|
||||
|
||||
protected:
|
||||
Type handle{};
|
||||
Type handle = nullptr;
|
||||
OwnerType owner = nullptr;
|
||||
const Dispatch* dld = nullptr;
|
||||
|
||||
|
|
@ -463,7 +463,7 @@ private:
|
|||
/// Destroys the held object if it exists.
|
||||
void Release() noexcept {
|
||||
if (handle) {
|
||||
Destroy(OwnerType(owner), Type(handle), *dld);
|
||||
Destroy(owner, handle, *dld);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -506,7 +506,7 @@ public:
|
|||
/// Destroys any held object.
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = {};
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
/// Returns the address of the held object.
|
||||
|
|
@ -522,7 +522,7 @@ public:
|
|||
|
||||
/// Returns true when there's a held object.
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != Type{};
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
|
|
@ -537,7 +537,7 @@ public:
|
|||
#endif
|
||||
|
||||
protected:
|
||||
Type handle{};
|
||||
Type handle = nullptr;
|
||||
const Dispatch* dld = nullptr;
|
||||
|
||||
private:
|
||||
|
|
@ -607,7 +607,7 @@ private:
|
|||
std::unique_ptr<AllocationType[]> allocations;
|
||||
std::size_t num = 0;
|
||||
VkDevice device = nullptr;
|
||||
PoolType pool{};
|
||||
PoolType pool = nullptr;
|
||||
const DeviceDispatch* dld = nullptr;
|
||||
};
|
||||
|
||||
|
|
@ -669,12 +669,12 @@ public:
|
|||
Image& operator=(const Image&) = delete;
|
||||
|
||||
Image(Image&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner},
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner},
|
||||
allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {}
|
||||
|
||||
Image& operator=(Image&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, VkImage{});
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
usage = rhs.usage;
|
||||
owner = rhs.owner;
|
||||
allocator = rhs.allocator;
|
||||
|
|
@ -693,11 +693,11 @@ public:
|
|||
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = VkImage{};
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != VkImage{};
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
void SetObjectNameEXT(const char* name) const;
|
||||
|
|
@ -709,7 +709,7 @@ public:
|
|||
private:
|
||||
void Release() const noexcept;
|
||||
|
||||
VkImage handle{};
|
||||
VkImage handle = nullptr;
|
||||
VkImageUsageFlags usage{};
|
||||
VkDevice owner = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
|
|
@ -730,13 +730,13 @@ public:
|
|||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
Buffer(Buffer&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
|
||||
allocation{rhs.allocation}, mapped{rhs.mapped},
|
||||
is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
|
||||
|
||||
Buffer& operator=(Buffer&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, VkBuffer{});
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
owner = rhs.owner;
|
||||
allocator = rhs.allocator;
|
||||
allocation = rhs.allocation;
|
||||
|
|
@ -756,11 +756,11 @@ public:
|
|||
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = VkBuffer{};
|
||||
handle = nullptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != VkBuffer{};
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
/// Returns the host mapped memory, an empty span otherwise.
|
||||
|
|
@ -786,7 +786,7 @@ public:
|
|||
private:
|
||||
void Release() const noexcept;
|
||||
|
||||
VkBuffer handle{};
|
||||
VkBuffer handle = nullptr;
|
||||
VkDevice owner = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
|
|
@ -1020,10 +1020,10 @@ public:
|
|||
[[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
|
||||
|
||||
[[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci,
|
||||
VkPipelineCache cache = {}) const;
|
||||
VkPipelineCache cache = nullptr) const;
|
||||
|
||||
[[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci,
|
||||
VkPipelineCache cache = {}) const;
|
||||
VkPipelineCache cache = nullptr) const;
|
||||
|
||||
[[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,13 @@
|
|||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QTreeView>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/fs/fs.h"
|
||||
|
|
@ -43,7 +42,7 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
|
|||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
tree_view->setAlternatingRowColors(true);
|
||||
tree_view->setSelectionMode(QHeaderView::ExtendedSelection);
|
||||
tree_view->setSelectionMode(QHeaderView::MultiSelection);
|
||||
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
|
||||
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
|
|
@ -249,11 +248,8 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList<QModelIndex> selected) {
|
|||
|
||||
void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) {
|
||||
const QModelIndex index = tree_view->indexAt(pos);
|
||||
auto selected = tree_view->selectionModel()->selectedRows();
|
||||
if (index.isValid() && selected.empty()) {
|
||||
QModelIndex idx = item_model->index(index.row(), 0);
|
||||
if (idx.isValid()) selected << idx;
|
||||
}
|
||||
auto selected = tree_view->selectionModel()->selectedIndexes();
|
||||
if (index.isValid() && selected.empty()) selected = {index};
|
||||
|
||||
if (selected.empty()) return;
|
||||
|
||||
|
|
@ -264,15 +260,6 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) {
|
|||
AddonDeleteRequested(selected);
|
||||
});
|
||||
|
||||
if (selected.length() == 1) {
|
||||
auto loc = selected.at(0).data(PATCH_LOCATION).toString();
|
||||
if (QFileInfo::exists(loc)) {
|
||||
QAction* open = menu.addAction(tr("&Open in File Manager"));
|
||||
connect(open, &QAction::triggered, this,
|
||||
[selected, loc]() { QDesktopServices::openUrl(QUrl::fromLocalFile(loc)); });
|
||||
}
|
||||
}
|
||||
|
||||
menu.exec(tree_view->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
|
@ -17,17 +16,14 @@
|
|||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
|
|
@ -379,12 +375,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||
return true;
|
||||
}
|
||||
|
||||
if (target == ScanTarget::PopulateGameList &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
|
|
@ -393,10 +383,18 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||
provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (Settings::values.ext_content_from_game_dirs.GetValue() &&
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI ||
|
||||
file_type == Loader::FileType::NSP)) {
|
||||
void(provider->AddEntriesFromContainer(file));
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::vector<u64> program_ids;
|
||||
|
|
|
|||
|
|
@ -2019,10 +2019,6 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (QtCommon::provider->AddEntriesFromContainer(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(*QtCommon::system, file);
|
||||
if (!loader) {
|
||||
return;
|
||||
|
|
@ -2037,8 +2033,19 @@ void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
|
|||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
QtCommon::provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id,
|
||||
file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue