mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 05:28:56 +02:00
Compare commits
21 commits
b85ada5d0f
...
d750b5bd8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d750b5bd8e | ||
|
|
0071516022 | ||
|
|
37501512d3 | ||
|
|
6cb9717f49 | ||
|
|
0c333fd09f | ||
|
|
4a60085a76 | ||
|
|
47ed86d3e2 | ||
|
|
2aea7f9584 | ||
|
|
59b0e66722 | ||
|
|
8de1dd151f | ||
|
|
98604d369a | ||
|
|
4337135910 | ||
|
|
395613b01f | ||
|
|
2896fa3835 | ||
|
|
0dad29698e | ||
|
|
5a0780b826 | ||
|
|
d35fc7b7ee | ||
|
|
8678cb06eb | ||
|
|
769edbfea3 | ||
|
|
0ff1d215c8 | ||
|
|
07e3a2aa46 |
429 changed files with 5018 additions and 4481 deletions
|
|
@ -115,7 +115,7 @@ for file in $FILES; do
|
|||
*.cmake|*.sh|*CMakeLists.txt)
|
||||
begin="#"
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*.kt*|*.cpp|*.h|*.qml)
|
||||
begin="//"
|
||||
;;
|
||||
*)
|
||||
|
|
@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then
|
|||
begin="#"
|
||||
shell=true
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*)
|
||||
begin="//"
|
||||
shell="false"
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
|
|||
# FreeBSD 15+ has libusb, versions below should disable it
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
|
||||
|
||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF)
|
||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
|
||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ set(GIT_DESC ${BUILD_VERSION})
|
|||
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
|
||||
if (NIGHTLY_BUILD)
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
|
||||
set(BUILD_AUTO_UPDATE_API "api.github.com")
|
||||
set(BUILD_AUTO_UPDATE_API "https://api.github.com")
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/repos/")
|
||||
set(BUILD_AUTO_UPDATE_REPO "Eden-CI/Nightly")
|
||||
set(REPO_NAME "Eden Nightly")
|
||||
else()
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API "git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API "https://git.eden-emu.dev")
|
||||
set(BUILD_AUTO_UPDATE_API_PATH "/api/v1/repos/")
|
||||
set(BUILD_AUTO_UPDATE_REPO "eden-emu/eden")
|
||||
set(REPO_NAME "Eden")
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ These options control executables and build flavors.
|
|||
|
||||
The following options are desktop only.
|
||||
|
||||
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
|
||||
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
|
||||
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED)
|
||||
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend
|
||||
- Unavailable on Windows/ARM64
|
||||
- You probably shouldn't turn this off.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Setting a Custom Date/Time in Eden
|
||||
|
||||
Use this guide whenever you want to modify the Date or Time that Eden reports to games. This can be useful for modifying RNG elements, skipping wait times in games, etc.
|
||||
Use this guide whenever you want to modify the Date or Time that Eden reports to games. This can be useful for modifying RNG elements, skipping wait times in games, etc.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Setting-a-Custom-Date-Time-in-Eden-2b357c2edaf680acb8d4e63ccc126564) for a version of this guide with images & visual elements.**
|
||||
|
||||
|
|
@ -16,5 +16,5 @@ Use this guide whenever you want to modify the Date or Time that Eden reports to
|
|||
|
||||
1. Navigate to *Emulation → Configure*.
|
||||
2. Click on the **System** item on the left-hand side navigation, then check the *Custom RTC Date* box.
|
||||
3. The Date/Time option now becomes editable. Set it to the value you want and hit **OK**.
|
||||
4. GREAT SCOTT! We have time traveled! You can of course go forward or backward in time (as long as it is not before the year 1970) and your game should update accordingly (e.g. certain *Super Mario Odyssey* moons that take time for flowers to grow will now be fully grown.).
|
||||
3. The Date/Time option now becomes editable. Set it to the value you want and hit **OK**.
|
||||
4. GREAT SCOTT! We have time traveled! You can of course go forward or backward in time (as long as it is not before the year 1970) and your game should update accordingly (e.g. certain *Super Mario Odyssey* moons that take time for flowers to grow will now be fully grown.).
|
||||
|
|
@ -16,6 +16,15 @@ The CPU must support FMA for an optimal gameplay experience. The GPU needs to su
|
|||
|
||||
If your GPU doesn't support or is just behind by a minor version, see Mesa environment variables below (*nix only).
|
||||
|
||||
## Releases and versions
|
||||
|
||||
- Stable releases/Versioned releases: Has a version number and it's the versions we expect 3rd party repositories to host (package managers and such), these are, well, stable, have low amount of regressions (wrt. to master and nightlies) and generally focus on "keeping things without regressions", recommended for the average user.
|
||||
- RC releases: Release candidate, generally "less stable but still stable" versions.
|
||||
- Full release: "The stablest possible you could get".
|
||||
- Nightly: Builds done around 2PM UTC (if there are any changes), generally stable, but not recommended for the average user. These contain daily updates and may contain critical fixes for some games.
|
||||
- Master: Unstable builds, can lead from a game working exceptionally fine to absolute crashing in some systems because someone forgot to check if NixOS or Solaris worked. These contain straight from the oven fixes, please don't use them unless you plan to contribute something! They're very experimental! Still 95% of the time it will work just fine.
|
||||
- PR builds: Highly experimental builds, testers may grab from these. The average user should treat them the same as master builds, except sometimes they straight up don't build/work.
|
||||
|
||||
## User configuration
|
||||
|
||||
### Configuration directories
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based a
|
|||
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
|
||||
- `-f`: Use fullscreen.
|
||||
- `-u <number>`: Select the index of the user to load as.
|
||||
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
|
||||
- `-qlaunch`: Launch QLaunch.
|
||||
- `-setup`: Launch setup applet.
|
||||
|
||||
|
|
@ -20,3 +21,4 @@ There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based a
|
|||
- `--program/-p`: Specify the program arguments to pass (optional).
|
||||
- `--user/-u`: Specify the user index.
|
||||
- `--version/-v`: Display version and quit.
|
||||
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
# Configuring Controller Profiles
|
||||
|
||||
Use this guide for when you want to configure specific controller settings to be reused.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Set Up and Configured
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
1. Launch Eden and wait for it to load.
|
||||
2. Navigate to *Emulation > Configure…*
|
||||
3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game.
|
||||
4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings.
|
||||
5. Select **OK** to close the settings menu.
|
||||
|
||||
## Setting Controller Profiles By Game
|
||||
|
||||
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- Eden Emulator set up and fully configured
|
||||
- Controller Profile Created
|
||||
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
|
||||
|
||||
---
|
||||
|
||||
### Steps
|
||||
|
||||
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
|
||||
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
|
||||
|
||||
</aside>
|
||||
1. Click **OK** to apply the profile mapping.
|
||||
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
|
||||
65
docs/user/Controllers.md
Normal file
65
docs/user/Controllers.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# User Handbook - Controllers
|
||||
|
||||
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
|
||||
|
||||
## Using external controllers on the Steamdeck
|
||||
|
||||
In desktop mode ignore your pro controller/xbox contoller external controller and use **Steam Virtual Gamepad 0 as Player 1**. If you have multiple external controllers set **Player 2 to Steam Virtual Gamepad 1**. Steam app must not be closed on desktop mode.
|
||||
|
||||
Here's the annoying part of it. When waking up the steam deck from sleep try not to touch any button on the Steamdeck and turn on your external controller. Then open the Eden.AppImage. If you're lucky you can get your external controller to be position 0 and also Steam Virtual Gamepad 0 in desktop mode. If not that is ok too unless you need to configure player 1 to have gyro. You might need to repeat this to get your external controller as Steam Virtual Gamepad 0 so you can config Player 1 having gyro. You might be able to config player 1 to have gyro with the Steamdeck itself. Or you can also config player 1, 2, 3, etc, to have gyro somehow. Make sure they are all using Virtual Gamepads though.
|
||||
|
||||
Turn off controller then go to gaming mode. Try not to touch any buttons on the physical Steamdeck. When in gaming mode turn on the external controller. If lucky it will be assigned as Steam Virtual Gamepad 0. If not just use steam Gamemode feature to rearrange controller positions order.
|
||||
|
||||
Basically the Steamdeck or the external controller is fighting for position 0 and it depends on what is touched first after waking from sleep.
|
||||
|
||||
## Configuring Controller Profiles
|
||||
|
||||
Use this guide for when you want to configure specific controller settings to be reused.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
#### Pre-Requisites
|
||||
|
||||
- Eden Set Up and Configured
|
||||
|
||||
---
|
||||
|
||||
#### Steps
|
||||
1. Launch Eden and wait for it to load.
|
||||
2. Navigate to *Emulation > Configure…*
|
||||
3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game.
|
||||
4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings.
|
||||
5. Select **OK** to close the settings menu.
|
||||
|
||||
### Setting Controller Profiles By Game
|
||||
|
||||
Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode.
|
||||
|
||||
**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.**
|
||||
|
||||
---
|
||||
|
||||
#### Pre-Requisites
|
||||
|
||||
- Eden Emulator set up and fully configured
|
||||
- Controller Profile Created
|
||||
- See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed.
|
||||
|
||||
---
|
||||
|
||||
#### Steps
|
||||
|
||||
1. *Right-Click* the game you want to apply the profile to in the main window and select **Properties.**
|
||||
2. Navigate to the **Input Profiles** tab in the window that appears. Drop down on *Player 1 profile* (or whatever player profile you want to apply it to) and select the profile you want.
|
||||
|
||||
<aside>
|
||||
|
||||
***NOTE***: You may have to resize the window to see all tabs, or press the arrows by the tabs to see **Input Profiles**.
|
||||
|
||||
</aside>
|
||||
1. Click **OK** to apply the profile mapping.
|
||||
2. Launch the game and confirm that the profile is applied, regardless of what the global configuration is.
|
||||
|
|
@ -11,10 +11,12 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/
|
|||
- **[The Basics](Basics.md)**
|
||||
- **[Quickstart](./QuickStart.md)**
|
||||
- **[Settings](./Settings.md)**
|
||||
- **[Installing Mods](./Mods.md)**
|
||||
- **[Run On macOS](./RunOnMacOS.md)**
|
||||
- **[Controllers](./Controllers.md)**
|
||||
- **[Controller profiles](./Controllers.md#configuring-controller-profiles)**
|
||||
- **[Audio](Audio.md)**
|
||||
- **[Graphics](Graphics.md)**
|
||||
- **[Installing Mods](./Mods.md)**
|
||||
- **[Run On macOS](./RunOnMacOS.md)**
|
||||
- **[Data, Savefiles and Storage](Storage.md)**
|
||||
- **[Orphaned Profiles](Orphaned.md)**
|
||||
- **[Troubleshooting](./Troubleshoot.md)**
|
||||
|
|
@ -23,7 +25,6 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/
|
|||
- **[Importing Saves](./ImportingSaves.md)**
|
||||
- **[Installing Atmosphere Mods](./InstallingAtmosphereMods.md)**
|
||||
- **[Installing Updates & DLCs](./InstallingUpdatesDLC.md)**
|
||||
- **[Controller Profiles](./ControllerProfiles.md)**
|
||||
- **[Alter Date & Time](./AlterDateTime.md)**
|
||||
|
||||
## 3rd-party Integration
|
||||
|
|
@ -35,6 +36,7 @@ A copy of this handbook is [available online](https://git.eden-emu.dev/eden-emu/
|
|||
- **[Obtainium](./ThirdParty.md#configuring-obtainium)**
|
||||
- **[ES-DE](./ThirdParty.md#configuring-es-de)**
|
||||
- **[Mirrors](./ThirdParty.md#mirrors)**
|
||||
- **[GameMode](./ThirdParty.md#configuring-gamemode)**
|
||||
|
||||
## Advanced
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
# Allowing Eden to Run on MacOS
|
||||
# User Handbook - Run on macOS
|
||||
|
||||
Current macOS support is still experimental and very reliant on MoltenVK developments, plans have shifted to properly provide support for KosmicKrisp and similar new GPU endeavours, but macOS users still are bound to MoltenVK itself.
|
||||
|
||||
Users of macOS may wish to use [Asahi Linux](https://wiki.gentoo.org/wiki/Project:Asahi/Guide) for the rising KosmicKrisp support.
|
||||
|
||||
As of writing, neither macOS nor Asahi has support for NCE; additionally Asahi has extraneous paging bugs with fastmem.
|
||||
|
||||
## Allowing Eden to Run on MacOS
|
||||
|
||||
Use this guide when you need to allow Eden to run on a Mac system, but are being blocked by Apple Security policy.
|
||||
|
||||
|
|
@ -6,19 +14,19 @@ Use this guide when you need to allow Eden to run on a Mac system, but are being
|
|||
|
||||
---
|
||||
|
||||
### Pre-Requisites
|
||||
#### Pre-Requisites
|
||||
|
||||
- Permissions to modify settings in MacOS
|
||||
|
||||
---
|
||||
|
||||
## Why am I Seeing This?
|
||||
### Why am I Seeing This?
|
||||
|
||||
Recent versions of MacOS (Catalina & newer) introduced the **Gatekeeper** security functionality, requiring software to be signed by Apple or a trusted (aka - paying) developer. If the signature isn’t on the list of trusted ones, it will stop the program from executing and display the message above.
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
### Steps
|
||||
|
||||
1. Open the *System Settings* panel.
|
||||
2. Navigate to *Privacy & Security*.
|
||||
|
|
|
|||
|
|
@ -50,5 +50,4 @@ See also [an extended breakdown of some options](./Graphics.md).
|
|||
|
||||
## 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
|
||||
See [controllers](./Controllers.md).
|
||||
|
|
|
|||
|
|
@ -1,39 +1,99 @@
|
|||
# User Handbook - Testing
|
||||
|
||||
While this is mainly aimed for testers - normal users can benefit from these guidelines to make their life easier when trying to outline and/or report an issue.
|
||||
|
||||
## Getting logs
|
||||
|
||||
In order to get more information, you can find logs in the following location:
|
||||
|
||||
|
||||
## How to Test a PR Against the Based Master When Issues Arise
|
||||
# Testing
|
||||
|
||||
When you're testing a pull request (PR) and encounter unexpected behavior, it's important to determine whether the issue was introduced by the PR or if it already exists in the base code. To do this, compare the behavior against the based master branch.
|
||||
|
||||
Even before an issue occurs, it is best practice to keep the same settings and delete the shader cache. Using an already made shader cache can make the PR look like it is having a regression in some rare cases.
|
||||
|
||||
### What to Do When Something Seems Off
|
||||
Try not to test PRs which are for documentation or extremely trivial changes (like a PR that changes the app icon), unless you really want to; generally avoid any PRs marked `[docs]`.
|
||||
|
||||
If a PR specifies it is for a given platform (i.e `linux`) then just test on Linux. If it says `NCE` then test on Android and Linux ARM64 (Raspberry Pi and such). macOS fixes may also affect Asahi, test that if you can too.
|
||||
|
||||
You may also build artifacts yourself, be aware that the resulting builds are NOT the same as those from CI, because of package versioning and build environment differences. One famous example is FFmpeg randomly breaking on many Arch distros due to packaging differences.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Think of the source code as a "tree", with the "trunk" of that tree being our `master` branch, any other branches are PRs or separate development branches, only our stable releases pull from `master` - all other branches are considered unstable and aren't recommended to pull from unless you're testing multiple branches at once.
|
||||
|
||||
Here's some terminology you may want to familiarize yourself with:
|
||||
|
||||
- PR: Pull request, a change in the codebase; from which the author of said change (the programmer) requests a pull of that branch into master (make it so the new code makes it into a release basically).
|
||||
- Bisect: Bilinear method of searching regressions, some regressions may be sporadic and can't be bisected, but the overwhelming majority are.
|
||||
- WIP: Work-in-progress.
|
||||
- Regression: A new bug/glitch caused by new code, i.e "Zelda broke in android after commit xyz".
|
||||
- Master: The "root" branch, this is where all merged code goes to, traditionally called `main`, `trunk` or just `master`, it contains all the code that eventually make it to stable releases.
|
||||
- `HEAD`: Latest commit in a given branch, `HEAD` of `master` is the latest commit on branch `master`.
|
||||
- `origin`: The default "remote", basically the URL from where git is located at, for most of the time that location is https://git.eden-emu.dev/eden-emu/eden.
|
||||
|
||||
## Testing checklist
|
||||
|
||||
For regressions/bugs from PRs or commits:
|
||||
|
||||
- [ ] Occurs in master?
|
||||
- If it occurs on master:
|
||||
- [ ] Occurs on previous stable release? (before this particular PR).
|
||||
- If it occurs on previous stable release:
|
||||
- [ ] Occurs on previous-previous stable release?
|
||||
- And so on and so forth... some bugs come from way before Eden was even conceived.
|
||||
- Otherwise, try bisecting between the previous stable release AND the latest `HEAD` of master
|
||||
- [ ] Occurs in given commit?
|
||||
- [ ] Occurs in PR?
|
||||
- If it occurs on PR:
|
||||
- [ ] Bisected PR? (if it has commits)
|
||||
- [ ] Found bisected commit?
|
||||
|
||||
If an issue sporadically appears, try to do multiple runs, try if possible, to count the number of times it has failed and the number of times it has "worked just fine"; say it worked 3 times but failed 1. then there is a 1/4th chance every run that the issue is replicated - so every bisect step would require 4 runs to ensure there is atleast a chance of triggering the bug.
|
||||
|
||||
## What to do when something seems off
|
||||
|
||||
If you notice something odd during testing:
|
||||
|
||||
- Reproduce the issue using the based master branch.
|
||||
- Observe whether the same behavior occurs.
|
||||
|
||||
### Two Possible Outcomes
|
||||
From there onwards there can be two possible outcomes:
|
||||
|
||||
- If the issue exists in the based master: This means the problem was already present before the PR. The PR most likely did not introduce the regression.
|
||||
- If the issue does not exist in the based master: This suggests the PR most likely introduced the regression and needs further investigation.
|
||||
|
||||
### Report your findings
|
||||
## Reporting Your Findings
|
||||
|
||||
When you report your results:
|
||||
|
||||
- Clearly state whether the behavior was observed in the based master.
|
||||
- Indicate whether the result is good (expected behavior) or bad (unexpected or broken behavior). Without mentioning if your post/report/log is good or bad it may confuse the Developer of the PR.
|
||||
- Example:
|
||||
```
|
||||
1. "Tested on based master — issue not present. Bad result for PR, likely regression introduced."
|
||||
2. "Tested on based master — issue already present. Good result for PR, not a regression."
|
||||
```
|
||||
- Indicate whether the result is good (expected behavior) or bad (unexpected or broken behavior). Without mentioning if your post/report/log is good or bad it may confuse the developer of the PR.
|
||||
|
||||
For example:
|
||||
|
||||
1. "Bad result for PR: Tested on based master - issue not present. Likely regression introduced."
|
||||
2. "Good result for PR: Tested on based master - issue already present. Not a regression."
|
||||
|
||||
This approach helps maintain clarity and accountability in the testing process and ensures regressions are caught and addressed efficiently.
|
||||
|
||||
If the behavior seems normal for a certain game/feature then it may not be always required to check against the based master.
|
||||
|
||||
This approach helps maintain clarity and accountability in the testing process and ensures regressions are caught and addressed efficiently. If the behavior seems normal for a certain game/feature then it may not be always required to check against the based master.
|
||||
|
||||
If a master build for the PR' based master does not exist. It will be helpful to just test past and future builds nearby. That would help with gathering more information about the problem.
|
||||
|
||||
**Always include [debugging info](../Debug.md) as needed**.
|
||||
**Always include [debugging info](../Debug.md) as needed**.
|
||||
|
||||
## Bisecting
|
||||
|
||||
One happy reminder, when testing, *know how to bisect!*
|
||||
|
||||
Say you're trying to find an issue between 1st of Jan and 8th of Jan, you can search by dividing "in half" the time between each commit:
|
||||
- Check for 4th of Jan
|
||||
- If 4th of Jan is "working" then the issue must be in the future
|
||||
- So then check 6th of Jan
|
||||
- If 6th of Jan isn't working then the issue must be in the past
|
||||
- So then check 5th of Jan
|
||||
- If 5th of Jan worked, then the issue starts at 6th of Jan
|
||||
|
||||
The faulty commit then, is 6th of Jan. This is called bisection https://git-scm.com/docs/git-bisect
|
||||
|
||||
## Notes
|
||||
|
||||
- PR's marked with **WIP** do NOT need to be tested unless explicitly asked (check the git in case)
|
||||
- Sometimes license checks may fail, hover over the build icon to see if builds did succeed, as the CI will push builds even if license checks fail.
|
||||
- All open PRs can be viewed [here](https://git.eden-emu.dev/eden-emu/eden/pulls/).
|
||||
- If site is down use one of the [mirrors](./user/ThirdParty.md#mirrors).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ The Eden emulator by itself lacks some functionality - or otherwise requires ext
|
|||
|
||||
While most of the links mentioned in this guide are relatively "safe"; we urge users to use their due diligence and appropriatedly verify the integrity of all files downloaded and ensure they're not compromised.
|
||||
|
||||
- [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)
|
||||
|
||||
|
|
@ -66,3 +65,9 @@ Note: Even though the site isn't Codeberg, it uses the same Forgejo/Gitea backen
|
|||
```xml
|
||||
<command label="Eden (Standalone)">%EMULATOR_EDEN% %ACTION%=android.nfc.action.TECH_DISCOVERED %DATA%=%ROMPROVIDER%</command>
|
||||
```
|
||||
|
||||
## Configuring GameMode
|
||||
|
||||
There is a checkbox to enable gamemode automatically. The `libgamemode.so` library must be findable on the standard `LD_LIBRARY_PATH` otherwise it will not properly be enabled. If for whatever reason it doesn't work, see [Arch wiki: GameMode](https://wiki.archlinux.org/title/GameMode) for more info.
|
||||
|
||||
You may launch the emulator directly via the wrapper `gamemode <program>`, and things should work out of the box.
|
||||
|
|
|
|||
|
|
@ -242,7 +242,6 @@ if (YUZU_CMD)
|
|||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_definitions(-DYUZU_QT_WIDGETS)
|
||||
add_subdirectory(qt_common)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:label="@string/preferences_settings"/>
|
||||
|
||||
<activity
|
||||
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreenActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:label="@string/preferences_settings"/>
|
||||
|
||||
<activity
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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-2.0-or-later
|
||||
|
||||
|
|
@ -6,10 +9,10 @@ package org.yuzu.yuzu_emu.adapters
|
|||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.findNavController
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
|
|
@ -67,8 +70,13 @@ class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
|
|||
title = YuzuApplication.appContext.getString(applet.titleId),
|
||||
path = appletPath
|
||||
)
|
||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
||||
binding.root.findNavController().navigate(action)
|
||||
binding.root.findNavController().navigate(
|
||||
R.id.action_global_emulationActivity,
|
||||
bundleOf(
|
||||
"game" to appletGame,
|
||||
"custom" to false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ class GpuUnswizzleSetting(
|
|||
override val isSaveable = true
|
||||
override val isRuntimeModifiable = true
|
||||
override val isSwitchable = true
|
||||
override val pairedSettingKey: String = ""
|
||||
override var global: Boolean
|
||||
get() {
|
||||
return BooleanSetting.GPU_UNSWIZZLE_ENABLED.global &&
|
||||
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.global &&
|
||||
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.global &&
|
||||
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.global
|
||||
}
|
||||
set(value) {
|
||||
BooleanSetting.GPU_UNSWIZZLE_ENABLED.global = value
|
||||
IntSetting.GPU_UNSWIZZLE_TEXTURE_SIZE.global = value
|
||||
IntSetting.GPU_UNSWIZZLE_STREAM_SIZE.global = value
|
||||
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.global = value
|
||||
}
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = "combined"
|
||||
override fun reset() {
|
||||
BooleanSetting.GPU_UNSWIZZLE_ENABLED.reset()
|
||||
|
|
@ -72,4 +86,4 @@ class GpuUnswizzleSetting(
|
|||
IntSetting.GPU_UNSWIZZLE_CHUNK_SIZE.setInt(value)
|
||||
|
||||
fun reset() = setting.reset()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -989,6 +989,7 @@ abstract class SettingsItem(
|
|||
override val isRuntimeModifiable: Boolean = false
|
||||
override val defaultValue: Boolean = true
|
||||
override val isSwitchable: Boolean = true
|
||||
override val pairedSettingKey: String = ""
|
||||
override var global: Boolean
|
||||
get() {
|
||||
return BooleanSetting.FASTMEM.global &&
|
||||
|
|
|
|||
|
|
@ -111,18 +111,10 @@ class SettingsActivity : AppCompatActivity() {
|
|||
if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
|
||||
navHostFragment.navController.popBackStack()
|
||||
} else {
|
||||
finishWithFragmentLikeAnimation()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishWithFragmentLikeAnimation() {
|
||||
finish()
|
||||
overridePendingTransition(
|
||||
androidx.navigation.ui.R.anim.nav_default_pop_enter_anim,
|
||||
androidx.navigation.ui.R.anim.nav_default_pop_exit_anim
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||
|
|
@ -178,7 +170,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
getString(R.string.settings_reset),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
finishWithFragmentLikeAnimation()
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.InsetsHelper
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
|
||||
enum class SettingsSubscreen {
|
||||
PROFILE_MANAGER,
|
||||
DRIVER_MANAGER,
|
||||
DRIVER_FETCHER,
|
||||
FREEDRENO_SETTINGS,
|
||||
APPLET_LAUNCHER,
|
||||
INSTALLABLE,
|
||||
GAME_FOLDERS,
|
||||
ABOUT,
|
||||
LICENSES,
|
||||
GAME_INFO,
|
||||
ADDONS,
|
||||
}
|
||||
|
||||
class SettingsSubscreenActivity : AppCompatActivity() {
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
|
||||
private val args by navArgs<SettingsSubscreenActivityArgs>()
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
ThemeHelper.setTheme(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||
if (savedInstanceState == null) {
|
||||
val navController = navHostFragment.navController
|
||||
val navGraph = navController.navInflater.inflate(
|
||||
R.navigation.settings_subscreen_navigation
|
||||
)
|
||||
navGraph.setStartDestination(resolveStartDestination())
|
||||
navController.setGraph(navGraph, createStartDestinationArgs())
|
||||
}
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||
InsetsHelper.GESTURE_NAVIGATION
|
||||
) {
|
||||
binding.navigationBarShade.setBackgroundColor(
|
||||
ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
binding.navigationBarShade,
|
||||
com.google.android.material.R.attr.colorSurface
|
||||
),
|
||||
ThemeHelper.SYSTEM_BAR_ALPHA
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() = navigateBack()
|
||||
}
|
||||
)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||
DirectoryInitialization.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateBack() {
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||
if (!navHostFragment.navController.popBackStack()) {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveStartDestination(): Int =
|
||||
when (args.destination) {
|
||||
SettingsSubscreen.PROFILE_MANAGER -> R.id.profileManagerFragment
|
||||
SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment
|
||||
SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment
|
||||
SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment
|
||||
SettingsSubscreen.INSTALLABLE -> R.id.installableFragment
|
||||
SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment
|
||||
SettingsSubscreen.ABOUT -> R.id.aboutFragment
|
||||
SettingsSubscreen.LICENSES -> R.id.licensesFragment
|
||||
SettingsSubscreen.GAME_INFO -> R.id.gameInfoFragment
|
||||
SettingsSubscreen.ADDONS -> R.id.addonsFragment
|
||||
}
|
||||
|
||||
private fun createStartDestinationArgs(): Bundle =
|
||||
when (args.destination) {
|
||||
SettingsSubscreen.DRIVER_MANAGER,
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS -> bundleOf("game" to args.game)
|
||||
|
||||
SettingsSubscreen.GAME_INFO,
|
||||
SettingsSubscreen.ADDONS -> bundleOf(
|
||||
"game" to requireNotNull(args.game) {
|
||||
"Game is required for ${args.destination}"
|
||||
}
|
||||
)
|
||||
|
||||
else -> Bundle()
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.navigationBarShade
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
|
||||
mlpNavShade.height = barInsets.bottom
|
||||
binding.navigationBarShade.layoutParams = mlpNavShade
|
||||
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,9 +21,10 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentAboutBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
|
@ -54,7 +55,7 @@ class AboutFragment : Fragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
binding.toolbarAbout.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.imageLogo.setOnLongClickListener {
|
||||
|
|
@ -72,8 +73,11 @@ class AboutFragment : Fragment() {
|
|||
)
|
||||
}
|
||||
binding.buttonLicenses.setOnClickListener {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.LICENSES,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
|
||||
val buildName = getString(R.string.app_name_suffixed)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.core.view.updatePadding
|
|||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
|
|
@ -61,7 +60,7 @@ class AddonsFragment : Fragment() {
|
|||
homeViewModel.setStatusBarShadeVisibility(false)
|
||||
|
||||
binding.toolbarAddons.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
|
@ -12,7 +12,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
|
@ -50,7 +49,7 @@ class AppletLauncherFragment : Fragment() {
|
|||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarApplets.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
val applets = listOf(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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-2.0-or-later
|
||||
|
||||
|
|
@ -5,15 +8,18 @@ package org.yuzu.yuzu_emu.fragments
|
|||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.InstallableActions
|
||||
|
||||
class ContentTypeSelectionDialogFragment : DialogFragment() {
|
||||
private val addonViewModel: AddonViewModel by activityViewModels()
|
||||
|
|
@ -23,6 +29,52 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
|
|||
|
||||
private var selectedItem = 0
|
||||
|
||||
private val installGameUpdateLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { documents ->
|
||||
if (documents.isEmpty()) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val game = addonViewModel.game
|
||||
if (game == null) {
|
||||
installContent(documents)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.verifying_content,
|
||||
false
|
||||
) { _, _ ->
|
||||
var updatesMatchProgram = true
|
||||
for (document in documents) {
|
||||
val valid = NativeLibrary.doesUpdateMatchProgram(
|
||||
game.programId,
|
||||
document.toString()
|
||||
)
|
||||
if (!valid) {
|
||||
updatesMatchProgram = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
if (updatesMatchProgram) {
|
||||
installContent(documents)
|
||||
} else {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.content_install_notice,
|
||||
descriptionId = R.string.content_install_notice_description,
|
||||
positiveAction = { installContent(documents) },
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
return@newInstance Any()
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val launchOptions =
|
||||
arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats))
|
||||
|
|
@ -31,12 +83,11 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
|
|||
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
|
||||
}
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
return MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.select_content_type)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
when (selectedItem) {
|
||||
0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*"))
|
||||
0 -> installGameUpdateLauncher.launch(arrayOf("*/*"))
|
||||
else -> {
|
||||
if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) {
|
||||
preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply()
|
||||
|
|
@ -47,7 +98,7 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
|
||||
.setSingleChoiceItems(launchOptions, selectedItem) { _: DialogInterface, i: Int ->
|
||||
selectedItem = i
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
|
@ -65,4 +116,13 @@ class ContentTypeSelectionDialogFragment : DialogFragment() {
|
|||
private const val SELECTED_ITEM = "SelectedItem"
|
||||
private const val MOD_NOTICE_SEEN = "ModNoticeSeen"
|
||||
}
|
||||
|
||||
private fun installContent(documents: List<Uri>) {
|
||||
InstallableActions.installContent(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
|
|
@ -142,7 +141,7 @@ class DriverFetcherFragment : Fragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
binding.toolbarDrivers.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.listDrivers.layoutManager = LinearLayoutManager(context)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.navigation.fragment.navArgs
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -27,6 +28,7 @@ import org.yuzu.yuzu_emu.adapters.DriverAdapter
|
|||
import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
|
||||
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
|
|
@ -105,7 +107,7 @@ class DriverManagerFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding.toolbarDrivers.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.buttonInstall.setOnClickListener {
|
||||
|
|
@ -113,9 +115,11 @@ class DriverManagerFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding.buttonFetch.setOnClickListener {
|
||||
binding.root.findNavController().navigate(
|
||||
R.id.action_driverManagerFragment_to_driverFetcherFragment
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.DRIVER_FETCHER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
|
||||
binding.listDrivers.apply {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,21 @@
|
|||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.FolderAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
|
||||
|
|
@ -25,7 +26,6 @@ import org.yuzu.yuzu_emu.model.DirectoryType
|
|||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
|
|
@ -36,6 +36,20 @@ class GameFoldersFragment : Fragment() {
|
|||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
|
||||
private val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result != null) {
|
||||
processGamesDir(result)
|
||||
}
|
||||
}
|
||||
|
||||
private val getExternalContentDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result != null) {
|
||||
processExternalContentDir(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
|
|
@ -59,7 +73,7 @@ class GameFoldersFragment : Fragment() {
|
|||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarFolders.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.listFolders.apply {
|
||||
|
|
@ -74,7 +88,6 @@ class GameFoldersFragment : Fragment() {
|
|||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
}
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
binding.buttonAdd.setOnClickListener {
|
||||
// Show a model to choose between Game and External Content
|
||||
val options = arrayOf(
|
||||
|
|
@ -87,10 +100,10 @@ class GameFoldersFragment : Fragment() {
|
|||
.setItems(options) { _, which ->
|
||||
when (which) {
|
||||
0 -> { // Game Folder
|
||||
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
}
|
||||
1 -> { // External Content Folder
|
||||
mainActivity.getExternalContentDirectory.launch(null)
|
||||
getExternalContentDirectory.launch(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +118,50 @@ class GameFoldersFragment : Fragment() {
|
|||
gamesViewModel.onCloseGameFoldersFragment()
|
||||
}
|
||||
|
||||
private fun processGamesDir(result: Uri) {
|
||||
requireContext().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
val uriString = result.toString()
|
||||
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
|
||||
if (folder != null) {
|
||||
Toast.makeText(
|
||||
requireContext().applicationContext,
|
||||
R.string.folder_already_added,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
AddGameFolderDialogFragment.newInstance(uriString, calledFromGameFragment = false)
|
||||
.show(parentFragmentManager, AddGameFolderDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private fun processExternalContentDir(result: Uri) {
|
||||
requireContext().contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
val uriString = result.toString()
|
||||
val folder = gamesViewModel.folders.value.firstOrNull {
|
||||
it.uriString == uriString && it.type == DirectoryType.EXTERNAL_CONTENT
|
||||
}
|
||||
if (folder != null) {
|
||||
Toast.makeText(
|
||||
requireContext().applicationContext,
|
||||
R.string.folder_already_added,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
val externalContentDir = GameDir(uriString, deepScan = false, DirectoryType.EXTERNAL_CONTENT)
|
||||
gamesViewModel.addFolder(externalContentDir, savedFromGameFragment = false)
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
|
@ -18,7 +18,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
|
@ -64,7 +63,7 @@ class GameInfoFragment : Fragment() {
|
|||
binding.apply {
|
||||
toolbarInfo.title = args.game.title
|
||||
toolbarInfo.setNavigationOnClickListener {
|
||||
view.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
val pathString = Uri.parse(args.game.path).path ?: ""
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
|
|||
import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.GameProperty
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
|
|
@ -250,8 +251,10 @@ class GamePropertiesFragment : Fragment() {
|
|||
R.string.info_description,
|
||||
R.drawable.ic_info_outline,
|
||||
action = {
|
||||
val action = GamePropertiesFragmentDirections
|
||||
.actionPerGamePropertiesFragmentToGameInfoFragment(args.game)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.GAME_INFO,
|
||||
args.game
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
|
|
@ -317,8 +320,11 @@ class GamePropertiesFragment : Fragment() {
|
|||
R.string.add_ons_description,
|
||||
R.drawable.ic_edit,
|
||||
action = {
|
||||
val action = GamePropertiesFragmentDirections
|
||||
.actionPerGamePropertiesFragmentToAddonsFragment(args.game)
|
||||
val action =
|
||||
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.ADDONS,
|
||||
args.game
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
|
|
@ -333,8 +339,11 @@ class GamePropertiesFragment : Fragment() {
|
|||
R.drawable.ic_build,
|
||||
detailsFlow = driverViewModel.selectedDriverTitle,
|
||||
action = {
|
||||
val action = GamePropertiesFragmentDirections
|
||||
.actionPerGamePropertiesFragmentToDriverManagerFragment(args.game)
|
||||
val action =
|
||||
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.DRIVER_MANAGER,
|
||||
args.game
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
|
|
@ -347,8 +356,11 @@ class GamePropertiesFragment : Fragment() {
|
|||
R.string.freedreno_per_game_description,
|
||||
R.drawable.ic_graphics,
|
||||
action = {
|
||||
val action = GamePropertiesFragmentDirections
|
||||
.actionPerGamePropertiesFragmentToFreedrenoSettingsFragment(args.game)
|
||||
val action =
|
||||
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS,
|
||||
args.game
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
|||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
|
|
@ -126,8 +127,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.profile_manager_description,
|
||||
R.drawable.ic_account_circle,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_profileManagerFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.PROFILE_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -137,8 +141,10 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_build,
|
||||
{
|
||||
val action = HomeSettingsFragmentDirections
|
||||
.actionHomeSettingsFragmentToDriverManagerFragment(null)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.DRIVER_MANAGER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ true },
|
||||
|
|
@ -154,7 +160,12 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.gpu_driver_settings,
|
||||
R.drawable.ic_graphics,
|
||||
{
|
||||
binding.root.findNavController().navigate(R.id.freedrenoSettingsFragment)
|
||||
val action =
|
||||
HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.FREEDRENO_SETTINGS,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -175,8 +186,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.applets_description,
|
||||
R.drawable.ic_applet,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_appletLauncherFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.APPLET_LAUNCHER,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
},
|
||||
{ NativeLibrary.isFirmwareAvailable() },
|
||||
R.string.applets_error_firmware,
|
||||
|
|
@ -189,8 +203,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.manage_yuzu_data_description,
|
||||
R.drawable.ic_install,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_installableFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.INSTALLABLE,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -200,8 +217,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.GAME_FOLDERS,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
@ -284,9 +304,11 @@ class HomeSettingsFragment : Fragment() {
|
|||
R.string.about_description,
|
||||
R.drawable.ic_info_outline,
|
||||
{
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity(
|
||||
SettingsSubscreen.ABOUT,
|
||||
null
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
|
@ -14,23 +14,23 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.InstallableAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
|
||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.model.Installable
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.InstallableActions
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
|
@ -45,6 +45,9 @@ class InstallableFragment : Fragment() {
|
|||
private val binding get() = _binding!!
|
||||
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||
private val addonViewModel: AddonViewModel by activityViewModels()
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -65,12 +68,10 @@ class InstallableFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
|
||||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarInstallables.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
|
||||
|
|
@ -84,8 +85,8 @@ class InstallableFragment : Fragment() {
|
|||
Installable(
|
||||
R.string.user_data,
|
||||
R.string.user_data_description,
|
||||
install = { mainActivity.importUserData.launch(arrayOf("application/zip")) },
|
||||
export = { mainActivity.exportUserData.launch("export.zip") }
|
||||
install = { importUserDataLauncher.launch(arrayOf("application/zip")) },
|
||||
export = { exportUserDataLauncher.launch("export.zip") }
|
||||
),
|
||||
Installable(
|
||||
R.string.manage_save_data,
|
||||
|
|
@ -127,27 +128,33 @@ class InstallableFragment : Fragment() {
|
|||
Installable(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
install = { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
install = { installGameUpdateLauncher.launch(arrayOf("*/*")) }
|
||||
),
|
||||
Installable(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
install = { getFirmwareLauncher.launch(arrayOf("application/zip")) }
|
||||
),
|
||||
Installable(
|
||||
R.string.uninstall_firmware,
|
||||
R.string.uninstall_firmware_description,
|
||||
install = { mainActivity.uninstallFirmware() }
|
||||
install = {
|
||||
InstallableActions.uninstallFirmware(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
homeViewModel = homeViewModel
|
||||
)
|
||||
}
|
||||
),
|
||||
Installable(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
install = { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
install = { getProdKeyLauncher.launch(arrayOf("*/*")) }
|
||||
),
|
||||
Installable(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
install = { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
install = { getAmiiboKeyLauncher.launch(arrayOf("*/*")) }
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -180,6 +187,132 @@ class InstallableFragment : Fragment() {
|
|||
windowInsets
|
||||
}
|
||||
|
||||
private val getProdKeyLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result != null) {
|
||||
InstallableActions.processKey(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
gamesViewModel = gamesViewModel,
|
||||
result = result,
|
||||
extension = "keys"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val getAmiiboKeyLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result != null) {
|
||||
InstallableActions.processKey(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
gamesViewModel = gamesViewModel,
|
||||
result = result,
|
||||
extension = "bin"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val getFirmwareLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result != null) {
|
||||
InstallableActions.processFirmware(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
homeViewModel = homeViewModel,
|
||||
result = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val installGameUpdateLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { documents ->
|
||||
if (documents.isEmpty()) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (addonViewModel.game == null) {
|
||||
InstallableActions.installContent(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents
|
||||
)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
R.string.verifying_content,
|
||||
false
|
||||
) { _, _ ->
|
||||
var updatesMatchProgram = true
|
||||
for (document in documents) {
|
||||
val valid = NativeLibrary.doesUpdateMatchProgram(
|
||||
addonViewModel.game!!.programId,
|
||||
document.toString()
|
||||
)
|
||||
if (!valid) {
|
||||
updatesMatchProgram = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (updatesMatchProgram) {
|
||||
requireActivity().runOnUiThread {
|
||||
InstallableActions.installContent(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents
|
||||
)
|
||||
}
|
||||
} else {
|
||||
requireActivity().runOnUiThread {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.content_install_notice,
|
||||
descriptionId = R.string.content_install_notice_description,
|
||||
positiveAction = {
|
||||
InstallableActions.installContent(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents
|
||||
)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
return@newInstance Any()
|
||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
private val importUserDataLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result != null) {
|
||||
InstallableActions.importUserData(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
gamesViewModel = gamesViewModel,
|
||||
driverViewModel = driverViewModel,
|
||||
result = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val exportUserDataLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { result ->
|
||||
if (result != null) {
|
||||
InstallableActions.exportUserData(
|
||||
activity = requireActivity(),
|
||||
fragmentManager = parentFragmentManager,
|
||||
result = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val importSaves =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
|
@ -13,7 +13,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
|
@ -48,7 +47,7 @@ class LicensesFragment : Fragment() {
|
|||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarLicenses.setNavigationOnClickListener {
|
||||
binding.root.findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
val licenses = listOf(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ProfileManagerFragment : Fragment() {
|
|||
homeViewModel.setStatusBarShadeVisibility(visible = false)
|
||||
|
||||
binding.toolbarProfiles.setNavigationOnClickListener {
|
||||
findNavController().popBackStack()
|
||||
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.preference.PreferenceManager
|
|||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||
|
|
@ -39,16 +38,10 @@ import org.yuzu.yuzu_emu.model.AddonViewModel
|
|||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import android.os.Build
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import androidx.core.content.edit
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import kotlin.text.compareTo
|
||||
|
|
@ -453,35 +446,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
fun processKey(result: Uri, extension: String = "keys") {
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
InstallableActions.processKey(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
gamesViewModel = gamesViewModel,
|
||||
result = result,
|
||||
extension = extension
|
||||
)
|
||||
|
||||
val resultCode: Int = NativeLibrary.installKeys(result.toString(), extension)
|
||||
|
||||
if (resultCode == 0) {
|
||||
// TODO(crueter): It may be worth it to switch some of these Toasts to snackbars,
|
||||
// since most of it is foreground-only anyways.
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.keys_install_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
gamesViewModel.reloadGames(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val resultString: String =
|
||||
resources.getStringArray(R.array.installKeysResults)[resultCode]
|
||||
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.keys_failed,
|
||||
descriptionString = resultString,
|
||||
helpLinkId = R.string.keys_missing_help
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
val getFirmware = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
|
|
@ -491,75 +462,21 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
fun processFirmware(result: Uri, onComplete: (() -> Unit)? = null) {
|
||||
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
|
||||
|
||||
val firmwarePath =
|
||||
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.firmware_installing
|
||||
) { progressCallback, _ ->
|
||||
var messageToShow: Any
|
||||
try {
|
||||
FileUtil.unzipToInternalStorage(
|
||||
result.toString(),
|
||||
cacheFirmwareDir,
|
||||
progressCallback
|
||||
)
|
||||
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
|
||||
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
|
||||
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
|
||||
MessageDialogFragment.newInstance(
|
||||
this,
|
||||
titleId = R.string.firmware_installed_failure,
|
||||
descriptionId = R.string.firmware_installed_failure_description
|
||||
)
|
||||
} else {
|
||||
firmwarePath.deleteRecursively()
|
||||
cacheFirmwareDir.copyRecursively(firmwarePath, true)
|
||||
NativeLibrary.initializeSystem(true)
|
||||
homeViewModel.setCheckKeys(true)
|
||||
getString(R.string.save_file_imported_success)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error("[MainActivity] Firmware install failed - ${e.message}")
|
||||
messageToShow = getString(R.string.fatal_error)
|
||||
} finally {
|
||||
cacheFirmwareDir.deleteRecursively()
|
||||
}
|
||||
messageToShow
|
||||
}.apply {
|
||||
onDialogComplete = onComplete
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
InstallableActions.processFirmware(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
homeViewModel = homeViewModel,
|
||||
result = result,
|
||||
onComplete = onComplete
|
||||
)
|
||||
}
|
||||
|
||||
fun uninstallFirmware() {
|
||||
val firmwarePath =
|
||||
File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.firmware_uninstalling
|
||||
) { progressCallback, _ ->
|
||||
var messageToShow: Any
|
||||
try {
|
||||
// Ensure the firmware directory exists before attempting to delete
|
||||
if (firmwarePath.exists()) {
|
||||
firmwarePath.deleteRecursively()
|
||||
// Optionally reinitialize the system or perform other necessary steps
|
||||
NativeLibrary.initializeSystem(true)
|
||||
homeViewModel.setCheckKeys(true)
|
||||
messageToShow = getString(R.string.firmware_uninstalled_success)
|
||||
} else {
|
||||
messageToShow = getString(R.string.firmware_uninstalled_failure)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error("[MainActivity] Firmware uninstall failed - ${e.message}")
|
||||
messageToShow = getString(R.string.fatal_error)
|
||||
}
|
||||
messageToShow
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
InstallableActions.uninstallFirmware(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
homeViewModel = homeViewModel
|
||||
)
|
||||
}
|
||||
|
||||
val installGameUpdate = registerForActivityResult(
|
||||
|
|
@ -606,101 +523,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
}
|
||||
|
||||
private fun installContent(documents: List<Uri>) {
|
||||
ProgressDialogFragment.newInstance(
|
||||
this@MainActivity,
|
||||
R.string.installing_game_content
|
||||
) { progressCallback, messageCallback ->
|
||||
var installSuccess = 0
|
||||
var installOverwrite = 0
|
||||
var errorBaseGame = 0
|
||||
var error = 0
|
||||
documents.forEach {
|
||||
messageCallback.invoke(FileUtil.getFilename(it))
|
||||
when (
|
||||
InstallResult.from(
|
||||
NativeLibrary.installFileToNand(
|
||||
it.toString(),
|
||||
progressCallback
|
||||
)
|
||||
)
|
||||
) {
|
||||
InstallResult.Success -> {
|
||||
installSuccess += 1
|
||||
}
|
||||
|
||||
InstallResult.Overwrite -> {
|
||||
installOverwrite += 1
|
||||
}
|
||||
|
||||
InstallResult.BaseInstallAttempted -> {
|
||||
errorBaseGame += 1
|
||||
}
|
||||
|
||||
InstallResult.Failure -> {
|
||||
error += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addonViewModel.refreshAddons(force = true)
|
||||
|
||||
val separator = System.lineSeparator() ?: "\n"
|
||||
val installResult = StringBuilder()
|
||||
if (installSuccess > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_install,
|
||||
installSuccess
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (installOverwrite > 0) {
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_success_overwrite,
|
||||
installOverwrite
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
val errorTotal: Int = errorBaseGame + error
|
||||
if (errorTotal > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(
|
||||
R.string.install_game_content_failed_count,
|
||||
errorTotal
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
if (errorBaseGame > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_base)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (error > 0) {
|
||||
installResult.append(
|
||||
getString(R.string.install_game_content_failure_description)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
this,
|
||||
titleId = R.string.install_game_content_failure,
|
||||
descriptionString = installResult.toString().trim(),
|
||||
helpLinkId = R.string.install_game_content_help_link
|
||||
)
|
||||
} else {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
this,
|
||||
titleId = R.string.install_game_content_success,
|
||||
descriptionString = installResult.toString().trim()
|
||||
)
|
||||
}
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
InstallableActions.installContent(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents
|
||||
)
|
||||
}
|
||||
|
||||
val exportUserData = registerForActivityResult(
|
||||
|
|
@ -709,25 +537,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.exporting_user_data,
|
||||
true
|
||||
) { progressCallback, _ ->
|
||||
val zipResult = FileUtil.zipFromInternalStorage(
|
||||
File(DirectoryInitialization.userDirectory!!),
|
||||
DirectoryInitialization.userDirectory!!,
|
||||
BufferedOutputStream(contentResolver.openOutputStream(result)),
|
||||
progressCallback,
|
||||
compression = false
|
||||
)
|
||||
return@newInstance when (zipResult) {
|
||||
TaskState.Completed -> getString(R.string.user_data_export_success)
|
||||
TaskState.Failed -> R.string.export_failed
|
||||
TaskState.Cancelled -> R.string.user_data_export_cancelled
|
||||
}
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
InstallableActions.exportUserData(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
result = result
|
||||
)
|
||||
}
|
||||
|
||||
val importUserData =
|
||||
|
|
@ -735,58 +549,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.importing_user_data
|
||||
) { progressCallback, _ ->
|
||||
val checkStream =
|
||||
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
|
||||
var isYuzuBackup = false
|
||||
checkStream.use { stream ->
|
||||
var ze: ZipEntry? = null
|
||||
while (stream.nextEntry?.also { ze = it } != null) {
|
||||
val itemName = ze!!.name.trim()
|
||||
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
|
||||
isYuzuBackup = true
|
||||
return@use
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isYuzuBackup) {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
this,
|
||||
titleId = R.string.invalid_yuzu_backup,
|
||||
descriptionId = R.string.user_data_import_failed_description
|
||||
)
|
||||
}
|
||||
|
||||
// Clear existing user data
|
||||
NativeConfig.unloadGlobalConfig()
|
||||
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
|
||||
|
||||
// Copy archive to internal storage
|
||||
try {
|
||||
FileUtil.unzipToInternalStorage(
|
||||
result.toString(),
|
||||
File(DirectoryInitialization.userDirectory!!),
|
||||
progressCallback
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
this,
|
||||
titleId = R.string.import_failed,
|
||||
descriptionId = R.string.user_data_import_failed_description
|
||||
)
|
||||
}
|
||||
|
||||
// Reinitialize relevant data
|
||||
NativeLibrary.initializeSystem(true)
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
gamesViewModel.reloadGames(false)
|
||||
driverViewModel.reloadDriverData()
|
||||
|
||||
return@newInstance getString(R.string.user_data_import_success)
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
InstallableActions.importUserData(
|
||||
activity = this,
|
||||
fragmentManager = supportFragmentManager,
|
||||
gamesViewModel = gamesViewModel,
|
||||
driverViewModel = driverViewModel,
|
||||
result = result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,327 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.model.InstallResult
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
object InstallableActions {
|
||||
fun processKey(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
gamesViewModel: GamesViewModel,
|
||||
result: Uri,
|
||||
extension: String = "keys"
|
||||
) {
|
||||
activity.contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
val resultCode = NativeLibrary.installKeys(result.toString(), extension)
|
||||
if (resultCode == 0) {
|
||||
Toast.makeText(
|
||||
activity.applicationContext,
|
||||
R.string.keys_install_success,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
gamesViewModel.reloadGames(true)
|
||||
return
|
||||
}
|
||||
|
||||
val resultString = activity.resources.getStringArray(R.array.installKeysResults)[resultCode]
|
||||
MessageDialogFragment.newInstance(
|
||||
titleId = R.string.keys_failed,
|
||||
descriptionString = resultString,
|
||||
helpLinkId = R.string.keys_missing_help
|
||||
).show(fragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun processFirmware(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
homeViewModel: HomeViewModel,
|
||||
result: Uri,
|
||||
onComplete: (() -> Unit)? = null
|
||||
) {
|
||||
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
|
||||
val firmwarePath = File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
val cacheFirmwareDir = File("${activity.cacheDir.path}/registered/")
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.firmware_installing
|
||||
) { progressCallback, _ ->
|
||||
var messageToShow: Any
|
||||
try {
|
||||
FileUtil.unzipToInternalStorage(
|
||||
result.toString(),
|
||||
cacheFirmwareDir,
|
||||
progressCallback
|
||||
)
|
||||
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
|
||||
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
|
||||
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity,
|
||||
titleId = R.string.firmware_installed_failure,
|
||||
descriptionId = R.string.firmware_installed_failure_description
|
||||
)
|
||||
} else {
|
||||
firmwarePath.deleteRecursively()
|
||||
cacheFirmwareDir.copyRecursively(firmwarePath, overwrite = true)
|
||||
NativeLibrary.initializeSystem(true)
|
||||
homeViewModel.setCheckKeys(true)
|
||||
activity.getString(R.string.save_file_imported_success)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
messageToShow = activity.getString(R.string.fatal_error)
|
||||
} finally {
|
||||
cacheFirmwareDir.deleteRecursively()
|
||||
}
|
||||
messageToShow
|
||||
}.apply {
|
||||
onDialogComplete = onComplete
|
||||
}.show(fragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun uninstallFirmware(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
homeViewModel: HomeViewModel
|
||||
) {
|
||||
val firmwarePath = File(NativeConfig.getNandDir() + "/system/Contents/registered/")
|
||||
ProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.firmware_uninstalling
|
||||
) { _, _ ->
|
||||
val messageToShow: Any = try {
|
||||
if (firmwarePath.exists()) {
|
||||
firmwarePath.deleteRecursively()
|
||||
NativeLibrary.initializeSystem(true)
|
||||
homeViewModel.setCheckKeys(true)
|
||||
activity.getString(R.string.firmware_uninstalled_success)
|
||||
} else {
|
||||
activity.getString(R.string.firmware_uninstalled_failure)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
activity.getString(R.string.fatal_error)
|
||||
}
|
||||
messageToShow
|
||||
}.show(fragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun installContent(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
addonViewModel: AddonViewModel,
|
||||
documents: List<Uri>
|
||||
) {
|
||||
ProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.installing_game_content
|
||||
) { progressCallback, messageCallback ->
|
||||
var installSuccess = 0
|
||||
var installOverwrite = 0
|
||||
var errorBaseGame = 0
|
||||
var error = 0
|
||||
documents.forEach {
|
||||
messageCallback.invoke(FileUtil.getFilename(it))
|
||||
when (
|
||||
InstallResult.from(
|
||||
NativeLibrary.installFileToNand(
|
||||
it.toString(),
|
||||
progressCallback
|
||||
)
|
||||
)
|
||||
) {
|
||||
InstallResult.Success -> installSuccess += 1
|
||||
InstallResult.Overwrite -> installOverwrite += 1
|
||||
InstallResult.BaseInstallAttempted -> errorBaseGame += 1
|
||||
InstallResult.Failure -> error += 1
|
||||
}
|
||||
}
|
||||
|
||||
addonViewModel.refreshAddons(force = true)
|
||||
|
||||
val separator = System.lineSeparator() ?: "\n"
|
||||
val installResult = StringBuilder()
|
||||
if (installSuccess > 0) {
|
||||
installResult.append(
|
||||
activity.getString(
|
||||
R.string.install_game_content_success_install,
|
||||
installSuccess
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (installOverwrite > 0) {
|
||||
installResult.append(
|
||||
activity.getString(
|
||||
R.string.install_game_content_success_overwrite,
|
||||
installOverwrite
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
val errorTotal = errorBaseGame + error
|
||||
if (errorTotal > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(
|
||||
activity.getString(
|
||||
R.string.install_game_content_failed_count,
|
||||
errorTotal
|
||||
)
|
||||
)
|
||||
installResult.append(separator)
|
||||
if (errorBaseGame > 0) {
|
||||
installResult.append(separator)
|
||||
installResult.append(activity.getString(R.string.install_game_content_failure_base))
|
||||
installResult.append(separator)
|
||||
}
|
||||
if (error > 0) {
|
||||
installResult.append(
|
||||
activity.getString(R.string.install_game_content_failure_description)
|
||||
)
|
||||
installResult.append(separator)
|
||||
}
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
activity,
|
||||
titleId = R.string.install_game_content_failure,
|
||||
descriptionString = installResult.toString().trim(),
|
||||
helpLinkId = R.string.install_game_content_help_link
|
||||
)
|
||||
} else {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
activity,
|
||||
titleId = R.string.install_game_content_success,
|
||||
descriptionString = installResult.toString().trim()
|
||||
)
|
||||
}
|
||||
}.show(fragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun exportUserData(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
result: Uri
|
||||
) {
|
||||
val userDirectory = DirectoryInitialization.userDirectory
|
||||
if (userDirectory == null) {
|
||||
Toast.makeText(
|
||||
activity.applicationContext,
|
||||
R.string.fatal_error,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.exporting_user_data,
|
||||
true
|
||||
) { progressCallback, _ ->
|
||||
val zipResult = FileUtil.zipFromInternalStorage(
|
||||
File(userDirectory),
|
||||
userDirectory,
|
||||
BufferedOutputStream(activity.contentResolver.openOutputStream(result)),
|
||||
progressCallback,
|
||||
compression = false
|
||||
)
|
||||
return@newInstance when (zipResult) {
|
||||
TaskState.Completed -> activity.getString(R.string.user_data_export_success)
|
||||
TaskState.Failed -> R.string.export_failed
|
||||
TaskState.Cancelled -> R.string.user_data_export_cancelled
|
||||
}
|
||||
}.show(fragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun importUserData(
|
||||
activity: FragmentActivity,
|
||||
fragmentManager: FragmentManager,
|
||||
gamesViewModel: GamesViewModel,
|
||||
driverViewModel: DriverViewModel,
|
||||
result: Uri
|
||||
) {
|
||||
val userDirectory = DirectoryInitialization.userDirectory
|
||||
if (userDirectory == null) {
|
||||
Toast.makeText(
|
||||
activity.applicationContext,
|
||||
R.string.fatal_error,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
|
||||
ProgressDialogFragment.newInstance(
|
||||
activity,
|
||||
R.string.importing_user_data
|
||||
) { progressCallback, _ ->
|
||||
val checkStream = ZipInputStream(
|
||||
BufferedInputStream(activity.contentResolver.openInputStream(result))
|
||||
)
|
||||
var isYuzuBackup = false
|
||||
checkStream.use { stream ->
|
||||
var ze: ZipEntry? = null
|
||||
while (stream.nextEntry?.also { ze = it } != null) {
|
||||
val itemName = ze!!.name.trim()
|
||||
if (itemName == "/config/config.ini" || itemName == "config/config.ini") {
|
||||
isYuzuBackup = true
|
||||
return@use
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isYuzuBackup) {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
activity,
|
||||
titleId = R.string.invalid_yuzu_backup,
|
||||
descriptionId = R.string.user_data_import_failed_description
|
||||
)
|
||||
}
|
||||
|
||||
NativeConfig.unloadGlobalConfig()
|
||||
File(userDirectory).deleteRecursively()
|
||||
|
||||
try {
|
||||
FileUtil.unzipToInternalStorage(
|
||||
result.toString(),
|
||||
File(userDirectory),
|
||||
progressCallback
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
return@newInstance MessageDialogFragment.newInstance(
|
||||
activity,
|
||||
titleId = R.string.import_failed,
|
||||
descriptionId = R.string.user_data_import_failed_description
|
||||
)
|
||||
}
|
||||
|
||||
NativeLibrary.initializeSystem(true)
|
||||
NativeConfig.initializeGlobalConfig()
|
||||
gamesViewModel.reloadGames(false)
|
||||
driverViewModel.reloadDriverData()
|
||||
|
||||
return@newInstance activity.getString(R.string.user_data_import_success)
|
||||
}.show(fragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <common/fs/path_util.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <common/logging.h>
|
||||
#include <common/settings.h>
|
||||
#include <input_common/main.h>
|
||||
#include "android_config.h"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <android/native_window_jni.h>
|
||||
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "input_common/drivers/android.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
|
|
|
|||
|
|
@ -42,8 +42,7 @@
|
|||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
#include "common/android/android_common.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "native.h"
|
||||
|
||||
namespace {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// 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-2.0-or-later
|
||||
|
||||
#include <common/android/android_common.h>
|
||||
#include <common/logging/log.h>
|
||||
#include <common/logging.h>
|
||||
#include <jni.h>
|
||||
|
||||
extern "C" {
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@
|
|||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_about"
|
||||
style="@style/Widget.Eden.TransparentTopAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
android:background="@android:color/transparent"
|
||||
app:elevation="0dp">
|
||||
android:touchscreenBlocksFocus="false">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_about"
|
||||
|
|
@ -41,15 +40,41 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:padding="24dp">
|
||||
android:paddingBottom="24dp"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_logo"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:src="@drawable/ic_yuzu" />
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_logo"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:src="@drawable/ic_yuzu" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="220dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/about_app_description"
|
||||
android:textAlignment="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -57,39 +82,6 @@
|
|||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardBackgroundColor="?attr/colorSurface"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp"
|
||||
app:cardElevation="0dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="24dp"
|
||||
android:paddingVertical="20dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/about"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:text="@string/about_app_description"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/button_contributors"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -205,7 +197,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="start"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@
|
|||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_about"
|
||||
style="@style/Widget.Eden.TransparentTopAppBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:touchscreenBlocksFocus="false"
|
||||
android:background="@android:color/transparent"
|
||||
app:elevation="0dp">
|
||||
android:touchscreenBlocksFocus="false">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar_about"
|
||||
|
|
@ -43,48 +42,35 @@
|
|||
android:orientation="vertical"
|
||||
android:paddingBottom="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_logo"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_marginVertical="24dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/ic_yuzu" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@android:color/transparent"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_logo"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:src="@drawable/ic_yuzu" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="20dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:orientation="vertical">
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@string/about" />
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/SynthwaveText.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/about_app_description" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/SynthwaveText.Body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@string/about_app_description" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/button_contributors"
|
||||
|
|
@ -206,7 +192,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginHorizontal="40dp">
|
||||
|
||||
|
|
@ -220,7 +206,9 @@
|
|||
app:icon="@drawable/ic_discord"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
|
|
@ -232,7 +220,9 @@
|
|||
app:icon="@drawable/ic_stoat"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
|
|
@ -244,7 +234,9 @@
|
|||
app:icon="@drawable/ic_x"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/EdenButton.Secondary"
|
||||
|
|
@ -256,7 +248,9 @@
|
|||
app:icon="@drawable/ic_website"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_github"
|
||||
|
|
@ -268,7 +262,9 @@
|
|||
app:icon="@drawable/ic_github"
|
||||
app:iconSize="24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp" />
|
||||
app:iconPadding="0dp"
|
||||
app:strokeColor="?attr/colorOutline"
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,10 +40,6 @@
|
|||
|
||||
<action
|
||||
android:id="@+id/action_global_settingsActivity"
|
||||
app:destination="@id/settingsActivity"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
app:destination="@id/settingsActivity" />
|
||||
|
||||
</navigation>
|
||||
|
|
|
|||
|
|
@ -20,26 +20,7 @@
|
|||
<fragment
|
||||
android:id="@+id/homeSettingsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.HomeSettingsFragment"
|
||||
android:label="HomeSettingsFragment" >
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_aboutFragment"
|
||||
app:destination="@id/aboutFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_installableFragment"
|
||||
app:destination="@id/installableFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
|
||||
app:destination="@id/driverManagerFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
|
||||
app:destination="@id/appletLauncherFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
|
||||
app:destination="@id/gameFoldersFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_profileManagerFragment"
|
||||
app:destination="@id/profileManagerFragment" />
|
||||
</fragment>
|
||||
android:label="HomeSettingsFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/firstTimeSetupFragment"
|
||||
|
|
@ -55,11 +36,7 @@
|
|||
<fragment
|
||||
android:id="@+id/aboutFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AboutFragment"
|
||||
android:label="AboutFragment" >
|
||||
<action
|
||||
android:id="@+id/action_aboutFragment_to_licensesFragment"
|
||||
app:destination="@id/licensesFragment" />
|
||||
</fragment>
|
||||
android:label="AboutFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/licensesFragment"
|
||||
|
|
@ -101,11 +78,23 @@
|
|||
|
||||
<action
|
||||
android:id="@+id/action_global_settingsActivity"
|
||||
app:destination="@id/settingsActivity"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
app:destination="@id/settingsActivity" />
|
||||
<activity
|
||||
android:id="@+id/settingsSubscreenActivity"
|
||||
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreenActivity"
|
||||
android:label="SettingsSubscreenActivity">
|
||||
<argument
|
||||
android:name="destination"
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen" />
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</activity>
|
||||
<action
|
||||
android:id="@+id/action_global_settingsSubscreenActivity"
|
||||
app:destination="@id/settingsSubscreenActivity" />
|
||||
<fragment
|
||||
android:id="@+id/installableFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
|
||||
|
|
@ -119,9 +108,6 @@
|
|||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
<action
|
||||
android:id="@+id/action_driverManagerFragment_to_driverFetcherFragment"
|
||||
app:destination="@id/driverFetcherFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/settings_subscreen_navigation"
|
||||
app:startDestination="@id/profileManagerFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/profileManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.ProfileManagerFragment"
|
||||
android:label="ProfileManagerFragment">
|
||||
<action
|
||||
android:id="@+id/action_profileManagerFragment_to_newUserDialog"
|
||||
app:destination="@id/newUserDialogFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/newUserDialogFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.EditUserDialogFragment"
|
||||
android:label="NewUserDialogFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/driverManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
|
||||
android:label="DriverManagerFragment">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/driverFetcherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.DriverFetcherFragment"
|
||||
android:label="fragment_driver_fetcher"
|
||||
tools:layout="@layout/fragment_driver_fetcher" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/freedrenoSettingsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.FreedrenoSettingsFragment"
|
||||
android:label="@string/freedreno_settings_title">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/appletLauncherFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment"
|
||||
android:label="AppletLauncherFragment">
|
||||
<action
|
||||
android:id="@+id/action_appletLauncherFragment_to_cabinetLauncherDialogFragment"
|
||||
app:destination="@id/cabinetLauncherDialogFragment" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/cabinetLauncherDialogFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
|
||||
android:label="CabinetLauncherDialogFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/aboutFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AboutFragment"
|
||||
android:label="AboutFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/licensesFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment"
|
||||
android:label="LicensesFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/gameInfoFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.GameInfoFragment"
|
||||
android:label="GameInfoFragment">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/addonsFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.AddonsFragment"
|
||||
android:label="AddonsFragment">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/installableFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
|
||||
android:label="InstallableFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/gameFoldersFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment"
|
||||
android:label="GameFoldersFragment" />
|
||||
|
||||
<activity
|
||||
android:id="@+id/emulationActivity"
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:label="EmulationActivity">
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
<argument
|
||||
android:name="custom"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_global_emulationActivity"
|
||||
app:destination="@id/emulationActivity"
|
||||
app:launchSingleTop="true" />
|
||||
|
||||
<activity
|
||||
android:id="@+id/settingsSubscreenActivity"
|
||||
android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreenActivity"
|
||||
android:label="SettingsSubscreenActivity">
|
||||
<argument
|
||||
android:name="destination"
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.ui.SettingsSubscreen" />
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
app:nullable="true"
|
||||
android:defaultValue="@null" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_global_settingsSubscreenActivity"
|
||||
app:destination="@id/settingsSubscreenActivity" />
|
||||
</navigation>
|
||||
|
|
@ -400,7 +400,7 @@
|
|||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="about_app_description">An open-source Switch emulator</string>
|
||||
<string name="contributors">Contributors</string>
|
||||
<string name="contributors_description">Contributors who made Eden for Android possible</string>
|
||||
<string name="contributors_description">People who made Eden for Android possible</string>
|
||||
<string name="contributors_link" translatable="false">https://git.eden-emu.dev/eden-emu/eden/activity/contributors</string>
|
||||
<string name="licenses_description">Projects that make Eden for Android possible</string>
|
||||
<string name="build">Build</string>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#include "audio_core/audio_event.h"
|
||||
#include "audio_core/audio_manager.h"
|
||||
#include "audio_core/in/audio_in_system.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#include "audio_core/audio_event.h"
|
||||
#include "audio_core/audio_manager.h"
|
||||
#include "audio_core/out/audio_out_system.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
#include "audio_core/renderer/command/resample/resample.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "core/guest_memory.h"
|
||||
#include "core/memory.h"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/mix_ramp.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/adsp/apps/audio_renderer/command_list_processor.h"
|
||||
#include "audio_core/renderer/command/mix/volume.h"
|
||||
#include "common/fixed_point.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/nodes/node_states.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/cubeb_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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
|
||||
|
||||
|
|
@ -9,7 +12,7 @@
|
|||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/oboe_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/sink/sdl2_sink.h"
|
||||
#include "audio_core/sink/sink_stream.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
#include "audio_core/sink/sdl2_sink.h"
|
||||
#endif
|
||||
#include "audio_core/sink/null_sink.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
|
|
|
|||
|
|
@ -75,16 +75,8 @@ add_library(
|
|||
input.h
|
||||
intrusive_red_black_tree.h
|
||||
literals.h
|
||||
logging/backend.cpp
|
||||
logging/backend.h
|
||||
logging/filter.cpp
|
||||
logging/filter.h
|
||||
logging/formatter.h
|
||||
logging/log.h
|
||||
logging/log_entry.h
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
logging/types.h
|
||||
logging.cpp
|
||||
logging.h
|
||||
lz4_compression.cpp
|
||||
lz4_compression.h
|
||||
make_unique_for_overwrite.h
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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
|
||||
|
||||
|
|
@ -9,7 +12,7 @@
|
|||
#include "common/android/android_common.h"
|
||||
#include "common/android/applets/software_keyboard.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/android/android_common.h"
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/android/applets/web_browser.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
static jclass s_native_library_class = nullptr;
|
||||
static jmethodID s_open_external_url = nullptr;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// 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
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
// Sometimes we want to try to continue even after hitting an assert.
|
||||
// However touching this file yields a global recompilation as this header is included almost
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
#ifdef ANDROID
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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
|
||||
|
||||
|
|
@ -7,7 +10,7 @@
|
|||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
namespace Common::FS {
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
#endif
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <shlobj.h> // Used in GetExeDirectory()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <fstream>
|
||||
#include "common/heap_tracker.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace Common {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/free_region_manager.h"
|
||||
#include "common/host_memory.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
|
||||
#if defined(__ANDROID__) && __ANDROID_API__ < 30
|
||||
#include <sys/syscall.h>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include <ankerl/unordered_dense.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
|
|
|
|||
104
src/common/log_classes.inc
Normal file
104
src/common/log_classes.inc
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
|
||||
CLS(Log)
|
||||
CLS(Common)
|
||||
SUB(Common, Filesystem)
|
||||
SUB(Common, Memory)
|
||||
CLS(Core)
|
||||
SUB(Core, ARM)
|
||||
SUB(Core, Timing)
|
||||
CLS(Config)
|
||||
CLS(Debug)
|
||||
SUB(Debug, Emulated)
|
||||
SUB(Debug, GPU)
|
||||
SUB(Debug, Breakpoint)
|
||||
SUB(Debug, GDBStub)
|
||||
CLS(Kernel)
|
||||
SUB(Kernel, SVC)
|
||||
CLS(Service)
|
||||
SUB(Service, ACC)
|
||||
SUB(Service, Audio)
|
||||
SUB(Service, AM)
|
||||
SUB(Service, AOC)
|
||||
SUB(Service, APM)
|
||||
SUB(Service, ARP)
|
||||
SUB(Service, BCAT)
|
||||
SUB(Service, BPC)
|
||||
SUB(Service, BGTC)
|
||||
SUB(Service, BTDRV)
|
||||
SUB(Service, BTM)
|
||||
SUB(Service, Capture)
|
||||
SUB(Service, ERPT)
|
||||
SUB(Service, ETicket)
|
||||
SUB(Service, EUPLD)
|
||||
SUB(Service, Fatal)
|
||||
SUB(Service, FGM)
|
||||
SUB(Service, Friend)
|
||||
SUB(Service, FS)
|
||||
SUB(Service, GRC)
|
||||
SUB(Service, HID)
|
||||
SUB(Service, IRS)
|
||||
SUB(Service, JIT)
|
||||
SUB(Service, LBL)
|
||||
SUB(Service, LDN)
|
||||
SUB(Service, LDR)
|
||||
SUB(Service, LM)
|
||||
SUB(Service, Migration)
|
||||
SUB(Service, Mii)
|
||||
SUB(Service, MM)
|
||||
SUB(Service, MNPP)
|
||||
SUB(Service, NCM)
|
||||
SUB(Service, NFC)
|
||||
SUB(Service, NFP)
|
||||
SUB(Service, NGC)
|
||||
SUB(Service, NIFM)
|
||||
SUB(Service, NIM)
|
||||
SUB(Service, NOTIF)
|
||||
SUB(Service, NPNS)
|
||||
SUB(Service, NS)
|
||||
SUB(Service, NVDRV)
|
||||
SUB(Service, Nvnflinger)
|
||||
SUB(Service, OLSC)
|
||||
SUB(Service, PCIE)
|
||||
SUB(Service, PCTL)
|
||||
SUB(Service, PCV)
|
||||
SUB(Service, PM)
|
||||
SUB(Service, PREPO)
|
||||
SUB(Service, PSC)
|
||||
SUB(Service, PTM)
|
||||
SUB(Service, SET)
|
||||
SUB(Service, SM)
|
||||
SUB(Service, SPL)
|
||||
SUB(Service, SSL)
|
||||
SUB(Service, TCAP)
|
||||
SUB(Service, Time)
|
||||
SUB(Service, USB)
|
||||
SUB(Service, VI)
|
||||
SUB(Service, WLAN)
|
||||
CLS(HW)
|
||||
SUB(HW, Memory)
|
||||
SUB(HW, LCD)
|
||||
SUB(HW, GPU)
|
||||
SUB(HW, AES)
|
||||
CLS(IPC)
|
||||
CLS(Frontend)
|
||||
CLS(Render)
|
||||
SUB(Render, Software)
|
||||
SUB(Render, OpenGL)
|
||||
SUB(Render, Vulkan)
|
||||
CLS(Shader)
|
||||
SUB(Shader, SPIRV)
|
||||
SUB(Shader, GLASM)
|
||||
SUB(Shader, GLSL)
|
||||
CLS(Audio)
|
||||
SUB(Audio, DSP)
|
||||
SUB(Audio, Sink)
|
||||
CLS(Input)
|
||||
CLS(Network)
|
||||
CLS(Loader)
|
||||
CLS(CheatEngine)
|
||||
CLS(Crypto)
|
||||
CLS(WebService)
|
||||
463
src/common/logging.cpp
Normal file
463
src/common/logging.cpp
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // For OutputDebugStringW
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||
/// instead of underscores as in the enumeration.
|
||||
/// @note GetClassName is a macro defined by Windows.h, grrr...
|
||||
const char* GetLogClassName(Class log_class) {
|
||||
switch (log_class) {
|
||||
#define CLS(x) case Class::x: return #x;
|
||||
#define SUB(x, y) case Class::x##_##y: return #x "." #y;
|
||||
#include "common/log_classes.inc"
|
||||
#undef CLS
|
||||
#undef SUB
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Returns the name of the passed log level as a C-string.
|
||||
const char* GetLevelName(Level log_level) {
|
||||
switch (log_level) {
|
||||
#define LVL(x) case Level::x: return #x;
|
||||
LVL(Trace)
|
||||
LVL(Debug)
|
||||
LVL(Info)
|
||||
LVL(Warning)
|
||||
LVL(Error)
|
||||
LVL(Critical)
|
||||
#undef LVL
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
||||
std::string FormatLogMessage(const Entry& entry) noexcept {
|
||||
if (!entry.filename) return "";
|
||||
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||
auto const class_name = GetLogClassName(entry.log_class);
|
||||
auto const level_name = GetLevelName(entry.log_level);
|
||||
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename It>
|
||||
Level GetLevelByName(const It begin, const It end) {
|
||||
for (u32 i = 0; i < u32(Level::Count); ++i) {
|
||||
const char* level_name = GetLevelName(Level(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name))
|
||||
return Level(i);
|
||||
}
|
||||
return Level::Count;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
Class GetClassByName(const It begin, const It end) {
|
||||
for (u32 i = 0; i < u32(Class::Count); ++i) {
|
||||
const char* level_name = GetLogClassName(Class(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name))
|
||||
return Class(i);
|
||||
}
|
||||
return Class::Count;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
instance.class_levels.fill(level);
|
||||
return true;
|
||||
}
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
instance.SetClassLevel(log_class, level);
|
||||
return true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void Filter::ParseFilterString(std::string_view filter_view) {
|
||||
auto clause_begin = filter_view.cbegin();
|
||||
while (clause_begin != filter_view.cend()) {
|
||||
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
|
||||
// If clause isn't empty
|
||||
if (clause_end != clause_begin) {
|
||||
ParseFilterRule(*this, clause_begin, clause_end);
|
||||
}
|
||||
if (clause_end != filter_view.cend()) {
|
||||
// Skip over the whitespace
|
||||
++clause_end;
|
||||
}
|
||||
clause_begin = clause_end;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||
/// do not be fooled this isn't generating new strings on .rodata :)
|
||||
constexpr const char* TrimSourcePath(std::string_view source) noexcept {
|
||||
const auto rfind = [source](const std::string_view match) {
|
||||
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
|
||||
};
|
||||
auto idx = (std::max)({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
|
||||
return source.data() + idx;
|
||||
}
|
||||
|
||||
/// @brief Interface for logging backends.
|
||||
struct Backend {
|
||||
virtual ~Backend() noexcept = default;
|
||||
virtual void Write(const Entry& entry) noexcept = 0;
|
||||
virtual void Flush() noexcept = 0;
|
||||
};
|
||||
|
||||
/// @brief Formatting specifier (to use with printf) of the equivalent fmt::format() expression
|
||||
#define CCB_PRINTF_FMT "[%4d.%06d] %s <%s> %s:%u:%s: %s"
|
||||
|
||||
/// @brief Instead of using fmt::format() just use the system's formatting capabilities directly
|
||||
struct DirectFormatArgs {
|
||||
const char *class_name;
|
||||
const char *level_name;
|
||||
uint32_t time_seconds;
|
||||
uint32_t time_fractional;
|
||||
};
|
||||
[[nodiscard]] inline DirectFormatArgs GetDirectFormatArgs(Entry const& entry) noexcept {
|
||||
return {
|
||||
.class_name = GetLogClassName(entry.log_class),
|
||||
.level_name = GetLevelName(entry.log_level),
|
||||
.time_seconds = uint32_t(entry.timestamp.count() / 1000000),
|
||||
.time_fractional = uint32_t(entry.timestamp.count() % 1000000),
|
||||
};
|
||||
}
|
||||
|
||||
/// @brief Backend that writes to stdout and with color
|
||||
struct ColorConsoleBackend final : public Backend {
|
||||
#ifdef _WIN32
|
||||
explicit ColorConsoleBackend() noexcept {
|
||||
console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
GetConsoleScreenBufferInfo(console_handle, &original_info);
|
||||
}
|
||||
~ColorConsoleBackend() noexcept override {
|
||||
SetConsoleTextAttribute(console_handle, original_info.wAttributes);
|
||||
}
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
if (enabled && console_handle != INVALID_HANDLE_VALUE) {
|
||||
WORD color = WORD([&entry]() {
|
||||
switch (entry.log_level) {
|
||||
case Level::Debug: return FOREGROUND_GREEN | FOREGROUND_BLUE; // Cyan
|
||||
case Level::Info: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; // Bright gray
|
||||
case Level::Warning: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
||||
case Level::Error: return FOREGROUND_RED | FOREGROUND_INTENSITY;
|
||||
case Level::Critical: return FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
||||
default: break;
|
||||
}
|
||||
return FOREGROUND_INTENSITY; // Grey
|
||||
}());
|
||||
SetConsoleTextAttribute(console_handle, color);
|
||||
auto const df = GetDirectFormatArgs(entry);
|
||||
std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||
}
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
CONSOLE_SCREEN_BUFFER_INFO original_info = {};
|
||||
HANDLE console_handle = INVALID_HANDLE_VALUE;
|
||||
std::atomic_bool enabled = false;
|
||||
#else // ^^^ Windows vvv POSIX
|
||||
explicit ColorConsoleBackend() noexcept {}
|
||||
~ColorConsoleBackend() noexcept override {}
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
if (enabled) {
|
||||
#define ESC "\x1b"
|
||||
auto const color_str = [&entry]() -> const char* {
|
||||
switch (entry.log_level) {
|
||||
#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n"
|
||||
case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan
|
||||
case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray
|
||||
case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow
|
||||
case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red
|
||||
case Level::Critical: return CCB_MAKE_COLOR_FMT("[1;35m"); // Bright magenta
|
||||
default: return CCB_MAKE_COLOR_FMT("[1;30m"); // Grey
|
||||
#undef CCB_MAKE_COLOR_FMT
|
||||
}
|
||||
}();
|
||||
auto const df = GetDirectFormatArgs(entry);
|
||||
std::fprintf(stdout, color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||
#undef ESC
|
||||
}
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
std::atomic_bool enabled = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef __OPENORBIS__
|
||||
/// @brief Backend that writes to a file passed into the constructor
|
||||
struct FileBackend final : public Backend {
|
||||
explicit FileBackend(const std::filesystem::path& filename) noexcept {
|
||||
auto old_filename = filename;
|
||||
old_filename += ".old.txt";
|
||||
// Existence checks are done within the functions themselves.
|
||||
// We don't particularly care if these succeed or not.
|
||||
void(FS::RemoveFile(old_filename));
|
||||
void(FS::RenameFile(filename, old_filename));
|
||||
file.emplace(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
|
||||
}
|
||||
~FileBackend() noexcept override = default;
|
||||
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
auto message = FormatLogMessage(entry).append(1, '\n');
|
||||
#ifndef __ANDROID__
|
||||
if (Settings::values.censor_username.GetValue()) {
|
||||
// This must be a static otherwise it would get checked on EVERY
|
||||
// instance of logging an entry...
|
||||
static std::string username = []() -> std::string {
|
||||
// in order of precedence
|
||||
// LOGNAME usually works on UNIX, USERNAME on Windows
|
||||
// Some UNIX systems suck and don't use LOGNAME so we also
|
||||
// need USER :(
|
||||
for (auto const var : { "LOGNAME", "USERNAME", "USER", })
|
||||
if (auto const s = ::getenv(var); s != nullptr)
|
||||
return std::string{s};
|
||||
return std::string{};
|
||||
}();
|
||||
if (!username.empty())
|
||||
boost::replace_all(message, username, "user");
|
||||
}
|
||||
#endif
|
||||
bytes_written += file->WriteString(message);
|
||||
|
||||
// Option to log each line rather than 4k buffers
|
||||
if (Settings::values.log_flush_line.GetValue())
|
||||
file->Flush();
|
||||
|
||||
using namespace Common::Literals;
|
||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
||||
const auto write_limit = Settings::values.extended_logging.GetValue() ? 1_GiB : 100_MiB;
|
||||
const bool write_limit_exceeded = bytes_written > write_limit;
|
||||
if (entry.log_level >= Level::Error || write_limit_exceeded) {
|
||||
// Stop writing after the write limit is exceeded.
|
||||
// Don't close the file so we can print a stacktrace if necessary
|
||||
if (write_limit_exceeded)
|
||||
enabled = false;
|
||||
file->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() noexcept override {
|
||||
file->Flush();
|
||||
}
|
||||
private:
|
||||
std::optional<FS::IOFile> file;
|
||||
std::size_t bytes_written = 0;
|
||||
bool enabled = true;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
/// @brief Backend that writes to Visual Studio's output window
|
||||
struct DebuggerBackend final : public Backend {
|
||||
explicit DebuggerBackend() noexcept = default;
|
||||
~DebuggerBackend() noexcept override = default;
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
/// @brief Backend that writes to the Android logcat
|
||||
struct LogcatBackend : public Backend {
|
||||
explicit LogcatBackend() noexcept = default;
|
||||
~LogcatBackend() noexcept override = default;
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
android_LogPriority android_log_priority = [&]() {
|
||||
switch (entry.log_level) {
|
||||
case Level::Debug: return ANDROID_LOG_DEBUG;
|
||||
case Level::Info: return ANDROID_LOG_INFO;
|
||||
case Level::Warning: return ANDROID_LOG_WARN;
|
||||
case Level::Error: return ANDROID_LOG_ERROR;
|
||||
case Level::Critical: return ANDROID_LOG_FATAL;
|
||||
case Level::Count:
|
||||
case Level::Trace: return ANDROID_LOG_VERBOSE;
|
||||
}
|
||||
}();
|
||||
auto const df = GetDirectFormatArgs(entry);
|
||||
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str());
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
};
|
||||
#endif
|
||||
|
||||
/// @brief Static state as a singleton.
|
||||
struct Impl {
|
||||
// Well, I mean it's the default constructor!
|
||||
explicit Impl() noexcept : filter(Level::Trace) {}
|
||||
|
||||
void StartBackendThread() noexcept {
|
||||
backend_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("Logger");
|
||||
Entry entry;
|
||||
const auto write_logs = [this, &entry]() {
|
||||
ForEachBackend([&entry](Backend& backend) {
|
||||
backend.Write(entry);
|
||||
});
|
||||
};
|
||||
do {
|
||||
message_queue.PopWait(entry, stop_token);
|
||||
write_logs();
|
||||
} while (!stop_token.stop_requested());
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
||||
// case where a system is repeatedly spamming logs even on close.
|
||||
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
|
||||
while (max_logs_to_write-- && message_queue.TryPop(entry))
|
||||
write_logs();
|
||||
});
|
||||
}
|
||||
|
||||
void StopBackendThread() noexcept {
|
||||
backend_thread.request_stop();
|
||||
if (backend_thread.joinable())
|
||||
backend_thread.join();
|
||||
ForEachBackend([](Backend& backend) { backend.Flush(); });
|
||||
}
|
||||
|
||||
void ForEachBackend(auto lambda) noexcept {
|
||||
lambda(static_cast<Backend&>(color_console_backend));
|
||||
#ifndef __OPENORBIS__
|
||||
if (file_backend)
|
||||
lambda(static_cast<Backend&>(*file_backend));
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
lambda(static_cast<Backend&>(debugger_backend));
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
lambda(static_cast<Backend&>(lc_backend));
|
||||
#endif
|
||||
}
|
||||
|
||||
Filter filter;
|
||||
ColorConsoleBackend color_console_backend{};
|
||||
#ifndef __OPENORBIS__
|
||||
std::optional<FileBackend> file_backend;
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
DebuggerBackend debugger_backend{};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
LogcatBackend lc_backend{};
|
||||
#endif
|
||||
MPSCQueue<Entry> message_queue{};
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
std::jthread backend_thread;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Constructor shall NOT depend upon Settings() or whatever
|
||||
// it's ran at global static ctor() time... so BE CAREFUL MFER!
|
||||
static std::optional<Common::Log::Impl> logging_instance{};
|
||||
|
||||
void Initialize() {
|
||||
if (logging_instance) {
|
||||
LOG_WARNING(Log, "Reinitializing logging backend");
|
||||
} else {
|
||||
logging_instance.emplace();
|
||||
logging_instance->filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
#ifndef __OPENORBIS__
|
||||
using namespace Common::FS;
|
||||
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||
void(CreateDir(log_dir));
|
||||
logging_instance->file_backend.emplace(log_dir / LOG_FILE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Start() {
|
||||
if (logging_instance)
|
||||
logging_instance->StartBackendThread();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
if (logging_instance)
|
||||
logging_instance->StopBackendThread();
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
if (logging_instance)
|
||||
logging_instance->filter = filter;
|
||||
}
|
||||
|
||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
if (logging_instance)
|
||||
logging_instance->color_console_backend.enabled = enabled;
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
|
||||
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
|
||||
logging_instance->message_queue.EmplaceWait(Entry{
|
||||
.message = fmt::vformat(format, args),
|
||||
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
||||
.log_class = log_class,
|
||||
.log_level = log_level,
|
||||
.filename = TrimSourcePath(filename),
|
||||
.function = function,
|
||||
.line_num = line_num,
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace Common::Log
|
||||
164
src/common/logging.h
Normal file
164
src/common/logging.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/swap.h"
|
||||
|
||||
// adapted from https://github.com/fmtlib/fmt/issues/2704
|
||||
// a generic formatter for enum classes
|
||||
#if FMT_VERSION >= 80100
|
||||
template <typename T>
|
||||
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
|
||||
: formatter<std::underlying_type_t<T>> {
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
return fmt::formatter<std::underlying_type_t<T>>::format(
|
||||
static_cast<std::underlying_type_t<T>>(value), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename U>
|
||||
struct fmt::formatter<SwapStructT<T, U>> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const SwapStructT<T, U>& reg, FormatContext& ctx) const {
|
||||
return fmt::format_to(ctx.out(), "{}", T(reg));
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define LOG_TRACE(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Debug, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Info, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Warning, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Error, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Critical, \
|
||||
__FILE__, __LINE__, __func__, __VA_ARGS__)
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
/// Specifies the severity or level of detail of the log message.
|
||||
enum class Level : u8 {
|
||||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to pollute logs.
|
||||
Debug, ///< Less detailed debugging information.
|
||||
Info, ///< Status information from important points during execution.
|
||||
Warning, ///< Minor or potential problems found during execution of a task.
|
||||
Error, ///< Major problems found during execution of a task that prevent it from being completed.
|
||||
Critical, ///< Major problems during execution that threaten the stability of the entire application.
|
||||
Count ///< Total number of logging levels
|
||||
};
|
||||
|
||||
/// Specifies the sub-system that generated the log message.
|
||||
enum class Class : u8 {
|
||||
#define SUB(A, B) A##_##B,
|
||||
#define CLS(A) A,
|
||||
#include "log_classes.inc"
|
||||
#undef SUB
|
||||
#undef CLS
|
||||
Count,
|
||||
};
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args);
|
||||
|
||||
template <typename... Args>
|
||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::format_string<Args...> format, const Args&... args) {
|
||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
/// Implements a log message filter which allows different log classes to have different minimum
|
||||
/// severity levels. The filter can be changed at runtime and can be parsed from a string to allow
|
||||
/// editing via the interface or loading from a configuration file.
|
||||
struct Filter {
|
||||
/// Initializes the filter with all classes having `default_level` as the minimum level.
|
||||
explicit Filter(Level level = Level::Info) {
|
||||
class_levels.fill(level);
|
||||
}
|
||||
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
|
||||
void SetClassLevel(Class log_class, Level level) {
|
||||
class_levels[std::size_t(log_class)] = level;
|
||||
}
|
||||
/// Parses a filter string and applies it to this filter.
|
||||
/// A filter string consists of a space-separated list of filter rules, each of the format
|
||||
/// `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
|
||||
/// `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
|
||||
/// a severity level name which will be set as the minimum logging level of the matched classes.
|
||||
/// Rules are applied left to right, with each rule overriding previous ones in the sequence.
|
||||
/// A few examples of filter rules:
|
||||
/// - `*:Info` -- Resets the level of all classes to Info.
|
||||
/// - `Service:Info` -- Sets the level of Service to Info.
|
||||
/// - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
||||
void ParseFilterString(std::string_view filter_view);
|
||||
/// Matches class/level combination against the filter, returning true if it passed.
|
||||
[[nodiscard]] bool CheckMessage(Class log_class, Level level) const {
|
||||
return u8(level) >= u8(class_levels[std::size_t(log_class)]);
|
||||
}
|
||||
/// Returns true if any logging classes are set to debug
|
||||
[[nodiscard]] bool IsDebug() const {
|
||||
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
|
||||
return u8(l) <= u8(Level::Debug);
|
||||
});
|
||||
}
|
||||
std::array<Level, std::size_t(Class::Count)> class_levels;
|
||||
};
|
||||
|
||||
/// Initializes the logging system. This should be the first thing called in main.
|
||||
void Initialize();
|
||||
|
||||
void Start();
|
||||
|
||||
/// Explicitly stops the logger thread and flushes the buffers
|
||||
void Stop();
|
||||
|
||||
/// The global filter will prevent any messages from even being processed if they are filtered.
|
||||
void SetGlobalFilter(const Filter& filter);
|
||||
void SetColorConsoleBackendEnabled(bool enabled);
|
||||
|
||||
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
|
||||
/// formatting on different frontends, as well as facilitating filtering and aggregation.
|
||||
struct Entry {
|
||||
std::string message;
|
||||
std::chrono::microseconds timestamp;
|
||||
Class log_class{};
|
||||
Level log_level{};
|
||||
const char* filename = nullptr;
|
||||
const char* function = nullptr;
|
||||
unsigned int line_num = 0;
|
||||
};
|
||||
|
||||
/// Formats a log entry into the provided text buffer.
|
||||
std::string FormatLogMessage(const Entry& entry) noexcept;
|
||||
|
||||
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
|
||||
void PrintColoredMessage(const Entry& entry) noexcept;
|
||||
|
||||
/// Formats and prints a log entry to the android logcat.
|
||||
void PrintMessageToLogcat(const Entry& entry) noexcept;
|
||||
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,369 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h> // For OutputDebugStringW
|
||||
#endif
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_paths.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/log_entry.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/settings.h"
|
||||
#ifdef _WIN32
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
#include "common/bounded_threadsafe_queue.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
namespace {
|
||||
|
||||
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||
/// do not be fooled this isn't generating new strings on .rodata :)
|
||||
constexpr const char* TrimSourcePath(std::string_view source) noexcept {
|
||||
const auto rfind = [source](const std::string_view match) {
|
||||
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
|
||||
};
|
||||
auto idx = (std::max)({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
|
||||
return source.data() + idx;
|
||||
}
|
||||
|
||||
/// @brief Interface for logging backends.
|
||||
struct Backend {
|
||||
virtual ~Backend() noexcept = default;
|
||||
virtual void Write(const Entry& entry) noexcept = 0;
|
||||
virtual void EnableForStacktrace() noexcept= 0;
|
||||
virtual void Flush() noexcept = 0;
|
||||
};
|
||||
|
||||
/// @brief Backend that writes to stderr and with color
|
||||
struct ColorConsoleBackend final : public Backend {
|
||||
explicit ColorConsoleBackend() noexcept = default;
|
||||
~ColorConsoleBackend() noexcept override = default;
|
||||
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
if (enabled.load(std::memory_order_relaxed))
|
||||
PrintColoredMessage(entry);
|
||||
}
|
||||
|
||||
void Flush() noexcept override {
|
||||
// stderr shouldn't be buffered
|
||||
}
|
||||
|
||||
void EnableForStacktrace() noexcept override {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void SetEnabled(bool enabled_) noexcept {
|
||||
enabled = enabled_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_bool enabled{false};
|
||||
};
|
||||
|
||||
#ifndef __OPENORBIS__
|
||||
/// @brief Backend that writes to a file passed into the constructor
|
||||
struct FileBackend final : public Backend {
|
||||
explicit FileBackend(const std::filesystem::path& filename) noexcept {
|
||||
auto old_filename = filename;
|
||||
old_filename += ".old.txt";
|
||||
|
||||
// Existence checks are done within the functions themselves.
|
||||
// We don't particularly care if these succeed or not.
|
||||
void(FS::RemoveFile(old_filename));
|
||||
void(FS::RenameFile(filename, old_filename));
|
||||
|
||||
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
|
||||
}
|
||||
|
||||
~FileBackend() noexcept override = default;
|
||||
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
auto message = FormatLogMessage(entry).append(1, '\n');
|
||||
#ifndef __ANDROID__
|
||||
if (Settings::values.censor_username.GetValue()) {
|
||||
// This must be a static otherwise it would get checked on EVERY
|
||||
// instance of logging an entry...
|
||||
static std::string username = []() -> std::string {
|
||||
// in order of precedence
|
||||
// LOGNAME usually works on UNIX, USERNAME on Windows
|
||||
// Some UNIX systems suck and don't use LOGNAME so we also
|
||||
// need USER :(
|
||||
for (auto const var : {
|
||||
"LOGNAME",
|
||||
"USERNAME",
|
||||
"USER",
|
||||
}) {
|
||||
if (auto const s = getenv(var); s != nullptr)
|
||||
return std::string{s};
|
||||
}
|
||||
|
||||
return std::string{};
|
||||
}();
|
||||
if (!username.empty())
|
||||
boost::replace_all(message, username, "user");
|
||||
}
|
||||
#endif
|
||||
bytes_written += file->WriteString(message);
|
||||
|
||||
// Option to log each line rather than 4k buffers
|
||||
if (Settings::values.log_flush_line.GetValue())
|
||||
file->Flush();
|
||||
|
||||
using namespace Common::Literals;
|
||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
||||
const auto write_limit = Settings::values.extended_logging.GetValue() ? 1_GiB : 100_MiB;
|
||||
const bool write_limit_exceeded = bytes_written > write_limit;
|
||||
if (entry.log_level >= Level::Error || write_limit_exceeded) {
|
||||
// Stop writing after the write limit is exceeded.
|
||||
// Don't close the file so we can print a stacktrace if necessary
|
||||
if (write_limit_exceeded)
|
||||
enabled = false;
|
||||
file->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Flush() noexcept override {
|
||||
file->Flush();
|
||||
}
|
||||
|
||||
void EnableForStacktrace() noexcept override {
|
||||
enabled = true;
|
||||
bytes_written = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FS::IOFile> file;
|
||||
std::size_t bytes_written = 0;
|
||||
bool enabled = true;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
/// @brief Backend that writes to Visual Studio's output window
|
||||
struct DebuggerBackend final : public Backend {
|
||||
explicit DebuggerBackend() noexcept = default;
|
||||
~DebuggerBackend() noexcept override = default;
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
void EnableForStacktrace() noexcept override {}
|
||||
};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
/// @brief Backend that writes to the Android logcat
|
||||
struct LogcatBackend : public Backend {
|
||||
explicit LogcatBackend() noexcept = default;
|
||||
~LogcatBackend() noexcept override = default;
|
||||
void Write(const Entry& entry) noexcept override {
|
||||
PrintMessageToLogcat(entry);
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
void EnableForStacktrace() noexcept override {}
|
||||
};
|
||||
#endif
|
||||
|
||||
bool initialization_in_progress_suppress_logging = true;
|
||||
|
||||
/// @brief Static state as a singleton.
|
||||
class Impl {
|
||||
public:
|
||||
static Impl& Instance() noexcept {
|
||||
if (!instance)
|
||||
std::abort();
|
||||
return *instance;
|
||||
}
|
||||
|
||||
static void Initialize() noexcept {
|
||||
if (instance) {
|
||||
LOG_WARNING(Log, "Reinitializing logging backend");
|
||||
return;
|
||||
}
|
||||
using namespace Common::FS;
|
||||
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||
void(CreateDir(log_dir));
|
||||
Filter filter;
|
||||
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter), Deleter);
|
||||
initialization_in_progress_suppress_logging = false;
|
||||
}
|
||||
|
||||
static void Start() noexcept {
|
||||
instance->StartBackendThread();
|
||||
}
|
||||
|
||||
static void Stop() noexcept {
|
||||
instance->StopBackendThread();
|
||||
}
|
||||
|
||||
Impl(const Impl&) noexcept = delete;
|
||||
Impl& operator=(const Impl&) noexcept = delete;
|
||||
|
||||
Impl(Impl&&) noexcept = delete;
|
||||
Impl& operator=(Impl&&) noexcept = delete;
|
||||
|
||||
void SetGlobalFilter(const Filter& f) noexcept {
|
||||
filter = f;
|
||||
}
|
||||
|
||||
void SetColorConsoleBackendEnabled(bool enabled) noexcept {
|
||||
color_console_backend.SetEnabled(enabled);
|
||||
}
|
||||
|
||||
bool CanPushEntry(Class log_class, Level log_level) const noexcept {
|
||||
return filter.CheckMessage(log_class, log_level);
|
||||
}
|
||||
|
||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, std::string&& message) noexcept {
|
||||
message_queue.EmplaceWait(CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message)));
|
||||
}
|
||||
|
||||
private:
|
||||
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) noexcept :
|
||||
filter{filter_}
|
||||
#ifndef __OPENORBIS__
|
||||
, file_backend{file_backend_filename}
|
||||
#endif
|
||||
{}
|
||||
|
||||
~Impl() noexcept = default;
|
||||
|
||||
void StartBackendThread() noexcept {
|
||||
backend_thread = std::jthread([this](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("Logger");
|
||||
Entry entry;
|
||||
const auto write_logs = [this, &entry]() {
|
||||
ForEachBackend([&entry](Backend& backend) {
|
||||
backend.Write(entry);
|
||||
});
|
||||
};
|
||||
do {
|
||||
message_queue.PopWait(entry, stop_token);
|
||||
write_logs();
|
||||
} while (!stop_token.stop_requested());
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
||||
// case where a system is repeatedly spamming logs even on close.
|
||||
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
|
||||
while (max_logs_to_write-- && message_queue.TryPop(entry))
|
||||
write_logs();
|
||||
});
|
||||
}
|
||||
|
||||
void StopBackendThread() noexcept {
|
||||
backend_thread.request_stop();
|
||||
if (backend_thread.joinable())
|
||||
backend_thread.join();
|
||||
ForEachBackend([](Backend& backend) { backend.Flush(); });
|
||||
}
|
||||
|
||||
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, std::string&& message) const noexcept {
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::microseconds;
|
||||
using std::chrono::steady_clock;
|
||||
return {
|
||||
.timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
|
||||
.log_class = log_class,
|
||||
.log_level = log_level,
|
||||
.filename = filename,
|
||||
.line_num = line_nr,
|
||||
.function = function,
|
||||
.message = std::move(message),
|
||||
};
|
||||
}
|
||||
|
||||
void ForEachBackend(auto lambda) noexcept {
|
||||
lambda(static_cast<Backend&>(color_console_backend));
|
||||
#ifndef __OPENORBIS__
|
||||
lambda(static_cast<Backend&>(file_backend));
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
lambda(static_cast<Backend&>(debugger_backend));
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
lambda(static_cast<Backend&>(lc_backend));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Deleter(Impl* ptr) noexcept {
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
|
||||
|
||||
Filter filter;
|
||||
ColorConsoleBackend color_console_backend{};
|
||||
#ifndef __OPENORBIS__
|
||||
FileBackend file_backend;
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
DebuggerBackend debugger_backend{};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
LogcatBackend lc_backend{};
|
||||
#endif
|
||||
|
||||
MPSCQueue<Entry> message_queue{};
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
std::jthread backend_thread;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void Initialize() {
|
||||
Impl::Initialize();
|
||||
}
|
||||
|
||||
void Start() {
|
||||
Impl::Start();
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
Impl::Stop();
|
||||
}
|
||||
|
||||
void DisableLoggingInTests() {
|
||||
initialization_in_progress_suppress_logging = true;
|
||||
}
|
||||
|
||||
void SetGlobalFilter(const Filter& filter) {
|
||||
Impl::Instance().SetGlobalFilter(filter);
|
||||
}
|
||||
|
||||
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
|
||||
if (!initialization_in_progress_suppress_logging) {
|
||||
auto& instance = Impl::Instance();
|
||||
if (instance.CanPushEntry(log_class, log_level))
|
||||
instance.PushEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
|
||||
}
|
||||
}
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/logging/filter.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
class Filter;
|
||||
|
||||
/// Initializes the logging system. This should be the first thing called in main.
|
||||
void Initialize();
|
||||
|
||||
void Start();
|
||||
|
||||
/// Explicitly stops the logger thread and flushes the buffers
|
||||
void Stop();
|
||||
|
||||
void DisableLoggingInTests();
|
||||
|
||||
/**
|
||||
* The global filter will prevent any messages from even being processed if they are filtered.
|
||||
*/
|
||||
void SetGlobalFilter(const Filter& filter);
|
||||
|
||||
void SetColorConsoleBackendEnabled(bool enabled);
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Common::Log {
|
||||
namespace {
|
||||
template <typename It>
|
||||
Level GetLevelByName(const It begin, const It end) {
|
||||
for (u32 i = 0; i < u32(Level::Count); ++i) {
|
||||
const char* level_name = GetLevelName(Level(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name))
|
||||
return Level(i);
|
||||
}
|
||||
return Level::Count;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
Class GetClassByName(const It begin, const It end) {
|
||||
for (u32 i = 0; i < u32(Class::Count); ++i) {
|
||||
const char* level_name = GetLogClassName(Class(i));
|
||||
if (Common::ComparePartialString(begin, end, level_name))
|
||||
return Class(i);
|
||||
}
|
||||
return Class::Count;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
|
||||
std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
instance.ResetAll(level);
|
||||
return true;
|
||||
}
|
||||
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
|
||||
instance.SetClassLevel(log_class, level);
|
||||
return true;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
||||
#define ALL_LOG_CLASSES() \
|
||||
CLS(Log) \
|
||||
CLS(Common) \
|
||||
SUB(Common, Filesystem) \
|
||||
SUB(Common, Memory) \
|
||||
CLS(Core) \
|
||||
SUB(Core, ARM) \
|
||||
SUB(Core, Timing) \
|
||||
CLS(Config) \
|
||||
CLS(Debug) \
|
||||
SUB(Debug, Emulated) \
|
||||
SUB(Debug, GPU) \
|
||||
SUB(Debug, Breakpoint) \
|
||||
SUB(Debug, GDBStub) \
|
||||
CLS(Kernel) \
|
||||
SUB(Kernel, SVC) \
|
||||
CLS(Service) \
|
||||
SUB(Service, ACC) \
|
||||
SUB(Service, Audio) \
|
||||
SUB(Service, AM) \
|
||||
SUB(Service, AOC) \
|
||||
SUB(Service, APM) \
|
||||
SUB(Service, ARP) \
|
||||
SUB(Service, BCAT) \
|
||||
SUB(Service, BPC) \
|
||||
SUB(Service, BGTC) \
|
||||
SUB(Service, BTDRV) \
|
||||
SUB(Service, BTM) \
|
||||
SUB(Service, Capture) \
|
||||
SUB(Service, ERPT) \
|
||||
SUB(Service, ETicket) \
|
||||
SUB(Service, EUPLD) \
|
||||
SUB(Service, Fatal) \
|
||||
SUB(Service, FGM) \
|
||||
SUB(Service, Friend) \
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, GRC) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, IRS) \
|
||||
SUB(Service, JIT) \
|
||||
SUB(Service, LBL) \
|
||||
SUB(Service, LDN) \
|
||||
SUB(Service, LDR) \
|
||||
SUB(Service, LM) \
|
||||
SUB(Service, Migration) \
|
||||
SUB(Service, Mii) \
|
||||
SUB(Service, MM) \
|
||||
SUB(Service, MNPP) \
|
||||
SUB(Service, NCM) \
|
||||
SUB(Service, NFC) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NGC) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NOTIF) \
|
||||
SUB(Service, NPNS) \
|
||||
SUB(Service, NS) \
|
||||
SUB(Service, NVDRV) \
|
||||
SUB(Service, Nvnflinger) \
|
||||
SUB(Service, OLSC) \
|
||||
SUB(Service, PCIE) \
|
||||
SUB(Service, PCTL) \
|
||||
SUB(Service, PCV) \
|
||||
SUB(Service, PM) \
|
||||
SUB(Service, PREPO) \
|
||||
SUB(Service, PSC) \
|
||||
SUB(Service, PTM) \
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, TCAP) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, USB) \
|
||||
SUB(Service, VI) \
|
||||
SUB(Service, WLAN) \
|
||||
CLS(HW) \
|
||||
SUB(HW, Memory) \
|
||||
SUB(HW, LCD) \
|
||||
SUB(HW, GPU) \
|
||||
SUB(HW, AES) \
|
||||
CLS(IPC) \
|
||||
CLS(Frontend) \
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
SUB(Render, OpenGL) \
|
||||
SUB(Render, Vulkan) \
|
||||
CLS(Shader) \
|
||||
SUB(Shader, SPIRV) \
|
||||
SUB(Shader, GLASM) \
|
||||
SUB(Shader, GLSL) \
|
||||
CLS(Audio) \
|
||||
SUB(Audio, DSP) \
|
||||
SUB(Audio, Sink) \
|
||||
CLS(Input) \
|
||||
CLS(Network) \
|
||||
CLS(Loader) \
|
||||
CLS(CheatEngine) \
|
||||
CLS(Crypto) \
|
||||
CLS(WebService)
|
||||
|
||||
// GetClassName is a macro defined by Windows.h, grrr...
|
||||
const char* GetLogClassName(Class log_class) {
|
||||
switch (log_class) {
|
||||
#define CLS(x) \
|
||||
case Class::x: \
|
||||
return #x;
|
||||
#define SUB(x, y) \
|
||||
case Class::x##_##y: \
|
||||
return #x "." #y;
|
||||
ALL_LOG_CLASSES()
|
||||
#undef CLS
|
||||
#undef SUB
|
||||
case Class::Count:
|
||||
break;
|
||||
}
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
const char* GetLevelName(Level log_level) {
|
||||
#define LVL(x) \
|
||||
case Level::x: \
|
||||
return #x
|
||||
switch (log_level) {
|
||||
LVL(Trace);
|
||||
LVL(Debug);
|
||||
LVL(Info);
|
||||
LVL(Warning);
|
||||
LVL(Error);
|
||||
LVL(Critical);
|
||||
case Level::Count:
|
||||
break;
|
||||
}
|
||||
#undef LVL
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
Filter::Filter(Level default_level) {
|
||||
ResetAll(default_level);
|
||||
}
|
||||
|
||||
void Filter::ResetAll(Level level) {
|
||||
class_levels.fill(level);
|
||||
}
|
||||
|
||||
void Filter::SetClassLevel(Class log_class, Level level) {
|
||||
class_levels[static_cast<std::size_t>(log_class)] = level;
|
||||
}
|
||||
|
||||
void Filter::ParseFilterString(std::string_view filter_view) {
|
||||
auto clause_begin = filter_view.cbegin();
|
||||
while (clause_begin != filter_view.cend()) {
|
||||
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
|
||||
|
||||
// If clause isn't empty
|
||||
if (clause_end != clause_begin) {
|
||||
ParseFilterRule(*this, clause_begin, clause_end);
|
||||
}
|
||||
|
||||
if (clause_end != filter_view.cend()) {
|
||||
// Skip over the whitespace
|
||||
++clause_end;
|
||||
}
|
||||
clause_begin = clause_end;
|
||||
}
|
||||
}
|
||||
|
||||
bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||
return u8(level) >= u8(class_levels[std::size_t(log_class)]);
|
||||
}
|
||||
|
||||
bool Filter::IsDebug() const {
|
||||
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
|
||||
return u8(l) <= u8(Level::Debug);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
|
||||
* instead of underscores as in the enumeration.
|
||||
*/
|
||||
const char* GetLogClassName(Class log_class);
|
||||
|
||||
/**
|
||||
* Returns the name of the passed log level as a C-string.
|
||||
*/
|
||||
const char* GetLevelName(Level log_level);
|
||||
|
||||
/**
|
||||
* Implements a log message filter which allows different log classes to have different minimum
|
||||
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
|
||||
* editing via the interface or loading from a configuration file.
|
||||
*/
|
||||
class Filter {
|
||||
public:
|
||||
/// Initializes the filter with all classes having `default_level` as the minimum level.
|
||||
explicit Filter(Level default_level = Level::Info);
|
||||
|
||||
/// Resets the filter so that all classes have `level` as the minimum displayed level.
|
||||
void ResetAll(Level level);
|
||||
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
|
||||
void SetClassLevel(Class log_class, Level level);
|
||||
|
||||
/**
|
||||
* Parses a filter string and applies it to this filter.
|
||||
*
|
||||
* A filter string consists of a space-separated list of filter rules, each of the format
|
||||
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
|
||||
* `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
|
||||
* a severity level name which will be set as the minimum logging level of the matched classes.
|
||||
* Rules are applied left to right, with each rule overriding previous ones in the sequence.
|
||||
*
|
||||
* A few examples of filter rules:
|
||||
* - `*:Info` -- Resets the level of all classes to Info.
|
||||
* - `Service:Info` -- Sets the level of Service to Info.
|
||||
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
|
||||
*/
|
||||
void ParseFilterString(std::string_view filter_view);
|
||||
|
||||
/// Matches class/level combination against the filter, returning true if it passed.
|
||||
bool CheckMessage(Class log_class, Level level) const;
|
||||
|
||||
/// Returns true if any logging classes are set to debug
|
||||
bool IsDebug() const;
|
||||
|
||||
private:
|
||||
std::array<Level, static_cast<std::size_t>(Class::Count)> class_levels;
|
||||
};
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/swap.h"
|
||||
|
||||
// adapted from https://github.com/fmtlib/fmt/issues/2704
|
||||
// a generic formatter for enum classes
|
||||
#if FMT_VERSION >= 80100
|
||||
template <typename T>
|
||||
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
|
||||
: formatter<std::underlying_type_t<T>> {
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
return fmt::formatter<std::underlying_type_t<T>>::format(
|
||||
static_cast<std::underlying_type_t<T>>(value), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename U>
|
||||
struct fmt::formatter<SwapStructT<T, U>> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const SwapStructT<T, U>& reg, FormatContext& ctx) const {
|
||||
return fmt::format_to(ctx.out(), "{}", T(reg));
|
||||
}
|
||||
};
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "common/logging/formatter.h"
|
||||
#include "common/logging/types.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, fmt::string_view format,
|
||||
const fmt::format_args& args);
|
||||
|
||||
template <typename... Args>
|
||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, fmt::format_string<Args...> format, const Args&... args) {
|
||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
} // namespace Common::Log
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define LOG_TRACE(log_class, ...) \
|
||||
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Debug, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Info, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Warning, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Error, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Critical, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
__VA_ARGS__)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// 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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/logging/types.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
|
||||
/// formatting on different frontends, as well as facilitating filtering and aggregation.
|
||||
struct Entry {
|
||||
std::chrono::microseconds timestamp;
|
||||
Class log_class{};
|
||||
Level log_level{};
|
||||
const char* filename = nullptr;
|
||||
unsigned int line_num = 0;
|
||||
const char* function = nullptr;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__ANDROID__)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/log_entry.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
||||
std::string FormatLogMessage(const Entry& entry) noexcept {
|
||||
if (!entry.filename) return "";
|
||||
|
||||
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||
auto const class_name = GetLogClassName(entry.log_class);
|
||||
auto const level_name = GetLevelName(entry.log_level);
|
||||
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional,
|
||||
class_name, level_name, entry.filename, entry.line_num, entry.function,
|
||||
entry.message);
|
||||
}
|
||||
|
||||
/// @brief Formats and prints a log entry to stderr.
|
||||
static void PrintMessage(const Entry& entry) noexcept {
|
||||
#ifdef _WIN32
|
||||
auto const str = FormatLogMessage(entry).append(1, '\n');
|
||||
fwrite(str.c_str(), 1, str.size(), stderr);
|
||||
#else
|
||||
#define ESC "\x1b"
|
||||
auto const color_str = [&entry]() -> const char* {
|
||||
switch (entry.log_level) {
|
||||
case Level::Debug: return ESC "[0;36m"; // Cyan
|
||||
case Level::Info: return ESC "[0;37m"; // Bright gray
|
||||
case Level::Warning: return ESC "[1;33m"; // Bright yellow
|
||||
case Level::Error: return ESC "[1;31m"; // Bright red
|
||||
case Level::Critical: return ESC "[1;35m"; // Bright magenta
|
||||
default: return ESC "[1;30m"; // Grey
|
||||
}
|
||||
}();
|
||||
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||
auto const class_name = GetLogClassName(entry.log_class);
|
||||
auto const level_name = GetLevelName(entry.log_level);
|
||||
fprintf(stderr, "%s[%4d.%06d] %s <%s> %s:%u:%s: %s" ESC "[0m\n", color_str,
|
||||
time_seconds, time_fractional, class_name, level_name, entry.filename,
|
||||
entry.line_num, entry.function, entry.message.c_str());
|
||||
#undef ESC
|
||||
#endif
|
||||
}
|
||||
|
||||
void PrintColoredMessage(const Entry& entry) noexcept {
|
||||
#ifdef _WIN32
|
||||
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||
if (console_handle == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
CONSOLE_SCREEN_BUFFER_INFO original_info = {};
|
||||
GetConsoleScreenBufferInfo(console_handle, &original_info);
|
||||
WORD color = WORD([&entry]() {
|
||||
switch (entry.log_level) {
|
||||
case Level::Debug: return FOREGROUND_GREEN | FOREGROUND_BLUE; // Cyan
|
||||
case Level::Info: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; // Bright gray
|
||||
case Level::Warning: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
||||
case Level::Error: return FOREGROUND_RED | FOREGROUND_INTENSITY;
|
||||
case Level::Critical: return FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
||||
default: break;
|
||||
}
|
||||
return FOREGROUND_INTENSITY; // Grey
|
||||
}());
|
||||
SetConsoleTextAttribute(console_handle, color);
|
||||
#endif
|
||||
PrintMessage(entry);
|
||||
#ifdef _WIN32
|
||||
SetConsoleTextAttribute(console_handle, original_info.wAttributes);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PrintMessageToLogcat(const Entry& entry) noexcept {
|
||||
#ifdef ANDROID
|
||||
android_LogPriority android_log_priority = [&]() {
|
||||
switch (entry.log_level) {
|
||||
case Level::Debug: return ANDROID_LOG_DEBUG;
|
||||
case Level::Info: return ANDROID_LOG_INFO;
|
||||
case Level::Warning: return ANDROID_LOG_WARN;
|
||||
case Level::Error: return ANDROID_LOG_ERROR;
|
||||
case Level::Critical: return ANDROID_LOG_FATAL;
|
||||
case Level::Count:
|
||||
case Level::Trace: return ANDROID_LOG_VERBOSE;
|
||||
}
|
||||
}();
|
||||
auto const str = FormatLogMessage(entry);
|
||||
__android_log_print(android_log_priority, "YuzuNative", "%s", str.c_str());
|
||||
#endif
|
||||
}
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
struct Entry;
|
||||
|
||||
/// Formats a log entry into the provided text buffer.
|
||||
std::string FormatLogMessage(const Entry& entry) noexcept;
|
||||
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
|
||||
void PrintColoredMessage(const Entry& entry) noexcept;
|
||||
/// Formats and prints a log entry to the android logcat.
|
||||
void PrintMessageToLogcat(const Entry& entry) noexcept;
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
/// Specifies the severity or level of detail of the log message.
|
||||
enum class Level : u8 {
|
||||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
|
||||
///< pollute logs.
|
||||
Debug, ///< Less detailed debugging information.
|
||||
Info, ///< Status information from important points during execution.
|
||||
Warning, ///< Minor or potential problems found during execution of a task.
|
||||
Error, ///< Major problems found during execution of a task that prevent it from being
|
||||
///< completed.
|
||||
Critical, ///< Major problems during execution that threaten the stability of the entire
|
||||
///< application.
|
||||
|
||||
Count ///< Total number of logging levels
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies the sub-system that generated the log message.
|
||||
*
|
||||
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
|
||||
* filter.cpp.
|
||||
*/
|
||||
enum class Class : u8 {
|
||||
Log, ///< Messages about the log system itself
|
||||
Common, ///< Library routines
|
||||
Common_Filesystem, ///< Filesystem interface library
|
||||
Common_Memory, ///< Memory mapping and management functions
|
||||
Core, ///< LLE emulation core
|
||||
Core_ARM, ///< ARM CPU core
|
||||
Core_Timing, ///< CoreTiming functions
|
||||
Config, ///< Emulator configuration (including commandline)
|
||||
Debug, ///< Debugging tools
|
||||
Debug_Emulated, ///< Debug messages from the emulated programs
|
||||
Debug_GPU, ///< GPU debugging tools
|
||||
Debug_Breakpoint, ///< Logging breakpoints and watchpoints
|
||||
Debug_GDBStub, ///< GDB Stub
|
||||
Kernel, ///< The HLE implementation of the CTR kernel
|
||||
Kernel_SVC, ///< Kernel system calls
|
||||
Service, ///< HLE implementation of system services. Each major service
|
||||
///< should have its own subclass.
|
||||
Service_ACC, ///< The ACC (Accounts) service
|
||||
Service_AM, ///< The AM (Applet manager) service
|
||||
Service_AOC, ///< The AOC (AddOn Content) service
|
||||
Service_APM, ///< The APM (Performance) service
|
||||
Service_ARP, ///< The ARP service
|
||||
Service_Audio, ///< The Audio (Audio control) service
|
||||
Service_BCAT, ///< The BCAT service
|
||||
Service_BGTC, ///< The BGTC (Background Task Controller) service
|
||||
Service_BPC, ///< The BPC service
|
||||
Service_BTDRV, ///< The Bluetooth driver service
|
||||
Service_BTM, ///< The BTM service
|
||||
Service_Capture, ///< The capture service
|
||||
Service_ERPT, ///< The error reporting service
|
||||
Service_ETicket, ///< The ETicket service
|
||||
Service_EUPLD, ///< The error upload service
|
||||
Service_Fatal, ///< The Fatal service
|
||||
Service_FGM, ///< The FGM service
|
||||
Service_Friend, ///< The friend service
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_GRC, ///< The game recording service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_IRS, ///< The IRS service
|
||||
Service_JIT, ///< The JIT service
|
||||
Service_LBL, ///< The LBL (LCD backlight) service
|
||||
Service_LDN, ///< The LDN (Local domain network) service
|
||||
Service_LDR, ///< The loader service
|
||||
Service_LM, ///< The LM (Logger) service
|
||||
Service_Migration, ///< The migration service
|
||||
Service_Mii, ///< The Mii service
|
||||
Service_MM, ///< The MM (Multimedia) service
|
||||
Service_MNPP, ///< The MNPP service
|
||||
Service_NCM, ///< The NCM service
|
||||
Service_NFC, ///< The NFC (Near-field communication) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NGC, ///< The NGC (No Good Content) service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NOTIF, ///< The NOTIF (Notification) service
|
||||
Service_NPNS, ///< The NPNS service
|
||||
Service_NS, ///< The NS services
|
||||
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
|
||||
Service_Nvnflinger, ///< The Nvnflinger service
|
||||
Service_OLSC, ///< The OLSC service
|
||||
Service_PCIE, ///< The PCIe service
|
||||
Service_PCTL, ///< The PCTL (Parental control) service
|
||||
Service_PCV, ///< The PCV service
|
||||
Service_PM, ///< The PM service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_PSC, ///< The PSC service
|
||||
Service_PTM, ///< The PTM service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_TCAP, ///< The TCAP service.
|
||||
Service_Time, ///< The time service
|
||||
Service_USB, ///< The USB (Universal Serial Bus) service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
Service_WLAN, ///< The WLAN (Wireless local area network) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
HW_Memory, ///< Memory-map and address translation
|
||||
HW_LCD, ///< LCD register emulation
|
||||
HW_GPU, ///< GPU control emulation
|
||||
HW_AES, ///< AES engine emulation
|
||||
IPC, ///< IPC interface
|
||||
Frontend, ///< Emulator UI
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
Shader, ///< Shader recompiler
|
||||
Shader_SPIRV, ///< Shader SPIR-V code generation
|
||||
Shader_GLASM, ///< Shader GLASM code generation
|
||||
Shader_GLSL, ///< Shader GLSL code generation
|
||||
Audio, ///< Audio emulation
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
Loader, ///< ROM loader
|
||||
CheatEngine, ///< Memory manipulation and engine VM functions
|
||||
Crypto, ///< Cryptographic engine/functions
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
WebService, ///< Interface to yuzu Web Services
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
} // namespace Common::Log
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -6,7 +9,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/param_package.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,14 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/fs/fs_util.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/time_zone.h"
|
||||
|
||||
#if defined(__linux__ ) && defined(ARCHITECTURE_arm64)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Settings {
|
||||
|
||||
// Clang 14 and earlier have errors when explicitly instantiating these classes
|
||||
|
|
@ -178,7 +182,11 @@ bool IsFastmemEnabled() {
|
|||
return bool(values.cpuopt_fastmem);
|
||||
else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe)
|
||||
return bool(values.cpuopt_unsafe_host_mmu);
|
||||
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
|
||||
#if defined(__linux__) && defined(ARCHITECTURE_arm64)
|
||||
// Only 4kb systems support host MMU right now
|
||||
// TODO: Support this
|
||||
return getpagesize() == 4096;
|
||||
#elif !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32) && !defined(__linux__)
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <thread>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#ifdef __APPLE__
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -10,7 +13,7 @@
|
|||
#include <fmt/chrono.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/time_zone.h"
|
||||
|
||||
namespace Common::TimeZone {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
@ -12,7 +15,7 @@
|
|||
#include <vector>
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// 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
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/dynarmic_cp15.h"
|
||||
#include "core/core.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/memory.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/arm/nce/visitor_base.h"
|
||||
|
||||
namespace Core {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <oaknut/code_block.hpp>
|
||||
#include <oaknut/oaknut.hpp>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "game_settings.h"
|
||||
#include "audio_core/audio_core.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/string_util.h"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
#include <boost/process/async_pipe.hpp>
|
||||
#endif
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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
|
||||
|
||||
|
|
@ -6,7 +9,7 @@
|
|||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging.h"
|
||||
#include <ranges>
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue