Compare commits

..

250 Commits
2124 ... master

Author SHA1 Message Date
RedBlackAka
4867bb2e2b
video_core: Change unimplemented gas stub behaviour for Vulkan (#2165) 2026-05-30 23:53:16 +02:00
Wunk
4e4c7e687b
renderer_gl: Fix disabled cubemap texture units (#2159)
Disabled texture units on OpenGL always use a null 2D texture, but there are
cases where the null texture should be a null cubemap rather than a 2D
texture to match the active texture binding state.

Fixes some errors that occur in `OpenGLState::Apply()` found
in Brain Age: Concentration Training.
2026-05-28 15:42:02 +02:00
OpenSauce04
56f738eb06 jni/config.cpp: Reworded default config assert message for clarity 2026-05-27 18:05:48 +01:00
RedBlackAka
b1e537a485
Qt: Do not show Microprofile option at all if disabled (#2156) 2026-05-26 22:44:51 +02:00
OpenSauce04
59da460177 Migrate translations to new language codes 2026-05-25 19:08:09 +01:00
OpenSauce04
8bdb60a6e1 Updated translations via Transifex 2026-05-25 19:08:09 +01:00
OpenSauce04
383a28795e ci: Build azahar-room Docker image for ARM64 2026-05-25 17:04:47 +01:00
OpenSauce04
725544f3b4 ci: Add --no-cache to Docker build command
This is mostly just for specificity. In practice this will never do anything in CI because there will never be any cache to use, but if there was, we wouldn't want to use it
2026-05-25 17:04:47 +01:00
OpenSauce04
135f10320a docker: Fix azahar-room Dockerfile failing to build with podman 2026-05-25 17:04:47 +01:00
OpenSauce04
0b7114cbf8 docker: Break up build commands into seperate RUN calls
I think this was from back when the Dockerfile was first being written, before the 'builder' and 'final' stages were introduced which make this former optimization attempt completely redundant
2026-05-25 17:04:47 +01:00
OpenSauce04
95d42cb40a docker: Remove now-redundant CMake options from azahar-room Dockerfile 2026-05-25 17:04:47 +01:00
OpenSauce04
f0bc64d967 IntListSetting.kt: Use generated layouts_to_cycle key 2026-05-24 12:55:09 +01:00
Masamune3210
ad8526c4cf
pica_core.cpp: Initialize IRQ_CMP (#2143)
* initialize IRQ_CMP

* add comment explaining requirement

* Fix formatting

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-05-24 11:07:57 +01:00
project516
ae7d7dca1f ci: Update github actions to NodeJS 24 (#2110)
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-05-23 21:24:54 +01:00
OpenSauce04
4a4b75b0de libretro.yml: Don't fail fast in macOS job matrix 2026-05-23 19:59:34 +01:00
PabloMK7
b186b04995
core: Refactor thread unschedule and add debug frontend unschedule (#2145) 2026-05-23 17:25:14 +02:00
Wunk
ab6896a2ca
core: Fix debug compile error (#2132)
Fixes a compilation error when building in Debug mode. `count` should be `async_data->count` in this log message.
2026-05-17 15:15:21 +02:00
OpenSauce04
e11f3da493 Updated translations via Transifex 2026-05-16 17:32:14 +01:00
OpenSauce04
8ffb94b06c Implement Z3DS compression CLI in new citra_cli static library 2026-05-16 17:13:31 +01:00
PabloMK7
267887d7a9
Add attestation support to increase release security (#2117)
* ci: Add sbom and attestation

* tools: Add verify-release.sh

* verify-release.sh: Set executable permission

* verify-release.sh: Put downloads into a gitignored directory

* tools: Make verify-release also download sbom

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-05-14 14:52:10 +02:00
OpenSauce04
778ca369cd ci: Strip libretro cores after building 2026-05-12 11:56:22 +01:00
OpenSauce04
dbe7fd979f cmake: Add EXCLUDE_FROM_ALL to targets where applicable 2026-05-09 14:01:56 +01:00
PabloMK7
1c7c7a5f1b
svc: Fix instruction cache invalidation only affecting current core (#2100) 2026-05-09 14:03:55 +02:00
OpenSauce04
bf59d26c48 externals: Update dllwalker to commit 2f8b349 2026-05-09 10:32:52 +01:00
OpenSauce04
652fc02175 ci: Implement MXE CI/CD build job 2026-05-09 10:32:52 +01:00
OpenSauce04
854e198196 cmake: Implemented bundle target for MXE builds
Just copies the content of bin/<type>/ to bundle/
2026-05-09 10:32:52 +01:00
OpenSauce04
5ecd402811 cmake: Explicitly use gcc-ar instead of ar for MXE builds 2026-05-09 10:32:52 +01:00
OpenSauce04
0ce2a30d20 Implement proper DLL resolution for MXE builds 2026-05-09 10:32:52 +01:00
OpenSauce04
644a181aff cmake: Explicitly disable BUILD_SHARED_LIBS 2026-05-09 10:32:52 +01:00
OpenSauce04
422c7865a3 For Linux --> Windows cross-compilation, copy all cross-compiled DLLs during build
As per the comment, this is just to get the build functioning pending a real solution
2026-05-09 10:32:52 +01:00
OpenSauce04
ca99574700 tests: Don't run catch_discover_tests when cross-compiling to a different OS 2026-05-09 10:32:52 +01:00
OpenSauce04
f902010f04 externals: Don't fall back to bundled OpenSSL if USE_SYSTEM_OPENSSL is enabled 2026-05-09 10:32:52 +01:00
PabloMK7
929a51afc6
audio: Add option to simulate headphones plugged in (#2099) 2026-05-08 15:19:53 +02:00
PabloMK7
260f08c497
core: Add async filesystem operations (#2098) 2026-05-08 11:35:47 +02:00
PabloMK7
921ea178b9
ui: Made rom loading errors more clear and user friendly (#2097) 2026-05-07 20:39:30 +02:00
PabloMK7
b540725090
Revamp GDB implemenation and add a some minor debug features (#2086) 2026-05-07 13:48:35 +02:00
PabloMK7
5ddbaeae23
gsp: Fix GPU interrupt queue and add GPU timing emulation (#2095) 2026-05-07 01:36:21 +02:00
OpenSauce04
b081f800a4 Revert "ci: Override Android SDK Ninja with newer version"
This reverts commit eee7f076ee.
2026-05-04 17:40:37 +01:00
OpenSauce04
76a71d76d4 externals: Revert to a patched version of OpenAL v1.24.1 2026-05-04 17:40:37 +01:00
Rodrigo Iglesias
83eef0012e macOS: normalize SDMC directory filenames (#2080)
* macOS: normalize SDMC directory filenames

* Guard macOS filename normalization behind __APPLE__

* Guard macOS filename normalization test

* Apply clang-format

* Update license headers
2026-05-03 00:21:53 +02:00
OpenSauce04
ec6a0dd1c8 ci: Migrate Transifex runner to latest tag
The `transifex` tag has now been removed due to a seperate image no longer being necessary
2026-04-26 15:29:29 +01:00
OpenSauce04
eb498e5ecd qt: Fixed outdated use of qt_add_lupdate 2026-04-26 15:25:38 +01:00
OpenSauce04
4fa793b945 android: Bump NDK and AGP versions
NDK: 27.1.x --> 27.3.x
AGP: 8.13.1 --> 8.13.2
2026-04-26 13:55:16 +01:00
OpenSauce04
5d84dfed91 Fix building w/ OpenAL on OpenBSD
- Explicitly disable Solaris backend on OpenBSD
- Update OpenAL to v1.25.1
2026-04-26 12:52:32 +01:00
OpenSauce04
eee7f076ee ci: Override Android SDK Ninja with newer version 2026-04-26 12:32:49 +01:00
Francesco Saltori
996abd1eaf Remove old CONTRIBUTING.md file 2026-04-25 21:57:46 +01:00
Wunk
91128d6625
shader_jit: Emit LG2/EX2 subroutines on-demand (#2046)
Rather than emitting these subroutine functions for _every_ shader, only emit
the subroutines when the `LG2` and `EX2` instructions are actually used.
This saves a good chunk of memory across all shaders.

Inspired by Tanuki3DS.
2026-04-24 20:34:46 +02:00
Eric Warmenhoven
37b6c91de6 libretro: update docker image for mxe github action 2026-04-24 14:50:09 +01:00
OpenSauce
b6c54ac8c7
Update PR template 2026-04-24 14:04:16 +01:00
OpenSauce04
b3ee2d8ac5 tools/README.md: Re-updated release checklist
Knew I'd forgotten something (:
2026-04-21 14:55:40 +01:00
OpenSauce04
9701a3d874 tools/README.md: Updated release checklist 2026-04-21 10:47:52 +01:00
OpenSauce04
a276623dbb Updated translations via Transifex 2026-04-21 10:47:49 +01:00
PabloMK7
2fff086e81
qt: Temporarily fix fullscreen on msys2 builds (#2049)
* qt: Temporarily fix fullscreen on msys2 builds

* Removed excessive endif comments

We really only need these when nesting ifdefs

* blockRoundedCorners: Invert if condition for readability

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-04-19 13:47:49 +01:00
OpenSauce04
5bc58c78ed OpenBSD build fixes
- Specify OpenBSD's X11 include directory
- Add OpenBSD-specific linker flag to allow W|X
2026-04-19 13:37:26 +01:00
PabloMK7
0fe6a8c7df
video_core: Properly handle non RGBA8 shadow textures (#2047) 2026-04-17 21:45:50 +02:00
Cobalt
d4b5633cf0
qt Fix compilation issues in status LED code (#2045)
solves a build issue a ***lot*** of [L4T Megascript](https://github.com/cobalt2727/L4T-Megascript) users were reporting to me on Linux. C++ is definitely not my strong suit, but per https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/pow-powf-powl?view=msvc-170#remarks `pow` has identical behavior in C++ projects. no clue why `powf` worked fine on your environment when developing this but not other people's.

```cmake
[ 98%] Building CXX object src/citra_qt/CMakeFiles/citra_qt.dir/qt_image_interface.cpp.o
/home/runner/azahar/src/citra_qt/notification_led.cpp:56:15: error: no member named 'powf' in namespace 'std'; did you mean simply 'powf'?
   56 |     float t = std::powf(pwm, 1.f / gamma);
      |               ^~~~~~~~~
      |               powf
/usr/include/aarch64-linux-gnu/bits/mathcalls.h:140:1: note: 'powf' declared here
  140 | __MATHCALL_VEC (pow,, (_Mdouble_ __x, _Mdouble_ __y));
      | ^
/usr/include/math.h:280:3: note: expanded from macro '__MATHCALL_VEC'
  280 |   __MATHCALL (function, suffix, args)
      |   ^
/usr/include/math.h:287:3: note: expanded from macro '__MATHCALL'
  287 |   __MATHDECL (_Mdouble_,function,suffix, args)
      |   ^
/usr/include/math.h:289:3: note: expanded from macro '__MATHDECL'
  289 |   __MATHDECL_1(type, function,suffix, args); \
      |   ^
note: (skipping 1 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
/usr/include/math.h:297:15: note: expanded from macro '__MATHDECL_1_IMPL'
  297 |   extern type __MATH_PRECNAME(function,suffix) args __THROW
      |               ^
/usr/include/math.h:326:34: note: expanded from macro '__MATH_PRECNAME'
  326 | # define __MATH_PRECNAME(name,r) name##f##r
      |                                  ^
<scratch space>:97:1: note: expanded from here
   97 | powf
      | ^
1 error generated.
make[2]: *** [src/citra_qt/CMakeFiles/citra_qt.dir/build.make:1573: src/citra_qt/CMakeFiles/citra_qt.dir/notification_led.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [CMakeFiles/Makefile2:3481: src/citra_qt/CMakeFiles/citra_qt.dir/all] Error 2
make: *** [Makefile:166: all] Error 2
```
2026-04-16 09:53:08 +02:00
OpenSauce04
afbaf8e485 android: Fixed incorrect location for Latin American Spanish locale files 2026-04-14 21:17:40 +01:00
OpenSauce04
52b1e01a6f Updated translations via Transifex 2026-04-14 20:15:51 +01:00
PabloMK7
f1cd5f5ff4
video_core: fix color blend min/max mode in OpenGL (#2038)
* video_core: fix check for fragment color blend emulation

* video_core: Fix typo in gl fragment shader gen
2026-04-14 19:26:22 +02:00
bug
1edc5de18e
android: Stop emulation state if activity destroyed and fix relaunching from intents (#2000) 2026-04-13 16:49:31 +02:00
PabloMK7
727377c012 discord rpc: Change how info is displayed 2026-04-13 14:22:58 +02:00
PabloMK7
4dbe0fd497 qt: Properly fix discord rich presence 2026-04-13 14:22:58 +02:00
PabloMK7
6d230d28da Revert "qt: Try to fix Discord Rich Presence not updating correctly (#2013)"
This reverts commit 5983a23d38.
2026-04-13 14:22:58 +02:00
PabloMK7
336d871a7f
core: Add CMAKE option to disable built-in keyblob (#2024)
* core: Add CMAKE option to disable built-in keyblob

* Additional assurance against accidental inclusion of default_keys.h

* default_keys.h: Make default_keys_enc constexpr

* default_keys.h: Make default_keys_enc_size constexpr

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-04-12 18:57:55 +01:00
Eric Warmenhoven
e8c75b4107
libretro: vulkan: wait before ticking (#2004)
Ensure the scheduler worker thread has finished processing all dispatched command chunks before the rasterizer cache's garbage collector destroys sentenced surfaces.
2026-04-11 22:56:09 +02:00
PabloMK7
0fc3d692b9
android: Allow deleting per title disk shader cache (#2032) 2026-04-11 20:22:20 +02:00
jbm11208
5983a23d38
qt: Try to fix Discord Rich Presence not updating correctly (#2013) 2026-04-11 17:19:13 +02:00
PabloMK7
599069eb33
android: Block activity recreation due to orientation changes on boot (#2030) 2026-04-11 16:40:07 +02:00
PabloMK7
6eca4afac5
core: Fix typo in InfoLedPattern (#2029) 2026-04-11 13:23:42 +02:00
Eric Warmenhoven
b2faa299d5 libretro: fix linker error with tests 2026-04-09 22:00:49 +01:00
PabloMK7
3d69741076 qt: Show emulated notification LED 2026-04-09 19:19:59 +02:00
PabloMK7
d29e15f219 core: Add notification LED emulation 2026-04-09 19:19:59 +02:00
OpenSauce04
c650473fdc Default to Vulkan renderer on Android 2026-04-06 18:58:52 +01:00
SiniKraft
000530c028 android : Fix navigation bar overlapping the Show Home Menu apps button 2026-04-06 11:25:14 +02:00
SiniKraft
df05b5f3db android : Fix emulation exit showing an Invalid Rom Format error 2026-04-06 11:25:14 +02:00
Wunkolo
06a535f50e shader_jit: Add SETEMIT unit test 2026-04-05 23:02:56 +02:00
Wunkolo
4cbd75b413 shader_jit: Optimize GeometryEmitter SETEMIT state
The `SETEMIT`/`SETE` instruction only actually encodes 4 bits of possible state,
but this is currently expanded into three separate bytes of
data(four with padding) and requires three separate byte-writes for the x64 and
a64 JITs to write into. These 4 bits from the instruction can instead be
compacted into a singular 1-byte write from the JIT by encoding these 4 bits of
state into a singular byte at JIT-time, and unpacking this data is instead done
by `GeometryEmitter::Emit`. This also allows the serializer to use a singular
byte for all 3 fields now as well.
2026-04-05 23:02:56 +02:00
Wunk
60f331b43b
cmake: Allow Catch test discovery (#1997)
Allows individual unit-tests to be discovered, tested, and debugged by IDEs without having to run _all_ of the unit-tests just to debug one specific test.
2026-04-05 23:01:05 +02:00
OpenSauce04
ba6f8cb744 Disable Vulkan renderer on NetBSD because OS Vulkan support doesn't exist 2026-04-05 13:11:40 +01:00
OpenSauce04
118579adb3 Fixed launch failures on NetBSD due to PaX MPROTECT restrictions 2026-04-05 13:11:40 +01:00
OpenSauce04
23393904e0 Fixed NetBSD build issues
- Added missing include
- Fixed X11 include directory not being included
2026-04-05 13:11:40 +01:00
GasInfinity
3066887ff4 fix: don't crash when getaddrinfo gets a small or empty buffer in soc:U
* those are valid in hw!
2026-03-29 20:08:41 +02:00
GasInfinity
901f010913 fix: properly handle getaddrinfo/getnameinfo return values in soc:U 2026-03-29 20:08:41 +02:00
Why? You Don't Know?
5fc9732f05
android: Convert bgColor default values to Int (#1959) 2026-03-29 20:05:54 +02:00
OpenSauce04
39363cd435 ci: Merge standalone macOS CI/CD jobs into single runner 2026-03-28 15:32:24 +00:00
GasInfinity
60661c3b8b fix: correct the response of SendToOther in soc:U 2026-03-28 14:14:25 +00:00
PabloMK7
be0f096f48
core: Set boss as a online LLE module (#1952) 2026-03-28 12:43:26 +01:00
Marcin Serwin
6201256e15
cmake: Add option to use system oaknut (#1947)
Signed-off-by: Marcin Serwin <marcin@serwin.dev>
2026-03-28 12:21:42 +01:00
PabloMK7
af188bb7b7
core: kernel: Implement thread cpu time limit for core1 (#1934) 2026-03-28 12:20:33 +01:00
RedBlackAka
0862e5e98a
Qt: Remove Vulkan warning and OpenGL Mesa override (#1938) 2026-03-28 12:17:15 +01:00
PabloMK7
49b0bef17d
android: Fix visibility of hidden system titles (#1935) 2026-03-28 12:04:43 +01:00
PabloMK7
f14f095e72
core: svc: Add better logging to svc failures (#1948) 2026-03-28 12:03:16 +01:00
PabloMK7
7e58ac5bcf android: Handle surface lost during swapchain creation 2026-03-27 18:31:13 +00:00
OpenSauce04
7220bd2edd externals: Updated to boost 1.90 + LLVM 22 workaround 2026-03-27 18:30:41 +00:00
PabloMK7
d4e9daa739
android: Fix compression and decompression on vanilla build (#1939) 2026-03-24 18:58:56 +01:00
OpenSauce04
9b045bf837 libretro: Replace render_touchscreen setting with enable_touch_pointer_timeout
Touch pointer rendering is now always enabled, but unless a controller is being used to move the touchscreen cursor, it will be hidden due to the timeout which is also enabled by default.
2026-03-23 13:07:39 +00:00
PabloMK7
7a600e28d2 android: Fix icon not showing if update title fails to load 2026-03-22 22:57:32 +01:00
PabloMK7
5a07260e1b loader: Fix identifying zcci files when system files are not set up 2026-03-22 22:57:32 +01:00
OpenSauce04
2c8297c34c android: Fixed native path intent URIs not launching apps correctly 2026-03-21 22:01:00 +00:00
OpenSauce
7f9f1e90ca
Added prerelease badge to readme 2026-03-20 18:42:21 +00:00
OpenSauce04
04a543290a Added AI policy document 2026-03-20 14:15:36 +00:00
OpenSauce04
8bcb8a225a Updated translations via Transifex 2026-03-20 12:42:47 +00:00
David Griswold
64cb0b57fb nullptr check on update_surface 2026-03-20 12:38:41 +00:00
OpenSauce04
7a60160f68 Updated translations via Transifex 2026-03-19 14:50:43 +00:00
OpenSauce04
dc91e8803e Updated compatibility data 2026-03-19 14:36:28 +00:00
PabloMK7
c55435b78d
android: Fix lifecycle bugs on SetupFragment (#1902)
* android: Attempt fixing lifecycle bugs on SetupFragment

* android: Fixed setup page number being lost on recreation

* Move the registerForActivityResult to MainActivity

* Code cleanup

* ViewUtils.kt: Added missing guard clause in showView

* Fixed permission buttons appearing to duplicate during setup

* ViewUtils.kt: Updated license header

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-03-19 13:48:58 +01:00
David Griswold
f721a474e4
force android emu_window to update height and width on surface change, solving aspect ratio issues on some screens (#1907) 2026-03-19 13:46:56 +01:00
PabloMK7
ab39df3ff0
android: Handle asynchronous screen disconnects (#1903) 2026-03-17 19:24:30 +01:00
OpenSauce04
2ff04dccba Removed confusing punctuation from "Failed to obtain loader" log message 2026-03-17 12:25:54 +00:00
PabloMK7
3d5ba09eb1
android: Fix launching applications through intent data in vanilla build (#1896)
* android: Fix launching applications through intent data in vanilla build

* GameHelper.kt: Use Uri.scheme where applicable

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-03-17 12:15:33 +00:00
Cobalt
ae9972b6be
Qt compat fix (again) (#1895)
* fix compilation on older QT6

* fix indent

my C++ is very rusty

* fix indents again

* Fixed formatting

* fix capitalization error

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-03-15 18:29:30 +00:00
OpenSauce04
e677f72bda android: Fixed onResume attempting to pause instead of unpause 2026-03-15 15:50:05 +00:00
OpenSauce04
4109bb200b android: Show Azahar version in toast when double-clicking on Applications 2026-03-15 14:51:53 +00:00
PabloMK7
6ad642a984
android: camera: Fix still image camera input (#1892) 2026-03-15 15:17:50 +01:00
Cobalt
ccd61d0134
qt: fix compilation on older QT6 (#1886)
* fix compilation on older QT6

* fix indent

my C++ is very rusty

* fix indents again

* Fixed formatting

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-03-15 13:46:50 +01:00
OpenSauce04
d97da17263 android: Fixed installed app shortcut creation failing on vanilla 2026-03-14 19:47:00 +00:00
OpenSauce04
0ff2aebdf1 android: Fixed Amiibo files failing to load on vanilla 2026-03-14 17:57:03 +00:00
Lillie
100b00b3b5 Fix typo "cartidges" 2026-03-13 10:02:38 +00:00
OpenSauce04
9e162705f4 Updated translations via Transifex 2026-03-12 20:54:03 +00:00
PabloMK7
b3f82618d7 android: Use StorageManager to get removable media path 2026-03-12 20:49:20 +00:00
OpenSauce04
fc6a410dfa android: Separate package IDs for build variants 2026-03-12 20:45:19 +00:00
OpenSauce04
56a563c239 macos: Add warning dialog when launching azahar executable directly 2026-03-12 18:14:12 +00:00
OpenSauce04
6715959382 shader_jit_a64_compiler: Added missing include
Fixes a build issue on ARM64 Linux w/ GCC

Fix proposed by PabloMK7
2026-03-12 17:18:29 +00:00
OpenSauce04
75cd4ce649 ci: Add build tests for ARM Linux
No binaries yet
2026-03-12 16:14:43 +00:00
Eric Warmenhoven
463db8ffe4 Move libretro ci file to .ci 2026-03-12 15:18:44 +00:00
Eric Warmenhoven
ecaebc54ff Rename libretro ci file 2026-03-12 15:01:29 +00:00
David Griswold
1febb83942
qt: Add controller touchpad support (#777) 2026-03-12 00:21:17 +01:00
PabloMK7
a3db3be4a6
video_core: vulkan: Fix Framebuffer move behaviour (#1865) 2026-03-11 23:36:03 +01:00
PabloMK7
909e4b7e1f
core: apt: Fix GetStartupArgument operation order (#1862) 2026-03-11 18:48:15 +01:00
PabloMK7
e92272ce31
core: fs: Implement NAND archives (#1861) 2026-03-11 15:06:28 +01:00
PabloMK7
51170ea85d
core: ac: Implement GetNZoneBeaconNotFoundEvent (#1860) 2026-03-11 13:49:56 +01:00
PabloMK7
9a7cc43d81
core: kernel: Set debug thread name based on process name (#1859) 2026-03-11 13:49:39 +01:00
OpenSauce04
e351fa56ce Updated translations via Transifex 2026-03-10 19:25:21 +00:00
OpenSauce04
845fadf49e android: Fix IOFile::GetFd not functioning as expected in vanilla
This fixes the cheats menu not loading correctly
2026-03-10 19:17:37 +00:00
Eric Warmenhoven
98910fed1c default libretro "touch support" option on 2026-03-10 16:28:22 +00:00
RedBlackAka
d9f28c5b2a
Qt: Improve update checker system to prevent downgrades #1749 (#1768)
* Qt: Improve update checker system to prevent downgrades

* Code cleanup

* Return 0 as fallback

* Satisfy clang-format

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-03-10 16:01:40 +00:00
OpenSauce04
784fc8cca9 Updated translations via Transifex 2026-03-08 19:45:39 +00:00
OpenSauce04
a35a619903 ci: Add version suffix to libretro archive filenames 2026-03-08 19:43:38 +00:00
Eric Warmenhoven
3a5fa35449 libretro: draw cursor in vulkan 2026-03-08 19:04:10 +00:00
OpenSauce04
0407568006 android: Fix long-press menu for games in app dir displaying no file error 2026-03-08 18:46:09 +00:00
PabloMK7
30779d35cd Fix 3DSX being treated as invalid applications 2026-03-08 18:46:09 +00:00
PabloMK7
32da5ea0ae Read media type and pass it to UninstallProgram 2026-03-08 18:46:09 +00:00
OpenSauce04
e878174df8 android: Fixed games located in an application directory not being accessible 2026-03-08 18:46:09 +00:00
OpenSauce04
7ad6621f91 android: Fixed CIA installation failure in vanilla variant
This introduces a very hacky way of telling TranslateFilePath that a path is already native and doesn't need translating. I don't like this very much, but addressing this in any other way is very much outside of the scope of this PR.
2026-03-08 18:46:09 +00:00
OpenSauce04
8e1ffc1bdc Fixed a possible app crash when calling AndroidStorage::GetUserDirectory 2026-03-08 18:46:09 +00:00
OpenSauce04
96485a22f8 android: Split path resolution logic of getUserDirectory into seperate function 2026-03-08 18:46:09 +00:00
OpenSauce04
97c9a51015 android: Re-implement title uninstallation via Service::AM::UninstallProgram 2026-03-08 18:46:09 +00:00
OpenSauce04
a8ebd0f551 DocumentsTree: Put resolvePath under a strict directory whitelist 2026-03-08 18:46:09 +00:00
OpenSauce04
fac63ce6b1 DocumentsTree: Re-implement getFilename without resolvePath 2026-03-08 18:46:09 +00:00
OpenSauce04
c71b2dc822 Move AndroidCanUseRawFS and AndroidTranslateFilename into AndroidStorage namespace 2026-03-08 18:46:09 +00:00
PabloMK7
e87635095a android: Fully use raw FS access on vanilla builds 2026-03-08 18:46:09 +00:00
PabloMK7
0c624f16a7
core: Stub AC::CancelConnectAsync (#1846) 2026-03-08 19:26:39 +01:00
PabloMK7
d1d14cef79
core: Disable BOSS for enabled LLE online services (#1847) 2026-03-08 19:24:52 +01:00
PabloMK7
7d5da9eaeb
core: Fix application jump parameters (#1845) 2026-03-08 18:41:31 +01:00
lannoene
abc1980418
Add DLP:SRVR + misc bug fixes (#1828)
* Add DLP:SRVR + add friend code seed hack for LM1 + add multiple filters in IPC debugger + fix cia_container smdh offset not being applied, possible IF statement underflowing + default initialize boss variables + fix IPC header asserts in AM functions + add extra debug info to IPC param assert

* Make server & client more resistant to high ping conditions

* Remove DLP from list of online recommended modules

* Fix license headers + fix clang formatting + fix server create network assert

* Fix recorder.cpp license header
2026-03-08 15:48:09 +01:00
Fausto Núñez Alberro
70c9e18eea
libretro: enable VK_EXT_custom_border_color extension (#1825)
Fixes crash on startup with Vulkan renderer. The extension and its
features must be enabled during device creation for samplers using
custom border colors to work.

Fixes #1824
2026-03-07 21:56:44 +01:00
PabloMK7
46ca83cc36
core: Enable LLE CECD and BOSS when online LLE modules are enabled (#1842) 2026-03-07 21:11:45 +01:00
PabloMK7
1e0df67cc4
file_util: Fix file behaviour on Windows (#1841) 2026-03-07 20:33:29 +01:00
PabloMK7
ced1ec0112
core: nwm: Implement NWM_SOC::GetMACAddress (#1840) 2026-03-07 17:53:10 +01:00
Fausto Núñez Alberro
1b41c78afc
libretro: expose large_screen_proportion as a core option (#1833)
Adds the large_screen_proportion setting to libretro core options,
allowing users to adjust the ratio between the large and small screens
in the "Large Screen, Small Screen" layout.

This is useful on devices like the Steam Deck where the default 4x ratio
makes the small screen too tiny to be practical.

Values range from 1.00x to 6.00x in 0.25 increments.

Closes #1832
2026-03-07 16:44:27 +01:00
PabloMK7
748b97ac5e
core: ndm: Implement suspend daemons count (#1839) 2026-03-07 14:26:40 +01:00
PabloMK7
2207be30a9
core: Add "toggle unique data console type" option (#1826) 2026-03-06 01:23:35 +01:00
PabloMK7
8d284aeccf
video_core: Fix a few vulkan validation issues (#1818) 2026-03-04 21:05:22 +01:00
jbm11208
d49aa070fd
qt: Always receive camera data from UI thread (#1812)
* Make camera functions thread-safe

* Revert redundant changes to qt_multimedia_camera.h and add comments to qt_camera_base.cpp
2026-03-04 16:58:20 +01:00
OpenSauce04
92cd488754 Move version numbers to end of release file filenames 2026-03-04 14:48:04 +00:00
OpenSauce04
efccedbbd2 cmake: Version info generation improvements
- Allow GIT-COMMIT and GIT-TAG files to override real git info (useful for testing version-related functionality such as update checks)
- Always re-configure scm_rev.cpp when configuring with CMake (fixes an issue where the version number would just not update in incremental builds)
2026-03-04 11:45:55 +00:00
OpenSauce04
93e831decb android: Fixed invalid default config content, resulting in a crash 2026-03-03 18:34:53 +00:00
OpenSauce04
d6fadff3ee Updated translations via Transifex 2026-03-03 16:30:03 +00:00
OpenSauce04
af980b4117 Remove _android suffix from Android libretro core filename 2026-03-03 16:26:08 +00:00
OpenSauce04
f23e296802 ci: Libretro artifacts are now packed within properly named archives 2026-03-03 16:04:12 +00:00
OpenSauce04
a0ab331928 ci: Use macOS 26 runner for Intel build 2026-03-02 23:27:32 +00:00
OpenSauce
068d6598bc
Configuration backend improvements Pt. 1 (#1762)
* Convert all setting keys in settings.h into hana strings

* Derive libretro setting keys from common/settings.h hana strings

* settings.h: Reduce code repetition in key definitions via macro

* Implemented mechanism to pass our C++ setting keys to Android/Kotlin

None of the Android keys have been moved over as of this commit, this is just prep work

* jni_settings_keys.cpp.in: Removed redundant code

* Migrate (almost) all Android setting string keys over to SettingKeys

Also some slight cleanup

* Updated license headers

* Fixed top custom width erroneously being used in place of top custom height

* Migrate (probably) all setting string keys to Settings::QKeys

* Migrated several previously missed string keys to generated keys

* SettingKeys.kt: Visually seperate shared and Android-exclusive keys, similar to GenerateSettingKeys.cmake

* android: sdl2_config --> android_config

Not sure why these values are named this way. Hold-over from SDL2 frontend?

* android: Generate and validate default config.ini dynamically

* Settings: Assume C-style string keys by default

Relative to the previous commit, the following names have changed:
- Settings::Keys --> Settings::HKeys
- Settings::CKeys --> Settings::Keys

* default_ini.h: Fixed formatting warning

* Comment cleanup

* android: Fixed compilation failure due to incorrect namespace

* config.cpp: Use ASSERT_MSG instead of LOG_ERROR and ASSERT(false)
2026-03-02 23:26:43 +00:00
TeamPuzel
fe2f637467 Fix UI freeze on macOS game-list population
it seems like the initial call to `GameList::PopulateAsync` in the window constructor is too early in the app life-cycle and doesn't run asynchronously, or Qt is using macOS API in an esoteric way, or maybe it's something else entirely.

Moving the list population to happen before the window is shown instead appears to fix the issue.
2026-03-02 22:55:40 +00:00
PabloMK7
0f9d5f29f3
video_core: Move shader and pipeline compilation into separate workers (#1802) 2026-03-01 23:09:13 +01:00
Eric Warmenhoven
cb09d1e064
libretro: add portability_subset extension if required (#1791) 2026-02-28 15:23:03 +01:00
Richard
526d9d4cea
android: Add auto-map controller button with long-press to clear all bindings (#1769) 2026-02-27 19:57:41 +01:00
David Griswold
b477ba09c1
Ability to select which layouts to cycle with the cycle layout hotkey (#1430) 2026-02-27 13:21:53 +01:00
David Griswold
6b2ac400eb
Android: Hotkey Enable Button (#1464) 2026-02-26 18:40:42 +01:00
David Griswold
5ac0ef8fde
hide portrait layout menu on landscape and vice versa (#1473) 2026-02-26 14:27:18 +01:00
David Griswold
3255620934
Implement integer scaling (#1400) 2026-02-26 13:33:13 +01:00
keynote
03d62efe13
artic_base_client: Fix high cpu usage (#1789)
Fixes high CPU usage by adding a small sleep to the Client::Read and Client::Write methods
2026-02-25 21:34:52 +01:00
PabloMK7
7d19679cc5
video_core: vk_texture_runtime: Refactor and fix resource leak (#1790) 2026-02-25 19:53:03 +01:00
PabloMK7
b3fd0b6c89
video_core: Apply texture filter to color surfaces (#1784) 2026-02-25 13:08:18 +01:00
RedBlackAka
15bdd27b9c
citra-meta: Use dedicated GPU by default on AMD (#1783) 2026-02-24 18:06:29 +01:00
OpenSauce04
d721cbe29b cmake: Only add catch2 library if ENABLE_TESTS is enabled 2026-02-23 23:13:21 +00:00
Eric Warmenhoven
17f4c52e56 libretro: default system type to New 3DS 2026-02-23 22:49:02 +00:00
Eric Warmenhoven
27c3e0e5c3 libretro: better safety on vkDevice feature checks 2026-02-23 22:49:02 +00:00
Eric Warmenhoven
8fac24d2a4 libretro: better load failure check 2026-02-23 22:49:02 +00:00
Eric Warmenhoven
fe59958b63 older tvos hardware does not support layered rendering 2026-02-23 22:49:02 +00:00
RedBlackAka
13e0fdeac1
libretro core: Add some ifdefs (#1765) 2026-02-23 21:35:02 +01:00
OpenSauce04
76db4b08f6 .gitlab-ci.yml: Fixed ARM64 macOS not having minimum OS correctly set 2026-02-23 18:25:05 +00:00
OpenSauce04
5d583a8a41 .gitlab-ci.yml: Bump macOS libretro core minimum OS version to 11.0 2026-02-23 15:57:48 +00:00
PabloMK7
8b72dcb235
video_core: Do not spam file IO when reading vulkan shader disk cache (#1774) 2026-02-23 15:45:26 +01:00
PabloMK7
fcb345e273
logging: Check filter before log format (#1773) 2026-02-23 14:26:20 +01:00
PabloMK7
4c054ff2e7
video_core: Fix transferability issue in vulkan shader disk cache (#1770) 2026-02-22 22:41:24 +01:00
lannoene
43cecd1692
Update File Core and Add HLE DLP Client (#1741) 2026-02-22 17:07:24 +01:00
OpenSauce04
ac0ec5edea Updated translations via Transifex 2026-02-21 17:17:12 +00:00
Chase Harkcom
c55165e19b
Fix segfault when resetting default settings (#1751) 2026-02-20 23:40:39 +01:00
jbm11208
1092295f2a
Fix Shadow Rendering / Texture Filtering (#1675)
* video_core/renderer_vulkan: Add texture filtering

* Fix Shadow Rendering (again...)

* Make individual image views per res scale

* Refactor texture runtime

* Fix some magic numbers

* More fixes and filter pipeline cache.

* Refactor Surface and Handle move and destructor

---------

Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2026-02-20 23:39:04 +01:00
RedBlackAka
9628300ff5
citra_meta: Use integrated SSE4.2 detection method (#1753) 2026-02-20 21:34:21 +01:00
RedBlackAka
4010f4bc1f
common/cpu_detect: Remove SSE/SSE2 detection (#1754) 2026-02-20 21:34:03 +01:00
OpenSauce04
f3fb0b729e Kill SDL2 frontend
Good riddance
2026-02-20 16:02:41 +00:00
Alexandre Bouvier
7bcbf8aba4 cmake: fix import name
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-libretro / android (push) Has been cancelled
citra-libretro / linux (push) Has been cancelled
citra-libretro / windows (push) Has been cancelled
citra-libretro / macos (arm64) (push) Has been cancelled
citra-libretro / macos (x86_64) (push) Has been cancelled
citra-libretro / ios (push) Has been cancelled
citra-libretro / tvos (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
azahar-stale / stale-issues (push) Has been cancelled
2026-02-20 14:41:31 +00:00
Eric Warmenhoven
d9b77cc21e
Implement libretro core (#1215)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-libretro / android (push) Waiting to run
citra-libretro / linux (push) Waiting to run
citra-libretro / windows (push) Waiting to run
citra-libretro / macos (arm64) (push) Waiting to run
citra-libretro / macos (x86_64) (push) Waiting to run
citra-libretro / ios (push) Waiting to run
citra-libretro / tvos (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
* libretro core

* Bringing citra libretro implementation over
* libretro: hook up vulkan renderer
* libretro: github actions
* libretro: gyro
* libretro: core options v2
* libretro: on ios turn off shader jit if unavailable
* moltenvk 1.3.0 introduces 8-bit indexes but allocates 16-bit for metal; this ends up allocating stream buffer * 2 = 132MiB. Instead, just use 16-bit indexes. (This will be necessary for standalone when bumping moltenvk version.)

* libretro core: address review feedback

* libretro: microphone support

* cmake: Add ENABLE_ROOM_STANDALONE to list of incompatible libretro flags

* libretro: proper initial geometry

* libretro: fix software renderer

* libretro: address review feedback

* .github/libretro.yml: Pin macOS runners at macOS 26

* ci: Remove explicit selection of Xcode 16.0

* .github/libretro.yml: remove unnecessary windows builder apt commands

* .github/libretro.yml: bump min macos version to 11.0

* ci: Re-enable CI jobs for all libretro cores

This is under the condition that we don't introduce build cache for these builds

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2026-02-19 22:30:25 +00:00
OpenSauce04
d0eaf07a40 cmake: Added missing newline to missing submodule message 2026-02-17 15:25:54 +00:00
OpenSauce04
354f5d698f Updated translations via Transifex 2026-02-17 13:36:36 +00:00
OpenSauce04
5c6b23c64d tools: Added enter-docker-dev-container.sh script 2026-02-17 13:32:01 +00:00
PabloMK7
c43f24e489
video_core: Fixes to vulkan disk cache (#1748) 2026-02-17 14:22:48 +01:00
RedBlackAka
5d4aef81fe
common/cpu_detect: Remove FMA4 detection (#1746) 2026-02-17 13:21:21 +01:00
RedBlackAka
6c6dd68780
Windows: Fix game shortcut character corruption (#1745) 2026-02-17 10:32:01 +01:00
PabloMK7
304db9173b
video_core: vulkan: Add disk shader cache (#1725)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-02-16 15:59:22 +01:00
PabloMK7
91abe7f7d0
common: Add NATVIS to BitField class for better VS debugging (#1731)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
azahar-stale / stale-issues (push) Has been cancelled
2026-02-13 14:30:04 +01:00
PabloMK7
3e27010c7b
Fix regex in PR verification
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-02-09 23:30:15 +01:00
OpenSauce04
0c478d7614 Add "Enable display refresh rate detection" setting on desktop 2026-02-09 20:08:34 +00:00
OpenSauce04
37e688f82d qt: Fixed some setting tooltips not having automatic line breaks
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-02-06 20:00:38 +00:00
PabloMK7
f010863ece
video_core: vulkan: Only store hashes in shader cache maps (#1710)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-02-02 18:25:03 +01:00
OpenSauce04
dd65ef4749 Updated translations via Transifex
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-01-29 12:17:35 +00:00
PabloMK7
fc137b0229
frontend: Revert removal of .3ds support (#1701)
* frontend: Revert removal of .3ds support

* Added 3ds extension to Info.plist

* Added .3ds extension to org.azahar_emu.Azahar.xml

* game_list.h: Removed leftover definitions

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-01-29 12:10:02 +00:00
lannoene
a9923b6844
core: small fixes to local play (#1690)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-01-27 10:30:26 +01:00
OpenSauce04
0a705b7449 Updated translations via Transifex
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-01-25 00:58:03 +00:00
OpenSauce04
7c4c77becf externals: Updated SDL to 2.32.10 2026-01-25 00:43:32 +00:00
OpenSauce04
951b556a2c ci: Only build appimage-wayland for tagged jobs
This is to reduce stress on the CI/CD build queue and cache
2026-01-25 00:41:25 +00:00
David Griswold
a6688abcf5 update language and use UP instead of DOWN on axis mapping 2026-01-24 23:31:01 +00:00
OpenSauce04
6d72a6f447 plgldr: Fixed plugins that should load for all titles failing to load due to a missing check 2026-01-24 22:59:59 +00:00
PabloMK7
f1fa564733
Revert: video_core/renderer_vulkan: Add texture filtering (#1678) 2026-01-24 23:48:54 +01:00
Immersion95
ad9ab71301
qt: Mention in the tooltip that async presentation adds 1 frame of input lag (#1669)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-transifex / transifex (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
Async Presentation (Vulkan) adds 1 frame of input lag - Mention in the tooltip
2026-01-23 15:35:59 +01:00
PabloMK7
93f54be3f9 video_core: Fix custom textures when loading a savestate
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-01-22 19:42:21 +01:00
PabloMK7
95a6814752 video_core: Make all state dirty after loading a savestate 2026-01-22 19:42:21 +01:00
PabloMK7
b0fea112e8 core: Fix accurate multiplication loading (home menu and savestate)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-01-22 15:11:38 +01:00
PabloMK7
94b558d3f1 hacks: Force accurate multiplication for M&L Paper Jam 2026-01-22 15:11:38 +01:00
PabloMK7
6e666a1831
log: Fix compilation with gcc backtrace (#1668) 2026-01-22 14:45:30 +01:00
PabloMK7
b54911a52e
github: Ignore punctuation marks in PR verification 2026-01-22 13:47:25 +01:00
David Griswold
102f7d24fc
android: prioritize non-built-in displays (#1667) 2026-01-22 12:55:58 +01:00
OpenSauce
a5ac24adc5
qt: Workaround for Qt directoryChanged event spam on macOS (#1665)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
* qt: Workaround for Qt directoryChanged event spam on macOS

* Use steady_clock instead of system_clock
2026-01-21 21:15:49 +00:00
OpenSauce04
3fdcd6b7dc qt: Implement Update Channel setting 2026-01-21 19:49:10 +00:00
PabloMK7
46429f9c02
Update PR template to use master branch 2026-01-21 18:02:11 +01:00
PabloMK7
a4c3135bf3
github: Add first time contributor verification (#1663) 2026-01-21 18:00:37 +01:00
OpenSauce04
d48d51828e android: Fix desynced default value for use_vsync setting
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-01-20 22:51:03 +00:00
Marcin Serwin
79d73bbcb9 citra_meta: search for Qt6::GuiPrivate before using it
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
Otherwise, the configuration on darwin fails for me with the following
error:

```
CMake Error at src/citra_meta/CMakeLists.txt:64 (target_link_libraries):
  Target "citra_meta" links to:

    Qt6::GuiPrivate

  but the target was not found.
```

Signed-off-by: Marcin Serwin <marcin@serwin.dev>
2026-01-20 21:33:41 +00:00
RedBlackAka
e9846de5be
qt: Exclude some features logic if disabled at compile time (#1630)
* Qt: Exclude more logic if disabled

* Fix license

* Fix

* Exclude stress test logic

* Fix RequestStop
2026-01-20 18:31:56 +01:00
Cool Guy
671faf8dca
qt: Do not preload textures when custom textures are off (#1629)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-01-20 15:07:54 +01:00
Francesco Saltori
c7e0364342
cheats: Fix previous cheats not being cleaned up when a game has no cheats file (#1640) 2026-01-20 14:38:44 +01:00
OpenSauce04
0805711cba Updated translations via Transifex
Some checks failed
citra-transifex / transifex (push) Waiting to run
citra-build / source (push) Has been cancelled
citra-build / linux (appimage) (push) Has been cancelled
citra-build / linux (appimage-wayland) (push) Has been cancelled
citra-build / linux (fresh) (push) Has been cancelled
citra-build / macos (arm64) (push) Has been cancelled
citra-build / macos (x86_64) (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (push) Has been cancelled
citra-build / android (googleplay) (push) Has been cancelled
citra-build / android (vanilla) (push) Has been cancelled
citra-build / docker (push) Has been cancelled
citra-format / clang-format (push) Has been cancelled
citra-build / macos-universal (push) Has been cancelled
2026-01-19 21:17:42 +00:00
OpenSauce04
54f2221ec6 cmake: Fix build info sometimes not generating correctly 2026-01-19 21:13:18 +00:00
PabloMK7
f71844b87f log: Close file after stop and fallback to stderr 2026-01-19 21:01:55 +00:00
PabloMK7
6634b8c9d9 citra_meta: Move DetachedTasks construction to main.cpp 2026-01-19 21:01:55 +00:00
OpenSauce04
64516e6420 cmake: Removed redundant BuildInstaller.cmake 2026-01-19 18:48:13 +00:00
OpenSauce04
7869f1c618 cmake: Upgrade bundled Qt to 6.9.3 2026-01-19 17:51:38 +00:00
jbm11208
0571187bd3
video_core/renderer_vulkan: fix shadow rendering (#1634)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
2026-01-18 23:56:35 +01:00
OpenSauce
4deb7e63b5
DirectoryInitialization.kt: Switch to getFilesDir for internalUserPath (#1646)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux (appimage) (push) Waiting to run
citra-build / linux (appimage-wayland) (push) Waiting to run
citra-build / linux (fresh) (push) Waiting to run
citra-build / macos (arm64) (push) Waiting to run
citra-build / macos (x86_64) (push) Waiting to run
citra-build / macos-universal (push) Blocked by required conditions
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
* DirectoryInitialization.kt: Switch to getFilesDir for internalUserPath

Was previously getExternalFilesDir

filesDir is used here, which is just recommended shorthand for getFilesDir

* DirectoryInitialization.kt: Updated license header
2026-01-18 18:41:54 +00:00
487 changed files with 56956 additions and 22987 deletions

View File

@ -12,6 +12,9 @@ fi
echo "Tag name is: $TAG_NAME"
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
mkdir -p build
docker save azahar-room:$TAG_NAME > build/azahar-room-$TAG_NAME.dockerimage
FILENAME="azahar-room-$TARGET-$TAG_NAME.dockerimage"
docker save azahar-room:$TAG_NAME > build/$FILENAME
echo "DOCKER_IMAGE_PATH=artifacts/$FILENAME" >> $GITHUB_ENV

133
.ci/libretro-ci.yml Normal file
View File

@ -0,0 +1,133 @@
.core-defs:
variables:
JNI_PATH: .
CORENAME: azahar
API_LEVEL: 21
BASE_CORE_ARGS: -DENABLE_LIBRETRO=ON -DENABLE_TESTS=OFF
CORE_ARGS: ${BASE_CORE_ARGS}
EXTRA_PATH: bin/Release
variables:
STATIC_RETROARCH_BRANCH: master
GIT_SUBMODULE_STRATEGY: recursive
# Inclusion templates, required for the build to work
include:
################################## DESKTOPS ############################## ##
# Windows 64-bit
- project: 'libretro-infrastructure/ci-templates'
file: '/windows-cmake-mingw.yml'
# Linux 64-bit
- project: 'libretro-infrastructure/ci-templates'
file: '/linux-cmake.yml'
# MacOS x86_64
- project: 'libretro-infrastructure/ci-templates'
file: '/osx-cmake-x86.yml'
# MacOS ARM64
- project: 'libretro-infrastructure/ci-templates'
file: '/osx-cmake-arm64.yml'
################################## CELLULAR ############################## ##
# Android
- project: 'libretro-infrastructure/ci-templates'
file: '/android-cmake.yml'
# iOS
- project: 'libretro-infrastructure/ci-templates'
file: '/ios-cmake.yml'
# tvOS
- project: 'libretro-infrastructure/ci-templates'
file: '/tvos-cmake.yml'
################################## CONSOLES ############################## ##
# Stages for building
stages:
- build-prepare
- build-shared
- build-static
##############################################################################
#################################### STAGES ##################################
##############################################################################
#
################################### DESKTOPS #################################
# Windows 64-bit
libretro-build-windows-x64:
extends:
- .core-defs
- .libretro-windows-cmake-x86_64
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-mxe-win-cross-cores:mingw12
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF -G Ninja
# Linux 64-bit
libretro-build-linux-x64:
extends:
- .core-defs
- .libretro-linux-cmake-x86_64
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-amd64-ubuntu:backports
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DENABLE_LTO=OFF
CC: /usr/bin/gcc-12
CXX: /usr/bin/g++-12
# MacOS x86_64
libretro-build-osx-x64:
tags:
- mac-apple-silicon
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCMAKE_OSX_ARCHITECTURES=x86_64
MACOSX_DEPLOYMENT_TARGET: "11.0"
extends:
- .core-defs
- .libretro-osx-cmake-x86_64
# MacOS ARM64
libretro-build-osx-arm64:
extends:
- .core-defs
- .libretro-osx-cmake-arm64
variables:
MACOSX_DEPLOYMENT_TARGET: "11.0"
################################### CELLULAR #################################
# Android ARMv8a
android-arm64-v8a:
extends:
- .libretro-android-cmake-arm64-v8a
- .core-defs
variables:
ANDROID_NDK_VERSION: 26.2.11394342
NDK_ROOT: /android-sdk-linux/ndk/$ANDROID_NDK_VERSION
LIBNAME: ${CORENAME}_libretro.so
artifacts:
paths:
- $LIBNAME
# iOS arm64
libretro-build-ios-arm64:
extends:
- .libretro-ios-cmake-arm64
- .core-defs
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
IOS_MINVER: "14.0"
EXTRA_PATH: bin/RelWithDebInfo
# tvOS arm64
libretro-build-tvos-arm64:
extends:
- .libretro-tvos-cmake-arm64
- .core-defs
variables:
CORE_ARGS: ${BASE_CORE_ARGS} -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
MINVER: "14.0"
EXTRA_PATH: bin/RelWithDebInfo
################################### CONSOLES #################################

13
.ci/libretro-pack.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash -ex
# Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-libretro-$OS-$TARGET-$GITDATE-$GITREV"
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
REV_NAME="azahar-libretro-$OS-$TARGET-$GITHUB_REF_NAME"
fi
# Create .zip
zip -j -9 $REV_NAME.zip $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*

View File

@ -1,6 +1,6 @@
#!/bin/bash -ex
if [[ "$TARGET" == "appimage"* ]]; then
if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
# Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang

View File

@ -2,13 +2,14 @@
ARTIFACTS_LIST=($ARTIFACTS)
BUNDLE_DIR=build/bundle
mkdir build
BUILD_DIR=build
UNIVERSAL_DIR=$BUILD_DIR/universal
BUNDLE_DIR=$UNIVERSAL_DIR/bundle
OTHER_BUNDLE_DIR=$BUILD_DIR/x86_64/bundle
# Set up the base artifact to combine into.
BASE_ARTIFACT=${ARTIFACTS_LIST[0]}
BASE_ARTIFACT_ARCH="${BASE_ARTIFACT##*-}"
mv $BASE_ARTIFACT $BUNDLE_DIR
# Set up the base bundle to combine into.
mkdir $UNIVERSAL_DIR
cp -a $BUILD_DIR/arm64/bundle $UNIVERSAL_DIR
# Executable binary paths that need to be combined.
BIN_PATHS=(Azahar.app/Contents/MacOS/azahar)
@ -19,21 +20,18 @@ DYLIB_PATHS=($(cd $BUNDLE_DIR && find . -name '*.dylib'))
unset IFS
# Combine all of the executable binaries and dylibs.
for OTHER_ARTIFACT in "${ARTIFACTS_LIST[@]:1}"; do
OTHER_ARTIFACT_ARCH="${OTHER_ARTIFACT##*-}"
for BIN_PATH in "${BIN_PATHS[@]}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_BUNDLE_DIR/$BIN_PATH
done
for BIN_PATH in "${BIN_PATHS[@]}"; do
lipo -create -output $BUNDLE_DIR/$BIN_PATH $BUNDLE_DIR/$BIN_PATH $OTHER_ARTIFACT/$BIN_PATH
done
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
for DYLIB_PATH in "${DYLIB_PATHS[@]}"; do
# Only merge if the libraries do not have conflicting arches, otherwise it will fail.
DYLIB_INFO=`file $BUNDLE_DIR/$DYLIB_PATH`
OTHER_DYLIB_INFO=`file $OTHER_ARTIFACT/$DYLIB_PATH`
if ! [[ "$DYLIB_INFO" =~ "$OTHER_ARTIFACT_ARCH" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "$BASE_ARTIFACT_ARCH" ]]; then
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_ARTIFACT/$DYLIB_PATH
OTHER_DYLIB_INFO=`file $OTHER_BUNDLE_DIR/$DYLIB_PATH`
if ! [[ "$DYLIB_INFO" =~ "x86_64" ]] && ! [[ "$OTHER_DYLIB_INFO" =~ "arm64" ]]; then
lipo -create -output $BUNDLE_DIR/$DYLIB_PATH $BUNDLE_DIR/$DYLIB_PATH $OTHER_BUNDLE_DIR/$DYLIB_PATH
fi
done
done
# Remove leftover libs so that they aren't distributed

View File

@ -4,12 +4,10 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
mkdir build && cd build
cmake .. -GNinja \
mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
cmake ../.. -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="$TARGET" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \
@ -18,9 +16,8 @@ ninja
ninja bundle
mv ./bundle/azahar.app ./bundle/Azahar.app # TODO: Can this be done in CMake?
ccache -s -v
CURRENT_ARCH=`arch`
if [ "$TARGET" = "$CURRENT_ARCH" ]; then
if [ "$BUILD_ARCH" = "$CURRENT_ARCH" ]; then
ctest -VV -C Release
fi

26
.ci/mxe.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash -ex
# TODO: Why doesn't the CI environment use the PATH set in the Dockerimage?
# It works fine when using the image locally.
export PATH="/mxe/usr/bin:${PATH}"
mkdir build && cd build
if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
x86_64-w64-mingw32.shared-cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DUSE_SYSTEM_BOOST=ON \
-DUSE_SYSTEM_CRYPTOPP=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
x86_64-w64-mingw32.shared-cmake --build . -- -j$(nproc)
x86_64-w64-mingw32.shared-strip -s bin/Release/*.exe
make bundle
ccache -s -v

View File

@ -3,20 +3,21 @@
# Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$GITHUB_REF_NAME-$OS-$TARGET"
else
RELEASE_NAME=azahar-head
fi
# Archive and upload the artifacts.
mkdir -p artifacts
function pack_artifacts() {
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
ARTIFACTS_PATH="$1"
# Set up root directory for archive.
@ -35,10 +36,10 @@ function pack_artifacts() {
fi
# Create .zip/.tar.gz
if [ "$OS" = "windows" ]; then
if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME"
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ]; then
elif [ "$OS" = "android" ] || [ "$OS" = "macos" ] || [ "$TARGET" = "mxe" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
else
@ -56,11 +57,23 @@ if [ -n "$UNPACKED" ]; then
FILENAME=$(basename "$ARTIFACT")
EXTENSION="${FILENAME##*.}"
# TODO: Deduplicate
REV_NAME="azahar-$OS-$TARGET-$GITDATE-$GITREV"
# Determine the name of the release being built.
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
RELEASE_NAME=azahar-$GITHUB_REF_NAME
REV_NAME="azahar-$OS-$TARGET-$GITHUB_REF_NAME"
else
RELEASE_NAME=azahar-head
fi
mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
done
elif [ -n "$PACK_INDIVIDUALLY" ]; then
# Pack and upload the artifacts one-by-one.
for ARTIFACT in build/bundle/*; do
TARGET=$(basename "$ARTIFACT")
pack_artifacts "$ARTIFACT"
done
else

16
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,16 @@
- [ ] I have read the [Azahar AI Policy document](https://github.com/azahar-emu/azahar/blob/master/AI-POLICY.md) and have disclosed any use of AI if applicable under those terms.
---------
---
<!--
If you are contributing to Azahar for the first time please
keep the block of text between `---` and write your
PR description below it. Do not write anything inside
or change this block of text!
If you are a recurrent contributor, remove this entire
block of text and proceed as normal.
-->
![Ignore Until Your PR has been created!](../blob/master/.github/ignore_unless_human.png?raw=true)
---

BIN
.github/ignore_unless_human.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -7,28 +7,112 @@ on:
pull_request:
branches: [ master ]
permissions:
id-token: write
contents: read
attestations: write
jobs:
source:
if: ${{ !github.head_ref }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Pack
run: ./.ci/source.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: ./
format: spdx-json
output-file: artifacts/source.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: source
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.tar.xz
sbom-path: artifacts/source.spdx.json
linux:
linux-x86_64:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: ["appimage", "appimage-wayland", "fresh"]
target: ["appimage", "appimage-wayland", "gcc-nopch"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
OS: linux
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
steps:
- uses: actions/checkout@v6
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ github.job }}-${{ matrix.target }}-
- name: Build
if: ${{ env.SHOULD_RUN == 'true' }}
run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
run: |
mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' && env.SHOULD_RUN == 'true' }}
run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Generate SBOM
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v7
with:
name: ${{ github.job }}-${{ matrix.target }}
path: artifacts/
- name: Attest artifacts
if: ${{ contains(matrix.target, 'appimage') && github.ref_type == 'tag' && env.SHOULD_RUN == 'true' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.AppImage
sbom-path: artifacts/linux-x86_64-${{ matrix.target }}.spdx.json
linux-arm64:
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
target: ["clang", "gcc-nopch"]
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
@ -39,114 +123,94 @@ jobs:
OS: linux
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
${{ github.job }}-${{ matrix.target }}-
- name: Build
run: ./.ci/linux.sh
- name: Move AppImage to artifacts directory
if: ${{ contains(matrix.target, 'appimage') }}
run: |
mkdir -p artifacts
mv build/bundle/*.AppImage artifacts/
- name: Rename AppImage
if: ${{ matrix.target == 'appimage-wayland' }}
run: |
mv artifacts/azahar.AppImage artifacts/azahar-wayland.AppImage
- name: Upload
if: ${{ contains(matrix.target, 'appimage') }}
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos:
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }}
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
runs-on: 'macos-26'
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v4
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
${{ runner.os }}-
- name: Install tools
run: brew install ccache ninja spirv-tools
- name: Build
run: ./.ci/macos.sh
- name: Prepare outputs for caching
run: cp -R build/bundle $OS-$TARGET
- name: Cache outputs for universal build
uses: actions/cache/save@v4
with:
path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
macos-universal:
runs-on: macos-26
needs: macos
env:
OS: macos
TARGET: universal
steps:
- uses: actions/checkout@v4
- name: Download x86_64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Download ARM64 build from cache
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Build (x86_64)
run: BUILD_ARCH=x86_64 ./.ci/macos.sh
- name: Build (arm64)
run: BUILD_ARCH=arm64 ./.ci/macos.sh
- name: Create universal app
run: ./.ci/macos-universal.sh
env:
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64
- name: Prepare for packing
run: |
mkdir build/bundle
cp -r build/x86_64/bundle build/bundle/x86_64
cp -r build/arm64/bundle build/bundle/arm64
cp -r build/universal/bundle build/bundle/universal
- name: Pack
env:
PACK_INDIVIDUALLY: 1
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v4
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: build/
format: spdx-json
output-file: artifacts/macos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
sbom-path: artifacts/macos.spdx.json
windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
target: ["msvc", "msys2"]
include:
- target: msvc
os: windows-latest
- target: msys2
os: windows-latest
- target: mxe
os: ubuntu-latest
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
defaults:
run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -154,14 +218,16 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: windows
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v4
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -169,7 +235,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC
if: ${{ matrix.target == 'msvc' }}
uses: ilammy/msvc-dev-cmd@v1
uses: azahar-emu/msvc-dev-cmd@v1
- name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget
@ -190,34 +256,62 @@ jobs:
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v3
uses: crazy-max/ghaction-chocolatey@v4
with:
args: install ptime wget
- name: Install NSIS
if: ${{ github.ref_type == 'tag' }}
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: |
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
ptime D:/a/_temp/nsis-setup.exe /S
shell: pwsh
- name: Disable line ending translation
run: git config --global core.autocrlf input
- name: Build
- name: Build (Native)
if: ${{ matrix.target != 'mxe' }}
run: ./.ci/windows.sh
- name: Generate installer
if: ${{ github.ref_type == 'tag' }}
- name: Build (MXE)
if: ${{ matrix.target == 'mxe' }}
run: ./.ci/mxe.sh
- name: Generate installer (Native)
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: |
cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\
shell: cmd
- name: Generate installer (MXE)
if: ${{ github.ref_type == 'tag' && matrix.target == 'mxe' }}
run: |
export PATH="/mxe/usr/bin:${PATH}" # TODO: Why do we have to do this if it's in the image?
cd src/installer
x86_64-w64-mingw32.shared-makensis -DPRODUCT_VARIANT=${{ matrix.target }} -DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir -p ../../artifacts
mv ./*.exe ../../artifacts/
- name: Pack
run: ./.ci/pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.zip
artifacts/*.exe
sbom-path: artifacts/windows-${{ matrix.target }}.spdx.json
android:
runs-on: ubuntu-latest
@ -229,17 +323,18 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: android
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/cache@v4
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
@ -277,23 +372,48 @@ jobs:
working-directory: src/android/app
env:
UNPACKED: 1
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: src/android
format: spdx-json
output-file: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
src/android/app/artifacts/*.apk
src/android/app/artifacts/*.aab
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
docker:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
os: ubuntu-24.04
- target: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
container:
image: docker:dind
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: apk add bash
- name: Fix git ownership
run: git config --global --add safe.directory .
- name: Build Docker image
@ -302,8 +422,23 @@ jobs:
run: |
mkdir -p artifacts
mv build/*.dockerimage artifacts/
- name: Upload
uses: actions/upload-artifact@v4
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
name: docker
image: ${{ env.DOCKER_IMAGE_PATH }}
format: spdx-json
output-file: artifacts/docker-room.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: docker-${{ env.TARGET }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
artifacts/*.dockerimage
sbom-path: artifacts/docker-room.spdx.json

View File

@ -0,0 +1,51 @@
name: Detect first-time contributors
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
issues: write
jobs:
detect:
runs-on: ubuntu-latest
if: >-
(github.repository == 'azahar-emu/azahar') &&
(github.event.pull_request.author_association != 'COLLABORATOR') &&
(github.event.pull_request.author_association != 'CONTRIBUTOR') &&
(github.event.pull_request.author_association != 'MANNEQUIN') &&
(github.event.pull_request.author_association != 'MEMBER') &&
(github.event.pull_request.author_association != 'OWNER')
steps:
- name: Detect PR if author is first-time contributor
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
// Add needs verification label so that the reopen action runs on comment.
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pr.number,
labels: ['needs verification'],
});
// Close the pull request and wait for verification.
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: 'closed',
});
// Show the new contributor how to verify (they need to write a short poem about the Wii and 3DS being lovers)
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: 'Welcome to the Azahar Emulator repository! Due to the surge of AI bots we have decided to add an extra verification step to new contributors. Please follow the exact instructions in your own written Pull Request description to reopen it.',
});

View File

@ -0,0 +1,79 @@
name: Verify first-time contributors
on:
issue_comment:
types: [created]
permissions:
pull-requests: write
issues: write
jobs:
verify:
runs-on: ubuntu-latest
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
steps:
- name: Verify and reopen PR
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;
const issue = context.payload.issue;
const comment = context.payload.comment;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: issue.number,
});
// Only allow verification of the comment user is the author
if (comment.user.login !== pr.user.login) {
return;
}
// Fetch user display and login names (lowercase)
const { data: user } = await github.rest.users.getByUsername({
username: pr.user.login,
});
const username = pr.user.login.toLowerCase();
const displayName = (user.name || '').toLowerCase();
// Make comment body lowercase and split words
const body = comment.body.toLowerCase().trim().replace(/[^a-z0-9_\-\s]/g, '').split(/\s+/);
// Check that the user verified themselves by writing a song about the NES and the SNES.
const verified =
(body.includes(username) ||
(displayName && body.includes(displayName))) &&
body.includes('azahar');
// Only reopen the PR and remove the label if verification succeeded
if (verified) {
await github.rest.pulls.update({
owner,
repo,
pull_number: issue.number,
state: 'open',
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: 'Verification successful! Pull request has been reopened. Please also edit your PR description to remove the block of text between `---` to make the description easier to read.',
});
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issue.number,
name: 'needs verification',
});
} catch {}
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: 'Verification failed! Pull request will remain closed.',
});
}

View File

@ -13,7 +13,7 @@ jobs:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build

304
.github/workflows/libretro.yml vendored Normal file
View File

@ -0,0 +1,304 @@
name: citra-libretro
on:
push:
branches: [ "*" ]
tags: [ "*" ]
pull_request:
branches: [ master ]
workflow_dispatch:
env:
CORE_ARGS: -DENABLE_LIBRETRO=ON
permissions:
id-token: write
contents: read
attestations: write
jobs:
android:
runs-on: ubuntu-22.04
env:
OS: android
TARGET: arm64-v8a
API_LEVEL: 21
ANDROID_NDK_VERSION: 26.2.11394342
ANDROID_ABI: arm64-v8a
BUILD_DIR: build/android-arm64-v8a
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set tag name
run: |
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
echo "GIT_TAG_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV
fi
echo $GIT_TAG_NAME
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Update Android SDK CMake version
run: |
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "ndk;$ANDROID_NDK_VERSION"
echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "cmake;3.30.3"
- name: Build
run: |
export NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/$ANDROID_NDK_VERSION
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake $CORE_ARGS -DANDROID_PLATFORM=android-$API_LEVEL -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_static -DANDROID_ABI=$ANDROID_ABI . -B $BUILD_DIR
${ANDROID_SDK_ROOT}/cmake/3.30.3/bin/cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-android.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-android.spdx.json
linux:
runs-on: ubuntu-22.04
env:
OS: linux
TARGET: x86_64
BUILD_DIR: build/linux-x86_64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: |
sudo apt-get update -y
sudo apt-get install -y llvm
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
llvm-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-linux.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-linux.spdx.json
windows:
runs-on: ubuntu-latest
env:
OS: windows
TARGET: x86_64
BUILD_DIR: build/windows-x86_64
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja
CMAKE: x86_64-w64-mingw32.static-cmake
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build in cross-container
run: |
docker pull $IMAGE
docker run --rm --user root \
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-w "${GITHUB_WORKSPACE}" \
$IMAGE \
bash -lc "\
${CMAKE} $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR && \
${CMAKE} --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc) && \
x86_64-w64-mingw32.static-strip -s $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*"
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-windows.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-windows.spdx.json
macos:
runs-on: macos-26
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env:
OS: macos
TARGET: ${{ matrix.target }}
MACOSX_DEPLOYMENT_TARGET: 11.0
BUILD_DIR: build/osx-${{ matrix.target }}
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: brew install spirv-tools
- name: Build
run: |
cmake $CORE_ARGS -DCMAKE_OSX_ARCHITECTURES=$TARGET . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-macos-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
ios:
runs-on: macos-26
env:
OS: ios
TARGET: arm64
BUILD_DIR: build/ios-arm64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-ios.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-ios.spdx.json
tvos:
runs-on: macos-26
env:
OS: tvos
TARGET: arm64
BUILD_DIR: build/tvos-arm64
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=-DIOS -DCMAKE_CXX_FLAGS=-DIOS -DIOS=ON -DCMAKE_SYSTEM_NAME=tvOS -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCITRA_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_ARCHITECTURES=arm64 -DENABLE_OPT=OFF
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release
strip -x $BUILD_DIR/$EXTRA_PATH/azahar_libretro.*
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Generate SBOM
if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with:
path: build/
format: spdx-json
output-file: libretro-tvos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
./*.zip
./*.spdx.json
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}
uses: actions/attest@v4
with:
subject-path: |
./*.zip
sbom-path: libretro-tvos.spdx.json

View File

@ -11,7 +11,7 @@ jobs:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch master branch

View File

@ -10,7 +10,7 @@ jobs:
permissions:
issues: write
steps:
- uses: actions/stale@v9.1.0
- uses: actions/stale@v10.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 90

View File

@ -7,10 +7,10 @@ on:
jobs:
transifex:
runs-on: ubuntu-latest
container: opensauce04/azahar-build-environment:transifex
container: opensauce04/azahar-build-environment:latest
if: ${{ github.repository == 'azahar-emu/azahar' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

7
.gitignore vendored
View File

@ -56,3 +56,10 @@ repo/
.ccache/
node_modules/
VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG
# verify-release.sh downloads
verify/

8
.gitmodules vendored
View File

@ -66,7 +66,7 @@
url = https://github.com/septag/dds-ktx
[submodule "openal-soft"]
path = externals/openal-soft
url = https://github.com/kcat/openal-soft
url = https://github.com/azahar-emu/openal-soft
[submodule "glslang"]
path = externals/glslang
url = https://github.com/KhronosGroup/glslang
@ -103,3 +103,9 @@
[submodule "externals/xxHash"]
path = externals/xxHash
url = https://github.com/Cyan4973/xxHash.git
[submodule "externals/libretro-common"]
path = externals/libretro-common/libretro-common
url = https://github.com/libretro/libretro-common.git
[submodule "dllwalker"]
path = externals/dllwalker
url = https://github.com/azahar-emu/dllwalker

20
AI-POLICY.md Normal file
View File

@ -0,0 +1,20 @@
# Azahar Emulator AI Use Policy
The following document outlines the acceptable and unacceptable uses of AI within the Azahar codebase.
It describes whether or not submissions which were exposed to large language models (LLMs) such as ChatGPT, Claude, DeepSeek, and similar models would be capable of being merged in a pull request or otherwise utilized.
- ✅ Use of AI to help developers discover or understand problems in the codebase is acceptable **under the condition that any discovered issue is independently verified by a human**.
- ✅ Use of AI to write code snippets of a sufficiently small size that they aren't reasonably copyrightable **with disclosure in the PR description** is acceptable.
- This will be handled on a case-by-case basis and is up to the interpretation of the maintainer, but generic algorithm snippets up to a maximum of approximately 5 lines of code are acceptable.
- ❌ Use of AI to write code for submission without disclosure is prohibited.
- ❌ Use of AI to write the entirety/ a significant portion of a contribution is prohibited.
- ❌ Use of AI to write snippets of code which are of a size such that they could reasonably be copyrightable is prohibited.
- ❌ Use of AI to rewrite incompatibly-licensed code for submission to Azahar is prohibited.
- ❌ Use of AI to autonomously submit pull requests or issues is prohibited.
Pull requests which violate these rules will be closed. Previously accepted submissions which are found to violate these rules will be retroactively removed from the codebase.
This document may be updated in the future if further clarification is required.
This policy is effective for code submitted on or after the 20th of March 2026.

View File

@ -13,24 +13,39 @@ cmake_policy(SET CMP0063 NEW)
cmake_policy(SET CMP0127 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW)
# Prefer building bundled dependencies as static instead of shared
set(BUILD_SHARED_LIBS OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals)
include(CMakeDependentOption)
include(FindPkgConfig)
project(citra LANGUAGES C CXX ASM)
# must be invoked after project() command when using CMAKE_TOOLCHAIN_FILE
include(FindPkgConfig)
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
enable_language(OBJC OBJCXX)
endif()
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND MINGW)
string(TOLOWER ${LIBTYPE} LIBTYPE_LOWER)
set(CMAKE_AR x86_64-w64-mingw32.${LIBTYPE_LOWER}-gcc-ar)
endif()
if (BSD STREQUAL "OpenBSD")
add_link_options(-z wxneeded)
endif()
option(ENABLE_LIBRETRO "Build as a LibRetro core" OFF)
# Some submodules like to pick their own default build type if not specified.
# Make sure we default to Release build type always, unless the generator has custom types.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()
if (APPLE)
if (APPLE AND NOT ENABLE_LIBRETRO)
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out.
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
@ -90,8 +105,18 @@ else()
set(DEFAULT_ENABLE_OPENGL ON)
endif()
# Track which options were explicitly set by the user (for libretro conflict detection)
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
set(_USER_SET_OPTIONS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
if(DEFINED ${_opt})
list(APPEND _USER_SET_OPTIONS ${_opt})
endif()
endforeach()
option(ENABLE_SDL2 "Enable using SDL2" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" OFF "ENABLE_SDL2;NOT ANDROID AND NOT IOS" OFF)
option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
# Set bundled qt as dependent options.
@ -105,6 +130,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
@ -113,7 +139,8 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
CMAKE_DEPENDENT_OPTION(ENABLE_SOFTWARE_RENDERER "Enables the software renderer" ON "NOT ANDROID" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENABLE_OPENGL} "NOT APPLE" OFF)
option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
# NetBSD doesn't support Vulkan yet, remove this check when it does.
CMAKE_DEPENDENT_OPTION(ENABLE_VULKAN "Enables the Vulkan renderer" ON "NOT (BSD MATCHES \"NetBSD\")" OFF)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
@ -123,6 +150,8 @@ option(ENABLE_SSE42 "Enable SSE4.2 optimizations on x86_64" ON)
option(ENABLE_DEVELOPER_OPTIONS "Enable functionality targeted at emulator developers" OFF)
option(ENABLE_BUILTIN_KEYBLOB "Enable the inclusion of the default crypto keys blob" 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})
@ -130,6 +159,31 @@ option(ENABLE_NATIVE_OPTIMIZATION "Enables processor-specific optimizations via
option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" ON)
# Handle incompatible options for libretro builds
if(ENABLE_LIBRETRO)
# Check for explicitly-set conflicting options
set(_CONFLICTS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
list(FIND _USER_SET_OPTIONS ${_opt} _idx)
if(NOT _idx EQUAL -1 AND ${_opt})
list(APPEND _CONFLICTS ${_opt})
endif()
endforeach()
if(_CONFLICTS)
string(REPLACE ";" ", " _CONFLICTS_STR "${_CONFLICTS}")
message(FATAL_ERROR
"ENABLE_LIBRETRO is incompatible with: ${_CONFLICTS_STR}\n"
"These options were explicitly enabled but are not supported for libretro builds.\n"
"Remove these options or set them to OFF.")
endif()
# Force disable incompatible options (handles defaulted-on options)
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
set(${_opt} OFF CACHE BOOL "Disabled for libretro" FORCE)
endforeach()
endif()
# Pass the following values to C++ land
if (ENABLE_QT)
add_definitions(-DENABLE_QT)
@ -143,9 +197,6 @@ endif()
if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2)
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")
@ -223,7 +274,7 @@ function(check_submodules_present)
foreach(module ${gitmodules})
string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
message(SEND_ERROR "Git submodule ${module} not found."
message(SEND_ERROR "Git submodule ${module} not found.\n"
"Please run: git submodule update --init --recursive")
endif()
endforeach()
@ -300,6 +351,9 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
# set up output paths for executable binaries
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
if (ENABLE_LIBRETRO)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# System imported libraries
# ======================
@ -310,7 +364,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT)
if (NOT USE_SYSTEM_QT)
download_qt(6.9.2)
download_qt(6.9.3)
endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
@ -359,7 +413,7 @@ if (APPLE)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY})
if (ENABLE_VULKAN)
if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
if (NOT USE_SYSTEM_MOLTENVK)
download_moltenvk()
endif()
@ -513,8 +567,6 @@ if (NOT ANDROID AND NOT IOS)
include(BundleTarget)
if (ENABLE_QT)
qt_bundle_target(citra_meta)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(citra_meta)
endif()
if (ENABLE_ROOM_STANDALONE)
bundle_target(citra_room_standalone)

View File

@ -1,26 +0,0 @@
# To use this as a script, make sure you pass in the variables BASE_DIR, SRC_DIR, BUILD_DIR, and TARGET_FILE
cmake_minimum_required(VERSION 3.15)
if(WIN32)
set(PLATFORM "windows")
elseif(APPLE)
set(PLATFORM "mac")
elseif(UNIX)
set(PLATFORM "linux")
else()
message(FATAL_ERROR "Cannot build installer for this unsupported platform")
endif()
list(APPEND CMAKE_MODULE_PATH "${BASE_DIR}/CMakeModules")
include(DownloadExternals)
download_qt(tools_ifw)
get_external_prefix(qt QT_PREFIX)
file(GLOB_RECURSE INSTALLER_BASE "${QT_PREFIX}/**/installerbase*")
file(GLOB_RECURSE BINARY_CREATOR "${QT_PREFIX}/**/binarycreator*")
set(CONFIG_FILE "${SRC_DIR}/config/config_${PLATFORM}.xml")
set(PACKAGES_DIR "${BUILD_DIR}/packages")
file(MAKE_DIRECTORY ${PACKAGES_DIR})
execute_process(COMMAND ${BINARY_CREATOR} -t ${INSTALLER_BASE} -n -c ${CONFIG_FILE} -p ${PACKAGES_DIR} ${TARGET_FILE})

View File

@ -198,6 +198,10 @@ if (BUNDLE_TARGET_EXECUTE)
# On Linux, always bundle an AppImage.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IS_MINGW)
return()
endif()
if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif()
@ -273,6 +277,13 @@ else()
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (MINGW)
add_custom_command(
TARGET bundle
# The target here is arbitrary
COMMAND cp -r "$<TARGET_FILE_DIR:citra_meta>/*" "${CMAKE_BINARY_DIR}/bundle/"
POST_BUILD)
else()
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND}
@ -283,6 +294,7 @@ else()
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
POST_BUILD)
endif()
endif()
endfunction()
# Adds a target to the bundle target, packing in required libraries.
@ -293,6 +305,11 @@ else()
create_base_bundle_target()
endif()
if (CMAKE_HOST_SYSTEM STREQUAL "Linux" AND MINGW)
# We don't really need to "bundle" MXE builds, so don't do anything
return()
endif()
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (bundle_qt AND APPLE)
# For Qt targets on Apple, expect an app bundle.
@ -331,6 +348,7 @@ else()
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}"
"-DIS_MINGW=${MINGW}"
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")

View File

@ -0,0 +1,11 @@
function(disable_pax_mprotect target)
if (BSD STREQUAL "NetBSD")
add_custom_command(TARGET ${target} POST_BUILD
COMMAND paxctl +m "$<TARGET_FILE:${target}>"
COMMENT "Disabling PaX MPROTECT restrictions for '${target}'"
VERBATIM
)
else()
message(FATAL_ERROR "disable_pax_mprotect only applies on NetBSD.")
endif()
endfunction()

View File

@ -1,13 +1,21 @@
# Gets a UTC timstamp and sets the provided variable to it
function(get_timestamp _var)
macro(generate_build_info)
find_package(Git QUIET)
# Gets a UTC timstamp and sets the provided variable to it
function(get_timestamp _var)
string(TIMESTAMP timestamp UTC)
set(${_var} "${timestamp}" PARENT_SCOPE)
endfunction()
get_timestamp(BUILD_DATE)
endfunction()
get_timestamp(BUILD_DATE)
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/cmake-modules")
if (EXISTS "${SRC_DIR}/.git/objects")
if (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
file(READ "${CMAKE_SOURCE_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
string(STRIP "${GIT_REV_RAW}" GIT_REV)
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
set(GIT_BRANCH "HEAD")
elseif (EXISTS "${CMAKE_SOURCE_DIR}/.git/objects")
# Find the package here with the known path so that the GetGit commands can find it as well
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
@ -16,34 +24,29 @@ if (EXISTS "${SRC_DIR}/.git/objects")
get_git_head_revision(GIT_REF_SPEC GIT_REV)
git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH)
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
# unified source archive
file(READ "${SRC_DIR}/GIT-COMMIT" GIT_REV_RAW LIMIT 64)
string(STRIP "${GIT_REV_RAW}" GIT_REV)
string(SUBSTRING "${GIT_REV_RAW}" 0 9 GIT_DESC)
set(GIT_BRANCH "HEAD")
else()
else()
# self-packed archive?
set(GIT_REV "UNKNOWN")
set(GIT_DESC "UNKNOWN")
set(GIT_BRANCH "UNKNOWN")
endif()
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
endif()
string(SUBSTRING "${GIT_REV}" 0 7 GIT_SHORT_REV)
# Set build version
set(REPO_NAME "")
set(BUILD_VERSION "0")
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
# Set build version
set(REPO_NAME "")
set(BUILD_VERSION "0")
set(BUILD_FULLNAME "${GIT_SHORT_REV}")
if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
set(GIT_TAG $ENV{GITHUB_REF_NAME})
endif()
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG")
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG)
elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
string(STRIP ${GIT_TAG} GIT_TAG)
endif()
endif()
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
set(BUILD_VERSION "${GIT_TAG}")
set(BUILD_FULLNAME "${BUILD_VERSION}")
endif()
endif()
endmacro()

View File

@ -1,8 +1,10 @@
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules")
include(GenerateBuildInfo)
generate_build_info()
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
@ -10,6 +12,10 @@ set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_disk_cache.h"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_pipeline_cache.h"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
@ -18,6 +24,7 @@ set(HASH_FILES
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
"${VIDEO_CORE}/shader/generator/profile.h"
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@ -41,4 +48,4 @@ foreach (F IN LISTS HASH_FILES)
set(COMBINED "${COMBINED}${TMP}")
endforeach()
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
configure_file("${CMAKE_SOURCE_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)

View File

@ -0,0 +1,282 @@
## This file should be the *only place* where setting keys exist as strings.
# All references to setting strings should be derived from the
# `setting_keys.h` and `jni_setting_keys.cpp` files generated here.
# !!! Changes made here should be mirrored to SettingKeys.kt if used on Android
# Shared setting keys (multi-platform)
foreach(KEY IN ITEMS
"use_artic_base_controller"
"enable_gamemode"
"use_cpu_jit"
"cpu_clock_percentage"
"is_new_3ds"
"lle_applets"
"deterministic_async_operations"
"enable_required_online_lle_modules"
"use_virtual_sd"
"use_custom_storage"
"compress_cia_installs"
"async_fs_operations"
"region_value"
"init_clock"
"init_time"
"init_time_offset"
"init_ticks_type"
"init_ticks_override"
"plugin_loader"
"allow_plugin_loader"
"steps_per_hour"
"apply_region_free_patch"
"graphics_api"
"physical_device"
"use_gles"
"renderer_debug"
"dump_command_buffers"
"spirv_shader_gen"
"disable_spirv_optimizer"
"async_shader_compilation"
"async_presentation"
"use_hw_shader"
"use_disk_shader_cache"
"shaders_accurate_mul"
"use_vsync"
"use_display_refresh_rate_detection"
"use_shader_jit"
"resolution_factor"
"frame_limit"
"turbo_limit"
"texture_filter"
"texture_sampling"
"delay_game_render_thread_us"
"simulate_3ds_gpu_timings"
"layout_option"
"swap_screen"
"upright_screen"
"secondary_display_layout"
"large_screen_proportion"
"screen_gap"
"small_screen_position"
"custom_top_x"
"custom_top_y"
"custom_top_width"
"custom_top_height"
"custom_bottom_x"
"custom_bottom_y"
"custom_bottom_width"
"custom_bottom_height"
"custom_second_layer_opacity"
"aspect_ratio"
"screen_top_stretch"
"screen_top_leftright_padding"
"screen_top_topbottom_padding"
"screen_bottom_stretch"
"screen_bottom_leftright_padding"
"screen_bottom_topbottom_padding"
"portrait_layout_option"
"custom_portrait_top_x"
"custom_portrait_top_y"
"custom_portrait_top_width"
"custom_portrait_top_height"
"custom_portrait_bottom_x"
"custom_portrait_bottom_y"
"custom_portrait_bottom_width"
"custom_portrait_bottom_height"
"bg_red"
"bg_green"
"bg_blue"
"render_3d"
"factor_3d"
"swap_eyes_3d"
"render_3d_which_display"
"mono_render_option"
"cardboard_screen_size"
"cardboard_x_shift"
"cardboard_y_shift"
"filter_mode"
"pp_shader_name"
"anaglyph_shader_name"
"dump_textures"
"custom_textures"
"preload_textures"
"async_custom_loading"
"disable_right_eye_render"
"audio_emulation"
"enable_audio_stretching"
"enable_realtime_audio"
"volume"
"output_type"
"output_device"
"input_type"
"input_device"
"simulate_headphones_plugged"
"delay_start_for_lle_modules"
"use_gdbstub"
"gdbstub_port"
"instant_debug_log"
"enable_rpc_server"
"log_filter"
"log_regex_filter"
"toggle_unique_data_console_type"
"break_on_unmapped_memory_access"
"use_integer_scaling"
"layouts_to_cycle"
"camera_inner_flip"
"camera_outer_left_flip"
"camera_outer_right_flip"
"camera_inner_name"
"camera_inner_config"
"camera_outer_left_name"
"camera_outer_left_config"
"camera_outer_right_name"
"camera_outer_right_config"
"video_encoder"
"video_encoder_options"
"video_bitrate"
"audio_encoder"
"audio_encoder_options"
"audio_bitrate"
"last_artic_base_addr"
"motion_device"
"touch_device"
"udp_input_address"
"udp_input_port"
"udp_pad_index"
"record_frame_times"
"language" # FIXME: DUPLICATE KEY (libretro equivalent: language_value)
"web_api_url"
"citra_username"
"citra_token"
)
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
if (ANDROID)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
endif()
endforeach()
# Qt exclusive setting keys
# Note: A lot of these are very generic because our Qt settings are currently put under groups:
# E.g. UILayout\geometry
# TODO: We should probably get rid of these groups and use complete keys at some point. -OS
# FIXME: Some of these settings don't use the standard snake_case. When we can migrate, address that. -OS
if (ENABLE_QT)
foreach(KEY IN ITEMS
"nickname"
"ip"
"port"
"room_nickname"
"room_name"
"room_port"
"host_type"
"max_player"
"room_description"
"multiplayer_filter_text"
"multiplayer_filter_games_owned"
"multiplayer_filter_hide_empty"
"multiplayer_filter_hide_full"
"username_ban_list"
"username"
"ip_ban_list"
"romsPath"
"symbolsPath"
"movieRecordPath"
"moviePlaybackPath"
"videoDumpingPath"
"gameListRootDir"
"gameListDeepScan"
"path"
"deep_scan"
"expanded"
"recentFiles"
"output_format"
"format_options"
"theme"
"program_id"
"geometry"
"state"
"geometryRenderWindow"
"gameListHeaderState"
"microProfileDialogGeometry"
"name"
"bind"
"profile"
"use_touchpad"
"controller_touch_device"
"use_touch_from_button"
"touch_from_button_map"
"touch_from_button_maps" # Why are these two so similar? Basically typo bait
"nand_directory"
"sdmc_directory"
"game_id"
"KeySeq"
"gamedirs"
"libvorbis"
"Context"
"favorites"
)
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
endforeach()
endif()
# Android exclusive setting keys (standalone app only, not Android libretro)
if (ANDROID)
foreach(KEY IN ITEMS
"expand_to_cutout_area"
"performance_overlay_enable"
"performance_overlay_show_fps"
"performance_overlay_show_frame_time"
"performance_overlay_show_speed"
"performance_overlay_show_app_ram_usage"
"performance_overlay_show_available_ram"
"performance_overlay_show_battery_temp"
"performance_overlay_background"
"use_frame_limit" # FIXME: DUPLICATE KEY (shared equivalent: frame_limit)
"android_hide_images"
"screen_orientation"
"performance_overlay_position"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
set(JNI_SETTING_KEY_DEFINITIONS "${JNI_SETTING_KEY_DEFINITIONS}
JNI_DEFINE_KEY(${KEY}, ${KEY_JNI_ESCAPED})")
endforeach()
endif()
# Libretro exclusive setting keys
if (ENABLE_LIBRETRO)
foreach(KEY IN ITEMS
"language_value"
"swap_screen_mode"
"use_libretro_save_path"
"analog_function"
"analog_deadzone"
"enable_mouse_touchscreen"
"enable_touch_touchscreen"
"enable_touch_pointer_timeout"
"enable_motion"
"motion_sensitivity"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
set(SETTING_KEY_DEFINITIONS "${SETTING_KEY_DEFINITIONS}\nDEFINE_KEY(${KEY})")
endforeach()
endif()
# Trim trailing comma and newline from SETTING_KEY_LIST
string(LENGTH "${SETTING_KEY_LIST}" SETTING_KEY_LIST_LENGTH)
math(EXPR SETTING_KEY_LIST_NEW_LENGTH "${SETTING_KEY_LIST_LENGTH} - 1")
string(SUBSTRING "${SETTING_KEY_LIST}" 0 ${SETTING_KEY_LIST_NEW_LENGTH} SETTING_KEY_LIST)
# Configure files
configure_file("common/setting_keys.h.in" "common/setting_keys.h" @ONLY)
if (ENABLE_QT)
configure_file("citra_qt/setting_qkeys.h.in" "citra_qt/setting_qkeys.h" @ONLY)
endif()
if (ANDROID AND NOT ENABLE_LIBRETRO)
configure_file("android/app/src/main/jni/jni_setting_keys.cpp.in" "android/app/src/main/jni/jni_setting_keys.cpp" @ONLY)
endif()

View File

@ -1 +0,0 @@
**The Contributor's Guide has moved to [the wiki](https://github.com/citra-emu/citra/wiki/Contributing).**

View File

@ -1,6 +1,8 @@
![Azahar Emulator](https://azahar-emu.org/resources/images/logo/azahar-name-and-logo.svg)
![GitHub Release](https://img.shields.io/github/v/release/azahar-emu/azahar?label=Current%20Release)
![Current Release](https://img.shields.io/github/v/release/azahar-emu/azahar?label=Current%20Release)
![Current Prerelease](https://img.shields.io/github/v/release/azahar-emu/azahar?include_prereleases&label=Current%20Prerelease)
![GitHub Downloads](https://img.shields.io/github/downloads/azahar-emu/azahar/total?logo=github&label=GitHub%20Downloads)
![Google Play Downloads](https://playbadges.pavi2410.com/badge/downloads?id=io.github.lime3ds.android&pretty&label=Play%20Store%20Downloads)
![Flathub Downloads](https://img.shields.io/flathub/downloads/org.azahar_emu.Azahar?logo=flathub&label=Flathub%20Downloads)

View File

@ -35,6 +35,7 @@
<string>cci</string>
<string>cxi</string>
<string>cia</string>
<string>3ds</string>
</array>
<key>CFBundleTypeName</key>
<string>Nintendo 3DS File</string>

@ -1 +1 @@
Subproject commit eadcdfb84b6f3b95734e867d99fe16a9e8db717f
Subproject commit d9f1126e42b606d02ecc89b10cb9a336a3b2f5a3

View File

@ -12,4 +12,4 @@ lang_map = ca@valencia:ca_ES_valencia
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID
lang_map = es_ES:b+es+ES, hu_HU:b+hu+HU, ru_RU:b+ru+RU, pt_BR:b+pt+BR, zh_CN:b+zh+CN, pl_PL:b+pl+PL, ca@valencia:b+ca+ES+valencia, ko_KR:b+ko+KR, da_DK:b+da+DK, ja_JP:b+ja+JP, lt_LT:b+lt+LT, ro_RO:b+ro+RO, tr_TR:b+tr+TR, vi_VN:b+vi+VN, zh_TW:b+zh+TW
lang_map = ca@valencia:b+ca+ES+valencia, es_419:b+es+419, pt_BR:b+pt+BR, zh_CN:b+zh+CN, zh_TW:b+zh+TW

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1668
dist/languages/de.ts vendored

File diff suppressed because it is too large Load Diff

2521
dist/languages/el.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

7928
dist/languages/es_419.ts vendored Normal file

File diff suppressed because it is too large Load Diff

1468
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load Diff

1602
dist/languages/fr.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1470
dist/languages/id.ts vendored

File diff suppressed because it is too large Load Diff

1555
dist/languages/it.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

File diff suppressed because it is too large Load Diff

1872
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load Diff

1472
dist/languages/nl.ts vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1511
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

File diff suppressed because it is too large Load Diff

1487
dist/languages/sv.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

1482
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load Diff

1470
dist/languages/zh_TW.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
<expanded-acronym>CTR Cart Image</expanded-acronym>
<icon name="azahar"/>
<glob pattern="*.cci"/>
<glob pattern="*.3ds"/>
<magic><match value="NCSD" type="string" offset="256"/></magic>
</mime-type>

View File

@ -4,23 +4,19 @@
# --- Builder ----------------
FROM opensauce04/azahar-build-environment:latest AS builder
COPY . /var/azahar-src
RUN mkdir /var/azahar-src/
COPY ./ /var/azahar-src/
RUN mkdir builddir && cd builddir && \
cmake /var/azahar-src -G Ninja \
RUN mkdir ./builddir/
WORKDIR ./builddir/
RUN cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON \
-DENABLE_OPENGL=OFF $( : "TODO: Can we disable these automatically when there's no frontend?") \
-DENABLE_VULKAN=OFF \
-DENABLE_SDL2=OFF \
-DENABLE_LIBUSB=OFF \
-DENABLE_CUBEB=OFF \
-DENABLE_OPENAL=OFF && \
ninja && \
mv bin/Release/azahar-room /usr/local/bin/ && \
cd .. && rm -rf builddir
-DENABLE_ROOM_STANDALONE=ON
RUN ninja
RUN mv ./bin/Release/azahar-room /usr/local/bin/
# --- Final ------------------
FROM debian:trixie AS final

View File

@ -50,15 +50,18 @@ else()
endif()
# Catch2
add_library(catch2 INTERFACE)
if(USE_SYSTEM_CATCH2)
if (ENABLE_TESTS)
add_library(catch2 INTERFACE)
if(USE_SYSTEM_CATCH2)
find_package(Catch2 3.0.0 REQUIRED)
else()
else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2)
add_subdirectory(catch2 EXCLUDE_FROM_ALL)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
# Crypto++
if(USE_SYSTEM_CRYPTOPP)
@ -76,7 +79,7 @@ else()
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake)
add_subdirectory(cryptopp-cmake EXCLUDE_FROM_ALL)
endif()
# dds-ktx
@ -109,7 +112,13 @@ endif()
# Oaknut
if ("arm64" IN_LIST ARCHITECTURE)
if(USE_SYSTEM_OAKNUT)
find_package(oaknut REQUIRED)
add_library(oaknut INTERFACE)
target_link_libraries(oaknut INTERFACE merry::oaknut)
else()
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif()
endif()
# Dynarmic
@ -133,7 +142,7 @@ endif()
# getopt
if (MSVC)
add_subdirectory(getopt)
add_subdirectory(getopt EXCLUDE_FROM_ALL)
endif()
# inih
@ -142,7 +151,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir)
else()
add_subdirectory(inih)
add_subdirectory(inih EXCLUDE_FROM_ALL)
endif()
# MicroProfile
@ -165,7 +174,7 @@ if (NOT MSVC)
endif()
# Open Source Archives
add_subdirectory(open_source_archives)
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
# faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -204,12 +213,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2)
add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
endif()
# libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_LIBUSB)
add_subdirectory(libusb)
add_subdirectory(libusb EXCLUDE_FROM_ALL)
set(LIBUSB_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif()
@ -253,7 +262,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet)
else()
add_subdirectory(enet)
add_subdirectory(enet EXCLUDE_FROM_ALL)
target_include_directories(enet INTERFACE ./enet/include)
endif()
@ -292,6 +301,15 @@ if (USE_DISCORD_PRESENCE)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()
# LibRetro
if (ENABLE_LIBRETRO)
add_library(libretro INTERFACE)
target_include_directories(libretro INTERFACE ./libretro-common/libretro-common/include)
if (ANDROID)
add_subdirectory(libretro-common EXCLUDE_FROM_ALL)
endif()
endif()
# JSON
add_library(json-headers INTERFACE)
if (USE_SYSTEM_JSON)
@ -309,12 +327,8 @@ endif()
# OpenSSL
if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()
if (NOT OPENSSL_FOUND)
else()
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/")
@ -356,7 +370,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE)
add_subdirectory(gamemode)
add_subdirectory(gamemode EXCLUDE_FROM_ALL)
endif()
# cpp-jwt
@ -379,13 +393,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else()
add_subdirectory(lodepng)
add_subdirectory(lodepng EXCLUDE_FROM_ALL)
endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID)
# libyuv
add_subdirectory(libyuv)
add_subdirectory(libyuv EXCLUDE_FROM_ALL)
target_include_directories(yuv INTERFACE ./libyuv/include)
endif()
@ -396,6 +410,9 @@ if (ENABLE_OPENAL)
find_package(OpenAL REQUIRED)
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
else()
if (BSD STREQUAL "OpenBSD")
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
endif()
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
set(ALSOFT_INSTALL OFF CACHE BOOL "")
@ -411,7 +428,7 @@ endif()
# OpenGL dependencies
if (ENABLE_OPENGL)
# Glad
add_subdirectory(glad)
add_subdirectory(glad EXCLUDE_FROM_ALL)
endif()
# Vulkan dependencies
@ -451,7 +468,7 @@ if (ENABLE_VULKAN)
set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang)
add_subdirectory(glslang EXCLUDE_FROM_ALL)
endif()
# sirit
@ -482,11 +499,24 @@ if (ENABLE_VULKAN)
else()
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
target_disable_warnings(vulkan-headers)
if (BSD STREQUAL "NetBSD")
# There may be a better way to do this with
# find_package(X11), but I couldn't get
# CMake to do it, so we're depending on
# the x11-links package and assuming the
# prefix location. -OS
target_include_directories(vulkan-headers INTERFACE
/usr/pkg/share/x11-links/include)
elseif (BSD STREQUAL "OpenBSD")
# This is fine to hardcode because it'll never change
target_include_directories(vulkan-headers INTERFACE
/usr/X11R6/include)
endif()
endif()
# adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools)
add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
endif()
endif()

2
externals/boost vendored

@ -1 +1 @@
Subproject commit 2c82bd787302398bcae990e3c9ab2b451284f4ca
Subproject commit 6a85c3100499e886e11c87a5c2109eedacea0a61

View File

@ -14,6 +14,7 @@ option(USE_SYSTEM_JSON "Use the system JSON (nlohmann-json3) package (instead of
option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)" OFF)
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
option(USE_SYSTEM_OAKNUT "Use the system oaknut (instead of the bundled one)" OFF)
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
@ -40,6 +41,7 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_JSON "Disable system JSON" OFF "USE_SYSTEM
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_OAKNUT "Disable system oaknut" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
@ -66,6 +68,7 @@ set(LIB_VAR_LIST
DYNARMIC
FMT
XBYAK
OAKNUT
INIH
FFMPEG_HEADERS
GLSLANG

1
externals/dllwalker vendored Submodule

@ -0,0 +1 @@
Subproject commit 2f8b349c26832cae612aa7082154c0697a9cbc8e

View File

@ -0,0 +1,16 @@
add_library(libretro_common STATIC
libretro-common/compat/compat_posix_string.c
libretro-common/compat/fopen_utf8.c
libretro-common/encodings/encoding_utf.c
libretro-common/compat/compat_strl.c
libretro-common/file/file_path.c
libretro-common/streams/file_stream.c
libretro-common/streams/file_stream_transforms.c
libretro-common/string/stdstring.c
libretro-common/time/rtime.c
libretro-common/vfs/vfs_implementation.c
)
target_include_directories(libretro_common PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common
${CMAKE_CURRENT_SOURCE_DIR}/libretro-common/include
)

@ -0,0 +1 @@
Subproject commit 7fc7feeddca391be65c94e6541381467684b814d

@ -1 +1 @@
Subproject commit 90191edd20bb877c5cbddfdac7ec0fe49ad93727
Subproject commit e399840fc6aba5f7bc3f0633e8ff10bba0640906

2
externals/sdl2/SDL vendored

@ -1 +1 @@
Subproject commit 2359383fc187386204c3bb22de89655a494cd128
Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71

View File

@ -1,6 +1,8 @@
# Enable modules to include each other's files
include_directories(.)
include(GenerateSettingKeys)
# CMake seems to only define _DEBUG on Windows
set_property(DIRECTORY APPEND PROPERTY
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
@ -110,10 +112,14 @@ else()
# In case a flag isn't supported on e.g. a certain architecture, don't error.
-Wno-unused-command-line-argument
# Build fortification options
-Wp,-D_GLIBCXX_ASSERTIONS
-fstack-protector-strong
)
if (NOT ENABLE_LIBRETRO)
add_compile_options(
-Wp,-D_GLIBCXX_ASSERTIONS
-fstack-clash-protection
)
endif()
# If we define _FORTIFY_SOURCE when it is already defined, compilation will fail
string(FIND "-D_FORTIFY_SOURCE" "${CMAKE_CXX_FLAGS} " FORTIFY_SOURCE_DEFINED)
@ -177,6 +183,9 @@ endif()
if(ENABLE_DEVELOPER_OPTIONS)
add_compile_definitions(ENABLE_DEVELOPER_OPTIONS)
endif()
if(ENABLE_BUILTIN_KEYBLOB)
add_compile_definitions(ENABLE_BUILTIN_KEYBLOB)
endif()
add_subdirectory(common)
add_subdirectory(core)
@ -189,18 +198,19 @@ if (ENABLE_TESTS)
add_subdirectory(tests)
endif()
if (ENABLE_SDL2_FRONTEND)
add_subdirectory(citra_sdl)
endif()
if (ENABLE_QT)
add_subdirectory(citra_qt)
endif()
if (ENABLE_QT OR ENABLE_SDL2_FRONTEND)
if (ENABLE_QT) # Or any other hypothetical future frontends
add_subdirectory(citra_cli)
add_subdirectory(citra_meta)
endif()
if (ENABLE_LIBRETRO)
add_subdirectory(citra_libretro)
endif()
if (ENABLE_ROOM)
add_subdirectory(citra_room)
endif()
@ -209,7 +219,7 @@ if (ENABLE_ROOM_STANDALONE)
add_subdirectory(citra_room_standalone)
endif()
if (ANDROID)
if (ANDROID AND NOT ENABLE_LIBRETRO)
add_subdirectory(android/app/src/main/jni)
target_include_directories(citra-android PRIVATE android/app/src/main)
endif()

View File

@ -24,12 +24,11 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
@Suppress("UnstableApiUsage")
android {
namespace = "org.citra.citra_emu"
compileSdkVersion = "android-35"
ndkVersion = "27.1.12297006"
ndkVersion = "27.3.13750724"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
@ -63,7 +62,7 @@ android {
defaultConfig {
// The application ID refers to Lime3DS to allow for
// the Play Store listing, which was originally set up for Lime3DS, to still be used.
applicationId = "io.github.lime3ds.android"
applicationId = "org.azahar_emu.azahar"
minSdk = 29
targetSdk = 35
versionCode = autoVersion
@ -80,7 +79,8 @@ android {
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
)
}
}
@ -173,6 +173,7 @@ android {
register("googlePlay") {
dimension = "version"
versionNameSuffix = "-googleplay"
applicationId = "io.github.lime3ds.android"
}
}

View File

@ -19,12 +19,14 @@ import android.view.Surface
import android.view.View
import android.widget.TextView
import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
@ -132,7 +134,27 @@ object NativeLibrary {
* If not set, it auto-detects a location
*/
external fun setUserDirectory(directory: String)
external fun getInstalledGamePaths(): Array<String?>
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
return games.mapNotNull { entry ->
entry?.let {
val sep = it.lastIndexOf('|')
if (sep == -1) return@mapNotNull null
val path = it.substring(0, sep)
val mediaType = Game.MediaType.fromInt(it.substring(sep + 1).toInt())
InstalledGame(path, mediaType!!)
}
}.toTypedArray()
}
private external fun getInstalledGamePathsImpl(): Array<String?>
// Create the config.ini file.
external fun createConfigFile()
@ -230,6 +252,16 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
external fun nativeFileExists(path: String): Boolean
external fun deleteOpenGLShaderCache(titleId: Long)
external fun deleteVulkanShaderCache(titleId: Long)
private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object()
@ -286,6 +318,12 @@ object NativeLibrary {
canContinue = false
}
CoreError.ErrorCoreExceptionRaised -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = false
}
CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
@ -408,7 +446,7 @@ object NativeLibrary {
return
}
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
if (resultCode == CoreError.ShutdownRequested.value) {
emulationActivity.finish()
return
}
@ -427,23 +465,51 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity
var captionId = R.string.loader_error_invalid_format
val result = requireArguments().getInt(RESULT_CODE)
if (result == ErrorLoader_ErrorEncrypted) {
captionId = R.string.loader_error_encrypted
var coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
val title: String
val message: String
when (coreError) {
CoreError.ErrorGetLoader, CoreError.ErrorLoader_ErrorInvalidFormat, CoreError.ErrorSystemMode -> {
title = getString(R.string.loader_error_invalid_format)
message = getString(R.string.loader_error_invalid_format_description)
}
CoreError.ErrorLoader_ErrorEncrypted -> {
title = getString(R.string.loader_error_encrypted)
message = getString(R.string.loader_error_encrypted_description)
}
CoreError.ErrorArticDisconnected -> {
title = getString(R.string.artic_base)
message = getString(R.string.artic_server_comm_error)
}
CoreError.ErrorN3DSApplication -> {
title = getString(R.string.loader_error_invalid_system_mode)
message = getString(R.string.loader_error_invalid_system_mode_description)
}
CoreError.ErrorLoader_ErrorPatches -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_applying_patches_description)
}
CoreError.ErrorLoader_ErrorPatchesInvalidTitle -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_patch_wrong_application)
}
else -> {
title = getString(R.string.loader_error_generic_title)
message = getString(R.string.loader_error_generic,
getString(coreError.stringRes), coreError.value)
}
if (result == ErrorArticDisconnected) {
captionId = R.string.artic_base
}
val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(captionId)
.setTitle(title)
.setMessage(
Html.fromHtml(
if (result == ErrorArticDisconnected)
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
else
CitraApplication.appContext.resources.getString(R.string.redump_games),
Html.fromHtml(message,
Html.FROM_HTML_MODE_LEGACY
)
)
@ -465,20 +531,6 @@ object NativeLibrary {
const val RESULT_CODE = "resultcode"
const val Success = 0
const val ErrorNotInitialized = 1
const val ErrorGetLoader = 2
const val ErrorSystemMode = 3
const val ErrorLoader = 4
const val ErrorLoader_ErrorEncrypted = 5
const val ErrorLoader_ErrorInvalidFormat = 6
const val ErrorLoader_ErrorGBATitle = 7
const val ErrorSystemFiles = 8
const val ErrorSavestate = 9
const val ErrorArticDisconnected = 10
const val ShutdownRequested = 11
const val ErrorUnknown = 12
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
val args = Bundle()
args.putInt(RESULT_CODE, resultCode)
@ -691,34 +743,47 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getUserDirectory(uriOverride: Uri? = null): String {
fun getNativePath(uri: Uri): String {
BuildUtil.assertNotGooglePlay()
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val dirSep = "/"
val udUri = uriOverride ?:
preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
val udPathSegment = udUri.lastPathSegment!!
val udVirtualPath = udPathSegment.substringAfter(":")
if (udPathSegment.startsWith("primary:")) { // User directory is located in primary storage
val uriString = uri.toString()
if (!uriString.contains(":")) { // These raw URIs happen when generating the game list. Why?
return uriString
}
if (uri.scheme == "file") {
return uri.path!!
}
val pathSegment = uri.lastPathSegment ?: return ""
val virtualPath = pathSegment.substringAfter(":")
if (pathSegment.startsWith("primary:")) { // User directory is located in primary storage
val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
return primaryStoragePath + dirSep + udVirtualPath + dirSep
return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device
val storageIdString = udPathSegment.substringBefore(":")
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString)
val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
if (udRemovablePath == null) {
if (removablePath == null) {
android.util.Log.e("NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)"
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
)
return ""
}
return udRemovablePath + dirSep + udVirtualPath + dirSep
return removablePath + dirSep + virtualPath
}
}
@Keep
@JvmStatic
fun getUserDirectory(): String {
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
return getNativePath(userDirectoryUri)
}
@Keep
@ -812,12 +877,31 @@ object NativeLibrary {
FileUtil.deleteDocument(path)
}
enum class CoreError {
ErrorSystemFiles,
ErrorSavestate,
ErrorArticDisconnected,
ErrorN3DSApplication,
ErrorUnknown
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
Success(0, R.string.core_error_success),
ErrorNotInitialized(1, R.string.core_error_not_initialized),
ErrorGetLoader(2, R.string.core_error_get_loader),
ErrorSystemMode(3, R.string.core_error_system_mode),
ErrorLoader(4, R.string.core_error_loader),
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
ErrorLoader_ErrorPatches(8, R.string.core_error_loader_error_patches),
ErrorLoader_ErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
ErrorSystemFiles(10, R.string.core_error_system_files),
ErrorSavestate(11, R.string.core_error_savestate),
ErrorArticDisconnected(12, R.string.core_error_artic_disconnected),
ErrorN3DSApplication(13, R.string.core_error_n3ds_application),
ErrorCoreExceptionRaised(14, R.string.core_error_core_exception_raised),
ErrorMemoryExceptionRaised(15, R.string.core_error_memory_exception_raised),
ShutdownRequested(16, R.string.core_error_shutdown_requested),
ErrorUnknown(17, R.string.core_error_unknown);
companion object {
fun fromInt(value: Int): CoreError {
return entries.find { it.value == value } ?: ErrorUnknown
}
}
}
enum class InstallStatus {

View File

@ -8,9 +8,9 @@ import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
@ -21,6 +21,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.core.os.BundleCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@ -43,6 +44,7 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.fragments.EmulationFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
@ -79,7 +81,9 @@ class EmulationActivity : AppCompatActivity() {
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
}
private var isRotationBlocked: Boolean = true
private var isEmulationRunning: Boolean = false
private var isEmulationReady: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -88,12 +92,20 @@ class EmulationActivity : AppCompatActivity() {
ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings()
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
// Block orientation until emulation is ready to prevent unneccesary
// surface recreation until the renderer is ready.
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
super.onCreate(savedInstanceState)
secondaryDisplay = SecondaryDisplay(this)
secondaryDisplay.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root)
@ -118,8 +130,6 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true
instance = this
applyOrientationSettings() // Check for orientation settings at startup
val game = try {
intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
@ -135,13 +145,46 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStart(game.titleId)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
NativeLibrary.stopEmulation()
NativeLibrary.playTimeManagerStop()
isEmulationReady = false
isRotationBlocked = true
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
emulationViewModel.setEmulationStarted(false)
val game = intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java)
}
if (game != null) {
NativeLibrary.playTimeManagerStart(game.titleId)
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
}
// On some devices, the system bars will not disappear on first boot or after some
// rotations. Here we set full screen immersive repeatedly in onResume and in
// onWindowFocusChanged to prevent the unwanted status bar state.
override fun onResume() {
super.onResume()
enableFullscreenImmersive()
applyOrientationSettings() // Check for orientation settings changes on runtime
if (isEmulationReady) {
// If emulation is ready then unblock rotation
isRotationBlocked = false
applyOrientationSettings()
emulationViewModel.setEmulationStarted(true)
} else {
if (!isRotationBlocked) {
applyOrientationSettings()
}
}
super.onResume()
}
override fun onStop() {
@ -150,8 +193,8 @@ class EmulationActivity : AppCompatActivity() {
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
enableFullscreenImmersive()
super.onWindowFocusChanged(hasFocus)
}
public override fun onRestart() {
@ -163,11 +206,15 @@ class EmulationActivity : AppCompatActivity() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean("isEmulationRunning", isEmulationRunning)
outState.putBoolean("isEmulationReady", isEmulationReady)
outState.putBoolean("isRotationBlocked", isRotationBlocked)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
}
override fun onDestroy() {
@ -221,6 +268,11 @@ class EmulationActivity : AppCompatActivity() {
fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true)
isEmulationReady = true
if (isRotationBlocked) {
isRotationBlocked = false
applyOrientationSettings()
}
Toast.makeText(
applicationContext,
getString(R.string.emulation_menu_help),
@ -267,41 +319,34 @@ class EmulationActivity : AppCompatActivity() {
return super.dispatchKeyEvent(event)
}
val button =
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) {
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) {
// If the hotkey is pressed, we don't want to open the drawer
if (!hotkeyUtility.HotkeyIsPressed) {
if (!hotkeyUtility.hotkeyIsPressed) {
onBackPressed()
return true
}
}
// Normal key events.
NativeLibrary.ButtonState.PRESSED
return hotkeyUtility.handleKeyPress(event)
}
KeyEvent.ACTION_UP -> {
hotkeyUtility.HotkeyIsPressed = false
NativeLibrary.ButtonState.RELEASED
return hotkeyUtility.handleKeyRelease(event)
}
else -> {
return false;
}
else -> return false
}
val input = event.device
?: // Controller was disconnected
return false
return NativeLibrary.onGamePadEvent(input.descriptor, button, action)
}
private fun onAmiiboSelected(selectedFile: String) {
val success = NativeLibrary.loadAmiibo(selectedFile)
if (!success) {
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
MessageDialogFragment.newInstance(
R.string.amiibo_load_error,
R.string.amiibo_load_error_message
@ -524,13 +569,19 @@ class EmulationActivity : AppCompatActivity() {
return true
}
val openFileLauncher =
val openAmiiboFileLauncher =
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, applicationContext, listOf<String>("bin")
) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) {
onAmiiboSelected(selectedFiles[0])
} else {
val fileUri = selectedFiles[0].toUri()
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
onAmiiboSelected(nativePath)
}
}
val openImageLauncher =

View File

@ -36,6 +36,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
@ -54,8 +55,10 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter(
@ -136,7 +139,7 @@ class GameAdapter(
val holder = view.tag as GameViewHolder
gameExists(holder)
if (holder.game.titleId == 0L) {
if (!holder.game.valid) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.properties)
.setMessage(R.string.properties_not_loaded)
@ -153,12 +156,21 @@ class GameAdapter(
if (holder.game.isInstalled) {
return true
}
val gameExists = DocumentFile.fromSingleUri(
val path = holder.game.path
val pathUri = path.toUri()
var gameExists: Boolean
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
gameExists =
DocumentFile.fromSingleUri(
CitraApplication.appContext,
Uri.parse(holder.game.path)
pathUri
)?.exists() == true
} else {
val nativePath = NativeLibrary.getNativePath(pathUri)
gameExists = NativeLibrary.nativeFileExists(nativePath)
}
return if (!gameExists) {
Log.error("[GameAdapter] ROM file does not exist: $path")
Toast.makeText(
CitraApplication.appContext,
R.string.loader_error_file_not_found,
@ -323,14 +335,16 @@ class GameAdapter(
}
}
val titleId = game.titleId
val dlcTitleId = titleId or 0x8C00000000L
val updateTitleId = titleId or 0xE00000000L
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())
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()
@ -501,6 +515,63 @@ class GameAdapter(
showUninstallContextMenu(it, game, bottomSheetDialog)
}
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
var selectedIndex = -1
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_cache_select_backend)
.setSingleChoiceItems(options, -1) { dialog, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) {_, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
Toast.LENGTH_LONG
)
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
}
activity.runOnUiThread {
progToast.cancel()
Toast.makeText(
CitraApplication.appContext,
R.string.shader_cache_deleted,
Toast.LENGTH_SHORT
).show()
}
}
}
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
.create()
dialog.setOnShowListener {
val positiveButton = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE)
positiveButton.isEnabled = false
val listView = dialog.listView
listView.setOnItemClickListener { _, _, position, _ ->
selectedIndex = position
positiveButton.isEnabled = true
}
}
dialog.show()
}
val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -556,7 +627,9 @@ class GameAdapter(
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.titleId == newItem.titleId
// The title is taken into account to support 3DSX, which all have the titleID 0.
// This only works now because we always return the English title, adjust if that changes.
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): 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.
@ -57,6 +57,7 @@ object StillImageCameraHelper {
val request = ImageRequest.Builder(context)
.data(uri)
.size(width, height)
.allowHardware(false)
.build()
return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
width,

View File

@ -12,6 +12,7 @@ import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
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.IntListSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings
@ -31,8 +32,16 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
}
fun cycleLayouts() {
val landscapeValues = context.resources.getIntArray(R.array.landscapeValues)
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty())
landscapeLayoutsToCycle.toIntArray()
else context.resources.getIntArray(
R.array.landscapeValues
)
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) {

View File

@ -6,18 +6,15 @@ package org.citra.citra_emu.display
import android.app.Presentation
import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Bundle
import android.view.Display
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.display.SecondaryDisplayLayout
import org.citra.citra_emu.NativeLibrary
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
@ -50,7 +47,7 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
val currentDisplayId = context.display.displayId
val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
return displays.firstOrNull {
val extDisplays = displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable &&
@ -59,9 +56,18 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
it.state != Display.STATE_OFF &&
it.isValid
}
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
?: extDisplays.firstOrNull()
return selected
}
fun updateDisplay() {
// return early if the parent context is dead or dying
if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
return
}
// decide if we are going to the external display or the internal one
var display = getExternalDisplay(context)
if (display == null ||
@ -74,12 +80,25 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
// otherwise, make a new presentation
releasePresentation()
try {
pres = SecondaryDisplayPresentation(context, display!!, this)
pres?.show()
}
// catch BadTokenException and InvalidDisplayException,
// the display became invalid asynchronously, so we can assign to null
// until onDisplayAdded/Removed/Changed is called and logic retriggered
catch (_: WindowManager.BadTokenException) {
pres = null
} catch (_: WindowManager.InvalidDisplayException) {
pres = null
}
}
fun releasePresentation() {
try {
pres?.dismiss()
} catch (_: Exception) { }
pres = null
}

View File

@ -11,5 +11,6 @@ enum class Hotkey(val button: Int) {
PAUSE_OR_RESUME(10004),
QUICKSAVE(10005),
QUICKLOAD(10006),
TURBO_LIMIT(10007);
TURBO_LIMIT(10007),
ENABLE(10008);
}

View File

@ -5,22 +5,108 @@
package org.citra.citra_emu.features.hotkeys
import android.content.Context
import android.view.KeyEvent
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
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
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.Settings
class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
private val context: Context) {
private val context: Context
) {
private val hotkeyButtons = Hotkey.entries.map { it.button }
var HotkeyIsPressed = false
private var hotkeyIsEnabled = false
var hotkeyIsPressed = false
private val currentlyPressedButtons = mutableSetOf<Int>()
fun handleKeyPress(keyEvent: KeyEvent): Boolean {
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val enableButton =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
.getString(Settings.HOTKEY_ENABLE, "")
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
hotkeyIsEnabled = hotkeyIsEnabled || enableButton == "" || thisKeyIsEnableButton
// Now process all internal buttons associated with this keypress
for (button in buttonSet) {
currentlyPressedButtons.add(button)
//option 1 - this is the enable command, which was already handled
if (button == Hotkey.ENABLE.button) {
handled = true
}
// option 2 - this is a different hotkey command
else if (hotkeyButtons.contains(button)) {
if (hotkeyIsEnabled) {
handled = handleHotkey(button) || handled
}
}
// option 3 - this is a normal key
else {
// if this key press is ALSO associated with a hotkey that will process, skip
// the normal key event.
if (!thisKeyIsHotkey || !hotkeyIsEnabled) {
handled = NativeLibrary.onGamePadEvent(
keyEvent.device.descriptor,
button,
NativeLibrary.ButtonState.PRESSED
) || handled
}
}
}
return handled
}
fun handleKeyRelease(keyEvent: KeyEvent): Boolean {
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) {
handled = true; hotkeyIsEnabled = false
}
for (button in buttonSet) {
// this is a hotkey button
if (hotkeyButtons.contains(button)) {
currentlyPressedButtons.remove(button)
if (!currentlyPressedButtons.any { hotkeyButtons.contains(it) }) {
// all hotkeys are no longer pressed
hotkeyIsPressed = false
}
} else {
// if this key ALSO sends a hotkey command that we already/will handle,
// or if we did not register the press of this button, e.g. if this key
// was also a hotkey pressed after enable, but released after enable button release, then
// skip the normal key event
if ((!thisKeyIsHotkey || !hotkeyIsEnabled) && currentlyPressedButtons.contains(
button
)
) {
handled = NativeLibrary.onGamePadEvent(
keyEvent.device.descriptor,
button,
NativeLibrary.ButtonState.RELEASED
) || handled
currentlyPressedButtons.remove(button)
}
}
}
return handled
}
fun handleHotkey(bindedButton: Int): Boolean {
if(hotkeyButtons.contains(bindedButton)) {
when (bindedButton) {
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
@ -29,26 +115,30 @@ class HotkeyUtility(
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context,
Toast.makeText(
context,
context.getString(R.string.saving),
Toast.LENGTH_SHORT).show()
Toast.LENGTH_SHORT
).show()
}
Hotkey.QUICKLOAD.button -> {
val wasLoaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
val stringRes = if(wasLoaded) {
val stringRes = if (wasLoaded) {
R.string.loading
} else {
R.string.quickload_not_found
}
Toast.makeText(context,
Toast.makeText(
context,
context.getString(stringRes),
Toast.LENGTH_SHORT).show()
Toast.LENGTH_SHORT
).show()
}
else -> {}
}
HotkeyIsPressed = true
hotkeyIsPressed = true
return true
}
return false
}
}

View File

@ -0,0 +1,144 @@
// 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.features.settings
// This list should mirror the list in GenerateSettingKeys.cmake,
// specifically the Shared and Android setting keys.
@Suppress("KotlinJniMissingFunction", "FunctionName")
object SettingKeys {
// Shared
external fun use_artic_base_controller(): String
external fun use_cpu_jit(): String
external fun cpu_clock_percentage(): String
external fun is_new_3ds(): String
external fun lle_applets(): String
external fun deterministic_async_operations(): String
external fun enable_required_online_lle_modules(): String
external fun use_virtual_sd(): String
external fun compress_cia_installs(): String
external fun async_fs_operations(): String
external fun region_value(): String
external fun init_clock(): String
external fun init_time(): String
external fun init_ticks_type(): String
external fun init_ticks_override(): String
external fun plugin_loader(): String
external fun allow_plugin_loader(): String
external fun steps_per_hour(): String
external fun apply_region_free_patch(): String
external fun graphics_api(): String
external fun use_gles(): String
external fun renderer_debug(): String
external fun spirv_shader_gen(): String
external fun disable_spirv_optimizer(): String
external fun async_shader_compilation(): String
external fun async_presentation(): String
external fun use_hw_shader(): String
external fun use_disk_shader_cache(): String
external fun shaders_accurate_mul(): String
external fun use_vsync(): String
external fun use_shader_jit(): String
external fun resolution_factor(): String
external fun frame_limit(): String
external fun turbo_limit(): String
external fun texture_filter(): String
external fun texture_sampling(): String
external fun delay_game_render_thread_us(): String
external fun simulate_3ds_gpu_timings(): String
external fun layout_option(): String
external fun swap_screen(): String
external fun upright_screen(): String
external fun secondary_display_layout(): String
external fun large_screen_proportion(): String
external fun screen_gap(): String
external fun small_screen_position(): String
external fun custom_top_x(): String
external fun custom_top_y(): String
external fun custom_top_width(): String
external fun custom_top_height(): String
external fun custom_bottom_x(): String
external fun custom_bottom_y(): String
external fun custom_bottom_width(): String
external fun custom_bottom_height(): String
external fun custom_second_layer_opacity(): String
external fun aspect_ratio(): String
external fun portrait_layout_option(): String
external fun custom_portrait_top_x(): String
external fun custom_portrait_top_y(): String
external fun custom_portrait_top_width(): String
external fun custom_portrait_top_height(): String
external fun custom_portrait_bottom_x(): String
external fun custom_portrait_bottom_y(): String
external fun custom_portrait_bottom_width(): String
external fun custom_portrait_bottom_height(): String
external fun bg_red(): String
external fun bg_green(): String
external fun bg_blue(): String
external fun render_3d(): String
external fun factor_3d(): String
external fun swap_eyes_3d(): String
external fun render_3d_which_display(): String
external fun cardboard_screen_size(): String
external fun cardboard_x_shift(): String
external fun cardboard_y_shift(): String
external fun filter_mode(): String
external fun pp_shader_name(): String
external fun anaglyph_shader_name(): String
external fun dump_textures(): String
external fun custom_textures(): String
external fun preload_textures(): String
external fun async_custom_loading(): String
external fun disable_right_eye_render(): String
external fun audio_emulation(): String
external fun enable_audio_stretching(): String
external fun enable_realtime_audio(): String
external fun simulate_headphones_plugged(): String
external fun volume(): String
external fun output_type(): String
external fun output_device(): String
external fun input_type(): String
external fun input_device(): String
external fun delay_start_for_lle_modules(): String
external fun use_gdbstub(): String
external fun gdbstub_port(): String
external fun instant_debug_log(): String
external fun enable_rpc_server(): String
external fun toggle_unique_data_console_type(): String
external fun log_filter(): String
external fun log_regex_filter(): String
external fun use_integer_scaling(): String
external fun layouts_to_cycle(): String
external fun camera_inner_flip(): String
external fun camera_outer_left_flip(): String
external fun camera_outer_right_flip(): String
external fun camera_inner_name(): String
external fun camera_inner_config(): String
external fun camera_outer_left_name(): String
external fun camera_outer_left_config(): String
external fun camera_outer_right_name(): String
external fun camera_outer_right_config(): String
external fun last_artic_base_addr(): String
external fun motion_device(): String
external fun touch_device(): String
external fun udp_input_address(): String
external fun udp_input_port(): String
external fun udp_pad_index(): String
external fun record_frame_times(): String
// Android
external fun expand_to_cutout_area(): String
external fun performance_overlay_enable(): String
external fun performance_overlay_show_fps(): String
external fun performance_overlay_show_frame_time(): String
external fun performance_overlay_show_speed(): String
external fun performance_overlay_show_app_ram_usage(): String
external fun performance_overlay_show_available_ram(): String
external fun performance_overlay_show_battery_temp(): String
external fun performance_overlay_background(): String
external fun use_frame_limit(): String
external fun android_hide_images(): String
external fun screen_orientation(): String
external fun performance_overlay_position(): String
}

View File

@ -0,0 +1,9 @@
// 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.features.settings.model
interface AbstractListSetting<E> : AbstractSetting {
var list: List<E>
}

View File

@ -4,56 +4,62 @@
package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class BooleanSetting(
override val key: String,
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),
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_SPEED("performance_overlay_show_speed", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_APP_RAM_USAGE("performance_overlay_show_app_ram_usage", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_AVAILABLE_RAM("performance_overlay_show_available_ram", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_BATTERY_TEMP("performance_overlay_show_battery_temp", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_BACKGROUND("performance_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),
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", Settings.SECTION_RENDERER, false),
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),
COMPRESS_INSTALLED_CIA_CONTENT("compress_cia_installs", Settings.SECTION_STORAGE, false),
ANDROID_HIDE_IMAGES("android_hide_images", Settings.SECTION_CORE, false),
APPLY_REGION_FREE_PATCH("apply_region_free_patch", Settings.SECTION_SYSTEM, true);
EXPAND_TO_CUTOUT_AREA(SettingKeys.expand_to_cutout_area(), Settings.SECTION_LAYOUT, false),
SPIRV_SHADER_GEN(SettingKeys.spirv_shader_gen(), Settings.SECTION_RENDERER, true),
ASYNC_SHADERS(SettingKeys.async_shader_compilation(), Settings.SECTION_RENDERER, false),
DISABLE_SPIRV_OPTIMIZER(SettingKeys.disable_spirv_optimizer(), Settings.SECTION_RENDERER, true),
PLUGIN_LOADER(SettingKeys.plugin_loader(), Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER(SettingKeys.allow_plugin_loader(), Settings.SECTION_SYSTEM, true),
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
SHADERS_ACCURATE_MUL(SettingKeys.shaders_accurate_mul(), Settings.SECTION_RENDERER, false),
DISK_SHADER_CACHE(SettingKeys.use_disk_shader_cache(), Settings.SECTION_RENDERER, true),
DUMP_TEXTURES(SettingKeys.dump_textures(), Settings.SECTION_UTILITY, false),
CUSTOM_TEXTURES(SettingKeys.custom_textures(), Settings.SECTION_UTILITY, false),
ASYNC_CUSTOM_LOADING(SettingKeys.async_custom_loading(), Settings.SECTION_UTILITY, true),
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false),
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
override var boolean: Boolean = defaultValue
@ -80,6 +86,7 @@ enum class BooleanSetting(
REQUIRED_ONLINE_LLE_MODULES,
NEW_3DS,
LLE_APPLETS,
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
VSYNC,
DEBUG_RENDERER,
CPU_JIT,
@ -87,6 +94,7 @@ enum class BooleanSetting(
SHADERS_ACCURATE_MUL,
USE_ARTIC_BASE_CONTROLLER,
COMPRESS_INSTALLED_CIA_CONTENT,
ASYNC_FS_OPERATIONS,
ANDROID_HIDE_IMAGES,
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
APPLY_REGION_FREE_PATCH

View File

@ -4,17 +4,18 @@
package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class FloatSetting(
override val key: String,
override val section: String,
override val defaultValue: Float
) : AbstractFloatSetting {
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY("custom_second_layer_opacity", Settings.SECTION_RENDERER, 100f),
BACKGROUND_RED("bg_red", Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE("bg_blue", Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN("bg_green", Settings.SECTION_RENDERER, 0f),
EMPTY_SETTING("", "", 0.0f);
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);
override var float: Float = defaultValue

View File

@ -0,0 +1,54 @@
// 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.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntListSetting(
override val key: String,
override val section: String,
override val defaultValue: List<Int>,
val canBeEmpty: Boolean = true
) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE(SettingKeys.layouts_to_cycle(), Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
private var backingList: List<Int> = defaultValue
private var lastValidList : List<Int> = defaultValue
override var list: List<Int>
get() = backingList
set(value) {
if (!canBeEmpty && value.isEmpty()) {
backingList = lastValidList
} else {
backingList = value
lastValidList = value
}
}
override val valueAsString: String
get() = list.joinToString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
if (setting == this) {
return false
}
}
return true
}
companion object {
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue }
}
}

View File

@ -4,57 +4,59 @@
package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntSetting(
override val key: String,
override val section: String,
override val defaultValue: Int
) : AbstractIntSetting {
FRAME_LIMIT("frame_limit", Settings.SECTION_RENDERER, 100),
EMULATED_REGION("region_value", Settings.SECTION_SYSTEM, -1),
INIT_CLOCK("init_clock", Settings.SECTION_SYSTEM, 0),
CAMERA_INNER_FLIP("camera_inner_flip", Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_LEFT_FLIP("camera_outer_left_flip", Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
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),
SECONDARY_DISPLAY_LAYOUT("secondary_display_layout",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),
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
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),
CPU_CLOCK_SPEED("cpu_clock_percentage", Settings.SECTION_CORE, 100),
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),
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
FRAME_LIMIT(SettingKeys.frame_limit(), Settings.SECTION_RENDERER, 100),
EMULATED_REGION(SettingKeys.region_value(), Settings.SECTION_SYSTEM, -1),
INIT_CLOCK(SettingKeys.init_clock(), Settings.SECTION_SYSTEM, 0),
CAMERA_INNER_FLIP(SettingKeys.camera_inner_flip(), Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 2),
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0),
CARDBOARD_SCREEN_SIZE(SettingKeys.cardboard_screen_size(), Settings.SECTION_LAYOUT, 85),
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
TEXTURE_SAMPLING(SettingKeys.texture_sampling(), Settings.SECTION_RENDERER, 0),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, 1),
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue

View File

@ -1,16 +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.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class ScaledFloatSetting(
override val key: String,
override val section: String,
override val defaultValue: Float,
val scale: Int
) : AbstractFloatSetting {
AUDIO_VOLUME("volume", Settings.SECTION_AUDIO, 1.0f, 100);
AUDIO_VOLUME(SettingKeys.volume(), Settings.SECTION_AUDIO, 1.0f, 100);
override var float: Float = defaultValue
get() = field * scale

View File

@ -113,6 +113,7 @@ class Settings {
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
const val SECTION_PERFORMANCE_OVERLAY = "Performance Overlay"
const val SECTION_STORAGE = "Storage"
const val SECTION_MISC = "Miscellaneous"
const val KEY_BUTTON_A = "button_a"
const val KEY_BUTTON_B = "button_b"
@ -135,6 +136,7 @@ class Settings {
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
const val HOTKEY_ENABLE = "hotkey_enable"
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
@ -202,6 +204,7 @@ class Settings {
R.string.button_zr
)
val hotKeys = listOf(
HOTKEY_ENABLE,
HOTKEY_SCREEN_SWAP,
HOTKEY_CYCLE_LAYOUT,
HOTKEY_CLOSE_GAME,
@ -211,6 +214,7 @@ class Settings {
HOTKEY_TURBO_LIMIT
)
val hotkeyTitles = listOf(
R.string.controller_hotkey_enable_button,
R.string.emulation_swap_screens,
R.string.emulation_cycle_landscape_layouts,
R.string.emulation_close_game,
@ -220,6 +224,7 @@ class Settings {
R.string.turbo_limit_hotkey
)
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
const val PREF_MATERIAL_YOU = "MaterialYouTheme"
const val PREF_THEME_MODE = "ThemeMode"

View File

@ -1,21 +1,23 @@
// 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.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class StringSetting(
override val key: String,
override val section: String,
override val defaultValue: String
) : AbstractStringSetting {
INIT_TIME("init_time", Settings.SECTION_SYSTEM, "946731601"),
CAMERA_INNER_NAME("camera_inner_name", Settings.SECTION_CAMERA, "ndk"),
CAMERA_INNER_CONFIG("camera_inner_config", Settings.SECTION_CAMERA, "_front"),
CAMERA_OUTER_LEFT_NAME("camera_outer_left_name", Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_LEFT_CONFIG("camera_outer_left_config", Settings.SECTION_CAMERA, "_back"),
CAMERA_OUTER_RIGHT_NAME("camera_outer_right_name", Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back");
INIT_TIME(SettingKeys.init_time(), Settings.SECTION_SYSTEM, "946731601"),
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
override var string: String = defaultValue

View File

@ -9,6 +9,7 @@ import android.content.SharedPreferences
import android.view.InputDevice
import android.view.InputDevice.MotionRange
import android.view.KeyEvent
import android.view.MotionEvent
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
@ -128,6 +129,7 @@ class InputBindingSetting(
Settings.KEY_BUTTON_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
Settings.KEY_BUTTON_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
Settings.KEY_BUTTON_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
Settings.HOTKEY_ENABLE -> Hotkey.ENABLE.button
Settings.HOTKEY_SCREEN_SWAP -> Hotkey.SWAP_SCREEN.button
Settings.HOTKEY_CYCLE_LAYOUT -> Hotkey.CYCLE_LAYOUT.button
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
@ -162,36 +164,40 @@ class InputBindingSetting(
fun removeOldMapping() {
// Try remove all possible keys we wrote for this setting
val oldKey = preferences.getString(reverseKey, "")
(setting as AbstractStringSetting).string = ""
if (oldKey != "") {
(setting as AbstractStringSetting).string = ""
preferences.edit()
.remove(abstractSetting.key) // Used for ui text
.remove(oldKey) // Used for button mapping
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
.remove(oldKey + "_GuestButton") // Used for axis button
.remove(oldKey + "_Inverted") // used for axis inversion
.apply()
.remove(reverseKey)
val buttonCodes = try {
preferences.getStringSet(oldKey, mutableSetOf<String>())!!.toMutableSet()
} catch (e: ClassCastException) {
// if this is an int pref, either old button or an axis, so just remove it
preferences.edit().remove(oldKey).apply()
return;
}
buttonCodes.remove(buttonCode.toString());
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
}
}
/**
* Helper function to write a gamepad button mapping for the setting.
*/
private fun writeButtonMapping(key: String) {
private fun writeButtonMapping(keyEvent: KeyEvent) {
val editor = preferences.edit()
// Remove mapping for another setting using this input
val oldButtonCode = preferences.getInt(key, -1)
if (oldButtonCode != -1) {
val oldKey = getButtonKey(oldButtonCode)
editor.remove(oldKey) // Only need to remove UI text setting, others will be overwritten
}
val key = getInputButtonKey(keyEvent)
// Pull in all codes associated with this key
// Migrate from the old int preference if need be
val buttonCodes = InputBindingSetting.getButtonSet(keyEvent)
buttonCodes.add(buttonCode)
// Cleanup old mapping for this setting
removeOldMapping()
// Write new mapping
editor.putInt(key, buttonCode)
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
// Write next reverse mapping for future cleanup
editor.putString(reverseKey, key)
@ -229,9 +235,8 @@ class InputBindingSetting(
}
val code = translateEventToKeyId(keyEvent)
writeButtonMapping(getInputButtonKey(code))
val uiString = "${keyEvent.device.name}: Button $code"
value = uiString
writeButtonMapping(keyEvent)
value = "${keyEvent.device.name}: ${getButtonName(code)}"
}
/**
@ -255,9 +260,10 @@ class InputBindingSetting(
} else {
buttonCode
}
writeAxisMapping(motionRange.axis, button, axisDir == '-')
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
value = uiString
// use UP (-) to map vertical, but use RIGHT (+) to map horizontal
val inverted = if (isHorizontalOrientation()) axisDir == '-' else axisDir == '+'
writeAxisMapping(motionRange.axis, button, inverted)
value = "Axis ${motionRange.axis}$axisDir"
}
override val type = TYPE_INPUT_BINDING
@ -265,6 +271,241 @@ class InputBindingSetting(
companion object {
private const val INPUT_MAPPING_PREFIX = "InputMapping"
private fun toTitleCase(raw: String): String =
raw.replace("_", " ").lowercase()
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
private const val BUTTON_NAME_L3 = "Button L3"
private const val BUTTON_NAME_R3 = "Button R3"
private val buttonNameOverrides = mapOf(
KeyEvent.KEYCODE_BUTTON_THUMBL to BUTTON_NAME_L3,
KeyEvent.KEYCODE_BUTTON_THUMBR to BUTTON_NAME_R3,
LINUX_BTN_DPAD_UP to "Dpad Up",
LINUX_BTN_DPAD_DOWN to "Dpad Down",
LINUX_BTN_DPAD_LEFT to "Dpad Left",
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
)
fun getButtonName(keyCode: Int): String =
buttonNameOverrides[keyCode]
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
private data class DefaultButtonMapping(
val settingKey: String,
val hostKeyCode: Int,
val guestButtonCode: Int
)
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
private data class DefaultAxisMapping(
val settingKey: String,
val hostAxis: Int,
val guestButton: Int,
val orientation: Int,
val inverted: Boolean
)
private val xboxFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
)
private val nintendoFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
)
private val commonButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
)
private val dpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
)
private val stickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
)
private val dpadAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
)
// Nintendo Switch Joy-Con specific mappings.
// Joy-Cons connected via Bluetooth on Android have several quirks:
// - They register as two separate InputDevices (left and right)
// - Android's evdev translation swaps A<->B (BTN_EAST->BUTTON_B, BTN_SOUTH->BUTTON_A)
// but does NOT swap X<->Y (BTN_NORTH->BUTTON_X, BTN_WEST->BUTTON_Y)
// - D-pad buttons arrive as KEYCODE_UNKNOWN (0) with Linux BTN_DPAD_* scan codes
// - Right stick uses AXIS_RX/AXIS_RY instead of AXIS_Z/AXIS_RZ
private const val NINTENDO_VENDOR_ID = 0x057e
// Linux BTN_DPAD_* values (0x220-0x223). Joy-Con D-pad buttons arrive as
// KEYCODE_UNKNOWN with these scan codes because Android's input layer doesn't
// translate them to KEYCODE_DPAD_*. translateEventToKeyId() falls back to
// the scan code in that case.
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
private const val LINUX_BTN_DPAD_RIGHT = 0x223 // 547
// Joy-Con face buttons: A/B are swapped by Android's evdev layer, but X/Y are not.
// This is different from both the standard Xbox table (full swap) and the
// Nintendo table (no swap).
private val joyconFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
)
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
private val joyconDpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
)
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
private val joyconStickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
)
/**
* Detects whether a device is a Nintendo Switch Joy-Con (as opposed to a
* Pro Controller or other Nintendo device) by checking vendor ID + device
* capabilities. Joy-Cons lack AXIS_HAT_X/Y and use AXIS_RX/RY for the
* right stick, while the Pro Controller has standard HAT axes and Z/RZ.
*/
fun isJoyCon(device: InputDevice?): Boolean {
if (device == null) return false
if (device.vendorId != NINTENDO_VENDOR_ID) return false
// Pro Controllers have HAT_X/HAT_Y (D-pad) and Z/RZ (right stick).
// Joy-Cons lack both: no HAT axes, right stick on RX/RY instead of Z/RZ.
var hasHatAxes = false
var hasStandardRightStick = false
for (range in device.motionRanges) {
when (range.axis) {
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> hasHatAxes = true
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> hasStandardRightStick = true
}
}
return !hasHatAxes && !hasStandardRightStick
}
private val allBindingKeys: Set<String> by lazy {
(Settings.buttonKeys + Settings.triggerKeys +
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
Settings.dPadButtonKeys).toSet()
}
fun clearAllBindings() {
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val editor = prefs.edit()
val allKeys = prefs.all.keys.toList()
for (key in allKeys) {
if (key.startsWith(INPUT_MAPPING_PREFIX) || key in allBindingKeys) {
editor.remove(key)
}
}
editor.apply()
}
private fun applyBindings(
buttonMappings: List<DefaultButtonMapping>,
axisMappings: List<DefaultAxisMapping>
) {
val prefs = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val editor = prefs.edit()
buttonMappings.forEach { applyDefaultButtonMapping(editor, it) }
axisMappings.forEach { applyDefaultAxisMapping(editor, it) }
editor.apply()
}
/**
* Applies Joy-Con specific bindings: scan code D-pad, partial face button
* swap, and AXIS_RX/RY right stick.
*/
fun applyJoyConBindings() {
applyBindings(
joyconFaceButtonMappings + commonButtonMappings + joyconDpadButtonMappings,
joyconStickAxisMappings
)
}
/**
* Applies auto-mapped bindings based on detected controller layout and d-pad type.
*
* @param isNintendoLayout true if the controller uses Nintendo face button layout
* (A=east, B=south), false for Xbox layout (A=south, B=east)
* @param useAxisDpad true if the d-pad should be mapped as axis (HAT_X/HAT_Y),
* false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT)
*/
fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) {
val faceButtons = if (isNintendoLayout) nintendoFaceButtonMappings else xboxFaceButtonMappings
val buttonMappings = if (useAxisDpad) {
faceButtons + commonButtonMappings
} else {
faceButtons + commonButtonMappings + dpadButtonMappings
}
val axisMappings = if (useAxisDpad) {
stickAxisMappings + dpadAxisMappings
} else {
stickAxisMappings
}
applyBindings(buttonMappings, axisMappings)
}
private fun applyDefaultButtonMapping(
editor: SharedPreferences.Editor,
mapping: DefaultButtonMapping
) {
val prefKey = getInputButtonKey(mapping.hostKeyCode)
editor.putInt(prefKey, mapping.guestButtonCode)
editor.putString(mapping.settingKey, getButtonName(mapping.hostKeyCode))
editor.putString(
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}",
prefKey
)
}
private fun applyDefaultAxisMapping(
editor: SharedPreferences.Editor,
mapping: DefaultAxisMapping
) {
val axisKey = getInputAxisKey(mapping.hostAxis)
editor.putInt(getInputAxisOrientationKey(mapping.hostAxis), mapping.orientation)
editor.putInt(getInputAxisButtonKey(mapping.hostAxis), mapping.guestButton)
editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted)
val dir = if (mapping.orientation == 0) '+' else '-'
editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir")
val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
editor.putString(reverseKey, axisKey)
}
/**
* Returns the settings key for the specified Citra button code.
*/
@ -287,19 +528,31 @@ class InputBindingSetting(
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
else -> ""
}
/**
* Helper function to get the settings key for an gamepad button.
*
* Get the mutable set of int button values this key should map to given an event
*/
@Deprecated("Use the new getInputButtonKey(keyEvent) method to handle unknown keys")
fun getInputButtonKey(keyCode: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyCode}"
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
val key = getInputButtonKey(keyCode)
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
var buttonCodes = try {
preferences.getStringSet(key, mutableSetOf<String>())
} catch (e: ClassCastException) {
val prefInt = preferences.getInt(key, -1);
val migratedSet = if (prefInt != -1) {
mutableSetOf(prefInt.toString())
} else {
mutableSetOf<String>()
}
migratedSet
}
if (buttonCodes == null) buttonCodes = mutableSetOf<String>()
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
}
/**
* Helper function to get the settings key for an gamepad button.
*
*/
fun getInputButtonKey(event: KeyEvent): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${translateEventToKeyId(event)}"
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
/**
* Helper function to get the settings key for an gamepad axis.

View File

@ -0,0 +1,46 @@
// 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.features.settings.model.view
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
class MultiChoiceSetting(
setting: AbstractSetting?,
titleId: Int,
descriptionId: Int,
val choicesId: Int,
val valuesId: Int,
val key: String? = null,
val defaultValue: List<Int>? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_MULTI_CHOICE
val selectedValues: List<Int>
get() {
if (setting == null) {
return defaultValue!!
}
try {
val setting = setting as IntListSetting
return setting.list
}catch (_: ClassCastException) {
}
return defaultValue!!
}
/**
* Write a value to the backing list. If that int was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the int.
* @return the existing setting with the new value applied.
*/
fun setSelectedValue(selection: List<Int>): IntListSetting {
val intSetting = setting as IntListSetting
intSetting.list = selection
return intSetting
}
}

View File

@ -1,10 +1,11 @@
// 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.features.settings.model.view
import androidx.annotation.DrawableRes
import org.citra.citra_emu.activities.EmulationActivity
class RunnableSetting(
titleId: Int,
@ -12,7 +13,11 @@ class RunnableSetting(
val isRuntimeRunnable: Boolean,
@DrawableRes val iconId: Int = 0,
val runnable: () -> Unit,
val value: (() -> String)? = null
val value: (() -> String)? = null,
val onLongClick: (() -> Boolean)? = null
) : SettingsItem(null, titleId, descriptionId) {
override val type = TYPE_RUNNABLE
override val isEditable: Boolean
get() = if (EmulationActivity.isRunning()) isRuntimeRunnable else true
}

View File

@ -22,7 +22,7 @@ abstract class SettingsItem(
) {
abstract val type: Int
val isEditable: Boolean
open val isEditable: Boolean
get() {
if (!EmulationActivity.isRunning()) return true
return setting?.isRuntimeEditable ?: false
@ -47,5 +47,6 @@ abstract class SettingsItem(
const val TYPE_INPUT_BINDING = 8
const val TYPE_STRING_INPUT = 9
const val TYPE_FLOAT_INPUT = 10
const val TYPE_MULTI_CHOICE = 11
}
}

View File

@ -41,12 +41,14 @@ import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SliderSetting
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
@ -55,6 +57,7 @@ import org.citra.citra_emu.features.settings.model.view.SwitchSetting
import org.citra.citra_emu.features.settings.ui.viewholder.DateTimeViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.HeaderViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.InputBindingSettingViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.MultiChoiceViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.RunnableViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SettingViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SingleChoiceViewHolder
@ -62,6 +65,7 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SliderViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.StringInputViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SubmenuViewHolder
import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHolder
import org.citra.citra_emu.fragments.AutoMapDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
import org.citra.citra_emu.utils.SystemSaveGame
@ -72,7 +76,8 @@ import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
private var clickedPosition: Int
@ -104,6 +109,10 @@ class SettingsAdapter(
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_MULTI_CHOICE -> {
MultiChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
SettingsItem.TYPE_SLIDER -> {
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
@ -181,21 +190,30 @@ class SettingsAdapter(
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_MULTI_CHOICE -> {
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).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
}
@ -232,6 +250,27 @@ class SettingsAdapter(
onSingleChoiceClick(item)
}
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
clickedItem = item
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choicesId, value, this)
.setOnDismissListener {
if (clickedPosition != -1) {
notifyItemChanged(clickedPosition)
clickedPosition = -1
}
}
.show()
}
fun onMultiChoiceClick(item: MultiChoiceSetting, position: Int) {
clickedPosition = position
onMultiChoiceClick(item)
}
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
clickedItem = item
dialog = context?.let {
@ -360,14 +399,14 @@ class SettingsAdapter(
sliderString = sliderProgress.roundToInt().toString()
if (textSliderValue?.text.toString() != sliderString) {
textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
}
} else {
val currentText = textSliderValue?.text.toString()
val currentTextValue = currentText.toFloat()
if (currentTextValue != sliderProgress) {
textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
textSliderValue?.setSelection(textSliderValue?.length() ?: 0)
}
}
}
@ -447,6 +486,7 @@ class SettingsAdapter(
}
it.setSelectedValue(value)
}
is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(it, which).toShort()
if (it.selectedValue.toShort() != value) {
@ -454,6 +494,7 @@ class SettingsAdapter(
}
it.setSelectedValue(value)
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
}
fragmentView?.putSetting(setting)
@ -499,6 +540,7 @@ class SettingsAdapter(
val setting = it.setSelectedValue(value)
fragmentView?.putSetting(setting)
}
else -> {
val setting = it.setSelectedValue(sliderProgress)
fragmentView?.putSetting(setting)
@ -527,6 +569,21 @@ class SettingsAdapter(
textInputValue = ""
}
//onclick for multichoice
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
val mcsetting = clickedItem as? MultiChoiceSetting
mcsetting?.let {
val value = getValueForMultiChoiceSelection(it, which)
if (it.selectedValues.contains(value) != isChecked) {
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
fragmentView?.putSetting(setting)
fragmentView?.onSettingChanged()
}
fragmentView.loadSettingsList()
}
}
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation)
@ -586,26 +643,42 @@ class SettingsAdapter(
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
}
fun onClickAutoMap() {
val activity = fragmentView.activityView as FragmentActivity
AutoMapDialogFragment.newInstance {
fragmentView.loadSettingsList()
fragmentView.onSettingChanged()
}.show(activity.supportFragmentManager, AutoMapDialogFragment.TAG)
}
fun onLongClickAutoMap(): Boolean {
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
InputBindingSetting.clearAllBindings()
fragmentView.loadSettingsList()
fragmentView.onSettingChanged()
}
return true
}
fun onClickRegenerateConsoleId() {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.regenerate_console_id)
.setMessage(R.string.regenerate_console_id_description)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
SystemSaveGame.regenerateConsoleId()
notifyDataSetChanged()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
fun onClickRegenerateMAC() {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.regenerate_mac_address)
.setMessage(R.string.regenerate_mac_address_description)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
SystemSaveGame.regenerateMac()
notifyDataSetChanged()
}
}
private fun showConfirmationDialog(titleId: Int, messageId: Int, onConfirm: () -> Unit) {
MaterialAlertDialogBuilder(context)
.setTitle(titleId)
.setMessage(messageId)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> onConfirm() }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
@ -631,6 +704,16 @@ class SettingsAdapter(
}
}
private fun getValueForMultiChoiceSelection(item: MultiChoiceSetting, which: Int): Int {
val valuesId = item.valuesId
return if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId)
valuesArray[which]
} else {
which
}
}
private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
val value = item.selectedValue
val valuesId = item.valuesId
@ -647,4 +730,20 @@ class SettingsAdapter(
}
return -1
}
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
val value = item.selectedValues;
val valuesId = item.valuesId;
if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId);
val res = BooleanArray(valuesArray.size){false}
for (index in valuesArray.indices) {
if (value.contains(valuesArray[index])) {
res[index] = true;
}
}
return res;
}
return BooleanArray(1){false};
}
}

View File

@ -14,6 +14,7 @@ import android.os.Build
import android.text.TextUtils
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.serialization.builtins.IntArraySerializer
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenLayout
@ -27,12 +28,14 @@ import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.StringSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.HeaderSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.RunnableSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
@ -596,6 +599,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.COMPRESS_INSTALLED_CIA_CONTENT.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.ASYNC_FS_OPERATIONS,
R.string.async_fs_operations,
R.string.async_fs_operations_description,
BooleanSetting.ASYNC_FS_OPERATIONS.key,
BooleanSetting.ASYNC_FS_OPERATIONS.defaultValue
)
)
}
}
@ -776,6 +788,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addControlsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_controls))
sl.apply {
add(
RunnableSetting(
R.string.controller_auto_map,
R.string.controller_auto_map_description,
true,
R.drawable.ic_controller,
{ settingsAdapter.onClickAutoMap() },
onLongClick = { settingsAdapter.onLongClickAutoMap() }
)
)
add(HeaderSetting(R.string.generic_buttons))
Settings.buttonKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
@ -811,7 +833,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(InputBindingSetting(button, Settings.triggerTitles[i]))
}
add(HeaderSetting(R.string.controller_hotkeys))
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
@ -898,6 +920,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.RESOLUTION_FACTOR.key,
IntSetting.RESOLUTION_FACTOR.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.USE_INTEGER_SCALING,
R.string.use_integer_scaling,
R.string.use_integer_scaling_description,
BooleanSetting.USE_INTEGER_SCALING.key,
BooleanSetting.USE_INTEGER_SCALING.defaultValue
)
)
add(
SwitchSetting(
@ -1148,6 +1179,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.UPRIGHT_SCREEN.defaultValue
)
)
add(
MultiChoiceSetting(
IntListSetting.LAYOUTS_TO_CYCLE,
R.string.layouts_to_cycle,
R.string.layouts_to_cycle_description,
R.array.landscapeLayouts,
R.array.landscapeLayoutValues,
IntListSetting.LAYOUTS_TO_CYCLE.key,
IntListSetting.LAYOUTS_TO_CYCLE.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.PORTRAIT_SCREEN_LAYOUT,
@ -1242,7 +1284,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
override val section = null
override val isRuntimeEditable = false
override val valueAsString = int.toString()
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue
override val defaultValue = FloatSetting.BACKGROUND_RED.defaultValue.toInt()
}
add(
SliderSetting(
@ -1265,7 +1307,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
override val section = null
override val isRuntimeEditable = false
override val valueAsString = int.toString()
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue
override val defaultValue = FloatSetting.BACKGROUND_GREEN.defaultValue.toInt()
}
add(
SliderSetting(
@ -1288,7 +1330,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
override val section = null
override val isRuntimeEditable = false
override val valueAsString = int.toString()
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue
override val defaultValue = FloatSetting.BACKGROUND_BLUE.defaultValue.toInt()
}
add(
SliderSetting(
@ -1671,6 +1713,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.ENABLE_REALTIME_AUDIO.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED,
R.string.simulate_headphones_plugged,
R.string.simulate_headphones_plugged_description,
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED.key,
BooleanSetting.SIMULATE_HEADPHONES_PLUGGED.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.AUDIO_INPUT_TYPE,
@ -1757,6 +1808,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.VSYNC.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS,
R.string.simulate_3ds_gpu_timings,
R.string.simulate_3ds_gpu_timings_description,
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS.key,
BooleanSetting.SIMULATE_3DS_GPU_TIMINGS.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.DEBUG_RENDERER,
@ -1784,6 +1844,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.ENABLE_RPC_SERVER.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE,
R.string.toggle_unique_data_console_type,
R.string.toggle_unique_data_console_type_desc,
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.key,
BooleanSetting.TOGGLE_UNIQUE_DATA_CONSOLE_TYPE.defaultValue
)
)
add(
SwitchSetting(
BooleanSetting.DELAY_START_LLE_MODULES,

View File

@ -0,0 +1,80 @@
// 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.features.settings.ui.viewholder
import android.view.View
import org.citra.citra_emu.databinding.ListItemSettingBinding
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
private lateinit var setting: SettingsItem
override fun bind(item: SettingsItem) {
setting = item
binding.textSettingName.setText(item.nameId)
if (item.descriptionId != 0) {
binding.textSettingDescription.visibility = View.VISIBLE
binding.textSettingDescription.setText(item.descriptionId)
} else {
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = getTextSetting()
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
}
}
private fun getTextSetting(): String {
when (val item = setting) {
is MultiChoiceSetting -> {
val resMgr = binding.textSettingDescription.context.resources
val values = resMgr.getIntArray(item.valuesId)
var resList:List<String> = emptyList();
values.forEachIndexed { i: Int, value: Int ->
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
resList = resList + resMgr.getStringArray(item.choicesId)[i];
}
}
return resList.joinToString();
}
else -> return ""
}
}
override fun onClick(clicked: View) {
if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting(!setting.isEditable)
return
}
if (setting is MultiChoiceSetting) {
adapter.onMultiChoiceClick(
(setting as MultiChoiceSetting),
bindingAdapterPosition
)
}
}
override fun onLongClick(clicked: View): Boolean {
if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else {
adapter.onClickDisabledSetting(!setting.isEditable)
}
return false
}
}

View File

@ -67,7 +67,10 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
override fun onLongClick(clicked: View): Boolean {
// no-op
if (!setting.isEditable) {
adapter.onClickDisabledSetting(true)
return true
}
return setting.onLongClick?.invoke() ?: true
}
}

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.
@ -12,6 +12,7 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.SettingSection
@ -255,6 +256,11 @@ object SettingsFile {
return stringSetting
}
val intListSetting = IntListSetting.from(key)
if (intListSetting != null) {
intListSetting.list = value.split(", ").map { it.toInt() }
}
return null
}

View File

@ -0,0 +1,152 @@
// 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.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.DialogAutoMapBinding
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.Log
/**
* Captures a single button press to detect controller layout (Xbox vs Nintendo)
* and d-pad type (axis vs button), then applies the appropriate bindings.
*/
class AutoMapDialogFragment : BottomSheetDialogFragment() {
private var _binding: DialogAutoMapBinding? = null
private val binding get() = _binding!!
private var onComplete: (() -> Unit)? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = DialogAutoMapBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
BottomSheetBehavior.from<View>(view.parent as View).state =
BottomSheetBehavior.STATE_EXPANDED
isCancelable = false
view.requestFocus()
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
binding.textTitle.setText(R.string.controller_auto_map)
binding.textMessage.setText(R.string.auto_map_prompt)
binding.imageFaceButtons.setImageResource(R.drawable.automap_face_buttons)
dialog?.setOnKeyListener { _, _, event -> onKeyEvent(event) }
binding.buttonCancel.setOnClickListener {
dismiss()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun onKeyEvent(event: KeyEvent): Boolean {
if (event.action != KeyEvent.ACTION_UP) return false
val keyCode = event.keyCode
val device = event.device
// Check if this is a Nintendo Switch Joy-Con (not Pro Controller).
// Joy-Cons have unique quirks: split devices, non-standard D-pad scan codes,
// partial A/B swap but no X/Y swap from Android's evdev layer.
val isJoyCon = InputBindingSetting.isJoyCon(device)
if (isJoyCon) {
Log.info("[AutoMap] Detected Joy-Con - using Joy-Con mappings")
InputBindingSetting.clearAllBindings()
InputBindingSetting.applyJoyConBindings()
onComplete?.invoke()
dismiss()
return true
}
// For non-Joy-Con controllers, determine layout from which keycode arrives
// for the east/right position.
// The user is pressing the button in the "A" (east/right) position on the 3DS diamond.
// Xbox layout: east position sends KEYCODE_BUTTON_B (97)
// Nintendo layout: east position sends KEYCODE_BUTTON_A (96)
val isNintendoLayout = when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A -> true
KeyEvent.KEYCODE_BUTTON_B -> false
else -> {
// Unrecognized button - ignore and wait for a valid press
Log.warning("[AutoMap] Ignoring unrecognized keycode $keyCode, waiting for A or B")
return true
}
}
val layoutName = if (isNintendoLayout) "Nintendo" else "Xbox"
Log.info("[AutoMap] Detected $layoutName layout (keyCode=$keyCode)")
val useAxisDpad = detectDpadType(device)
val dpadName = if (useAxisDpad) "axis" else "button"
Log.info("[AutoMap] Detected $dpadName d-pad (device=${device?.name})")
InputBindingSetting.clearAllBindings()
InputBindingSetting.applyAutoMapBindings(isNintendoLayout, useAxisDpad)
onComplete?.invoke()
dismiss()
return true
}
companion object {
const val TAG = "AutoMapDialogFragment"
fun newInstance(
onComplete: () -> Unit
): AutoMapDialogFragment {
val dialog = AutoMapDialogFragment()
dialog.onComplete = onComplete
return dialog
}
/**
* Returns true for axis d-pad (HAT_X/HAT_Y), false for button d-pad (DPAD_UP/DOWN/LEFT/RIGHT).
* Prefers axis when both are present. Defaults to axis if detection fails.
*/
private fun detectDpadType(device: InputDevice?): Boolean {
if (device == null) return true
val hasAxisDpad = device.motionRanges.any {
it.axis == MotionEvent.AXIS_HAT_X || it.axis == MotionEvent.AXIS_HAT_Y
}
if (hasAxisDpad) return true
val dpadKeyCodes = intArrayOf(
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT
)
val hasButtonDpad = device.hasKeys(*dpadKeyCodes).any { it }
return !hasButtonDpad
}
}
}

View File

@ -11,12 +11,14 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.Configuration
import android.net.Uri
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.text.Editable
import android.text.TextWatcher
@ -72,6 +74,7 @@ import org.citra.citra_emu.features.settings.model.SettingsViewModel
import org.citra.citra_emu.features.settings.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
import org.citra.citra_emu.utils.EmulationMenuSettings
@ -107,6 +110,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private val onPause = Runnable{ togglePause() }
private val onShutdown = Runnable{ emulationState.stop() }
// Only used if a game is passed through intent on google play variant
private var gameFd: Int? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is EmulationActivity) {
@ -124,24 +130,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
super.onCreate(savedInstanceState)
val intent = requireActivity().intent
val intentUri: Uri? = intent.data
var intentUri: Uri? = intent.data
val oldIntentInfo = Pair(
intent.getStringExtra("SelectedGame"),
intent.getStringExtra("SelectedTitle")
)
var intentGame: Game? = null
intentUri = if (intentUri == null && oldIntentInfo.first != null) {
Uri.parse(oldIntentInfo.first)
} else {
intentUri
}
if (intentUri != null) {
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
GameHelper.getGame(intentUri, isInstalled = false, addedToLibrary = false)
if (!BuildUtil.isGooglePlayBuild) {
val intentUriString = intentUri.toString()
// We need to build a special path as the incoming URI may be SAF exclusive
Log.warning("[EmulationFragment] Cannot determine native path of URI \"" +
intentUriString + "\", using file descriptor instead.")
if (!intentUriString.startsWith("!")) {
gameFd = requireContext().contentResolver.openFileDescriptor(intentUri, "r")?.detachFd()
intentUri = if (gameFd != null) {
Uri.parse("fd://" + gameFd.toString())
} else {
null
}
} else if (oldIntentInfo.first != null) {
val gameUri = Uri.parse(oldIntentInfo.first)
intentGame = if (Game.extensions.contains(FileUtil.getExtension(gameUri))) {
GameHelper.getGame(gameUri, isInstalled = false, addedToLibrary = false)
} else {
null
}
}
intentGame =
intentUri?.let {
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(it, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
}
}
@ -160,6 +178,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
return
}
Log.info("[EmulationFragment] Starting application " + game.path)
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
emulationState = EmulationState(game.path)
@ -175,6 +195,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
savedInstanceState: Bundle?
): View {
_binding = FragmentEmulationBinding.inflate(inflater)
binding.inGameMenu.menu.findItem(R.id.menu_landscape_screen_layout).isVisible =
CitraApplication.appContext.resources.configuration.orientation !=
Configuration.ORIENTATION_PORTRAIT
binding.inGameMenu.menu.findItem(R.id.menu_portrait_screen_layout).isVisible =
CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
return binding.root
}
@ -479,7 +505,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
super.onResume()
Choreographer.getInstance().postFrameCallback(this)
if (NativeLibrary.isRunning()) {
emulationState.pause()
emulationState.unpause()
// If the overlay is enabled, we need to update the position if changed
val position = IntSetting.PERFORMANCE_OVERLAY_POSITION.int
@ -517,8 +543,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
override fun onDestroy() {
if (::emulationState.isInitialized && requireActivity().isFinishing) {
emulationState.stop()
}
EmulationLifecycleUtil.removeHook(onPause)
EmulationLifecycleUtil.removeHook(onShutdown)
if (gameFd != null) {
ParcelFileDescriptor.adoptFd(gameFd!!).close()
gameFd = null
}
super.onDestroy()
}
@ -845,7 +878,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_emulation_amiibo_load -> {
emulationActivity.openFileLauncher.launch(false)
emulationActivity.openAmiiboFileLauncher.launch(false)
true
}

View File

@ -7,16 +7,12 @@ 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
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
@ -40,6 +36,7 @@ import org.citra.citra_emu.adapters.GameAdapter
import org.citra.citra_emu.databinding.FragmentGamesBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
@ -50,7 +47,6 @@ 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(
@ -65,8 +61,14 @@ class GamesFragment : Fragment() {
companion object {
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
if (outputUri != null) {
val outputPath: String =
if (!BuildUtil.isGooglePlayBuild) {
"!" + NativeLibrary.getNativePath(outputUri)
} else {
outputUri.toString()
}
CompressProgressDialogViewModel.reset()
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputUri.toString())
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputPath)
dialog.showNow(
fragment.requireActivity().supportFragmentManager,
CompressProgressDialogFragment.TAG
@ -74,9 +76,9 @@ class GamesFragment : Fragment() {
fragment.lifecycleScope.launch(Dispatchers.IO) {
val status = if (shouldCompress) {
NativeLibrary.compressFile(inputPath, outputUri.toString())
NativeLibrary.compressFile(inputPath, outputPath)
} else {
NativeLibrary.decompressFile(inputPath, outputUri.toString())
NativeLibrary.decompressFile(inputPath, outputPath)
}
fragment.requireActivity().runOnUiThread {
@ -224,34 +226,6 @@ class GamesFragment : Fragment() {
setInsets()
}
override fun onResume() {
super.onResume()
if (show3DSFileWarning &&
!PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
.getBoolean("show_3ds_files_warning", false)) {
val message = HtmlCompat.fromHtml(getString(R.string.warning_3ds_files),
HtmlCompat.FROM_HTML_MODE_LEGACY)
context?.let {
val alert = MaterialAlertDialogBuilder(it)
.setTitle(getString(R.string.important))
.setMessage(message)
.setPositiveButton(R.string.dont_show_again) { _, _ ->
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
.edit() {
putBoolean("show_3ds_files_warning", true)
}
}
.show()
val alertMessage = alert.findViewById<View>(android.R.id.message) as TextView
alertMessage.movementMethod = LinkMovementMethod.getInstance()
}
}
show3DSFileWarning = false
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null

View File

@ -33,7 +33,7 @@ import org.citra.citra_emu.adapters.HomeSettingAdapter
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.StringSetting
import org.citra.citra_emu.features.settings.SettingKeys
import org.citra.citra_emu.features.settings.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.model.Game
@ -89,7 +89,7 @@ class HomeSettingsFragment : Fragment() {
{
val inflater = LayoutInflater.from(context)
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
var textInputValue: String = preferences.getString("last_artic_base_addr", "")!!
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
inputBinding.editTextInput.setText(textInputValue)
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
@ -103,7 +103,7 @@ class HomeSettingsFragment : Fragment() {
.setPositiveButton(android.R.string.ok) { _, _ ->
if (textInputValue.isNotEmpty()) {
preferences.edit()
.putString("last_artic_base_addr", textInputValue)
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
.apply()
val menu = Game(
title = getString(R.string.artic_base),

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