Compare commits

..

202 commits

Author SHA1 Message Date
jbm11208
e24f8da113
Add per-title vulkan pipeline cache (#1118) 2025-06-23 20:51:25 +02:00
Qidi
680cbb559d
Fix aac_decoder memory leak (#1167)
* Fix aac_decoder memory leak

Only call NeAACDecInit on the first AAC frame and create new NeAACDecoder on DecoderCommand::Init request

* update license headers

* fix oversight

* reorganized code

-put open new FAAD2 AAC decoder code into a separate function
-removed LOG_INFO for open/close FAAD2 AAC decoder
-added LOG_ERROR when no decoder is created to handle decode request, either decoder creation failed or DecoderCommand::Init command not received

* Update aac_decoder.cpp

fix clang coding style check

* fix load savestate

Loading a savestate creates a situation where decode requests aren't preceded by an init request, so we open a new decoder by default in the constructor. A new AACDecoder instance is always constructed on load savestate.
2025-06-21 11:50:39 +01:00
OpenSauce04
9dc9bf3baf Updated language translations via Transifex 2025-06-20 23:24:51 +01:00
PabloMK7
5f81ac40c8
rasterizer: Increase uniform buffer size (#1174) 2025-06-20 22:10:04 +02:00
PabloMK7
0deb0f50b8
Add "SWP" frame time information (#1173) 2025-06-20 19:26:12 +02:00
kleidis
2b51691d57
android: Fix hotkey presses opening nav drawer even after being bound (#1122)
* android: Fix hotkey presses opening nav drawer even after being bound

* Removed unnecessary return

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-06-19 23:48:18 +01:00
marsia
11687fe32b android: Add landscape layout for setup pages 2025-06-19 21:14:26 +00:00
OpenSauce04
9d4d6c5fdc externals: Update libyuv to latest commit
Fixes an Android build failure while using CMake >=4.0
2025-06-19 21:06:10 +00:00
PabloMK7
f26b9b174a
cmake: Bump min version to 3.25 globally and to 3.30.3 for android (#1151)
* cmake: Bump min version to 3.25 and update android to 3.30.3

* app/build.gradle.kts: Set minimum CMake version rather than exact version

---------

Co-authored-by: OpenSauce <opensauce04@gmail.com>
2025-06-18 21:44:42 +02:00
qr243vbi
ce8798fffe
Replace deprecated and removed in boost 1.88 io_service to io_context (#1126)
* Replace deprecated and removed in boost 1.88 io_service for io_context

* More thoroughly replaced references to io_service with io_context

* Updated license header

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-06-17 18:41:22 +00:00
PabloMK7
3c3dd2bd86
am: Improve cia encrypted content detection (#1152) 2025-06-17 14:17:01 +02:00
OpenSauce04
eec1466b7b Updated language translations via Transifex 2025-06-16 16:08:56 +01:00
OpenSauce04
5910edb9fa android: Fixed accurate multiplication setting using async shader value 2025-06-13 11:16:42 +01:00
OpenSauce04
63f52580ca qt: Renamed toggle console setting name to be more accurate 2025-06-09 20:19:40 +01:00
OpenSauce04
d1b80788f6 ci: Corrected Windows Vulkan SDK URL which was taken down by upstream 2025-06-09 19:29:08 +01:00
OpenSauce04
7eff38ed10 Updated language translations via Transifex 2025-06-08 18:16:44 +01:00
PabloMK7
995538fa3e
vulkan: Report error code on Vulkan::CreateSurface (#1130) 2025-06-08 17:23:17 +02:00
PabloMK7
57105076a0
frontend: Save RPC setting to config file (#1129) 2025-06-08 12:10:39 +02:00
OpenSauce04
868e946dee app/build.gradle.kts: Fixed incorrect usage of layout.buildDirectory 2025-06-06 12:37:54 +01:00
PabloMK7
fe7fe3ed24
android: Fix crash when user directory permissions are lost (#1110)
* android: Fix crash when user directory permissions are lost

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-06-03 23:06:46 +02:00
OpenSauce04
2acbcd333c app/build.gradle.kts: Removed now-obsolete isMinifyEnabled value from relWithDebInfo build variant 2025-06-03 21:48:33 +01:00
OpenSauce04
7853bacddd app/build.gradle.kts: Replaced deprecated buildDir value with layout.buildDirectory 2025-06-03 21:48:33 +01:00
OpenSauce04
d827b63044 Migrate deprecated buildconfig value in gradle.properties to build.gradle.kts 2025-06-03 21:48:33 +01:00
OpenSauce04
14e8ee7fe3 cmake: Suppress -Wpsabi warnings when building with GCC 2025-06-03 20:58:30 +01:00
RedBlackAka
bc1edd1f1a
Fix and restore macOS native menu (#1111)
* Fix and restore macOS native menu

* Handle menu more elegantly by making norole default

Handle menu roles more universally by making norole default and manually define previously automatically assigned roles
2025-06-03 18:12:24 +01:00
OpenSauce04
cfe767e301 Updated translations via Transifex 2025-05-31 18:54:12 +01:00
OpenSauce04
063a80695c Updated outdated Vulkan dependencies to SDK 1.4.304.1 2025-05-31 16:37:04 +01:00
PabloMK7
a974fc4ac8 kernel: Improve SVC handling timings 2025-05-31 15:23:14 +00:00
PabloMK7
ad97506867
video_core: Remove assert in AccelerateTextureCopy (#1092) 2025-05-31 14:25:57 +02:00
PabloMK7
cf87efa3c0 video_core: Vectorize RasterizerAccelerated::AnalyzeVertexArray
Uses SIMD operations on the RasterizerAccelerated::AnalyzeVertexArray
function, which is hot code. Slightly reduces GPU processing time
on all games.

This idea was suggested by an anonymous contributor.

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-05-31 11:12:28 +00:00
PabloMK7
88b3dff278 citra_common: Enable SSE4.2 on x86_64 builds
Enables the use of SSE4.2 instructions on x86_64 CPUs, allowing
compilers to automatically vectorize some loops on citra_common.
A CMake toggle ENABLE_SSE42 (ON by default) has been added
to enable this behaviour.

This change breaks compatibility with CPUs that do not have
SSE4.2 instructions. All modern CPUs (from 2011 onwards) should
always have these instructions. Manual compilation will be
needed for older CPUs.

A message has been added to report if the CPU is incompatible
when starting the emulator.

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-05-31 11:12:28 +00:00
OpenSauce
9ecd26d2ce
android: Enhance shortcut customization with a custom dialog (#824)
* android: Enhance shortcut customization with a custom dialog

Adds ability to customize game shortcuts with:
- Custom name input
- Editable icon via image picker
- Ability to stretch to fit or zoom to fit the shortcut icon

* Code cleanup

* SearchFragment.kt: Updated license header

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-05-29 19:17:01 +01:00
PabloMK7
4cfb5c9d55
android: Add missing shader JIT option (#1085) 2025-05-29 19:10:26 +02:00
OpenSauce04
a6c72531b4 strings.xml: Removed stray quotation mark from advanced string 2025-05-29 17:15:52 +01:00
PabloMK7
ae43740690
Fix disable spirv optimizer option not saving (#1084) 2025-05-29 18:11:33 +02:00
PabloMK7
ec964c8610
Add more advanced frame time information (#1083) 2025-05-29 17:38:10 +02:00
PabloMK7
fd2ce82b6e
Add toggle to disable SPIRV optimization pass (#1080)
* Add toggle to disable SPIRV optimization pass

* vk_shader_util.cpp: Nitpicky comment tweak

* Consistently refer to "optimizer" instead of "optimization"

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-05-28 21:22:59 +01:00
OpenSauce
fd2551439e
android: Add rotate screen upright toggle to UI (#819)
* Add upright boolean for portrait mode

* Add the `upright_screen` boolean to the UI as a switch & in-game

* ScreenAdjustmentUtil.kt: Updated license header

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-05-28 18:50:57 +00:00
OpenSauce
f3c63abd30
android: Only use Boolean value type for SwitchSettings (#821)
* android: Only use `Boolean` value type for `SwitchSetting`s

* SwitchSetting.kt: Updated license header
2025-05-28 19:00:48 +01:00
OpenSauce
f771952e62
android: Reorganize setup process to use multiple buttons per-page (#820)
* Refactor SetupFragment to support multiple buttons in one page

* Add new `PageButton` data class

* Programmatic button creation && button disabling in setUpAdapter

* Refactor SetupWarningDialogFragment to support multiple titles, descriptions, and help links

* Rework CitraDirectoryHelper to support button step state

* Update warning message for user folder selection step

* Updated license headers

* Code cleanup

* "skip setting the user folder" --> "skip setting up the user folder"

* Fixed typos in string names

* Break `select_emulator_data_folder_description` string over two lines

* `select_emulator_data_folder` --> `select_emulator_data_folders`

* Code cleanup #2

* Removed seemingly accidentally duplicated block of code

* Removed stray newlines

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-05-27 18:41:27 +01:00
OpenSauce04
6df92285e1 update_checker.cpp: Use fmt::format instead of std::format
It seems that certain environments still don't have access to `std::format` yet, and I missed this because it built fine on my machine and CI passed because the code using `std::format` wasn't included in non-tagged builds.
2025-05-27 14:00:08 +01:00
OpenSauce04
2c8a3b17b4 Updated language translations via Transifex 2025-05-27 13:03:07 +01:00
OpenSauce04
0ca46dc05f update_checker.cpp: Use GitHub's tags API to get latest tag for prereleases
The `releases` API can sometimes return releases out of order if they were uploaded too close to eachother, for whatever reason.
2025-05-27 13:00:32 +01:00
OpenSauce04
95eb701e12 update_checker.cpp: Add alpha and beta substrings to prerelease detection logic 2025-05-27 11:39:00 +01:00
OpenSauce04
2aa29a62cc android: Enable android:windowOptOutEdgeToEdgeEnforcement for Android 15+ 2025-05-26 21:28:09 +01:00
RedBlackAka
f20e5be513 Update MoltenVK to 1.2.9 2025-05-26 19:33:27 +01:00
PabloMK7
5e2161d90b
video_core: Refactor state tracking (#1059) 2025-05-26 14:37:03 +02:00
PabloMK7
bf587885cb
Allow SPIR-V when using Shadow2D texture mapping (#1057) 2025-05-24 00:05:16 +02:00
PabloMK7
ee63706887
Fix incorrect geo shader config start index parameter bit length (#1056) 2025-05-22 18:20:13 +02:00
PabloMK7
e83b81ec98
Add setting to toggle RPC server (disabled by default) (#1047) 2025-05-18 17:27:37 +02:00
OpenSauce04
8b939a9dab video_core: Fixed incorrect Vulkan mode when speed unthrottled w/ vsync
When the frame limit was set to 0 (unthrottled), the Vulkan present mode would be unintentionally set to FIFO, which caps out at the monitor's refresh rate
2025-05-16 17:48:30 +01:00
OpenSauce04
d878bfec3b file_util.cpp: Removed unnecessary definitions 2025-05-16 17:11:21 +01:00
OpenSauce04
33889fec1d file_util.cpp: Use _stat64 instead of stat where appropriate
Avoids compile failures introduced in an MSYS2 update
2025-05-16 17:11:21 +01:00
OpenSauce04
51dc3c6858 citra_room: Added removed option warning for --enable-citra-mods/-e 2025-05-15 16:19:20 +01:00
OpenSauce04
4ea9d76093 Updated license headers 2025-05-15 16:19:20 +01:00
Kleidis
d94654ea10 network: Removed enable_citra_mods room option
Moderator status is now determined
solely by the user's moderator flag
2025-05-15 16:19:20 +01:00
Kleidis
650cf43cd8 qt: Update moderation UI visibility in chat room 2025-05-15 16:19:20 +01:00
Kleidis
54732c560c network: Grant private room hosts moderator privileges 2025-05-15 16:19:20 +01:00
Briar
580d46e3d6
android: Add aspect ratio setting for single screen layout mode (#913)
* android: Add aspect ratio setting for single screen layout mode

Co-Authored-By: Morph <39850852+Morph1984@users.noreply.github.com>

* Partial rewrite to address issues and improve readability

---------

Co-authored-by: Morph <39850852+Morph1984@users.noreply.github.com>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-05-12 19:58:01 +01:00
OpenSauce04
c204ebf021 android: Fixed Turbo toast appearing in situations other than toggling Turbo
+ minor code cleanup
2025-05-09 20:34:55 +01:00
Kex
ea8e403f83 Make argument of --username required 2025-05-09 11:52:17 +01:00
OpenSauce04
73760e087a android: Added periods to the ends of performance overlay setting descriptions 2025-05-08 21:44:07 +01:00
OpenSauce04
3ce9f277d9 android: Corrected some nitpicky formatting inconsistencies in drawable xmls 2025-05-08 19:29:20 +01:00
OpenSauce
378d830a93
android: Improve performance stats overlay settings and functionality (#808)
* android: Improve performance stats overlay settings and functionality

* Add battery temp functions

* Readd frametime

* Corrected `perf_overlay_position` being placed in the wrong `default_ini.h` file

* Fixed the word "overlay" being repeatedly misspelled in function names

* `updateshowStatsOverlay` --> `updateShowStatsOverlay`

* Increased frequency of performance overlay updates

Changed from every 3 seconds to every 1 second

* Adjusted overlay margins to avoid text being lost behind rounded corner cutouts

* Fix performance overlay updates being stacked when changing orientation

* Changed out host RAM usage statistic for available host RAM

* Removed seemingly unused code

* "FT" --> "Frametime" in overlay

* Use non-breaking spaces to control how the overlay text breaks

Also used a vertical box drawing character instead of a pipe for the divider because it looks slightly nicer

* Renamed/adjusted remnants of the "Show System Memory Usage" setting

* Replaced Performance Stats Overlay icon with a stock clip art image from Android Studio

* Made performance overlay setting value names and strings less generic

* Rebranded Performance Stats Overlay as simply "Performance Overlay"

* Rewrote performance overlay settings description

* Improved naming consistency

* Rebranded "Show Overlay" toggle to "Show Controller Overlay"

This is to avoid confusion with the new performance overlay

* nitpick: Fixed order of imports in EmulationFragment.kt

* More string name consistency improvements

* Fixed compile failure due to a binding name not being updated

* Changed Performance Overlay setting headers

* EmulationFragment.kt: Formatting corrections

* Removed seemingly misplaced call to `updateShowPerformanceOverlay`

* `OVERLAY_POSITION` --> `PERFORMANCE_OVERLAY_POSITION`

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
Co-authored-by: Zephyron <zephyron@citron-emu.orgq>
2025-05-08 19:26:30 +01:00
OpenSauce
16980b0ffc
readme: Updated minimum MacOS version to reflect a recent change 2025-05-07 23:29:02 +01:00
OpenSauce
5297c95e52
readme: Added a missing divider between Windows and MacOS install instructions 2025-05-07 23:27:34 +01:00
OpenSauce
519f67a20a
Updated readme 2025-05-07 23:26:26 +01:00
OpenSauce04
74f3a2457e Updated translations via Transifex 2025-05-07 21:21:49 +01:00
PabloMK7
18447ac6ac Fix savestates with the OpenGL renderer 2025-05-07 21:05:34 +01:00
Briar
e946e38cd5 renderer_vulkan: Actually initialize background colors on start 2025-05-07 14:41:19 +01:00
OpenSauce04
beacac3f11 Corrected some minor visual inconsistencies compared to other tabs 2025-05-06 22:18:17 +01:00
SeppNel
89974af0ef Add scroll to layout page (graphics) 2025-05-06 22:18:17 +01:00
OpenSauce04
32d5036fc6 gitignore: Added .markdown-preview.html
Produced by the `markdown-preview-mode` Emacs package
2025-05-06 19:33:46 +01:00
OpenSauce04
86b3c732e5 tools: Added additional note about translation inconsistencies in certain dev environments 2025-05-06 19:31:18 +01:00
OpenSauce04
2ce26ee9ea Updated translations via Transifex 2025-05-06 19:23:34 +01:00
OpenSauce04
8559aba978 ci: Fixed EXTRA_CMAKE_FLAGS variable being set up incorrectly with tagged builds 2025-04-30 18:21:07 +01:00
OpenSauce04
ff72997511 Re-updated language translations via Transifex 2025-04-30 17:22:23 +01:00
OpenSauce04
9a7364ee92 Updated language translations via Transifex 2025-04-30 15:10:40 +01:00
OpenSauce04
4e35a0b575 Updated compatibility list 2025-04-30 15:06:07 +01:00
OpenSauce04
6ddb63582f macos: Use non-native Qt menubar as a temp workaround for #933 2025-04-30 15:05:14 +01:00
OpenSauce04
31908f732f qt: Added new update checker logic which correctly handles prereleases 2025-04-30 13:39:57 +01:00
Jan200101
4a3d4de9c2 Remove PrefersNonDefaultGPU from desktop file 2025-04-30 13:22:06 +01:00
OpenSauce04
b1850e6f51 tools: Added pre-release checklist to tools readme 2025-04-30 10:44:51 +01:00
OpenSauce04
1d562cdd11 tools: Added new tool scripts for updating translations and the compatibility list
Also added a brief readme file to the directory
2025-04-30 10:39:41 +01:00
Malachi
4a34bcfcbf Fix to_char error on MacOS 2025-04-26 23:03:53 +01:00
OpenSauce04
5fc48cfc8d ci: Remove libs directory for MacOS during macos-universal.sh so that it doesn't get distributed
This directory isn't at all necessary, and just wastes space in the distributed archive
2025-04-26 22:35:38 +01:00
OpenSauce04
3947f896e5 ci: Don't compile standalone room for MacOS
Closes #672
2025-04-26 22:35:10 +01:00
OpenSauce04
1e8cc02d56 cmake: Reduce files copied to bundle directory by create_base_bundle_target 2025-04-26 22:19:16 +01:00
OpenSauce04
b4699d9d47 ci: For source artifacts from tagged builds, use the name of the tag in the filename
Closes #779
2025-04-26 16:49:38 +01:00
OpenSauce04
a53ce91852 Fix CMake CMP0175 warnings
Closes #981
2025-04-26 16:42:37 +01:00
PabloMK7
01d7ff7a08
Fix installing DLCs with encrypted flags for missing contents (#979) 2025-04-24 21:53:09 +02:00
PabloMK7
493f59cef5
Fix off-by-one error in savestates that could cause crashes (#977) 2025-04-24 19:04:59 +02:00
PabloMK7
939a6e0f7a
Allow shader cache to switch to different titles at runtime (#978) 2025-04-24 19:03:48 +02:00
OpenSauce04
975ad17442 Internally rename turbo_speed and similar to turbo_limit
This aligns with the pre-existing `frame_limit` value
2025-04-23 16:59:28 +01:00
OpenSauce04
dcbf79df14 Overhaul turbo speed implementation to use temporary_frame_limit 2025-04-23 16:59:28 +01:00
PabloMK7
eba3c2c08f
Fix cheats and rpc server affecting the wrong processes (#956) 2025-04-23 17:09:37 +02:00
PabloMK7
391f91f735
Support downloading owned DLCs (#950) 2025-04-23 17:08:48 +02:00
OpenSauce04
bac344d059 ci: For Linux, only move AppImage to artifacts directory
Closes #795
2025-04-21 13:47:30 +01:00
OpenSauce04
0051182338 ci: Windows installer is now deployed directly to artifacts directory 2025-04-21 13:33:19 +01:00
SeppNel
527610d599
citra_qt: Fix duplicated hotkey entry and order. (#945)
* Fix order

* Match hotkey name with citra_qt.cpp
2025-04-20 12:19:20 +01:00
Midou36O
1ff5042685
Fallback to silence when no samples have been captured. (#955) 2025-04-19 15:21:13 +02:00
Daniel Nylander
b1480396fa Add Swedish to citra.nsi 2025-04-19 08:49:33 +01:00
lannoene
de1b520498
Fix File::Write() not conforming to debug header validation (#952) 2025-04-18 12:51:15 +02:00
OpenSauce04
a6782e8a13 android: Restructure strings.xml 2025-04-16 22:30:28 +01:00
OpenSauce04
fd3e4068b6 android: Renamed quicksave_saving and quickload_loading strings to simply saving and loading
This better reflects their use, as they are no longer used exclusively for quicksaves.
2025-04-16 22:08:05 +01:00
kleidis
8fbfb94bec android: Add toast popup while saving states through UI 2025-04-16 22:08:05 +01:00
kleidis
d43597b520 android: Allow saving to quickslot from UI
Idk why this was done by the original author as there are no conflicts
2025-04-16 22:08:05 +01:00
David Griswold
967263fc80
Implement screen gap setting (#622)
* implement screen gap

* type conversion fix for windows

* int setting
2025-04-16 19:05:46 +01:00
OpenSauce04
fdbc74b506 Updated help strings to reflect new room implementation 2025-04-16 16:39:09 +01:00
OpenSauce04
544c6b4bbc ci: Disable standalone room executable for AppImage 2025-04-16 16:39:09 +01:00
OpenSauce04
b8267c0a39 cmake: Added citra_room_standalone target and ENABLE_ROOM_STANDALONE option 2025-04-16 16:39:09 +01:00
OpenSauce04
2670b517e8 citra_room: Merge functionality into citra_meta 2025-04-16 16:39:09 +01:00
HurricanePootis
e90930b0b9 Qt: set desktop filename and organization domain #934 2025-04-15 20:18:03 +01:00
PabloMK7
72eb16f933
Fix ncch loader building wrong update title ID (#930) 2025-04-15 16:27:32 +02:00
OpenSauce
81ce14e0db
readme: We don't support 32 bit Android, reflect that in requirements 2025-04-15 13:00:08 +01:00
OpenSauce04
d974a0062f android: Disable title bar on Android 9 2025-04-15 12:41:32 +01:00
OpenSauce04
7c9ea780b3 Updated language translations via Transifex 2025-04-14 22:36:30 +01:00
OpenSauce04
4bc1655b05 dist: Updated compatibility list 2025-04-14 22:19:44 +01:00
OpenSauce04
ebb462f1d5 strings.xml: Uninstall Game --> Uninstall Application 2025-04-14 20:02:45 +01:00
OpenSauce04
3a0878f40f configure_hotkeys.cpp: per-game --> per-application 2025-04-14 19:58:23 +01:00
David Griswold
240e968d73
android: Lime3DS to Azahar migration adjustments (#917)
* change dialog without write permissions

* Added update path from lime to azahar on android

* Shows the correct dialog info now

* remove unnecessary comments

* Adjusted `select_which_user_directory_to_use` string to be more readable

* improve the dialog box itself

* tougher fix than originally expected but all works as planned

* remove unnecessary code

* Updated license headers

* MainActivity.kt: Removed stray newline

* PermissionsHandler.kt: Move repeated "LIME3DS_DIRECTORY" string to `LIME3DS_DIRECTORY` constant

* Nitpicky comment adjustments

* Reverted superficial changes to HomeViewModel.kt

* PermissionsHandler.kt: `updateDirectory` --> `attemptAutomaticUpdateDirectory`

+ nitpicky formatting adjustment

* Moved PR additions to PermissionsHandler.kt to new file CitraDirectoryUtils.kt

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-04-14 19:08:11 +01:00
PabloMK7
52ccaabca8 Fix system files setup for region changed consoles 2025-04-13 19:16:12 +01:00
PabloMK7
8acc5e22a0
Use common open source font on CHN/KOR/TWN (#920) 2025-04-13 17:38:04 +02:00
Briar
8dcef46a11
android: Add turbo speed hotkey touch controls overlay button (#911)
* android: Add turbo speed hotkey touch controls overlay

* Adjusted default controller overlay location of turbo button

* Changed "Turbo Speed" overlay button to simply be referred to as "Turbo"

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-04-12 22:59:53 +01:00
Briar
14413d896f Fix FRAME_LIMIT int not being reset on exit if the turbo hotkey is spammed 2025-04-12 20:32:22 +01:00
Briar
bf4eef8e58 android: Clean up the turbo speed hotkey implementation 2025-04-12 20:32:22 +01:00
OpenSauce04
2a7a5078fc ci: Various build workflow adjustments
- Updated to NSIS 3.11
- Updated NSIS installer download URL location
- Use wget to download NSIS setup executable
- Made `Install NSIS` step more verbose via ptime
- Only run `Install NSIS` step for tagged builds
- Reorganized build.yml to have all `if`s at start of scope
2025-04-12 13:57:09 +01:00
OpenSauce04
76a23e4c72 Revert "Removed seemingly redundant isEnabled overrides"
This reverts commit cd58ce998a.
2025-04-11 23:35:25 +01:00
David Griswold
8e477e35bb
android: Original Portrait Layout (#625)
* Original Portrait Layout

Original Portrait Layout

* type conversion fix for win

* Updated license headers

* Applied clang-format

* android: Reordered Portrait Screen Layout menu

Custom Layout is now at the bottom of the list

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-04-11 18:23:07 +01:00
OpenSauce
89d2e67459
qt: Add quicksave / quickload hotkeys (#811)
* qt: Add quicksave / quickload hotkeys

* Adjusted strings

* main.ui: Remove stray newline

---------

Co-authored-by: David Griswold <novachild@gmail.com>
2025-04-11 17:21:49 +01:00
OpenSauce
32e378a29b
framebuffer: Add hybrid layout mode to FrameLayoutFromResolutionScale (#807)
* framebuffer: Add hybrid layout mode to FrameLayoutFromResolutionScale

* framebuffer_layout.cpp: Moved seemingly misplaced `default` case to proper location

* framebuffer_layout.cpp: Fixed incorrect calculation of framebuffer dimensions for hybrid layout

* framebuffer_layout.cpp: Made implicit cast from float to int explicit

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-04-11 16:16:19 +01:00
OpenSauce
12bc825b8a
android: Add isEnabled setting item conditional check (#814)
* android: Add `isEnabled` setting item conditional check

Co-authored-by: Charles Lombardo <clombardo169@gmail.com>
(Thanks to him for the idea of using DiffUtil)

Now it is possible to have a conditional check for each setting type which once met will disable itself and re-enable once the condition is unmet again in real-time

* Refactor setting checks to deduplicate repeated `isEditable && isEnabled` conditionals

This is done by adding a new value, `setting.isActive` which is equivalent to `setting.isEditable && setting.isEnabled`

* Removed seemingly redundant `isEnabled` overrides

* Updated license headers

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-04-11 14:35:21 +01:00
OpenSauce04
c69b642f54 Corrected minor license header inconsistency 2025-04-11 12:31:16 +01:00
OpenSauce
0e0137a9ff
Add turbo speed hotkey (#605)
* Add the turbo slider

* [WIP] Add fast forward toggle hotkey

* Make Increase/Decrease speed hotkeys change turbo key instead of `frame_limit`

* Allow non-runtime editable settings on `general` settings tab`

* `frame_limit` is now  non-runtime-editable

* Disable `toggle per game speed limit` if turbo mode is set

* Reset `frame_limit` back to initial value once the emulator closes

* Improve `AdjustSpeedLimit`

- Set frameskip value directly
- Bypass if turbo mode isn't active

* Some code cleanup

* Move `turbo_speed_slider` from UISettings to CommonSettings

Also rename to just `turbo_speed`

* android: Add turbo mode hotkey

* Fixed build failure + Applied clang-format

* configure_general.ui Make padding on right side of sliders consistent

Not sure why there's a difference here, so I just threw in a spacer

* android: Corrected build failures caused by bad merge

* Updated `turbo_speed_description` to be a little more descriptive

* android: Corrected turbo crash caused by bad JNI function names

* Updated license headers

* HotkeyFunctions.kt: Fixed minor fomatting irregularities

* Applied clang-format

---------

Co-authored-by: kleidis <167202775+kleidis@users.noreply.github.com>
2025-04-11 12:29:07 +01:00
Gamer64
4ea8c6fda5 android: Implement support for automatic resolution scale
Available on PC, but was missing on Android.
2025-04-10 21:39:49 +01:00
Kleidis
eb310a4a60 android: Update emulation_pause icon on resume
If the fragment got paused while emualtionState also was paused manually
via the menu bar, once the fragment got unpasued , the binding would
still remain paused
2025-04-10 21:34:51 +01:00
OpenSauce04
903a9ad81f Updated license headers 2025-04-08 20:15:38 +01:00
OpenSauce04
e341dcf238 qt: For Qt 6.9.0 and above, use QImage::flipped over QImage::mirrored
The latter has been deprecated, and is causing build failures where deprecations warnings are treated as errors.
2025-04-08 20:15:38 +01:00
JP Brunache
8d769ed9cb Fix typo in README.md 2025-04-08 11:29:50 +01:00
OpenSauce04
71c9c0c924 qt: Explicitly set window icon 2025-04-07 21:35:16 +01:00
OpenSauce04
6865b4c8a7 qt: Various updates to the settings menu to improve consistency
- All buttons which open a modal interface now disable themselves until their interface is closed
- Renamed button_linked_console to button_unlink_console to better reflect what it actually does
- Changed the warning icon of the Regenerate Console ID button to be the same as the Regenerate MAC Address button
2025-04-07 21:28:35 +01:00
OpenSauce04
c0bb7abdbc qt: Corrected stray mention of Lime3DS in FFmpeg error message 2025-04-07 15:30:48 +01:00
OpenSauce04
aa58dd5f68 Suppress MSVC warnings C4711 and C5045 during compilation 2025-04-07 14:36:00 +01:00
OpenSauce04
9500859043 externals: Upgraded SDL2 to version 2.32.4
Closes #391
2025-04-03 11:30:27 +01:00
OpenSauce
93eeb501c0
android: Add uninstall game/updates/dlc and open folder entries to about game dialog (#823)
* android: Add uninstall game and open folder options

* Updated license header

* getGameDirectories: Cleanup

* Fixed "Open Updates Folder" button not working correctly

* Made "Open Extra Folder" behaviour consistent with other options when Extra folder not found

* strings.xml: Corrected double newline

* android: Adjusted about game dialog layout

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-04-02 18:57:04 +01:00
OpenSauce04
f298d7551c externals: Fixed CMake 4.0 compilation error when Discord RPC is enabled 2025-04-01 20:18:55 +01:00
OpenSauce
5ade69f5f4
android: Add game thumbnail to EmulationFragment nav drawer (#809)
* android: Add game thumbnail to `EmulationFragment` nav drawer

* Updated license header

* EmulationFragment.kt: Corrected formatting

* header_in_game.xml: Changed placeholder text

---------

Co-authored-by: Kleidis <167202775+kleidis@users.noreply.github.com>
2025-03-30 15:19:55 +01:00
OpenSauce
e6199780a5
android: Implement Expand to Display Cutout option (#597)
* android: Implement Expand to Display Cutout option

* Removed irrelevant comment

* Moved Expand to Cutout Area setting to Layout section

---------

Co-authored-by: Gamer64 <76565986+Gamer64ytb@users.noreply.github.com>
2025-03-30 14:59:46 +01:00
OpenSauce04
2c1fc0199b externals: Use azahar-emu org mirror of soundtouch 2025-03-30 10:51:37 +01:00
OpenSauce04
dec12bd54a Updated language translations via Transifex 2025-03-30 10:51:37 +01:00
PabloMK7
5c7622100b
Check that the country setting is valid for selected region (#847)
* Check that the country setting is valid for selected region

* `SystemSaveGame.checkCountryCompatibility` -> `SystemSaveGame.getCountryCompatibility`

* SettingsFragmentPresenter.kt: Moved `checkCountryCompatibility` definition out of `addSystemSettings`

* SettingsFragmentPresenter.kt: Renamed `compat` value to `compatFlags` for better readability

* configure_system.ui: Corrected indentation

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-03-29 21:50:00 +00:00
PabloMK7
eda2d6f9fa
Mark console as "linked" when using the azahar artic setup tool (#833)
* Mark console as "linked" when using the azahar artic setup tool

* Updated strings related to console linking

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2025-03-28 11:10:59 +00:00
OpenSauce04
dee576bfeb Use reverse TLD filenames for installed Linux files where appropriate 2025-03-28 00:02:10 +00:00
toksn
7dda835679 Use correct "input_type" key for AUDIO_INPUT_TYPE 2025-03-27 18:01:45 +00:00
Reg Tiangha
1de19fcbc2 android: Add Texture Sampling settings UI 2025-03-27 09:18:27 +00:00
PabloMK7
d8077fdea6 Fix fatal error caused by auto-detect region 2025-03-26 22:06:36 +00:00
OpenSauce
9203b23868
Stereoscopic 3D Enhancements (#602)
* Stereoscopic 3D Enhancements

- Increase maximum 3D depth to 255%
- Fix touch screen to only render 2D when separate window layout is used
- Cleanup some 3D option localizations

* qt: Added warning label below depth slider for values over 100%

* Fixed broken rendering for Interlaced, Reverse Interlaced and Anaglyph options when using 3D with seperate windows

* android: Added warning label below depth slider for values over 100%

* Fixed a bracket and break statement being incorrectly positioned

---------

Co-authored-by: oneup03 <oneup03@gmail.com>
2025-03-24 21:26:14 +00:00
OpenSauce04
61feb3aee2 qt: Break online LLE module checkbox text over two lines 2025-03-24 19:00:31 +00:00
OpenSauce
bd1f5b978b Bump CMake MacOS compilation target to MacOS 13
The minimum required MacOS version was already MacOS 13 due to dependencies, but until now the compilation target was set to MacOS 11
2025-03-24 17:56:24 +00:00
OpenSauce
d406c5d81e
Updated readme 2025-03-24 14:33:15 +00:00
OpenSauce04
cfa59dc0bb ci: Re-added stale workflow 2025-03-23 17:51:16 +00:00
SeppNel
8cdafaa828
Fix file read memory leak (#750)
* Fix file read memory leak

* Also fix synchronous path

* Use make_unique_for_overwrite

* License
2025-03-23 12:56:18 +00:00
OpenSauce04
f083a6e5d3 Updated French translation via Transifex 2025-03-22 21:49:19 +00:00
OpenSauce04
75918be261 Updated language translations via Transifex 2025-03-22 21:49:18 +00:00
OpenSauce04
2b0d412070 cmake: Fixed compilation failure if CMAKE_CXX_FLAGS is an empty string 2025-03-22 21:44:46 +00:00
OpenSauce04
5b910d6f0e cmake: Correctly handle _FORTIFY_SOURCE being pre-defined in CXXFLAGS 2025-03-22 21:10:56 +00:00
PabloMK7
4d04f633fa android: Show unsupported encrypted app message instead of invalid region 2025-03-22 16:55:38 +00:00
OpenSauce04
c7fe6333b5 dist: Increased resolution of azahar.png from 430x to 512x 2025-03-22 16:44:52 +00:00
OpenSauce04
16dac366cf Install 512x512 icon to CMAKE_INSTALL_PREFIX 2025-03-22 16:22:51 +00:00
OpenSauce04
860aace2f5 android: Updated notification icon to reflect Azahar's logo 2025-03-22 16:20:28 +00:00
PabloMK7
844b166fbf Fix install CIA format string 2025-03-22 15:37:06 +00:00
PabloMK7
7e9b5743fb Fix temporary frame limit 2025-03-22 00:23:40 +01:00
OpenSauce04
7920188417 Applied clang-format 2025-03-21 22:42:09 +00:00
OpenSauce04
3718bab5cb qt: Corrected broken link in .3ds/encryption warning message 2025-03-21 22:37:53 +00:00
OpenSauce04
ec9c3dd276 Updated languages via Transifex 2025-03-21 20:25:48 +00:00
OpenSauce04
ffb8bf15b2 Fixed encrypted+.3ds warning string being poorly formatted 2025-03-21 20:14:28 +00:00
OpenSauce04
22934aa46e Updated languages via Transifex 2025-03-21 19:33:35 +00:00
PabloMK7
17a6bfb7dd
qt: Change update URL to the website (#757) 2025-03-21 20:19:45 +01:00
OpenSauce04
edb01754ea strings.xml: Fixed minor formatting issue 2025-03-21 19:08:31 +00:00
PabloMK7
1259401889 Show warning that 3ds files are no longer supported 2025-03-21 18:39:57 +00:00
PabloMK7
84e2f31415 Make AM:GetPersonalizedTicketInfoList only return personal tickets 2025-03-21 19:22:59 +01:00
OpenSauce04
70be7d987e video_core: Fixed emulation window artefacts on OpenGL + Wayland 2025-03-21 17:04:27 +00:00
RedBlackAka
9763488577
installer: Clean up Windows Start Menu entry
* Clean up Windows Start Menu entry

* Clean up old Start Menu shortcuts when upgrading
2025-03-21 17:00:00 +00:00
OpenSauce04
b225e856df Updated all files under dist to refer to Azahar instead of Citra
This resolves some icon theming issues on Linux

Co-authored-by: HurricanePootis <53066639+HurricanePootis@users.noreply.github.com>
2025-03-21 16:37:10 +00:00
OpenSauce04
2f28911395 cmake: Allow ENABLE_OPENGL option to be overridden on Linux aarch64
This option was originally disabled due to some devices not supporting OpenGL, however it was implemented by hardcoding the option to be set to OFF via CMAKE_DEPENDENT_OPTION. This change now allows the user to manually set ENABLE_OPENGL to ON in the CMake options, which was previously not possible.
2025-03-20 17:29:29 +00:00
Taylor Rodríguez
d82be7ac7c
Fix bug where log file was not generated on first run (#729)
* Fix bug where log file was not generated on first run

This fix resolves issue #727.

On first start, Log::Initialize attempts to create the `azahar-emu/log/`
directory. However, it fails because `azahar-emu/` does not exist. Using
FileUtil::CreateFullPath instead will create both `azahar-emu` and
`log/`.

* Update license header
2025-03-20 11:49:51 +00:00
PabloMK7
3783ac9f49 Fix incorrect syntax in construction of ARM_DynCom 2025-03-19 15:22:10 +01:00
PabloMK7
5acb2eee91
Fix language related issues (#735) 2025-03-19 15:21:22 +01:00
OpenSauce04
19551b1eb6 ci: Enabled update checker for tagged Windows and MacOS builds
This was supposed to be enabled for all platforms, but was erroneously only enabled for Linux
2025-03-19 10:09:43 +00:00
OpenSauce04
d59ea25cbe installer: Replaced reference to "Dolphin.exe" left over from the Dolphin installer we're based on 2025-03-18 12:53:10 +00:00
OpenSauce04
dcd6fe8258 qt: Updated translations via Transifex 2025-03-18 12:06:29 +00:00
PabloMK7
fac8ae2682 Fix VS uniform fields type declaration 2025-03-17 22:59:47 +00:00
PabloMK7
9d03856026 Fix uninitialized movable check on artic setup 2025-03-17 20:07:29 +00:00
PabloMK7
0eb4a71720
Implement framebuffer vertical flip flag (#699)
* Implement framebuffer vertical flip flag

* Make VerticalMirror const
2025-03-17 19:39:55 +00:00
PabloMK7
007b809ad7 Add support for uninitialized movable 2025-03-17 19:37:16 +00:00
PabloMK7
66d8e58dcd Fix artic traffic label being white on light theme 2025-03-17 19:34:45 +00:00
Mae Dartmann
8cb3dde72f externals: Bump SDL2 to fix build with newer pipewire versions 2025-03-17 17:33:07 +00:00
OpenSauce04
eb3aa52391 externals: Updated externals to avoid CMake 4.0 deprecation errors 2025-03-17 17:13:05 +00:00
PabloMK7
c13d2d7208 Fix incorrect crypto file handling if exefs override fails 2025-03-17 16:19:56 +00:00
PabloMK7
dac463d74a Fix system files setup on macos 2025-03-17 16:19:56 +00:00
PabloMK7
d72948ca22 am: Fix force new 3ds deviceID 2025-03-15 23:24:04 +00:00
OpenSauce04
ccb26303ad qt: Added Report Compatibility button which redirects to Azahar compatibility list 2025-03-15 22:21:59 +00:00
288 changed files with 22677 additions and 17409 deletions

View file

@ -2,7 +2,10 @@
if [ "$TARGET" = "appimage" ]; then
# Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_LINKER=/etc/bin/ld.lld)
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang
-DCMAKE_LINKER=/etc/bin/ld.lld
-DENABLE_ROOM_STANDALONE=OFF)
# Bundle required QT wayland libraries
export EXTRA_QT_PLUGINS="waylandcompositor"
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
@ -12,7 +15,7 @@ else
fi
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=($EXTRA_CMAKE_FLAGS -DENABLE_QT_UPDATE_CHECKER=ON)
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DENABLE_QT_UPDATE_CHECKER=ON)
fi
mkdir build && cd build
@ -21,8 +24,9 @@ cmake .. -G Ninja \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
strip -s bin/Release/*

View file

@ -11,7 +11,7 @@ BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
mv $BASE_ARTIFACT $BUNDLE_DIR
# Executable binary paths that need to be combined.
BIN_PATHS=(azahar-room Azahar.app/Contents/MacOS/azahar)
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
# Dylib paths that need to be combined.
IFS=$'\n'
@ -36,8 +36,11 @@ for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
done
done
# Remove leftover libs so that they aren't distributed
rm -rf "${BUNDLE_DIR}/libs"
# Re-sign executables and bundles after combining.
APP_PATHS=(azahar-room Azahar.app)
APP_PATHS=(Azahar.app)
for APP_PATH in "${APP_PATHS[@]}"; do
codesign --deep -fs - $BUNDLE_DIR/$APP_PATH
done

View file

@ -11,6 +11,7 @@ cmake .. -GNinja \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja

View file

@ -14,7 +14,7 @@ else
fi
# Archive and upload the artifacts.
mkdir artifacts
mkdir -p artifacts
function pack_artifacts() {
ARTIFACTS_PATH="$1"
@ -50,11 +50,6 @@ function pack_artifacts() {
rm -rf "$REV_NAME"
}
if [ "$OS" = "windows" ] && [ "$GITHUB_REF_TYPE" = "tag" ]; then
# Move the installer to the artifacts directory
mv src/installer/bin/*.exe artifacts/
fi
if [ -n "$UNPACKED" ]; then
# Copy the artifacts to be uploaded unpacked.
for ARTIFACT in build/bundle/*; do

View file

@ -4,6 +4,10 @@ GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-unified-source-${GITDATE}-${GITREV}"
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
REV_NAME="azahar-unified-source-$GITHUB_REF_NAME"
fi
COMPAT_LIST='dist/compatibility_list/compatibility_list.json'
mkdir artifacts

View file

@ -49,9 +49,11 @@ jobs:
${{ runner.os }}-${{ matrix.target }}-
- name: Build
run: ./.ci/linux.sh
- name: Pack
- name: Move AppImage to artifacts directory
if: ${{ matrix.target == 'appimage' }}
run: ./.ci/pack.sh
run: |
mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/
- name: Upload
if: ${{ matrix.target == 'appimage' }}
uses: actions/upload-artifact@v4
@ -156,7 +158,7 @@ jobs:
- name: Install vulkan-sdk (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: |
wget https://sdk.lunarg.com/sdk/download/1.3.296.0/windows/VulkanSDK-1.3.296.0-Installer.exe -O D:/a/_temp/vulkan.exe
wget https://sdk.lunarg.com/sdk/download/1.4.304.1/windows/VulkanSDK-1.4.304.1-Installer.exe -O D:/a/_temp/vulkan.exe
D:/a/_temp/vulkan.exe --accept-licenses --default-answer --confirm-command install
- name: Set up MSYS2
if: ${{ matrix.target == 'msys2' }}
@ -188,8 +190,8 @@ jobs:
run: |
cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir bin
move /y *.exe bin\
mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\
shell: cmd
- name: Pack
run: ./.ci/pack.sh
@ -230,6 +232,9 @@ jobs:
run: |
sudo apt-get update -y
sudo apt-get install ccache apksigner -y
- name: Update Android SDK CMake version
run: |
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
- name: Build
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
env:

23
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: azahar-stale
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale-issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 90
days-before-issue-close: 10
stale-issue-message: "This issue has been marked as stale. If there is no activity within the next 10 days, this issue will be closed."
close-issue-message: "This issue has been closed as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
remove-issue-stale-when-updated: true
exempt-issue-labels: "priority - low,priority - medium,priority - high,priority - urgent,documentation,enhancement,miscellaneous,task,refactor"

1
.gitignore vendored
View file

@ -13,6 +13,7 @@ src/common/scm_rev.cpp
# Project/editor files
*.swp
*.kdev4
.markdown-preview.html
.idea/
.vs/
.vscode/

View file

@ -1,5 +1,6 @@
# CMake 3.12 required for 20 to be a valid value for CXX_STANDARD
cmake_minimum_required(VERSION 3.15)
# CMake >=3.12 required for 20 to be a valid value for CXX_STANDARD,
# and >=3.25 required to make LTO work on Android.
cmake_minimum_required(VERSION 3.25)
# Don't override the warning flags in MSVC:
cmake_policy(SET CMP0092 NEW)
@ -41,8 +42,8 @@ if (APPLE)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
else()
# Minimum macOS 11
set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0")
# Minimum macOS 13
set(CMAKE_OSX_DEPLOYMENT_TARGET "13.4")
endif()
endif()
@ -86,7 +87,8 @@ option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_ROOM "Enable generating dedicated room executable" ON "NOT ANDROID AND NOT IOS" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_ROOM "Enable dedicated room functionality" ON "NOT ANDROID AND NOT IOS" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone dedicated room executable" ON "ENABLE_ROOM" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
@ -104,6 +106,8 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF)
option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
# Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -118,10 +122,22 @@ endif()
if (ENABLE_QT_TRANSLATION)
add_definitions(-DENABLE_QT_TRANSLATION)
endif()
if (ENABLE_ROOM)
add_definitions(-DENABLE_ROOM)
endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
message(STATUS "SSE4.2 enabled for x86_64")
if(MSVC)
SET(SSE42_COMPILE_OPTION /arch:SSE4.2)
else()
SET(SSE42_COMPILE_OPTION -msse4.1 -msse4.2)
endif()
endif()
include(CitraHandleSystemLibs)
if (CITRA_USE_PRECOMPILED_HEADERS)
@ -484,9 +500,8 @@ if (NOT ANDROID AND NOT IOS)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(citra_meta)
endif()
if (ENABLE_ROOM)
bundle_target(citra_room)
if (ENABLE_ROOM_STANDALONE)
bundle_target(citra_room_standalone)
endif()
endif()

View file

@ -279,22 +279,12 @@ else()
add_custom_target(bundle)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/"
POST_BUILD)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/azahar.png" "${CMAKE_BINARY_DIR}/bundle/dist/azahar.png")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting"
POST_BUILD)
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
@ -305,7 +295,8 @@ else()
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
POST_BUILD)
endif()
endfunction()

View file

@ -171,16 +171,16 @@ endfunction()
function(download_moltenvk)
if (IOS)
set(MOLTENVK_PLATFORM "iOS")
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
else()
set(MOLTENVK_PLATFORM "macOS")
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
endif()
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.7-rc2/MoltenVK-all.tar
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar
${MOLTENVK_TAR} SHOW_PROGRESS)
endif()
@ -189,7 +189,7 @@ function(download_moltenvk)
endif()
# Add the MoltenVK library path to the prefix so find_library can locate it.
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${MOLTENVK_PLATFORM}")
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()

View file

@ -10,23 +10,27 @@ It was created from the merging of PabloMK7's Citra fork and the Lime3DS project
The goal of this project is to be the de-facto platform for future development.
> [!NOTE]
> Azahar has not fully released yet. For this reason, there are no compiled binaries available for download.
>
> It is recommended that only developers and early adopters should use the emulator until our first stable release.
>
> Here be dragons.
<!--
# Installation
### Windows & MacOS
### Windows
Download the latest release from [Releases](https://github.com/azahar-emu/azahar/releases).
If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2.
---
### MacOS
Download the latest release from [Releases](https://github.com/azahar-emu/azahar/releases).
The `macos-universal` download will work on both Intel and Apple Silicon Macs.
---
### Android
The recommended method of downloading Azahar on Android is via the [Google Play store](https://play.google.com/store/apps/details?id=io.github.lime3ds.android).
The recommended method of downloading Azahar on Android is via the Google Play store:
<a href='https://play.google.com/store/apps/details?id=io.github.lime3ds.android'><img width='180' alt='Get it on Google Play' src='https://raw.githubusercontent.com/pioug/google-play-badges/06ccd9252af1501613da2ca28eaffe31307a4e6d/svg/English.svg'/></a>
Alternatively, you can install the app using Obtainium:
1. Download and install Obtainium from [here](https://github.com/ImranR98/Obtainium/releases) (use the file named `app-release.apk`)
@ -42,12 +46,12 @@ Keep in mind that you will not recieve automatic updates when installing via the
---
### Linux
Azahar is available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
The recommended format for using Azahar on Linux is the Flatpak available on Flathub:
We are also on Flathub:
<a href='https://flathub.org/apps/org.azahar_emu.Azahar'><img width='180' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
<a href=https://flathub.org/apps/org.azahar-emu.azahar><img width='180' alt='Download on Flathub' src='https://dl.flathub.org/assets/badges/flathub-badge-en.png'/></a>
-->
# Build instructions
@ -83,14 +87,14 @@ Below are the minimum requirements to run Azahar:
### Desktop
```
Operating System: Windows 10 (64-bit), MacOS 13 (Ventura), or modern 64-bit Linux
CPU: x86-64 (64-bit) CPU. Single core performance higher than 1,800 on Passmark
Operating System: Windows 10 (64-bit), MacOS 13.4 (Ventura), or modern 64-bit Linux
CPU: x86-64/ARM64 CPU (Windows for ARM not supported). Single core performance higher than 1,800 on Passmark
GPU: OpenGL 4.3 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended
```
### Android
```
Operating System: Android 9.0+
Operating System: Android 9.0+ (64-bit)
CPU: Snapdragon 835 SoC or better
GPU: OpenGL ES 3.2 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended

1
dist/azahar.desktop vendored
View file

@ -12,4 +12,3 @@ Exec=azahar %f
Categories=Game;Emulator;
MimeType=application/x-ctr-3dsx;application/x-ctr-cci;application/x-ctr-cia;application/x-ctr-cxi;
Keywords=3DS;Nintendo;
PrefersNonDefaultGPU=true

@ -1 +1 @@
Subproject commit b153099f511759824941f5797ca69e2d39e57de3
Subproject commit 31c12299126baf892b965defc6ba7810b9c42ccf

File diff suppressed because it is too large Load diff

1117
dist/languages/da_DK.ts vendored

File diff suppressed because it is too large Load diff

1151
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

1048
dist/languages/el.ts vendored

File diff suppressed because it is too large Load diff

1085
dist/languages/es_ES.ts vendored

File diff suppressed because it is too large Load diff

995
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

1129
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load diff

1047
dist/languages/hu_HU.ts vendored

File diff suppressed because it is too large Load diff

1011
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

1450
dist/languages/it.ts vendored

File diff suppressed because it is too large Load diff

1051
dist/languages/ja_JP.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

995
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

1113
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load diff

1101
dist/languages/pl_PL.ts vendored

File diff suppressed because it is too large Load diff

1093
dist/languages/pt_BR.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1678
dist/languages/ru_RU.ts vendored

File diff suppressed because it is too large Load diff

1097
dist/languages/sv.ts vendored

File diff suppressed because it is too large Load diff

1643
dist/languages/tr_TR.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1089
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,21 @@
# Copyright Citra Emulator Project / Azahar Emulator Project
# Licensed under GPLv2 or any later version
# Refer to the license.txt file included.
import struct
import random
import enum
import socket
CURRENT_REQUEST_VERSION = 1
MAX_REQUEST_DATA_SIZE = 32
MAX_PACKET_SIZE = 48
MAX_REQUEST_DATA_SIZE = 1024
MAX_PACKET_SIZE = 1024 + 0x10
class RequestType(enum.IntEnum):
ReadMemory = 1,
WriteMemory = 2
WriteMemory = 2,
ProcessList = 3,
SetGetProcess = 4,
CITRA_PORT = 45987
@ -34,6 +40,55 @@ class Citra:
return raw_reply[4*4:]
return None
def process_list(self):
processes = {}
read_processes = 0
while True:
request_data = struct.pack("II", read_processes, 0x7FFFFFFF)
request, request_id = self._generate_header(RequestType.ProcessList, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
raw_reply = self.socket.recv(MAX_PACKET_SIZE)
reply_data = self._read_and_validate_header(raw_reply, request_id, RequestType.ProcessList)
if reply_data:
read_count = struct.unpack("I", reply_data[0:4])[0]
reply_data = reply_data[4:]
if read_count == 0:
break
read_processes += read_count
for i in range(read_count):
proc_data = reply_data[i * 0x14 : (i + 1) * 0x14]
proc_id, title_id, proc_name = struct.unpack("<IQ8s", proc_data)
proc_name = proc_name.rstrip(b"\x00").decode("ascii")
processes[proc_id] = (title_id, proc_name)
else:
break
return processes
def get_process(self):
request_data = struct.pack("II", 0, 0)
request, request_id = self._generate_header(RequestType.SetGetProcess, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
raw_reply = self.socket.recv(MAX_PACKET_SIZE)
reply_data = self._read_and_validate_header(raw_reply, request_id, RequestType.SetGetProcess)
if reply_data:
return struct.unpack("I", reply_data)[0]
else:
return None
def set_process(self, process_id):
request_data = struct.pack("II", 1, process_id)
request, request_id = self._generate_header(RequestType.SetGetProcess, len(request_data))
request += request_data
self.socket.sendto(request, (self.address, CITRA_PORT))
self.socket.recv(MAX_PACKET_SIZE)
def read_memory(self, read_address, read_size):
"""
>>> c.read_memory(0x100000, 4)

2
externals/libyuv vendored

@ -1 +1 @@
Subproject commit c060118bea3f28ceb837d3c85e479d3bb4c21726
Subproject commit 6f729fbe658a40dfd993fa8b22bd612bb17cde5c

View file

@ -136,6 +136,9 @@ else()
# which is a problem for older versions (e.g. GCC 11).
add_compile_options("-Wno-attributes")
add_compile_options("-Wno-interference-size")
# Suppress irrelevant ABI warnings generated by GCC
add_compile_options("-Wno-psabi")
add_link_options("-Wno-psabi")
endif()
if (MINGW)
@ -199,6 +202,10 @@ if (ENABLE_ROOM)
add_subdirectory(citra_room)
endif()
if (ENABLE_ROOM_STANDALONE)
add_subdirectory(citra_room_standalone)
endif()
if (ANDROID)
add_subdirectory(android/app/src/main/jni)
target_include_directories(citra-android PRIVATE android/app/src/main)

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -22,7 +22,7 @@ plugins {
val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toInt()
val abiFilter = listOf("arm64-v8a", "x86_64")
val downloadedJniLibsPath = "${buildDir}/downloadedJniLibs"
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
@Suppress("UnstableApiUsage")
android {
@ -51,6 +51,7 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
}
lint {
@ -124,7 +125,6 @@ android {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
signingConfig = signingConfigs.getByName("debug")
isMinifyEnabled = true
isShrinkResources = true
isDebuggable = true
isJniDebuggable = true
@ -150,7 +150,7 @@ android {
externalNativeBuild {
cmake {
version = "3.22.1"
version = "3.25.0+"
path = file("../../../CMakeLists.txt")
}
}
@ -186,8 +186,8 @@ dependencies {
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/sdk-1.3.261.1/android-binaries-sdk-1.3.261.1-android.zip")
dest(file("${buildDir}/tmp/Vulkan-ValidationLayers.zip"))
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.304.1/android-binaries-1.4.304.1.zip")
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
onlyIfModified(true)
}

View file

@ -186,6 +186,10 @@ object NativeLibrary {
external fun unlinkConsole()
external fun setTemporaryFrameLimit(speed: Double)
external fun disableTemporaryFrameLimit()
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()
@ -773,6 +777,7 @@ object NativeLibrary {
const val BUTTON_DEBUG = 781
const val BUTTON_GPIO14 = 782
const val BUTTON_SWAP = 800
const val BUTTON_TURBO = 801
}
/**

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -15,6 +15,7 @@ import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
@ -44,6 +45,7 @@ import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.viewmodel.EmulationViewModel
class EmulationActivity : AppCompatActivity() {
@ -51,7 +53,7 @@ class EmulationActivity : AppCompatActivity() {
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
var isActivityRecreated = false
private val emulationViewModel: EmulationViewModel by viewModels()
private val settingsViewModel: SettingsViewModel by viewModels()
val settingsViewModel: SettingsViewModel by viewModels()
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
@ -67,6 +69,8 @@ class EmulationActivity : AppCompatActivity() {
private var isEmulationRunning: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings()
@ -192,9 +196,16 @@ class EmulationActivity : AppCompatActivity() {
}
private fun enableFullscreenImmersive() {
// TODO: Remove this once we properly account for display insets in the input overlay
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
val attributes = window.attributes
attributes.layoutInDisplayCutoutMode =
if (BooleanSetting.EXPAND_TO_CUTOUT_AREA.boolean) {
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
} else {
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
window.attributes = attributes
WindowCompat.setDecorFitsSystemWindows(window, false)
@ -227,20 +238,26 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> {
hotkeyUtility.handleHotkey(button)
// On some devices, the back gesture / button press is not intercepted by androidx
// and fails to open the emulation menu. So we're stuck running deprecated code to
// cover for either a fault on androidx's side or in OEM skins (MIUI at least)
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed()
// If the hotkey is pressed, we don't want to open the drawer
if (!hotkeyUtility.HotkeyIsPressed) {
onBackPressed()
}
}
hotkeyUtility.handleHotkey(button)
// Normal key events.
NativeLibrary.ButtonState.PRESSED
}
KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
KeyEvent.ACTION_UP -> {
hotkeyUtility.HotkeyIsPressed = false
NativeLibrary.ButtonState.RELEASED
}
else -> return false
}
val input = event.device

View file

@ -1,10 +1,11 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.adapters
import android.graphics.drawable.Icon
import android.content.Intent
import android.net.Uri
import android.os.SystemClock
import android.text.TextUtils
@ -19,7 +20,12 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.BitmapFactory
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.core.graphics.scale
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
@ -28,6 +34,7 @@ import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
@ -41,17 +48,29 @@ import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
import org.citra.citra_emu.databinding.CardGameBinding
import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.features.settings.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater) :
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater, private val openImageLauncher: ActivityResultLauncher<String>?) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener, View.OnLongClickListener {
private var lastClickTime = 0L
private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null
fun handleShortcutImageResult(uri: Uri?) {
val path = uri?.toString()
if (path != null) {
imagePath = path
dialogShortcutBinding!!.imageScaleSwitch.isEnabled = imagePath != null
refreshShortcutDialogIcon()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
// Create a new view.
@ -203,6 +222,117 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
}
}
private data class GameDirectories(
val gameDir: String,
val saveDir: String,
val modsDir: String,
val texturesDir: String,
val appDir: String,
val dlcDir: String,
val updatesDir: String,
val extraDir: String
)
private fun getGameDirectories(game: Game): GameDirectories {
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
return GameDirectories(
gameDir = game.path.substringBeforeLast("/"),
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
modsDir = "load/mods/${String.format("%016X", game.titleId)}",
texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
)
}
private fun showOpenContextMenu(view: View, game: Game) {
val dirs = getGameDirectories(game)
val popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_open, menu)
listOf(
R.id.game_context_open_app to dirs.appDir,
R.id.game_context_open_save_dir to dirs.saveDir,
R.id.game_context_open_updates to dirs.updatesDir,
R.id.game_context_open_dlc to dirs.dlcDir,
R.id.game_context_open_extra to dirs.extraDir
).forEach { (id, dir) ->
menu.findItem(id)?.isEnabled =
CitraApplication.documentsTree.folderUriHelper(dir)?.let {
DocumentFile.fromTreeUri(view.context, it)?.exists()
} ?: false
}
}
popup.setOnMenuItemClickListener { menuItem ->
val intent = Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setType("*/*")
val uri = when (menuItem.itemId) {
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
else -> null
}
uri?.let {
intent.data = it
view.context.startActivity(intent)
true
} ?: false
}
popup.show()
}
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
val dirs = getGameDirectories(game)
val popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
listOf(
R.id.game_context_uninstall to dirs.gameDir,
R.id.game_context_uninstall_dlc to dirs.dlcDir,
R.id.game_context_uninstall_updates to dirs.updatesDir
).forEach { (id, dir) ->
menu.findItem(id)?.isEnabled =
CitraApplication.documentsTree.folderUriHelper(dir)?.let {
DocumentFile.fromTreeUri(view.context, it)?.exists()
} ?: false
}
}
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir)
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
.toString())
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
.toString())
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()
}
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
true
} else {
false
}
}
popup.show()
}
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
@ -222,21 +352,67 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
}
bottomSheetView.findViewById<MaterialButton>(R.id.game_shortcut).setOnClickListener {
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
CoroutineScope(Dispatchers.IO).launch {
val bitmap = (bottomSheetView.findViewById<ImageView>(R.id.game_icon).drawable as BitmapDrawable).bitmap
val icon = Icon.createWithBitmap(bitmap)
val shortcut = ShortcutInfo.Builder(context, game.title)
.setShortLabel(game.title)
.setIcon(icon)
.setIntent(game.launchIntent.apply {
putExtra("launched_from_shortcut", true)
})
.build()
shortcutManager.requestPinShortcut(shortcut, null)
// Default to false for zoomed in shortcut icons
preferences.edit() {
putBoolean(
"shouldStretchIcon",
false
)
}
dialogShortcutBinding = DialogShortcutBinding.inflate(activity.layoutInflater)
dialogShortcutBinding!!.shortcutNameInput.setText(game.title)
GameIconUtils.loadGameIcon(activity, game, dialogShortcutBinding!!.shortcutIcon)
dialogShortcutBinding!!.shortcutIcon.setOnClickListener {
openImageLauncher?.launch("image/*")
}
dialogShortcutBinding!!.imageScaleSwitch.setOnCheckedChangeListener { _, isChecked ->
preferences.edit {
putBoolean(
"shouldStretchIcon",
isChecked
)
}
refreshShortcutDialogIcon()
}
MaterialAlertDialogBuilder(context)
.setTitle(R.string.create_shortcut)
.setView(dialogShortcutBinding!!.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
if (shortcutName.isEmpty()) {
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
return@setPositiveButton
}
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
CoroutineScope(Dispatchers.IO).launch {
val icon = Icon.createWithBitmap(iconBitmap)
val shortcut = ShortcutInfo.Builder(context, shortcutName)
.setShortLabel(shortcutName)
.setIcon(icon)
.setIntent(game.launchIntent.apply {
putExtra("launchedFromShortcut", true)
})
.build()
shortcutManager?.requestPinShortcut(shortcut, null)
imagePath = null
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
imagePath = null
}
.show()
bottomSheetDialog.dismiss()
}
bottomSheetView.findViewById<MaterialButton>(R.id.cheats).setOnClickListener {
@ -245,6 +421,14 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
bottomSheetDialog.dismiss()
}
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game)
}
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener {
showUninstallContextMenu(it, game, bottomSheetDialog)
}
val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -252,6 +436,47 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
bottomSheetDialog.show()
}
private fun refreshShortcutDialogIcon() {
if (imagePath != null) {
val originalBitmap = BitmapFactory.decodeStream(
CitraApplication.appContext.contentResolver.openInputStream(
imagePath!!.toUri()
)
)
val scaledBitmap = {
val preferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
if (preferences.getBoolean("shouldStretchIcon", true)) {
// stretch to fit
originalBitmap.scale(108, 108)
} else {
// Zoom in to fit the bitmap while keeping the aspect ratio
val width = originalBitmap.width
val height = originalBitmap.height
val targetSize = 108
if (width > height) {
// Landscape orientation
val scaleFactor = targetSize.toFloat() / height
val scaledWidth = (width * scaleFactor).toInt()
val scaledBmp = originalBitmap.scale(scaledWidth, targetSize)
val startX = (scaledWidth - targetSize) / 2
Bitmap.createBitmap(scaledBmp, startX, 0, targetSize, targetSize)
} else {
val scaleFactor = targetSize.toFloat() / width
val scaledHeight = (height * scaleFactor).toInt()
val scaledBmp = originalBitmap.scale(targetSize, scaledHeight)
val startY = (scaledHeight - targetSize) / 2
Bitmap.createBitmap(scaledBmp, 0, startY, targetSize, targetSize)
}
}
}()
dialogShortcutBinding!!.shortcutIcon.setImageBitmap(scaledBitmap)
}
}
private fun isValidGame(extension: String): Boolean {
return Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }

View file

@ -1,9 +1,10 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.adapters
import android.content.res.ColorStateList
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
@ -14,9 +15,11 @@ import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import org.citra.citra_emu.databinding.PageSetupBinding
import org.citra.citra_emu.model.ButtonState
import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.model.StepState
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
@ -42,8 +45,40 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
fun bind(page: SetupPage) {
this.page = page
if (page.stepCompleted.invoke() == StepState.STEP_COMPLETE) {
onStepCompleted()
if (page.pageSteps.invoke() == PageState.PAGE_STEPS_COMPLETE) {
onStepCompleted(0, pageFullyCompleted = true)
}
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
for (pageButton in page.pageButtons) {
val pageButtonView = LayoutInflater.from(activity)
.inflate(
R.layout.page_button,
binding.pageButtonContainer,
false
) as MaterialButton
pageButtonView.apply {
id = pageButton.titleId
icon = ResourcesCompat.getDrawable(
activity.resources,
pageButton.iconId,
activity.theme
)
text = activity.resources.getString(pageButton.titleId)
}
pageButtonView.setOnClickListener {
pageButton.buttonAction.invoke(this@SetupPageViewHolder)
}
binding.pageButtonContainer.addView(pageButtonView)
// Disable buton add if its already completed
if (pageButton.buttonState.invoke() == ButtonState.BUTTON_ACTION_COMPLETE) {
onStepCompleted(pageButton.titleId, pageFullyCompleted = false)
}
}
}
binding.icon.setImageDrawable(
@ -57,31 +92,26 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
binding.textDescription.text =
Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
binding.textDescription.movementMethod = LinkMovementMethod.getInstance()
binding.buttonAction.apply {
text = activity.resources.getString(page.buttonTextId)
if (page.buttonIconId != 0) {
icon = ResourcesCompat.getDrawable(
activity.resources,
page.buttonIconId,
activity.theme
)
}
iconGravity =
if (page.leftAlignedIcon) {
MaterialButton.ICON_GRAVITY_START
} else {
MaterialButton.ICON_GRAVITY_END
}
setOnClickListener {
page.buttonAction.invoke(this@SetupPageViewHolder)
}
}
}
override fun onStepCompleted() {
ViewUtils.hideView(binding.buttonAction, 200)
ViewUtils.showView(binding.textConfirmation, 200)
override fun onStepCompleted(pageButtonId: Int, pageFullyCompleted: Boolean) {
val button = binding.pageButtonContainer.findViewById<MaterialButton>(pageButtonId)
if (pageFullyCompleted) {
ViewUtils.hideView(binding.pageButtonContainer, 200)
ViewUtils.showView(binding.textConfirmation, 200)
}
if (button != null) {
button.isEnabled = false
button.animate()
.alpha(0.38f)
.setDuration(200)
.start()
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
button.iconTint =
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
}
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -68,4 +68,13 @@ class ScreenAdjustmentUtil(
settings.saveSetting(IntSetting.ORIENTATION_OPTION, SettingsFile.FILE_NAME_CONFIG)
activity.requestedOrientation = orientationOption
}
fun toggleScreenUpright() {
val uprightBoolean = BooleanSetting.UPRIGHT_SCREEN.boolean
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -41,7 +41,8 @@ enum class SmallScreenPosition(val int: Int) {
enum class PortraitScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h
TOP_FULL_WIDTH(0),
CUSTOM_PORTRAIT_LAYOUT(1);
CUSTOM_PORTRAIT_LAYOUT(1),
ORIGINAL(2);
companion object {
fun from(int: Int): PortraitScreenLayout {

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -10,5 +10,6 @@ enum class Hotkey(val button: Int) {
CLOSE_GAME(10003),
PAUSE_OR_RESUME(10004),
QUICKSAVE(10005),
QUICKLOAD(10006);
QUICKLOAD(10006),
TURBO_LIMIT(10007);
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,11 +9,15 @@ import android.widget.Toast
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.display.ScreenAdjustmentUtil
class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) {
class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
private val context: Context) {
val hotkeyButtons = Hotkey.entries.map { it.button }
private val hotkeyButtons = Hotkey.entries.map { it.button }
var HotkeyIsPressed = false
fun handleHotkey(bindedButton: Int): Boolean {
if(hotkeyButtons.contains(bindedButton)) {
@ -22,16 +26,17 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, priv
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context,
context.getString(R.string.quicksave_saving),
context.getString(R.string.saving),
Toast.LENGTH_SHORT).show()
}
Hotkey.QUICKLOAD.button -> {
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
val stringRes = if(wasLoaded) {
R.string.quickload_loading
R.string.loading
} else {
R.string.quickload_not_found
}
@ -41,6 +46,7 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, priv
}
else -> {}
}
HotkeyIsPressed = true
return true
}
return false

View file

@ -9,16 +9,46 @@ enum class BooleanSetting(
override val section: String,
override val defaultValue: Boolean
) : AbstractBooleanSetting {
EXPAND_TO_CUTOUT_AREA("expand_to_cutout_area", Settings.SECTION_LAYOUT, false),
SPIRV_SHADER_GEN("spirv_shader_gen", Settings.SECTION_RENDERER, true),
ASYNC_SHADERS("async_shader_compilation", Settings.SECTION_RENDERER, false),
DISABLE_SPIRV_OPTIMIZER("disable_spirv_optimizer", Settings.SECTION_RENDERER, true),
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
OVERLAY_SHOW_FPS("overlay_show_fps", Settings.SECTION_LAYOUT, true),
OVERLAY_SHOW_FRAMETIME("overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
OVERLAY_SHOW_SPEED("overlay_show_speed", Settings.SECTION_LAYOUT, false),
OVERLAY_SHOW_APP_RAM_USAGE("overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
OVERLAY_SHOW_AVAILABLE_RAM("overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
OVERLAY_SHOW_BATTERY_TEMP("overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
OVERLAY_BACKGROUND("overlay_background", Settings.SECTION_LAYOUT, false),
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true),
DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false),
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false);
REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false),
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, false),
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, true),
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, true),
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, false),
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, true),
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, false),
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, false),
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, true),
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, false),
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, true),
ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, false),
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, true),
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, true),
SHADER_JIT("use_shader_jit", Settings.SECTION_RENDERER, true),
VSYNC("use_vsync_new", Settings.SECTION_RENDERER, true),
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, true),
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, false),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, false),
UPRIGHT_SCREEN("upright_screen", Settings.SECTION_LAYOUT, false);
override var boolean: Boolean = defaultValue
@ -43,6 +73,14 @@ enum class BooleanSetting(
DELAY_START_LLE_MODULES,
DETERMINISTIC_ASYNC_OPERATIONS,
REQUIRED_ONLINE_LLE_MODULES,
NEW_3DS,
LLE_APPLETS,
VSYNC,
DEBUG_RENDERER,
CPU_JIT,
ASYNC_CUSTOM_LOADING,
SHADERS_ACCURATE_MUL,
USE_ARTIC_BASE_CONTROLLER
)
fun from(key: String): BooleanSetting? =

View file

@ -33,6 +33,7 @@ enum class IntSetting(
LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
SCREEN_GAP("screen_gap",Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
@ -43,29 +44,16 @@ enum class IntSetting(
PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
AUDIO_INPUT_TYPE("input_type", Settings.SECTION_AUDIO, 0),
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1),
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, 1),
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
LINEAR_FILTERING("filter_mode", Settings.SECTION_RENDERER, 1),
SHADERS_ACCURATE_MUL("shaders_accurate_mul", Settings.SECTION_RENDERER, 0),
DISK_SHADER_CACHE("use_disk_shader_cache", Settings.SECTION_RENDERER, 1),
DUMP_TEXTURES("dump_textures", Settings.SECTION_UTILITY, 0),
CUSTOM_TEXTURES("custom_textures", Settings.SECTION_UTILITY, 0),
ASYNC_CUSTOM_LOADING("async_custom_loading", Settings.SECTION_UTILITY, 1),
PRELOAD_TEXTURES("preload_textures", Settings.SECTION_UTILITY, 0),
ENABLE_AUDIO_STRETCHING("enable_audio_stretching", Settings.SECTION_AUDIO, 1),
ENABLE_REALTIME_AUDIO("enable_realtime_audio", Settings.SECTION_AUDIO, 0),
CPU_JIT("use_cpu_jit", Settings.SECTION_CORE, 1),
HW_SHADER("use_hw_shader", Settings.SECTION_RENDERER, 1),
VSYNC("use_vsync_new", Settings.SECTION_RENDERER, 1),
DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0),
TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0),
TEXTURE_SAMPLING("texture_sampling", Settings.SECTION_RENDERER, 0),
USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1),
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0),
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0);
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue
override val valueAsString: String
@ -85,16 +73,8 @@ enum class IntSetting(
private val NOT_RUNTIME_EDITABLE = listOf(
EMULATED_REGION,
INIT_CLOCK,
NEW_3DS,
LLE_APPLETS,
GRAPHICS_API,
VSYNC,
DEBUG_RENDERER,
CPU_JIT,
ASYNC_CUSTOM_LOADING,
AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER,
SHADERS_ACCURATE_MUL,
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -111,6 +111,7 @@ class Settings {
const val SECTION_THEME = "Theme"
const val SECTION_CUSTOM_LANDSCAPE = "Custom Landscape Layout"
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
const val KEY_BUTTON_A = "button_a"
const val KEY_BUTTON_B = "button_b"
@ -139,6 +140,7 @@ class Settings {
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
val buttonKeys = listOf(
KEY_BUTTON_A,
@ -204,7 +206,8 @@ class Settings {
HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD
HOTKEY_QUICKlOAD,
HOTKEY_TURBO_LIMIT
)
val hotkeyTitles = listOf(
R.string.emulation_swap_screens,
@ -213,6 +216,7 @@ class Settings {
R.string.emulation_toggle_pause,
R.string.emulation_quicksave,
R.string.emulation_quickload,
R.string.turbo_limit_hotkey
)
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"

View file

@ -12,7 +12,8 @@ class DateTimeSetting(
titleId: Int,
descriptionId: Int,
val key: String? = null,
private val defaultValue: String? = null
private val defaultValue: String? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -133,6 +133,7 @@ class InputBindingSetting(
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
else -> -1
}

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -28,6 +28,13 @@ abstract class SettingsItem(
return setting?.isRuntimeEditable ?: false
}
open var isEnabled: Boolean = true
val isActive: Boolean
get() {
return this.isEditable && this.isEnabled
}
companion object {
const val TYPE_HEADER = 0
const val TYPE_SWITCH = 1

View file

@ -15,7 +15,8 @@ class SingleChoiceSetting(
val choicesId: Int,
val valuesId: Int,
val key: String? = null,
val defaultValue: Int? = null
val defaultValue: Int? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE

View file

@ -20,7 +20,8 @@ class SliderSetting(
val max: Int,
val units: String,
val key: String? = null,
val defaultValue: Float? = null
val defaultValue: Float? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
val selectedFloat: Float

View file

@ -12,7 +12,8 @@ class StringInputSetting(
titleId: Int,
descriptionId: Int,
val defaultValue: String,
val characterLimit: Int = 0
val characterLimit: Int = 0,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_INPUT

View file

@ -15,7 +15,8 @@ class StringSingleChoiceSetting(
val choices: Array<String>,
val values: Array<String>?,
val key: String? = null,
private val defaultValue: String? = null
private val defaultValue: String? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,53 +9,31 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
class SwitchSetting(
setting: AbstractSetting,
setting: AbstractBooleanSetting,
titleId: Int,
descriptionId: Int,
val key: String? = null,
val defaultValue: Any? = null
val defaultValue: Boolean = false,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH
val isChecked: Boolean
get() {
if (setting == null) {
return defaultValue as Boolean
return defaultValue
}
// Try integer setting
try {
val setting = setting as AbstractIntSetting
return setting.int == 1
} catch (_: ClassCastException) {
}
// Try boolean setting
try {
val setting = setting as AbstractBooleanSetting
return setting.boolean
} catch (_: ClassCastException) {
}
return defaultValue as Boolean
val setting = setting as AbstractBooleanSetting
return setting.boolean
}
/**
* Write a value to the backing boolean. If that boolean was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.
* Write a value to the backing boolean.
*
* @param checked Pretty self explanatory.
* @return the existing setting with the new value applied.
*/
fun setChecked(checked: Boolean): AbstractSetting {
// Try integer setting
try {
val setting = setting as AbstractIntSetting
setting.int = if (checked) 1 else 0
return setting
} catch (_: ClassCastException) {
}
// Try boolean setting
fun setChecked(checked: Boolean): AbstractBooleanSetting {
val setting = setting as AbstractBooleanSetting
setting.boolean = checked
return setting

View file

@ -12,6 +12,7 @@ import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.TurboHelper
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
val settings: Settings get() = activityView.settings
@ -66,6 +67,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
//added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS
}
NativeLibrary.reloadSettings()
}

View file

@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.graphics.Color
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.Editable
@ -17,11 +16,11 @@ import android.text.TextWatcher
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -66,7 +65,6 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHold
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
import org.citra.citra_emu.utils.SystemSaveGame
import java.lang.IllegalStateException
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
@ -153,15 +151,71 @@ class SettingsAdapter(
return getItem(position)?.type ?: -1
}
fun setSettingsList(settings: ArrayList<SettingsItem>?) {
this.settings = settings ?: arrayListOf()
notifyDataSetChanged()
fun setSettingsList(newSettings: ArrayList<SettingsItem>?) {
if (settings == null) {
settings = newSettings ?: arrayListOf()
notifyDataSetChanged()
return
}
val oldSettings = settings
val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldSettings?.size ?: 0
override fun getNewListSize() = newSettings?.size ?: 0
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldSettings?.get(oldItemPosition)?.setting
val newItem = newSettings?.get(newItemPosition)?.setting
return oldItem?.key == newItem?.key
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldSettings?.get(oldItemPosition)
val newItem = newSettings?.get(newItemPosition)
if (oldItem == null || newItem == null || oldItem.type != newItem.type) {
return false
}
return when (oldItem.type) {
SettingsItem.TYPE_SLIDER -> {
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
}
SettingsItem.TYPE_SWITCH -> {
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
}
SettingsItem.TYPE_SINGLE_CHOICE -> {
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_DATETIME_SETTING -> {
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
}
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_STRING_INPUT -> {
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
}
else -> {
oldItem == newItem
}
}
}
})
settings = newSettings ?: arrayListOf()
diffResult.dispatchUpdatesTo(this)
}
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
val setting = item.setChecked(checked)
fragmentView.putSetting(setting)
fragmentView.onSettingChanged()
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
if (fragmentView.activityView != null)
// Reload the settings list to update the UI
fragmentView.loadSettingsList()
}
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
@ -247,6 +301,7 @@ class SettingsAdapter(
notifyItemChanged(clickedPosition)
val setting = item.setSelectedValue(rtcString)
fragmentView.putSetting(setting)
fragmentView.loadSettingsList()
clickedItem = null
}
datePicker.show(
@ -402,6 +457,7 @@ class SettingsAdapter(
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
@ -425,6 +481,7 @@ class SettingsAdapter(
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
@ -447,6 +504,7 @@ class SettingsAdapter(
fragmentView?.putSetting(setting)
}
}
fragmentView.loadSettingsList()
closeDialog()
}
}
@ -459,6 +517,7 @@ class SettingsAdapter(
}
val setting = it.setSelectedValue(textInputValue ?: "")
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog()
}
}
@ -488,6 +547,7 @@ class SettingsAdapter(
}
notifyItemChanged(position)
fragmentView.onSettingChanged()
fragmentView.loadSettingsList()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
@ -495,10 +555,19 @@ class SettingsAdapter(
return true
}
fun onClickDisabledSetting() {
MessageDialogFragment.newInstance(
R.string.setting_not_editable,
fun onClickDisabledSetting(isRuntimeDisabled: Boolean) {
val titleId = if (isRuntimeDisabled)
R.string.setting_not_editable
else
R.string.setting_disabled
val messageId = if (isRuntimeDisabled)
R.string.setting_not_editable_description
else
R.string.setting_disabled_description
MessageDialogFragment.newInstance(
titleId,
messageId
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
}

View file

@ -47,6 +47,7 @@ import org.citra.citra_emu.utils.BirthdayMonth
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
private var menuTag: String? = null
@ -102,6 +103,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_CUSTOM_LANDSCAPE -> addCustomLandscapeSettings(sl)
Settings.SECTION_CUSTOM_PORTRAIT -> addCustomPortraitSettings(sl)
Settings.SECTION_PERFORMANCE_OVERLAY -> addPerformanceOverlaySettings(sl)
else -> {
fragmentView.showToastMessage("Unimplemented menu", false)
return
@ -218,11 +220,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
sl.apply {
add(
SwitchSetting(
IntSetting.USE_FRAME_LIMIT,
BooleanSetting.USE_FRAME_LIMIT,
R.string.frame_limit_enable,
R.string.frame_limit_enable_description,
IntSetting.USE_FRAME_LIMIT.key,
IntSetting.USE_FRAME_LIMIT.defaultValue
BooleanSetting.USE_FRAME_LIMIT.key,
BooleanSetting.USE_FRAME_LIMIT.defaultValue
)
)
add(
@ -237,6 +239,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.FRAME_LIMIT.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.TURBO_LIMIT,
R.string.turbo_limit,
R.string.turbo_limit_description,
100,
400,
"%",
IntSetting.TURBO_LIMIT.key,
IntSetting.TURBO_LIMIT.defaultValue.toFloat()
)
)
}
}
@ -281,20 +295,20 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(HeaderSetting(R.string.emulation_settings))
add(
SwitchSetting(
IntSetting.NEW_3DS,
BooleanSetting.NEW_3DS,
R.string.new_3ds,
0,
IntSetting.NEW_3DS.key,
IntSetting.NEW_3DS.defaultValue
BooleanSetting.NEW_3DS.key,
BooleanSetting.NEW_3DS.defaultValue
)
)
add(
SwitchSetting(
IntSetting.LLE_APPLETS,
BooleanSetting.LLE_APPLETS,
R.string.lle_applets,
0,
IntSetting.LLE_APPLETS.key,
IntSetting.LLE_APPLETS.defaultValue
BooleanSetting.LLE_APPLETS.key,
BooleanSetting.LLE_APPLETS.defaultValue
)
)
add(
@ -526,7 +540,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
DateTimeSetting(
StringSetting.INIT_TIME,
R.string.simulated_clock,
R.string.init_time_description,
R.string.simulated_clock_description,
StringSetting.INIT_TIME.key,
StringSetting.INIT_TIME.defaultValue
)
@ -774,11 +788,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(HeaderSetting(R.string.miscellaneous))
add(
SwitchSetting(
IntSetting.USE_ARTIC_BASE_CONTROLLER,
BooleanSetting.USE_ARTIC_BASE_CONTROLLER,
R.string.use_artic_base_controller,
R.string.use_artic_base_controller_description,
IntSetting.USE_ARTIC_BASE_CONTROLLER.key,
IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
BooleanSetting.USE_ARTIC_BASE_CONTROLLER.key,
BooleanSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue
)
)
}
@ -825,6 +839,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.SPIRV_SHADER_GEN.defaultValue,
)
)
add(
SwitchSetting(
BooleanSetting.DISABLE_SPIRV_OPTIMIZER,
R.string.disable_spirv_optimizer,
R.string.disable_spirv_optimizer_description,
BooleanSetting.DISABLE_SPIRV_OPTIMIZER.key,
BooleanSetting.DISABLE_SPIRV_OPTIMIZER.defaultValue,
)
)
add(
SwitchSetting(
BooleanSetting.ASYNC_SHADERS,
@ -847,29 +870,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.LINEAR_FILTERING,
BooleanSetting.LINEAR_FILTERING,
R.string.linear_filtering,
R.string.linear_filtering_description,
IntSetting.LINEAR_FILTERING.key,
IntSetting.LINEAR_FILTERING.defaultValue
BooleanSetting.LINEAR_FILTERING.key,
BooleanSetting.LINEAR_FILTERING.defaultValue
)
)
add(
SwitchSetting(
IntSetting.SHADERS_ACCURATE_MUL,
BooleanSetting.SHADERS_ACCURATE_MUL,
R.string.shaders_accurate_mul,
R.string.shaders_accurate_mul_description,
IntSetting.SHADERS_ACCURATE_MUL.key,
IntSetting.SHADERS_ACCURATE_MUL.defaultValue
BooleanSetting.SHADERS_ACCURATE_MUL.key,
BooleanSetting.SHADERS_ACCURATE_MUL.defaultValue
)
)
add(
SwitchSetting(
IntSetting.DISK_SHADER_CACHE,
BooleanSetting.DISK_SHADER_CACHE,
R.string.use_disk_shader_cache,
R.string.use_disk_shader_cache_description,
IntSetting.DISK_SHADER_CACHE.key,
IntSetting.DISK_SHADER_CACHE.defaultValue
BooleanSetting.DISK_SHADER_CACHE.key,
BooleanSetting.DISK_SHADER_CACHE.defaultValue
)
)
add(
@ -914,7 +937,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.factor3d,
R.string.factor3d_description,
0,
100,
255,
"%",
IntSetting.STEREOSCOPIC_3D_DEPTH.key,
IntSetting.STEREOSCOPIC_3D_DEPTH.defaultValue.toFloat()
@ -922,11 +945,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.DISABLE_RIGHT_EYE_RENDER,
BooleanSetting.DISABLE_RIGHT_EYE_RENDER,
R.string.disable_right_eye_render,
R.string.disable_right_eye_render_description,
IntSetting.DISABLE_RIGHT_EYE_RENDER.key,
IntSetting.DISABLE_RIGHT_EYE_RENDER.defaultValue
BooleanSetting.DISABLE_RIGHT_EYE_RENDER.key,
BooleanSetting.DISABLE_RIGHT_EYE_RENDER.defaultValue
)
)
@ -971,29 +994,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(HeaderSetting(R.string.utility))
add(
SwitchSetting(
IntSetting.DUMP_TEXTURES,
BooleanSetting.DUMP_TEXTURES,
R.string.dump_textures,
R.string.dump_textures_description,
IntSetting.DUMP_TEXTURES.key,
IntSetting.DUMP_TEXTURES.defaultValue
BooleanSetting.DUMP_TEXTURES.key,
BooleanSetting.DUMP_TEXTURES.defaultValue
)
)
add(
SwitchSetting(
IntSetting.CUSTOM_TEXTURES,
BooleanSetting.CUSTOM_TEXTURES,
R.string.custom_textures,
R.string.custom_textures_description,
IntSetting.CUSTOM_TEXTURES.key,
IntSetting.CUSTOM_TEXTURES.defaultValue
BooleanSetting.CUSTOM_TEXTURES.key,
BooleanSetting.CUSTOM_TEXTURES.defaultValue
)
)
add(
SwitchSetting(
IntSetting.ASYNC_CUSTOM_LOADING,
BooleanSetting.ASYNC_CUSTOM_LOADING,
R.string.async_custom_loading,
R.string.async_custom_loading_description,
IntSetting.ASYNC_CUSTOM_LOADING.key,
IntSetting.ASYNC_CUSTOM_LOADING.defaultValue
BooleanSetting.ASYNC_CUSTOM_LOADING.key,
BooleanSetting.ASYNC_CUSTOM_LOADING.defaultValue
)
)
@ -1038,6 +1061,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.ORIENTATION_OPTION.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.EXPAND_TO_CUTOUT_AREA,
R.string.expand_to_cutout_area,
R.string.expand_to_cutout_area_description,
BooleanSetting.EXPAND_TO_CUTOUT_AREA.key,
BooleanSetting.EXPAND_TO_CUTOUT_AREA.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.SCREEN_LAYOUT,
@ -1049,6 +1081,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.SCREEN_LAYOUT.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.UPRIGHT_SCREEN,
R.string.emulation_rotate_upright,
0,
BooleanSetting.UPRIGHT_SCREEN.key,
BooleanSetting.UPRIGHT_SCREEN.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.PORTRAIT_SCREEN_LAYOUT,
@ -1060,6 +1101,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.ASPECT_RATIO,
R.string.emulation_aspect_ratio,
0,
R.array.aspectRatioNames,
R.array.aspectRatioValues,
IntSetting.ASPECT_RATIO.key,
IntSetting.ASPECT_RATIO.defaultValue,
isEnabled = IntSetting.SCREEN_LAYOUT.int == 1,
)
)
add(
SingleChoiceSetting(
IntSetting.SMALL_SCREEN_POSITION,
@ -1071,6 +1124,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.SMALL_SCREEN_POSITION.defaultValue
)
)
add(
SliderSetting(
IntSetting.SCREEN_GAP,
R.string.screen_gap,
R.string.screen_gap_description,
0,
480,
"px",
IntSetting.SCREEN_GAP.key,
IntSetting.SCREEN_GAP.defaultValue.toFloat()
)
)
add(
SliderSetting(
FloatSetting.LARGE_SCREEN_PROPORTION,
@ -1083,6 +1148,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
FloatSetting.LARGE_SCREEN_PROPORTION.defaultValue
)
)
add(
SubmenuSetting(
R.string.performance_overlay_options,
R.string.performance_overlay_options_description,
R.drawable.ic_stats,
Settings.SECTION_PERFORMANCE_OVERLAY
)
)
add(
SubmenuSetting(
R.string.emulation_landscape_custom_layout,
@ -1102,6 +1175,116 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
}
private fun addPerformanceOverlaySettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.performance_overlay_options))
sl.apply {
add(HeaderSetting(R.string.visibility))
add(
SwitchSetting(
object : AbstractBooleanSetting {
override val key = "EmulationMenuSettings_showPerfPerformanceOverlay"
override val section = Settings.SECTION_LAYOUT
override val defaultValue = false
override var boolean: Boolean
get() = EmulationMenuSettings.showPerformanceOverlay
set(value) { EmulationMenuSettings.showPerformanceOverlay = value }
override val isRuntimeEditable = true
override val valueAsString: String get() = boolean.toString()
},
R.string.performance_overlay_enable,
0,
"EmulationMenuSettings_showPerfPerformanceOverlay",
false
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_BACKGROUND,
R.string.overlay_background,
R.string.overlay_background_description,
"overlay_background",
false
)
)
add(
SingleChoiceSetting(
IntSetting.PERFORMANCE_OVERLAY_POSITION,
R.string.overlay_position,
R.string.overlay_position_description,
R.array.statsPosition,
R.array.statsPositionValues,
)
)
add(HeaderSetting(R.string.information))
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_FPS,
R.string.overlay_show_fps,
R.string.overlay_show_fps_description,
"overlay_show_fps",
true
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_FRAMETIME,
R.string.overlay_show_frametime,
R.string.overlay_show_frametime_description,
"overlay_show_frame_time",
true
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_SPEED,
R.string.overlay_show_speed,
R.string.overlay_show_speed_description,
"overlay_show_speed",
false
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE,
R.string.overlay_show_app_ram_usage,
R.string.overlay_show_app_ram_usage_description,
"overlay_show_app_ram_usage",
false
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM,
R.string.overlay_show_available_ram,
R.string.overlay_show_available_ram_description,
"overlay_show_available_ram",
false
)
)
add(
SwitchSetting(
BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP,
R.string.overlay_show_battery_temp,
R.string.overlay_show_battery_temp_description,
"overlay_show_battery_temp",
false
)
)
}
}
private fun addCustomLandscapeSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_landscape_custom_layout))
sl.apply {
@ -1329,20 +1512,20 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.ENABLE_AUDIO_STRETCHING,
BooleanSetting.ENABLE_AUDIO_STRETCHING,
R.string.audio_stretch,
R.string.audio_stretch_description,
IntSetting.ENABLE_AUDIO_STRETCHING.key,
IntSetting.ENABLE_AUDIO_STRETCHING.defaultValue
BooleanSetting.ENABLE_AUDIO_STRETCHING.key,
BooleanSetting.ENABLE_AUDIO_STRETCHING.defaultValue
)
)
add(
SwitchSetting(
IntSetting.ENABLE_REALTIME_AUDIO,
BooleanSetting.ENABLE_REALTIME_AUDIO,
R.string.realtime_audio,
R.string.realtime_audio_description,
IntSetting.ENABLE_REALTIME_AUDIO.key,
IntSetting.ENABLE_REALTIME_AUDIO.defaultValue
BooleanSetting.ENABLE_REALTIME_AUDIO.key,
BooleanSetting.ENABLE_REALTIME_AUDIO.defaultValue
)
)
add(
@ -1397,38 +1580,47 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(
SwitchSetting(
IntSetting.CPU_JIT,
BooleanSetting.CPU_JIT,
R.string.cpu_jit,
R.string.cpu_jit_description,
IntSetting.CPU_JIT.key,
IntSetting.CPU_JIT.defaultValue
BooleanSetting.CPU_JIT.key,
BooleanSetting.CPU_JIT.defaultValue
)
)
add(
SwitchSetting(
IntSetting.HW_SHADER,
BooleanSetting.HW_SHADER,
R.string.hw_shaders,
R.string.hw_shaders_description,
IntSetting.HW_SHADER.key,
IntSetting.HW_SHADER.defaultValue
BooleanSetting.HW_SHADER.key,
BooleanSetting.HW_SHADER.defaultValue
)
)
add(
SwitchSetting(
IntSetting.VSYNC,
BooleanSetting.SHADER_JIT,
R.string.shader_jit,
R.string.shader_jit_description,
BooleanSetting.SHADER_JIT.key,
BooleanSetting.SHADER_JIT.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.VSYNC,
R.string.vsync,
R.string.vsync_description,
IntSetting.VSYNC.key,
IntSetting.VSYNC.defaultValue
BooleanSetting.VSYNC.key,
BooleanSetting.VSYNC.defaultValue
)
)
add(
SwitchSetting(
IntSetting.DEBUG_RENDERER,
BooleanSetting.DEBUG_RENDERER,
R.string.renderer_debug,
R.string.renderer_debug_description,
IntSetting.DEBUG_RENDERER.key,
IntSetting.DEBUG_RENDERER.defaultValue
BooleanSetting.DEBUG_RENDERER.key,
BooleanSetting.DEBUG_RENDERER.defaultValue
)
)
add(
@ -1440,6 +1632,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.INSTANT_DEBUG_LOG.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.ENABLE_RPC_SERVER,
R.string.enable_rpc_server,
R.string.enable_rpc_server_desc,
BooleanSetting.ENABLE_RPC_SERVER.key,
BooleanSetting.ENABLE_RPC_SERVER.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.DELAY_START_LLE_MODULES,

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -47,7 +47,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime)
if (setting.isEditable) {
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
@ -59,18 +59,18 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
override fun onClick(clicked: View) {
if (setting.isEditable) {
if (setting.isActive) {
adapter.onDateTimeClick(setting, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -45,7 +45,7 @@ class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter
if (setting.isEditable) {
adapter.onInputBindingClick(setting, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
}
@ -53,7 +53,7 @@ class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter
if (setting.isEditable) {
adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -60,7 +60,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun onClick(clicked: View) {
if (!setting.isRuntimeRunnable && EmulationActivity.isRunning()) {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(true)
} else {
setting.runnable.invoke()
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -27,7 +27,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = getTextSetting()
if (setting.isEditable) {
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
@ -65,8 +65,8 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
}
override fun onClick(clicked: View) {
if (!setting.isEditable) {
adapter.onClickDisabledSetting()
if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting(!setting.isEditable)
return
}
@ -84,10 +84,10 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -35,7 +35,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}"
}
if (setting.isEditable) {
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
@ -47,18 +47,18 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
}
override fun onClick(clicked: View) {
if (setting.isEditable) {
if (setting.isActive) {
adapter.onSliderClick(setting, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -25,21 +25,31 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
}
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = setting.setting?.valueAsString
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
} else {
binding.textSettingName.alpha = 0.5f
binding.textSettingDescription.alpha = 0.5f
binding.textSettingValue.alpha = 0.5f
}
}
override fun onClick(clicked: View) {
if (!setting.isEditable) {
adapter.onClickDisabledSetting()
if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting(!setting.isEditable)
return
}
adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition)
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -33,26 +33,26 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
}
binding.switchWidget.isEnabled = setting.isEditable
binding.switchWidget.isEnabled = setting.isActive
val textAlpha = if (setting.isEditable) 1f else 0.5f
val textAlpha = if (setting.isActive) 1f else 0.5f
binding.textSettingName.alpha = textAlpha
binding.textSettingDescription.alpha = textAlpha
}
override fun onClick(clicked: View) {
if (setting.isEditable) {
if (setting.isActive) {
binding.switchWidget.toggle()
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting()
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -141,7 +141,7 @@ class CopyDirProgressDialog : DialogFragment() {
override fun onComplete() {
CitraDirectoryHelper.initializeCitraDirectory(path)
callback?.onStepCompleted()
callback?.onStepCompleted(0, false)
viewModel.setCopyComplete(true)
}
})

View file

@ -1,14 +1,18 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.fragments
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.net.Uri
import android.os.BatteryManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@ -16,16 +20,19 @@ import android.os.SystemClock
import android.text.Editable
import android.text.TextWatcher
import android.view.Choreographer
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
@ -43,6 +50,7 @@ import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import java.io.File
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
@ -56,6 +64,7 @@ import org.citra.citra_emu.databinding.FragmentEmulationBinding
import org.citra.citra_emu.display.PortraitScreenLayout
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.display.ScreenLayout
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.SettingsViewModel
import org.citra.citra_emu.features.settings.ui.SettingsActivity
@ -174,8 +183,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
binding.surfaceInputOverlay.setIsInEditMode(false)
}
// Show/hide the "Show FPS" overlay
updateShowFpsOverlay()
// Show/hide the "Stats" overlay
updateShowPerformanceOverlay()
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
updateStatsPosition(position)
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.drawerLayout.addDrawerListener(object : DrawerListener {
@ -232,8 +244,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
)
}
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
val iconView = findViewById<ImageView>(R.id.game_icon)
titleView.text = game.title
GameIconUtils.loadGameIcon(requireActivity(), game, iconView)
}
binding.inGameMenu.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.menu_emulation_pause -> {
@ -287,6 +306,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true
}
R.id.menu_rotate_upright -> {
screenAdjustmentUtil.toggleScreenUpright()
true
}
R.id.menu_lock_drawer -> {
when (EmulationMenuSettings.drawerLockMode) {
DrawerLayout.LOCK_MODE_UNLOCKED -> {
@ -447,6 +471,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
Choreographer.getInstance().postFrameCallback(this)
if (NativeLibrary.isRunning()) {
NativeLibrary.unPauseEmulation()
// If the overlay is enabled, we need to update the position if changed
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
updateStatsPosition(position)
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
menuItem.title = resources.getString(R.string.pause_emulation)
menuItem.icon = ResourcesCompat.getDrawable(
resources,
R.drawable.ic_pause,
requireContext().theme
)
}
return
}
@ -533,7 +570,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val slot = i
var enableClick = isSaving
val text = if (slot == NativeLibrary.QUICKSAVE_SLOT) {
enableClick = false
getString(R.string.emulation_quicksave_slot)
} else {
getString(R.string.emulation_empty_state_slot, slot)
@ -542,11 +578,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
add(text).setEnabled(enableClick).setOnMenuItemClickListener {
if(isSaving) {
NativeLibrary.saveState(slot)
Toast.makeText(context,
getString(R.string.saving),
Toast.LENGTH_SHORT).show()
} else {
NativeLibrary.loadState(slot)
binding.drawerLayout.close()
Toast.makeText(context,
getString(R.string.quickload_loading),
getString(R.string.loading),
Toast.LENGTH_SHORT).show()
}
true
@ -557,8 +596,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
savestates?.forEach {
var enableClick = true
val text = if(it.slot == NativeLibrary.QUICKSAVE_SLOT) {
// do not allow saving in quicksave slot
enableClick = !isSaving
getString(R.string.emulation_occupied_quicksave_slot, it.time)
} else{
getString(R.string.emulation_occupied_state_slot, it.slot, it.time)
@ -624,7 +661,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.menu.apply {
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
findItem(R.id.menu_show_fps).isChecked = EmulationMenuSettings.showFps
findItem(R.id.menu_performance_overlay_show).isChecked =
EmulationMenuSettings.showPerformanceOverlay
findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
EmulationMenuSettings.joystickRelCenter
@ -640,15 +678,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true
}
R.id.menu_show_fps -> {
EmulationMenuSettings.showFps = !EmulationMenuSettings.showFps
updateShowFpsOverlay()
R.id.menu_performance_overlay_show -> {
EmulationMenuSettings.showPerformanceOverlay = !EmulationMenuSettings.showPerformanceOverlay
updateShowPerformanceOverlay()
true
}
R.id.menu_haptic_feedback -> {
EmulationMenuSettings.hapticFeedback = !EmulationMenuSettings.hapticFeedback
updateShowFpsOverlay()
true
}
@ -886,10 +923,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
PortraitScreenLayout.TOP_FULL_WIDTH.int ->
R.id.menu_portrait_layout_top_full
PortraitScreenLayout.ORIGINAL.int ->
R.id.menu_portrait_layout_original
PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int ->
R.id.menu_portrait_layout_custom
else ->
R.id.menu_portrait_layout_top_full
@ -904,6 +941,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true
}
R.id.menu_portrait_layout_original -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.ORIGINAL.int)
true
}
R.id.menu_portrait_layout_custom -> {
Toast.makeText(
requireContext(),
@ -933,12 +975,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private fun showToggleControlsDialog() {
val editor = preferences.edit()
val enabledButtons = BooleanArray(15)
val enabledButtons = BooleanArray(16)
enabledButtons.forEachIndexed { i: Int, _: Boolean ->
// Buttons that are disabled by default
var defaultValue = true
when (i) {
6, 7, 12, 13, 14 -> defaultValue = false
6, 7, 12, 13, 14, 15 -> defaultValue = false
}
enabledButtons[i] = preferences.getBoolean("buttonToggle$i", defaultValue)
}
@ -1116,10 +1158,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
.apply()
val editor = preferences.edit()
for (i in 0 until 15) {
for (i in 0 until 16) {
var defaultValue = true
when (i) {
6, 7, 12, 13, 14 -> defaultValue = false
6, 7, 12, 13, 14, 15 -> defaultValue = false
}
editor.putBoolean("buttonToggle$i", defaultValue)
}
@ -1128,34 +1170,150 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
binding.surfaceInputOverlay.resetButtonPlacement()
}
fun updateShowFpsOverlay() {
if (EmulationMenuSettings.showFps) {
fun updateShowPerformanceOverlay() {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
if (EmulationMenuSettings.showPerformanceOverlay) {
val SYSTEM_FPS = 0
val FPS = 1
val FRAMETIME = 2
val SPEED = 3
val SPEED = 2
val FRAMETIME = 3
val TIME_SVC = 4
val TIME_IPC = 5
val TIME_GPU = 6
val TIME_SWAP = 7
val TIME_REM = 8
perfStatsUpdater = Runnable {
val sb = StringBuilder()
val perfStats = NativeLibrary.getPerfStats()
val dividerString = "\u00A0\u2502 "
if (perfStats[FPS] > 0) {
binding.showFpsText.text = String.format(
"FPS: %d Speed: %d%% FT: %.2fms",
(perfStats[FPS] + 0.5).toInt(),
(perfStats[SPEED] * 100.0 + 0.5).toInt(),
(perfStats[FRAMETIME] * 1000.0f).toFloat()
)
if (BooleanSetting.OVERLAY_SHOW_FPS.boolean) {
sb.append(String.format("FPS:\u00A0%d", (perfStats[FPS] + 0.5).toInt()))
}
if (BooleanSetting.OVERLAY_SHOW_FRAMETIME.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
sb.append(
String.format(
"Frame:\u00A0%.1fms (GPU: [CMD:\u00A0%.1fms SWP:\u00A0%.1fms] IPC:\u00A0%.1fms SVC:\u00A0%.1fms Rem:\u00A0%.1fms)",
(perfStats[FRAMETIME] * 1000.0f).toFloat(),
(perfStats[TIME_GPU] * 1000.0f).toFloat(),
(perfStats[TIME_SWAP] * 1000.0f).toFloat(),
(perfStats[TIME_IPC] * 1000.0f).toFloat(),
(perfStats[TIME_SVC] * 1000.0f).toFloat(),
(perfStats[TIME_REM] * 1000.0f).toFloat(),
)
)
}
if (BooleanSetting.OVERLAY_SHOW_SPEED.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
sb.append(
String.format(
"Speed:\u00A0%d%%",
(perfStats[SPEED] * 100.0 + 0.5).toInt()
)
)
}
if (BooleanSetting.OVERLAY_SHOW_APP_RAM_USAGE.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
val appRamUsage =
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB")
}
if (BooleanSetting.OVERLAY_SHOW_AVAILABLE_RAM.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
context?.let { ctx ->
val activityManager =
ctx.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val megabyteBytes = 1048576L
val availableRam = memInfo.availMem / megabyteBytes
sb.append("Available\u00A0RAM:\u00A0$availableRam\u00A0MB")
}
}
if (BooleanSetting.OVERLAY_SHOW_BATTERY_TEMP.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
val batteryTemp = getBatteryTemperature()
val tempF = celsiusToFahrenheit(batteryTemp)
sb.append(String.format("%.1f°C/%.1f°F", batteryTemp, tempF))
}
if (BooleanSetting.OVERLAY_BACKGROUND.boolean) {
binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black)
} else {
binding.performanceOverlayShowText.setBackgroundResource(0)
}
binding.performanceOverlayShowText.text = sb.toString()
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 3000)
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 1000)
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
binding.showFpsText.visibility = View.VISIBLE
binding.performanceOverlayShowText.visibility = View.VISIBLE
} else {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
binding.showFpsText.visibility = View.GONE
binding.performanceOverlayShowText.visibility = View.GONE
}
}
private fun updateStatsPosition(position: Int) {
val params = binding.performanceOverlayShowText.layoutParams as CoordinatorLayout.LayoutParams
val padding = (20 * resources.displayMetrics.density).toInt() // 20dp
params.setMargins(padding, 0, padding, 0)
when (position) {
0 -> {
params.gravity = (Gravity.TOP or Gravity.START)
}
1 -> {
params.gravity = (Gravity.TOP or Gravity.CENTER_HORIZONTAL)
}
2 -> {
params.gravity = (Gravity.TOP or Gravity.END)
}
3 -> {
params.gravity = (Gravity.BOTTOM or Gravity.START)
}
4 -> {
params.gravity = (Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
}
5 -> {
params.gravity = (Gravity.BOTTOM or Gravity.END)
}
}
binding.performanceOverlayShowText.layoutParams = params
}
private fun getBatteryTemperature(): Float {
try {
val batteryIntent = requireContext().registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
// Temperature in tenths of a degree Celsius
val temperature = batteryIntent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
// Convert to degrees Celsius
return temperature / 10.0f
} catch (e: Exception) {
return 0.0f
}
}
private fun celsiusToFahrenheit(celsius: Float): Float {
return (celsius * 9 / 5) + 32
}
override fun surfaceCreated(holder: SurfaceHolder) {
// We purposely don't do anything here.
// All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
@ -1190,23 +1348,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
v.setPadding(left, cutInsets.top, right, 0)
// Ensure FPS text doesn't get cut off by rounded display corners
val sidePadding = resources.getDimensionPixelSize(R.dimen.spacing_large)
if (cutInsets.left == 0) {
binding.showFpsText.setPadding(
sidePadding,
cutInsets.top,
cutInsets.right,
cutInsets.bottom
)
} else {
binding.showFpsText.setPadding(
cutInsets.left,
cutInsets.top,
cutInsets.right,
cutInsets.bottom
)
}
windowInsets
}
}

View file

@ -5,6 +5,7 @@
package org.citra.citra_emu.fragments
import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
@ -12,7 +13,10 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -36,8 +40,6 @@ import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
import androidx.core.content.edit
import androidx.core.text.HtmlCompat
class GamesFragment : Fragment() {
private var _binding: FragmentGamesBinding? = null
@ -46,6 +48,13 @@ class GamesFragment : Fragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
private val homeViewModel: HomeViewModel by activityViewModels()
private var show3DSFileWarning: Boolean = true
private lateinit var gameAdapter: GameAdapter
private val openImageLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
gameAdapter.handleShortcutImageResult(uri)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -69,12 +78,18 @@ class GamesFragment : Fragment() {
val inflater = LayoutInflater.from(requireContext())
gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity,
inflater,
openImageLauncher
)
binding.gridGames.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.game_grid_columns)
)
adapter = GameAdapter(requireActivity() as AppCompatActivity, inflater)
adapter = this@GamesFragment.gameAdapter
}
binding.swipeRefresh.apply {

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -36,6 +36,8 @@ import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
import java.time.temporal.ChronoField
import java.util.Locale
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
@ -43,6 +45,13 @@ class SearchFragment : Fragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
private val homeViewModel: HomeViewModel by activityViewModels()
private lateinit var gameAdapter: GameAdapter
private val openImageLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
gameAdapter.handleShortcutImageResult(uri)
}
private lateinit var preferences: SharedPreferences
@ -73,12 +82,18 @@ class SearchFragment : Fragment() {
val inflater = LayoutInflater.from(requireContext())
gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity,
inflater,
openImageLauncher
)
binding.gridGamesSearch.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.game_grid_columns)
)
adapter = GameAdapter(requireActivity() as AppCompatActivity, inflater)
adapter = this@SearchFragment.gameAdapter
}
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }

View file

@ -27,7 +27,7 @@ class SelectUserDirectoryDialogFragment : DialogFragment() {
.setTitle(R.string.select_citra_user_folder)
.setMessage(R.string.selecting_user_directory_without_write_permissions)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
mainActivity?.openCitraDirectory?.launch(null)
mainActivity?.openCitraDirectoryLostPermission?.launch(null)
}
.show()
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -35,9 +35,11 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.SetupAdapter
import org.citra.citra_emu.databinding.FragmentSetupBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.model.ButtonState
import org.citra.citra_emu.model.PageButton
import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.model.StepState
import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.utils.CitraDirectoryHelper
import org.citra.citra_emu.utils.GameHelper
@ -113,151 +115,195 @@ class SetupFragment : Fragment() {
0,
true,
R.string.get_started,
{ pageForward() }
)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
add(
SetupPage(
R.drawable.ic_notification,
R.string.notifications,
R.string.notifications_description,
0,
false,
R.string.give_permission,
{
notificationCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
},
false,
true,
{
if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
) {
StepState.STEP_COMPLETE
} else {
StepState.STEP_INCOMPLETE
}
},
R.string.notification_warning,
R.string.notification_warning_description,
0
)
)
}
add(
SetupPage(
R.drawable.ic_microphone,
R.string.microphone_permission,
R.string.microphone_permission_description,
0,
false,
R.string.give_permission,
{
microphoneCallback = it
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
},
false,
false,
{
if (
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
) {
StepState.STEP_COMPLETE
} else {
StepState.STEP_INCOMPLETE
}
pageButtons = mutableListOf<PageButton>().apply {
add(
PageButton(
R.drawable.ic_arrow_forward,
R.string.get_started,
0,
buttonAction = {
pageForward()
},
buttonState = {
ButtonState.BUTTON_ACTION_UNDEFINED
}
)
)
}
)
)
add(
SetupPage(
R.drawable.ic_camera,
R.string.camera_permission,
R.string.camera_permission_description,
R.drawable.ic_permission,
R.string.permissions,
R.string.permissions_description,
0,
false,
R.string.give_permission,
{
cameraCallback = it
permissionLauncher.launch(Manifest.permission.CAMERA)
},
false,
false,
{
if (
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
StepState.STEP_COMPLETE
} else {
StepState.STEP_INCOMPLETE
0,
pageButtons = mutableListOf<PageButton>().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
add(
PageButton(
R.drawable.ic_notification,
R.string.notifications,
R.string.notifications_description,
buttonAction = {
pageButtonCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
},
buttonState = {
if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
isUnskippable = false,
hasWarning = true,
R.string.notification_warning,
R.string.notification_warning_description,
)
)
}
}
)
)
add(
SetupPage(
R.drawable.ic_home,
R.string.select_citra_user_folder,
R.string.select_citra_user_folder_description,
0,
true,
R.string.select,
{
userDirCallback = it
openCitraDirectory.launch(null)
},
true,
true,
{
if (PermissionsHandler.hasWriteAccess(requireContext())) {
StepState.STEP_COMPLETE
} else {
StepState.STEP_INCOMPLETE
}
},
R.string.cannot_skip,
R.string.cannot_skip_directory_description,
R.string.cannot_skip_directory_help
)
)
add(
SetupPage(
R.drawable.ic_controller,
R.string.games,
R.string.games_description,
0,
true,
R.string.select,
{
gamesDirCallback = it
getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
add(
PageButton(
R.drawable.ic_microphone,
R.string.microphone_permission,
R.string.microphone_permission_description,
buttonAction = {
pageButtonCallback = it
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
},
buttonState = {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
)
)
add(
PageButton(
R.drawable.ic_camera,
R.string.camera_permission,
R.string.camera_permission_description,
buttonAction = {
pageButtonCallback = it
permissionLauncher.launch(Manifest.permission.CAMERA)
},
buttonState = {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
)
)
},
false,
true,
{
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
StepState.STEP_COMPLETE
} else {
StepState.STEP_INCOMPLETE
}
},
R.string.add_games_warning,
R.string.add_games_warning_description,
R.string.add_games_warning_help
)
) {
if (
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED &&
NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
) {
PageState.PAGE_STEPS_COMPLETE
} else {
PageState.PAGE_STEPS_INCOMPLETE
}
}
)
add(
SetupPage(
R.drawable.ic_folder,
R.string.select_emulator_data_folders,
R.string.select_emulator_data_folders_description,
0,
true,
R.string.select,
pageButtons = mutableListOf<PageButton>().apply {
add(
PageButton(
R.drawable.ic_home,
R.string.select_citra_user_folder,
R.string.select_citra_user_folder_description,
buttonAction = {
pageButtonCallback = it
openCitraDirectory.launch(null)
},
buttonState = {
if (PermissionsHandler.hasWriteAccess(requireContext())) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
isUnskippable = true,
hasWarning = false,
R.string.cannot_skip,
R.string.cannot_skip_directory_description,
R.string.cannot_skip_directory_help
)
)
add(
PageButton(
R.drawable.ic_controller,
R.string.games,
R.string.games_description,
buttonAction = {
pageButtonCallback = it
getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
},
buttonState = {
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
isUnskippable = false,
hasWarning = true,
R.string.add_games_warning,
R.string.add_games_warning_description,
)
)
},
) {
if (
PermissionsHandler.hasWriteAccess(requireContext()) &&
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
) {
PageState.PAGE_STEPS_COMPLETE
} else {
PageState.PAGE_STEPS_INCOMPLETE
}
}
)
add(
SetupPage(
R.drawable.ic_check,
@ -266,7 +312,21 @@ class SetupFragment : Fragment() {
R.drawable.ic_arrow_forward,
false,
R.string.text_continue,
{ finishSetup() }
pageButtons = mutableListOf<PageButton>().apply {
add(
PageButton(
R.drawable.ic_arrow_forward,
R.string.text_continue,
0,
buttonAction = {
finishSetup()
},
buttonState = {
ButtonState.BUTTON_ACTION_UNDEFINED
}
)
)
}
)
)
}
@ -303,35 +363,47 @@ class SetupFragment : Fragment() {
val index = binding.viewPager2.currentItem
val currentPage = pages[index]
// Checks if the user has completed the task on the current page
if (currentPage.hasWarning || currentPage.isUnskippable) {
val stepState = currentPage.stepCompleted.invoke()
if (stepState == StepState.STEP_COMPLETE ||
stepState == StepState.STEP_UNDEFINED
) {
pageForward()
return@setOnClickListener
}
// This allows multiple sets of warning messages to be displayed on the same dialog if necessary
val warningMessages =
mutableListOf<Triple<Int, Int, Int>>() // title, description, helpLink
if (currentPage.isUnskippable) {
MessageDialogFragment.newInstance(
currentPage.warningTitleId,
currentPage.warningDescriptionId,
currentPage.warningHelpLinkId
).show(childFragmentManager, MessageDialogFragment.TAG)
return@setOnClickListener
}
currentPage.pageButtons?.forEach { button ->
if (button.hasWarning || button.isUnskippable) {
val buttonState = button.buttonState()
if (buttonState == ButtonState.BUTTON_ACTION_COMPLETE) {
return@forEach
}
if (!hasBeenWarned[index]) {
SetupWarningDialogFragment.newInstance(
currentPage.warningTitleId,
currentPage.warningDescriptionId,
currentPage.warningHelpLinkId,
index
).show(childFragmentManager, SetupWarningDialogFragment.TAG)
return@setOnClickListener
if (button.isUnskippable) {
MessageDialogFragment.newInstance(
button.warningTitleId,
button.warningDescriptionId,
button.warningHelpLinkId
).show(childFragmentManager, MessageDialogFragment.TAG)
return@setOnClickListener
}
if (!hasBeenWarned[index]) {
warningMessages.add(
Triple(
button.warningTitleId,
button.warningDescriptionId,
button.warningHelpLinkId
)
)
}
}
}
if (warningMessages.isNotEmpty()) {
SetupWarningDialogFragment.newInstance(
warningMessages.map { it.first }.toIntArray(),
warningMessages.map { it.second }.toIntArray(),
warningMessages.map { it.third }.toIntArray(),
index
).show(childFragmentManager, SetupWarningDialogFragment.TAG)
return@setOnClickListener
}
pageForward()
}
binding.buttonBack.setOnClickListener { pageBackward() }
@ -366,19 +438,24 @@ class SetupFragment : Fragment() {
_binding = null
}
private lateinit var notificationCallback: SetupCallback
private lateinit var microphoneCallback: SetupCallback
private lateinit var cameraCallback: SetupCallback
private lateinit var pageButtonCallback: SetupCallback
private val checkForButtonState: () -> Unit = {
val page = pages[binding.viewPager2.currentItem]
page.pageButtons?.forEach {
if (it.buttonState() == ButtonState.BUTTON_ACTION_COMPLETE) {
pageButtonCallback.onStepCompleted(it.titleId, pageFullyCompleted = false)
}
if (page.pageSteps() == PageState.PAGE_STEPS_COMPLETE) {
pageButtonCallback.onStepCompleted(0, pageFullyCompleted = true)
}
}
}
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
val page = pages[binding.viewPager2.currentItem]
when (page.titleId) {
R.string.notifications -> notificationCallback.onStepCompleted()
R.string.microphone_permission -> microphoneCallback.onStepCompleted()
R.string.camera_permission -> cameraCallback.onStepCompleted()
}
checkForButtonState.invoke()
return@registerForActivityResult
}
@ -394,8 +471,6 @@ class SetupFragment : Fragment() {
.show()
}
private lateinit var userDirCallback: SetupCallback
private val openCitraDirectory = registerForActivityResult<Uri, Uri>(
ActivityResultContracts.OpenDocumentTree()
) { result: Uri? ->
@ -403,11 +478,9 @@ class SetupFragment : Fragment() {
return@registerForActivityResult
}
CitraDirectoryHelper(requireActivity()).showCitraDirectoryDialog(result, userDirCallback)
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState)
}
private lateinit var gamesDirCallback: SetupCallback
private val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) {
@ -427,7 +500,7 @@ class SetupFragment : Fragment() {
homeViewModel.setGamesDir(requireActivity(), result.path!!)
gamesDirCallback.onStepCompleted()
checkForButtonState.invoke()
}
private fun finishSetup() {

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -14,18 +14,18 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.R
class SetupWarningDialogFragment : DialogFragment() {
private var titleId: Int = 0
private var descriptionId: Int = 0
private var helpLinkId: Int = 0
private var titleIds: IntArray = intArrayOf()
private var descriptionIds: IntArray = intArrayOf()
private var helpLinkIds: IntArray = intArrayOf()
private var page: Int = 0
private lateinit var setupFragment: SetupFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
titleId = requireArguments().getInt(TITLE)
descriptionId = requireArguments().getInt(DESCRIPTION)
helpLinkId = requireArguments().getInt(HELP_LINK)
titleIds = requireArguments().getIntArray(TITLES) ?: intArrayOf()
descriptionIds = requireArguments().getIntArray(DESCRIPTIONS) ?: intArrayOf()
helpLinkIds = requireArguments().getIntArray(HELP_LINKS) ?: intArrayOf()
page = requireArguments().getInt(PAGE)
setupFragment = requireParentFragment() as SetupFragment
@ -39,16 +39,23 @@ class SetupWarningDialogFragment : DialogFragment() {
}
.setNegativeButton(R.string.warning_cancel, null)
if (titleId != 0) {
builder.setTitle(titleId)
} else {
builder.setTitle("")
// Message builder to build multiple strings into one
val messageBuilder = StringBuilder()
for (i in titleIds.indices) {
if (titleIds[i] != 0) {
messageBuilder.append(getString(titleIds[i])).append("\n\n")
}
if (descriptionIds[i] != 0) {
messageBuilder.append(getString(descriptionIds[i])).append("\n\n")
}
}
if (descriptionId != 0) {
builder.setMessage(descriptionId)
}
if (helpLinkId != 0) {
builder.setTitle("Warning")
builder.setMessage(messageBuilder.toString().trim())
if (helpLinkIds.any { it != 0 }) {
builder.setNeutralButton(R.string.warning_help) { _: DialogInterface?, _: Int ->
val helpLinkId = helpLinkIds.first { it != 0 }
val helpLink = resources.getString(helpLinkId)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(helpLink))
startActivity(intent)
@ -61,23 +68,23 @@ class SetupWarningDialogFragment : DialogFragment() {
companion object {
const val TAG = "SetupWarningDialogFragment"
private const val TITLE = "Title"
private const val DESCRIPTION = "Description"
private const val HELP_LINK = "HelpLink"
private const val TITLES = "Titles"
private const val DESCRIPTIONS = "Descriptions"
private const val HELP_LINKS = "HelpLinks"
private const val PAGE = "Page"
fun newInstance(
titleId: Int,
descriptionId: Int,
helpLinkId: Int,
titleIds: IntArray,
descriptionIds: IntArray,
helpLinkIds: IntArray,
page: Int
): SetupWarningDialogFragment {
val dialog = SetupWarningDialogFragment()
val bundle = Bundle()
bundle.apply {
putInt(TITLE, titleId)
putInt(DESCRIPTION, descriptionId)
putInt(HELP_LINK, helpLinkId)
putIntArray(TITLES, titleIds)
putIntArray(DESCRIPTIONS, descriptionIds)
putIntArray(HELP_LINKS, helpLinkIds)
putInt(PAGE, page)
}
dialog.arguments = bundle

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -11,21 +11,35 @@ data class SetupPage(
val buttonIconId: Int,
val leftAlignedIcon: Boolean,
val buttonTextId: Int,
val pageButtons: List<PageButton>? = null,
val pageSteps: () -> PageState = { PageState.PAGE_STEPS_UNDEFINED },
)
data class PageButton(
val iconId: Int,
val titleId: Int,
val descriptionId: Int,
val buttonAction: (callback: SetupCallback) -> Unit,
val buttonState: () -> ButtonState = { ButtonState.BUTTON_ACTION_UNDEFINED },
val isUnskippable: Boolean = false,
val hasWarning: Boolean = false,
val stepCompleted: () -> StepState = { StepState.STEP_UNDEFINED },
val warningTitleId: Int = 0,
val warningDescriptionId: Int = 0,
val warningHelpLinkId: Int = 0
)
interface SetupCallback {
fun onStepCompleted()
fun onStepCompleted(pageButtonId : Int, pageFullyCompleted: Boolean)
}
enum class StepState {
STEP_COMPLETE,
STEP_INCOMPLETE,
STEP_UNDEFINED
enum class PageState {
PAGE_STEPS_COMPLETE,
PAGE_STEPS_INCOMPLETE,
PAGE_STEPS_UNDEFINED
}
enum class ButtonState {
BUTTON_ACTION_COMPLETE,
BUTTON_ACTION_INCOMPLETE,
BUTTON_ACTION_UNDEFINED
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -25,6 +25,7 @@ import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.TurboHelper
import java.lang.NullPointerException
import kotlin.math.min
@ -44,6 +45,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
private val settingsViewModel = NativeLibrary.sEmulationActivity.get()!!.settingsViewModel
// Stores the ID of the pointer that interacted with the 3DS touchscreen.
private var touchscreenPointerId = -1
@ -104,6 +106,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
swapScreen()
}
if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
TurboHelper.toggleTurbo(true)
}
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.id, button.status)
shouldUpdateView = true
}
@ -468,6 +474,18 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
)
)
}
if (preferences.getBoolean("buttonToggle15", false)) {
overlayButtons.add(
initializeOverlayButton(
context,
R.drawable.button_turbo,
R.drawable.button_turbo_pressed,
NativeLibrary.ButtonType.BUTTON_TURBO,
orientation
)
)
}
}
fun refreshControls() {
@ -673,6 +691,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
NativeLibrary.ButtonType.BUTTON_SWAP.toString() + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_SWAP_Y).toFloat() / 1000 * maxY
)
.putFloat(
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + "-X",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_X).toFloat() / 1000 * maxX
)
.putFloat(
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_Y).toFloat() / 1000 * maxY
)
.apply()
}
@ -816,6 +842,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
NativeLibrary.ButtonType.BUTTON_SWAP.toString() + portrait + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_SWAP_PORTRAIT_Y).toFloat() / 1000 * maxY
)
.putFloat(
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + portrait + "-X",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_PORTRAIT_X).toFloat() / 1000 * maxX
)
.putFloat(
NativeLibrary.ButtonType.BUTTON_TURBO.toString() + portrait + "-Y",
resources.getInteger(R.integer.N3DS_BUTTON_TURBO_PORTRAIT_Y).toFloat() / 1000 * maxY
)
.apply()
}
@ -928,6 +962,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
NativeLibrary.ButtonType.BUTTON_START,
NativeLibrary.ButtonType.BUTTON_SELECT,
NativeLibrary.ButtonType.BUTTON_SWAP -> 0.08f
NativeLibrary.ButtonType.BUTTON_TURBO -> 0.10f
NativeLibrary.ButtonType.TRIGGER_L,
NativeLibrary.ButtonType.TRIGGER_R,

View file

@ -12,6 +12,7 @@ import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager
import android.view.animation.PathInterpolator
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -307,16 +308,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
windowInsets
}
val openCitraDirectory = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree()
) { result: Uri? ->
if (result == null) {
return@registerForActivityResult
}
private fun createOpenCitraDirectoryLauncher(
permissionsLost: Boolean
): ActivityResultLauncher<Uri?> {
return registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result: Uri? ->
if (result == null) {
return@registerForActivityResult
}
CitraDirectoryHelper(this@MainActivity).showCitraDirectoryDialog(result)
CitraDirectoryHelper(this@MainActivity, permissionsLost)
.showCitraDirectoryDialog(result, buttonState = {})
}
}
val openCitraDirectory = createOpenCitraDirectoryLauncher(permissionsLost = false)
val openCitraDirectoryLostPermission = createOpenCitraDirectoryLauncher(permissionsLost = true)
val ciaFileInstaller = registerForActivityResult(
OpenFileResultContract()
) { result: Intent? ->

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -16,15 +16,15 @@ import org.citra.citra_emu.viewmodel.HomeViewModel
/**
* Citra directory initialization ui flow controller.
*/
class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity) {
fun showCitraDirectoryDialog(result: Uri, callback: SetupCallback? = null) {
class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity, private val lostPermission: Boolean) {
fun showCitraDirectoryDialog(result: Uri, callback: SetupCallback? = null, buttonState: () -> Unit) {
val citraDirectoryDialog = CitraDirectoryDialogFragment.newInstance(
fragmentActivity,
result.toString(),
CitraDirectoryDialogFragment.Listener { moveData: Boolean, path: Uri ->
val previous = PermissionsHandler.citraDirectory
// Do noting if user select the previous path.
if (path == previous) {
if (path == previous && !lostPermission) {
return@Listener
}
@ -36,7 +36,7 @@ class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity) {
)
if (!moveData || previous.toString().isEmpty()) {
initializeCitraDirectory(path)
callback?.onStepCompleted()
buttonState()
val viewModel = ViewModelProvider(fragmentActivity)[HomeViewModel::class.java]
viewModel.setUserDir(fragmentActivity, path.path!!)
viewModel.setPickingUserDir(false)

View file

@ -106,6 +106,40 @@ class DocumentsTree {
return node.uri ?: return Uri.EMPTY
}
@Synchronized
fun folderUriHelper(path: String, createIfNotExists: Boolean = false): Uri? {
root ?: return null
val components = path.split(DELIMITER).filter { it.isNotEmpty() }
var current = root
for (component in components) {
if (!current!!.loaded) {
structTree(current)
}
var child = current.findChild(component)
// Create directory if it doesn't exist and creation is enabled
if (child == null && createIfNotExists) {
try {
val createdDir = FileUtil.createDir(current.uri.toString(), component) ?: return null
child = DocumentsNode(createdDir, true).apply {
parent = current
}
current.addChild(child)
} catch (e: Exception) {
error("[DocumentsTree]: Cannot create directory, error: " + e.message)
return null
}
} else if (child == null) {
return null
}
current = child
}
return current?.uri
}
@Synchronized
fun isDirectory(filepath: String): Boolean {
val node = resolvePath(filepath) ?: return false

View file

@ -27,11 +27,11 @@ object EmulationMenuSettings {
.apply()
}
var showFps: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
var showPerformanceOverlay: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_showPerformanceOverlay", false)
set(value) {
preferences.edit()
.putBoolean("EmulationMenuSettings_ShowFps", value)
.putBoolean("EmulationMenuSettings_showPerformanceOverlay", value)
.apply()
}
var hapticFeedback: Boolean

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -34,7 +34,8 @@ object PermissionsHandler {
context.contentResolver.releasePersistableUriPermission(uri, takeFlags)
} catch (e: Exception) {
Log.error("[PermissionsHandler]: Cannot check citra data directory permission, error: " + e.message)
// Do not use native library logging, as the native library may not be loaded yet
android.util.Log.e("PermissionsHandler", "Cannot check citra data directory permission, error: ${e.message}")
}
return false
}

View file

@ -0,0 +1,45 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.utils
import android.widget.Toast
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.IntSetting
object TurboHelper {
private var turboSpeedEnabled = false
fun isTurboSpeedEnabled(): Boolean {
return turboSpeedEnabled
}
fun reloadTurbo(showToast: Boolean) {
val context = CitraApplication.appContext
val toastMessage: String
if (turboSpeedEnabled) {
NativeLibrary.setTemporaryFrameLimit(IntSetting.TURBO_LIMIT.int.toDouble())
toastMessage = context.getString(R.string.turbo_enabled_toast)
} else {
NativeLibrary.disableTemporaryFrameLimit()
toastMessage = context.getString(R.string.turbo_disabled_toast)
}
if (showToast) {
Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show()
}
}
fun setTurboEnabled(state: Boolean, showToast: Boolean) {
turboSpeedEnabled = state
reloadTurbo(showToast)
}
fun toggleTurbo(showToast: Boolean) {
setTurboEnabled(!TurboHelper.isTurboSpeedEnabled(), showToast)
}
}

View file

@ -142,6 +142,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.async_presentation);
ReadSetting("Renderer", Settings::values.async_shader_compilation);
ReadSetting("Renderer", Settings::values.spirv_shader_gen);
ReadSetting("Renderer", Settings::values.disable_spirv_optimizer);
ReadSetting("Renderer", Settings::values.use_hw_shader);
ReadSetting("Renderer", Settings::values.use_shader_jit);
ReadSetting("Renderer", Settings::values.resolution_factor);
@ -149,8 +150,8 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling);
// Work around to map Android setting for enabling the frame limiter to the format Citra expects
ReadSetting("Renderer", Settings::values.turbo_limit);
// Workaround to map Android setting for enabling the frame limiter to the format Citra expects
if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) {
ReadSetting("Renderer", Settings::values.frame_limit);
} else {
@ -183,11 +184,13 @@ void Config::ReadValues() {
layoutInt = static_cast<int>(Settings::LayoutOption::LargeScreen);
}
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layoutInt);
Settings::values.screen_gap = static_cast<int>(sdl2_config->GetReal("Layout", "screen_gap", 0));
Settings::values.large_screen_proportion =
static_cast<float>(sdl2_config->GetReal("Layout", "large_screen_proportion", 2.25));
Settings::values.small_screen_position = static_cast<Settings::SmallScreenPosition>(
sdl2_config->GetInteger("Layout", "small_screen_position",
static_cast<int>(Settings::SmallScreenPosition::TopRight)));
ReadSetting("Layout", Settings::values.screen_gap);
ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width);
@ -195,10 +198,12 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.custom_bottom_x);
ReadSetting("Layout", Settings::values.custom_bottom_y);
ReadSetting("Layout", Settings::values.custom_bottom_width);
ReadSetting("Layout", Settings::values.aspect_ratio);
ReadSetting("Layout", Settings::values.custom_bottom_height);
ReadSetting("Layout", Settings::values.cardboard_screen_size);
ReadSetting("Layout", Settings::values.cardboard_x_shift);
ReadSetting("Layout", Settings::values.cardboard_y_shift);
ReadSetting("Layout", Settings::values.upright_screen);
Settings::values.portrait_layout_option =
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
@ -290,6 +295,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.use_gdbstub);
ReadSetting("Debugging", Settings::values.gdbstub_port);
ReadSetting("Debugging", Settings::values.instant_debug_log);
ReadSetting("Debugging", Settings::values.enable_rpc_server);
for (const auto& service_module : Service::service_module_map) {
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);

View file

@ -113,6 +113,10 @@ async_shader_compilation =
# 0: GLSL, 1: SPIR-V (default)
spirv_shader_gen =
# Whether to disable the SPIRV optimizer. Disabling it reduces stutter, but may slightly worsen performance
# 0: Enabled, 1: Disabled (default)
disable_spirv_optimizer =
# Whether to use hardware shaders to emulate 3DS shaders
# 0: Software, 1 (default): Hardware
use_hw_shader =
@ -156,6 +160,10 @@ use_frame_limit =
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
# Alternative frame limit which can be triggered by the user
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
turbo_limit =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 0.0 for all.
bg_red =
@ -167,7 +175,7 @@ bg_green =
render_3d =
# Change 3D Intensity
# 0 - 100: Intensity. 0 (default)
# 0 - 255: Intensity. 0 (default)
factor_3d =
# The name of the post processing shader to apply.
@ -202,6 +210,21 @@ disable_right_eye_render =
# 5: Custom Layout
layout_option =
# Position of the performance overlay
# 0: Top Left
# 1: Center Top
# 2: Top Right
# 3: Bottom Left
# 4: Center Bottom
# 5: Bottom Right
performance_overlay_position =
# Screen Gap - adds a gap between screens in all two-screen modes
# Measured in pixels relative to the 240px default height of the screens
# Scales with the larger screen (so 24 is 10% of the larger screen height)
# Default value is 0.0
screen_gap =
# Large Screen Proportion - Relative size of large:small in large screen mode
# Default value is 2.25
large_screen_proportion =
@ -256,6 +279,10 @@ custom_portrait_bottom_height =
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
swap_screen =
# Expands the display area to include the cutout (or notch) area
# 0 (default): Off, 1: On
expand_to_cutout_area =
# Screen placement settings when using Cardboard VR (render3d = 4)
# 30 - 100: Screen size as a percentage of the viewport. 85 (default)
cardboard_screen_size =
@ -422,6 +449,10 @@ gdbstub_port=24689
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
instant_debug_log =
# Enable RPC server for scripting purposes. Allows accessing guest memory remotely.
# 0 (default): Off, 1: On
enable_rpc_server =
# Delay the start of apps when LLE modules are enabled
# 0: Off, 1 (default): On
delay_start_for_lle_modules =

View file

@ -215,7 +215,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
std::unique_ptr<Frontend::GraphicsContext> cpu_context;
system.GPU().Renderer().Rasterizer()->LoadDiskResources(stop_run, &LoadDiskCacheProgress);
system.GPU().Renderer().Rasterizer()->LoadDefaultDiskResources(stop_run,
&LoadDiskCacheProgress);
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
@ -643,16 +644,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNI
jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& core = Core::System::GetInstance();
jdoubleArray j_stats = env->NewDoubleArray(4);
jdoubleArray j_stats = env->NewDoubleArray(9);
if (core.IsPoweredOn()) {
auto results = core.GetAndResetPerfStats();
// Converting the structure into an array makes it easier to pass it to the frontend
double stats[4] = {results.system_fps, results.game_fps, results.frametime,
results.emulation_speed};
double stats[9] = {results.system_fps, results.game_fps,
results.emulation_speed, results.time_vblank_interval,
results.time_hle_svc, results.time_hle_ipc,
results.time_gpu, results.time_swap,
results.time_remaining};
env->SetDoubleArrayRegion(j_stats, 0, 4, stats);
env->SetDoubleArrayRegion(j_stats, 0, 9, stats);
}
return j_stats;
@ -781,4 +785,14 @@ void Java_org_citra_citra_1emu_NativeLibrary_unlinkConsole(JNIEnv* env, jobject
HW::UniqueData::UnlinkConsole();
}
void Java_org_citra_citra_1emu_NativeLibrary_setTemporaryFrameLimit(JNIEnv* env, jobject obj,
jdouble speed) {
Settings::temporary_frame_limit = speed;
Settings::is_temporary_frame_limit = true;
}
void Java_org_citra_citra_1emu_NativeLibrary_disableTemporaryFrameLimit(JNIEnv* env, jobject obj) {
Settings::is_temporary_frame_limit = false;
}
} // extern "C"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -1,11 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/citra_outlineVariant" />
</shape>

View file

@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620Z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620Z"/>
</vector>

View file

@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M160,840q-33,0 -56.5,-23.5T80,760v-560q0,-33 23.5,-56.5T160,120h560q33,0 56.5,23.5T800,200v80h80v80h-80v80h80v80h-80v80h80v80h-80v80q0,33 -23.5,56.5T720,840L160,840ZM160,760h560v-560L160,200v560ZM240,680h200v-160L240,520v160ZM480,400h160v-120L480,280v120ZM240,480h200v-200L240,280v200ZM480,680h160v-240L480,440v240ZM160,200v560,-560Z"/>
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M160,840q-33,0 -56.5,-23.5T80,760v-560q0,-33 23.5,-56.5T160,120h560q33,0 56.5,23.5T800,200v80h80v80h-80v80h80v80h-80v80h80v80h-80v80q0,33 -23.5,56.5T720,840L160,840ZM160,760h560v-560L160,200v560ZM240,680h200v-160L240,520v160ZM480,400h160v-120L480,280v120ZM240,480h200v-200L240,280v200ZM480,680h160v-240L480,440v240ZM160,200v560,-560Z"/>
</vector>

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