Compare commits

..

287 commits

Author SHA1 Message Date
Daniel López Guimaraes
c9d2593c2c
apt: Properly implement app ID checks (#2225)
The IsSystemAppletId and IsApplicationAppletId checks were prone to
collisions due to the way they were implemented. For example, the
application app ID would pass IsSystemAppletId since 0x300 & 0x100 != 0

To account for masking collisions, add a generic `TypeMask` enum value
used to mask the app ID type correctly, and then match the masked value
with the app ID we're looking for.

Fixes issues with system applets that use objects from the caller
applications, since the edge case for sysapplet parameters being sent to
applications was also passing the other way around.
2026-06-19 23:56:06 +02:00
PabloMK7
9e43e451d1
core: Implement APT:MapProgramIdForDebug (#2224) 2026-06-19 09:00:33 +02:00
PabloMK7
0a269688a3
core: Implement CFG:TranslateCountryInfo (#2223) 2026-06-19 09:00:08 +02:00
OpenSauce04
3a77813821 Updated translations via Transifex 2026-06-17 21:50:43 +01:00
OpenSauce04
fbeb53c25f Fix enabling Discord RPC causing linker warnings about minimum macOS version 2026-06-17 17:17:21 +01:00
OpenSauce04
1137d530b0 Rename USE_DISCORD_PRESENCE option and def to ENABLE_DISCORD_RPC 2026-06-17 17:17:21 +01:00
Sergei Golishnikov
04f3a93854
macos: fix Vulkan/Metal renderer on macOS 26+ and bump MoltenVK (#2149)
* macos: fix Vulkan/Metal renderer on macOS 26+ and bump MoltenVK

Four related changes that together let the Vulkan backend boot a game
on Apple Silicon Macs running macOS 26.x. Each is needed independently;
the combination was tested on a Mac16,6 (M4 Max) / macOS 26.4.1.

CMakeModules/DownloadExternals.cmake:
  Bump bundled MoltenVK from v1.2.9 to v1.4.1. v1.2.9 does not
  recognize macOS 26 surface objects and traps with an Objective-C
  "unrecognized selector" inside vkGetPhysicalDeviceSurfaceCapabilitiesKHR.

src/video_core/renderer_vulkan/vk_instance.cpp:
  Disable VK_EXT_tooling_info on MoltenVK. MoltenVK advertises the
  extension but does not expose vkGetPhysicalDeviceToolPropertiesEXT
  through its dispatcher, so the call inside CollectToolingInfo()
  trips a vulkan-hpp assertion in Debug and dereferences NULL in
  Release.

src/citra_qt/bootmanager.cpp:
  Force-install a fresh CAMetalLayer on the NSView before handing it
  to createMetalSurfaceEXT. Qt 6.11's QWindow::MetalSurface does not
  always back the view with a CAMetalLayer on macOS 26, and MoltenVK
  1.3+ aborts in MVKSurface::getNaturalExtent() if the layer is not
  the right class. Implemented via objc_msgSend so no .mm conversion
  is needed.

src/citra_meta/CMakeLists.txt:
  Gate LaunchScreen.storyboard behind 'if (IOS)'. The storyboard is
  iOS-only and previously broke the Xcode generator on macOS with
  "iOS storyboards do not support target device type 'mac'."

* Move Metal layer creation logic into MetalUtil::CreateMetalLayer ObjC++ function

This change makes the code much more readable, as the logic is now in its native language

---------

Co-authored-by: SergeyMild <>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-06-17 16:15:42 +01:00
Cobalt
28960bf166
Fix loadTranslation for QT 6 older than 6.7 (#2200)
* language fix for QT pre 6.7

haha funny number

* be more explicit

* make clang-format happy
2026-06-17 14:37:55 +01:00
OpenSauce04
176cd5cf14 qt: Fix <System> selection becoming blank when selected 2026-06-17 14:04:55 +01:00
0x48kirenh
735c63174c
core/memory: Fix undefined behavior and optimize Luma alias path (#2202)
* core/memory: Fix undefined behavior and optimize Luma alias path

* chore: clean up Luma mapping comments

* style: fix clang-format formatting
2026-06-17 09:13:56 +02:00
OpenSauce04
f015a208e5 qt: Use upstream Qt Base translations when available 2026-06-16 22:46:24 +01:00
OpenSauce04
deb130cdb7 ci: Add libretro- prefix to libretro CI jobs
Makes it easier to different standalone build jobs from libretro build jobs
2026-06-16 17:28:38 +01:00
OpenSauce04
4687226fa5 android: Force use of Vulkan if OpenGL renderer is ANGLE
This commit also renames the AndroidStorage namespace and related files to AndroidUtils, and merges `jni/utils.cpp`/`.h` into it.
2026-06-16 17:27:33 +01:00
OpenSauce04
bd733284a3 android: Retrieve and log OpenGL renderer string 2026-06-16 17:27:33 +01:00
OpenSauce04
cd1ef53569 Remove unused imports via ktlint 2026-06-16 16:13:05 +01:00
OpenSauce04
7786dae94d android/.editorconfig: Enable no-unused-imports rule
Reportedly finicky, use for now but maybe delete later if there are issues.
See https://github.com/ktlint/ktlint/issues/3038
2026-06-16 16:13:05 +01:00
OpenSauce04
5ea4bb2511 Android studio integration for ktlint (runs on build) 2026-06-16 16:13:05 +01:00
OpenSauce04
22d7be4e3c ci: Add job for ktlint 2026-06-16 16:13:05 +01:00
OpenSauce04
60e44ff049 Updated license headers 2026-06-16 16:13:05 +01:00
OpenSauce04
d70099278a android: Manual kotlin formatting corrections 2026-06-16 16:13:05 +01:00
OpenSauce04
1afa24387f android/.editorconfig: Disable ktlint_standard_comment 2026-06-16 16:13:05 +01:00
OpenSauce04
bfaaaee10d tools: Add fix-kotlin-formatting.sh
Identical to the previously added check-kotlin-formatting.sh, except it attempts to automatically address formatting issues where possible
2026-06-16 16:13:05 +01:00
OpenSauce04
2940069fe2 android: Apply automatic kotlin formatting fixes 2026-06-16 16:13:05 +01:00
OpenSauce04
a2a20fcc65 Add Kotlin code formatting/style configuration files 2026-06-16 16:13:05 +01:00
OpenSauce04
b4bda9d7f5 qt: Change type of default_hotkeys to std::vector 2026-06-16 14:18:30 +01:00
OpenSauce04
609ed7caf3 cmake: Enable ENABLE_QT_TRANSLATION by default 2026-06-15 15:45:52 +01:00
Wunk
9ffb39eab2
renderer_gl: Add DebugScopes to rasterizer and texture-runtime (#2133)
* renderer_gl: Add `DebugScopes` to rasterizer and texture-runtime

Some debug scopes I added while working on https://github.com/azahar-emu/azahar/pull/2053 to help better attribute OpenGL API calls to the particular C++ callstack that called it. Helps greatly for diagnosing rendering issues.

* renderer_gl: Fix DebugScope unconditional incrementing of `global_scope_depth`

`global_scope_depth` was being incremented unconditionally in the ctor, but the
dtor was decrementing it conditionally based on
`Settings::values.renderer_debug`, allowing an imbalanced scope-depth hazard in
the case that `Settings::values.renderer_debug` is operated at runtime during a
debug-scope.
2026-06-14 22:53:18 +01:00
OpenSauce04
792cde35ca tools: Use arch-specific Docker image tag when entering via script
Makes testing local changes more convenient
2026-06-14 18:53:31 +01:00
72374
398b13abc2
Fix typo in Renderer_DelayGameRenderThreadUs (#2076) 2026-06-14 18:27:57 +01:00
OpenSauce04
823901a364 Formatting nitpicks
Meant to merge these with #1385, oops
2026-06-14 17:41:54 +01:00
NovaChild
8a6d597dec
android: Secondary Layout and Menu Improvements (#1385)
* Add a new Secondary Display Layout option on android that makes the secondary display honor swap button

* add quick menu option for secondary layout

* fix icon

* added other secondary layouts

* Add a new Secondary Display Layout option on android that makes the secondary display honor swap button

# Conflicts:
#	src/core/frontend/framebuffer_layout.cpp

* add quick menu option for secondary layout

* fix icon

* added other secondary layouts

* updated secondary menu with functionality to switch external displays

 Conflicts:
	src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt

* safety checks for crash prevention

* make secondary menu only appear if a secondary display is available

* update default displayid behavior to exclude "Built"

* update odin 2 bugfix to handle other languages

* Apply clang-format

* Rename "Opposite of Primary Display" option for brevity

* Rename menu_secondary_layout_reverse_primary for consistency

* Rename SecondaryDisplayLayout::ReversePrimary for consistency

* first round of code fixes based on review

* Added android-side enable_secondary_display boolean setting, replacing secondary_display_layout = none on the android side. Exposed it in Layout settings, and secondary display layout is now only selectable if it is enabled.  Support for the old option is still in the code, but should no longer be selectable.

Also renamed opposite to reverse_primary in a few other places.

* Update framebuffer layout and renderers to correctly render hybrid mode on both primary and secondary displays

* Apply clang format

* Re-apply terminology changes which were undone by a recent commit

* Removed unused string emulation_secondary_display_default

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-06-14 17:37:32 +01:00
exertustfm
3dc357a8c9
qt: Fix incorrect system language detection (#1558) 2026-06-06 18:37:42 +01:00
crueter
379649dbce
cmake: Fix MoltenVK fetch order/library conflicts (#2183)
* [cmake] Fix MoltenVK fetch order/library conflicts

Rather than dealing with `find_library` shenanigans, just set the
library path directly (when using bundled MoltenVK). System MoltenVK
solely uses `find_library`.

Avoids cache nonsense that can cause system/bundled versions to get
mixed up, and overall makes the system/bundled mvk handling a lot more
consistent

```
cmake -S . -B build -DUSE_SYSTEM_MOLTENVK=ON
-- Using MoltenVK at /opt/homebrew/lib/libMoltenVK.dylib.
cmake -S . -B build -DUSE_SYSTEM_MOLTENVK=OFF
-- Using MoltenVK at /Users/crueter/code/azahar/build/externals/MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib.
cmake -S . -B build -DUSE_SYSTEM_MOLTENVK=ON
-- Using MoltenVK at /opt/homebrew/lib/libMoltenVK.dylib.
```

Signed-off-by: crueter <crueter@eden-emu.dev>

* remove old comment

Signed-off-by: crueter <crueter@eden-emu.dev>

* Cleanup

---------

Signed-off-by: crueter <crueter@eden-emu.dev>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-06-05 16:36:48 +01:00
PabloMK7
c03248f158
externals: Switch to cryptopp-modern (#2139)
* externals: Switch to cryptopp-modern

* Revert cryptopp package name under USE_SYSTEM_CRYPTOPP condition

---------

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
2026-06-04 22:38:47 +02:00
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)
Some checks failed
citra-build / source (push) Failing after 6m47s
citra-build / linux-x86_64 (appimage-wayland) (push) Successful in 4m21s
citra-build / linux-x86_64 (gcc-nopch) (push) Failing after 4s
citra-build / android (googleplay) (push) Successful in 7s
citra-build / android (vanilla) (push) Failing after 3m24s
citra-build / docker (push) Failing after 20s
citra-format / clang-format (push) Failing after 1s
citra-libretro / android (push) Failing after 2m54s
citra-build / linux-x86_64 (appimage) (push) Failing after 14m13s
citra-libretro / linux (push) Failing after 3m16s
citra-libretro / windows (push) Failing after 3m22s
citra-transifex / transifex (push) Has been skipped
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (push) Has been cancelled
citra-build / windows (msvc) (push) Has been cancelled
citra-build / windows (msys2) (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
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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
2026-05-07 20:39:30 +02:00
PabloMK7
b540725090
Revamp GDB implemenation and add a some minor debug features (#2086)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
2026-05-07 13:48:35 +02:00
PabloMK7
5ddbaeae23
gsp: Fix GPU interrupt queue and add GPU timing emulation (#2095)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
2026-05-07 01:36:21 +02:00
OpenSauce04
b081f800a4 Revert "ci: Override Android SDK Ninja with newer version"
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
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)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
* 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
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
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
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
2026-04-25 21:57:46 +01:00
Wunk
91128d6625
shader_jit: Emit LG2/EX2 subroutines on-demand (#2046)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
Knew I'd forgotten something (:
2026-04-21 14:55:40 +01:00
OpenSauce04
9701a3d874 tools/README.md: Updated release checklist
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
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)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
* 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)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
2026-04-17 21:45:50 +02:00
Cobalt
d4b5633cf0
qt Fix compilation issues in status LED code (#2045)
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
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
Some checks failed
citra-build / source (push) Has been cancelled
citra-build / linux-x86_64 (appimage) (push) Has been cancelled
citra-build / linux-x86_64 (appimage-wayland) (push) Has been cancelled
citra-build / linux-x86_64 (gcc-nopch) (push) Has been cancelled
citra-build / linux-arm64 (clang) (push) Has been cancelled
citra-build / linux-arm64 (gcc-nopch) (push) Has been cancelled
citra-build / macos (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
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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
2026-04-13 16:49:31 +02:00
PabloMK7
727377c012 discord rpc: Change how info is displayed
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
* 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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
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
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 2026-02-20 14:41:31 +00:00
Eric Warmenhoven
d9b77cc21e
Implement libretro core (#1215)
* 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) 2026-02-16 15:59:22 +01:00
PabloMK7
91abe7f7d0
common: Add NATVIS to BitField class for better VS debugging (#1731) 2026-02-13 14:30:04 +01:00
PabloMK7
3e27010c7b
Fix regex in PR verification 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 2026-02-06 20:00:38 +00:00
PabloMK7
f010863ece
video_core: vulkan: Only store hashes in shader cache maps (#1710) 2026-02-02 18:25:03 +01:00
OpenSauce04
dd65ef4749 Updated translations via Transifex 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) 2026-01-27 10:30:26 +01:00
OpenSauce04
0a705b7449 Updated translations via Transifex 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)
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 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) 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)
* 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 2026-01-20 22:51:03 +00:00
Marcin Serwin
79d73bbcb9 citra_meta: search for Qt6::GuiPrivate before using it
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) 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 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) 2026-01-18 23:56:35 +01:00
OpenSauce
4deb7e63b5
DirectoryInitialization.kt: Switch to getFilesDir for internalUserPath (#1646)
* 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
OpenSauce04
f5053868b4 Updated translations via Transifex 2026-01-15 11:42:46 +00:00
OpenSauce04
56f2bccbe0 Updated readme 2026-01-15 11:34:59 +00:00
RedBlackAka
e37cbcf2ff
UI: Skip Vulkan in API quick selection if unavailable (#1618) 2026-01-12 16:38:11 +01:00
591 changed files with 61036 additions and 24694 deletions

View file

@ -12,6 +12,9 @@ fi
echo "Tag name is: $TAG_NAME" 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 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

View file

@ -6,8 +6,7 @@ cmake .. -GNinja \
-DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
-DENABLE_QT_TRANSLATION=ON
ninja ninja
ccache -s -v ccache -s -v

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 #!/bin/bash -ex
if [[ "$TARGET" == "appimage"* ]]; then if [[ "$TARGET" == "appimage"* ]] || [[ "$TARGET" == "clang"* ]]; then
# Compile the AppImage we distribute with Clang. # Compile the AppImage we distribute with Clang.
export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++ export EXTRA_CMAKE_FLAGS=(-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_COMPILER=clang -DCMAKE_C_COMPILER=clang
@ -25,9 +25,8 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \ -DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \ -DENABLE_DISCORD_RPC=ON \
"${EXTRA_CMAKE_FLAGS[@]}" "${EXTRA_CMAKE_FLAGS[@]}"
ninja ninja
strip -s bin/Release/* strip -s bin/Release/*

View file

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

View file

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

25
.ci/mxe.sh Executable file
View file

@ -0,0 +1,25 @@
#!/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_DISCORD_RPC=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. # Determine the full revision name.
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`" 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. # Archive and upload the artifacts.
mkdir -p artifacts mkdir -p artifacts
function pack_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" ARTIFACTS_PATH="$1"
# Set up root directory for archive. # Set up root directory for archive.
@ -35,10 +36,10 @@ function pack_artifacts() {
fi fi
# Create .zip/.tar.gz # Create .zip/.tar.gz
if [ "$OS" = "windows" ]; then if [ "$OS" = "windows" ] && [ "$TARGET" != "mxe" ]; then
ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip" ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
powershell Compress-Archive "$REV_NAME" "$ARCHIVE_FULL_NAME" 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" ARCHIVE_FULL_NAME="$ARCHIVE_NAME.zip"
zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME" zip -r "$ARCHIVE_FULL_NAME" "$REV_NAME"
else else
@ -56,11 +57,23 @@ if [ -n "$UNPACKED" ]; then
FILENAME=$(basename "$ARTIFACT") FILENAME=$(basename "$ARTIFACT")
EXTENSION="${FILENAME##*.}" 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" mv "$ARTIFACT" "artifacts/$REV_NAME.$EXTENSION"
done done
elif [ -n "$PACK_INDIVIDUALLY" ]; then elif [ -n "$PACK_INDIVIDUALLY" ]; then
# Pack and upload the artifacts one-by-one. # Pack and upload the artifacts one-by-one.
for ARTIFACT in build/bundle/*; do for ARTIFACT in build/bundle/*; do
TARGET=$(basename "$ARTIFACT")
pack_artifacts "$ARTIFACT" pack_artifacts "$ARTIFACT"
done done
else else

View file

@ -6,7 +6,7 @@ gcc -v
tx --version tx --version
mkdir build && cd build mkdir build && cd build
cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF cmake .. -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF
make translation make translation
cd .. cd ..

View file

@ -10,8 +10,7 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_DISCORD_RPC=ON \
-DUSE_DISCORD_PRESENCE=ON \
"${EXTRA_CMAKE_FLAGS[@]}" "${EXTRA_CMAKE_FLAGS[@]}"
ninja ninja
ninja bundle ninja bundle

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: pull_request:
branches: [ master ] branches: [ master ]
permissions:
id-token: write
contents: read
attestations: write
jobs: jobs:
source: source:
if: ${{ !github.head_ref }} if: ${{ !github.head_ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Pack - name: Pack
run: ./.ci/source.sh 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 - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: source name: source
path: artifacts/ 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 runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: 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: container:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
@ -39,114 +123,94 @@ jobs:
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}- ${{ github.job }}-${{ matrix.target }}-
- name: Build - name: Build
run: ./.ci/linux.sh 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: macos:
runs-on: ${{ (matrix.target == 'x86_64' && 'macos-15-intel') || 'macos-26' }} runs-on: 'macos-26'
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env: env:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos OS: macos
TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-
- name: Install tools - name: Install tools
run: brew install ccache ninja spirv-tools run: brew install ccache ninja spirv-tools
- name: Build - name: Build (x86_64)
run: ./.ci/macos.sh run: BUILD_ARCH=x86_64 ./.ci/macos.sh
- name: Prepare outputs for caching - name: Build (arm64)
run: cp -R build/bundle $OS-$TARGET run: BUILD_ARCH=arm64 ./.ci/macos.sh
- 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: Create universal app - name: Create universal app
run: ./.ci/macos-universal.sh run: ./.ci/macos-universal.sh
env: - name: Prepare for packing
ARTIFACTS: ${{ env.OS }}-x86_64 ${{ env.OS }}-arm64 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 - name: Pack
env:
PACK_INDIVIDUALLY: 1
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Upload - name: Generate SBOM
uses: actions/upload-artifact@v4 if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with: 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/ 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: windows:
runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: 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: defaults:
run: run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0} shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -154,14 +218,16 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: windows OS: windows
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v5
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -169,7 +235,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC - name: Set up MSVC
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
uses: ilammy/msvc-dev-cmd@v1 uses: azahar-emu/msvc-dev-cmd@v1
- name: Install extra tools (MSVC) - name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget 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 qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2) - name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }} if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v3 uses: crazy-max/ghaction-chocolatey@v4
with: with:
args: install ptime wget args: install ptime wget
- name: Install NSIS - name: Install NSIS
if: ${{ github.ref_type == 'tag' }} if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: | run: |
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe 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 ptime D:/a/_temp/nsis-setup.exe /S
shell: pwsh shell: pwsh
- name: Disable line ending translation - name: Disable line ending translation
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Build - name: Build (Native)
if: ${{ matrix.target != 'mxe' }}
run: ./.ci/windows.sh run: ./.ci/windows.sh
- name: Generate installer - name: Build (MXE)
if: ${{ github.ref_type == 'tag' }} if: ${{ matrix.target == 'mxe' }}
run: ./.ci/mxe.sh
- name: Generate installer (Native)
if: ${{ github.ref_type == 'tag' && matrix.target != 'mxe' }}
run: | run: |
cd src\installer cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi "C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir ..\..\artifacts 2> NUL mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\ move /y *.exe ..\..\artifacts\
shell: cmd 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 - name: Pack
run: ./.ci/pack.sh 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 - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ 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: android:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -229,17 +323,18 @@ jobs:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros CCACHE_SLOPPINESS: time_macros
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: android OS: android
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }} SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4 uses: actions/cache@v5
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -277,23 +372,48 @@ jobs:
working-directory: src/android/app working-directory: src/android/app
env: env:
UNPACKED: 1 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 - name: Upload
if: ${{ env.SHOULD_RUN == 'true' }} if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/ 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: 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: 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Install tools
run: apk add bash
- name: Fix git ownership - name: Fix git ownership
run: git config --global --add safe.directory . run: git config --global --add safe.directory .
- name: Build Docker image - name: Build Docker image
@ -302,8 +422,23 @@ jobs:
run: | run: |
mkdir -p artifacts mkdir -p artifacts
mv build/*.dockerimage artifacts/ mv build/*.dockerimage artifacts/
- name: Upload - name: Generate SBOM
uses: actions/upload-artifact@v4 if: ${{ github.ref_type == 'tag' }}
uses: anchore/sbom-action@v0
with: 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/ 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,10 +13,22 @@ jobs:
image: opensauce04/azahar-build-environment:latest image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Build - name: Build
env: env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
run: ./.ci/clang-format.sh run: ./.ci/clang-format.sh
ktlint:
runs-on: ubuntu-latest
container:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build
run: ./tools/check-kotlin-formatting.sh

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:
libretro-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
libretro-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
libretro-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
libretro-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
libretro-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
libretro-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 image: opensauce04/azahar-build-environment:latest
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Fetch master branch - name: Fetch master branch

View file

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

View file

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

10
.gitignore vendored
View file

@ -17,7 +17,8 @@ src/common/scm_rev.cpp
*.swp *.swp
*.kdev4 *.kdev4
.markdown-preview.html .markdown-preview.html
.idea/ .idea/*
!.idea/inspectionProfiles
.vs/ .vs/
.vscode/ .vscode/
.cache/ .cache/
@ -56,3 +57,10 @@ repo/
.ccache/ .ccache/
node_modules/ node_modules/
VULKAN_SDK/ VULKAN_SDK/
# Version info files
GIT-COMMIT
GIT-TAG
# verify-release.sh downloads
verify/

17
.gitmodules vendored
View file

@ -55,18 +55,12 @@
[submodule "sdl2"] [submodule "sdl2"]
path = externals/sdl2/SDL path = externals/sdl2/SDL
url = https://github.com/libsdl-org/SDL url = https://github.com/libsdl-org/SDL
[submodule "cryptopp-cmake"]
path = externals/cryptopp-cmake
url = https://github.com/abdes/cryptopp-cmake.git
[submodule "cryptopp"]
path = externals/cryptopp
url = https://github.com/weidai11/cryptopp.git
[submodule "dds-ktx"] [submodule "dds-ktx"]
path = externals/dds-ktx path = externals/dds-ktx
url = https://github.com/septag/dds-ktx url = https://github.com/septag/dds-ktx
[submodule "openal-soft"] [submodule "openal-soft"]
path = externals/openal-soft path = externals/openal-soft
url = https://github.com/kcat/openal-soft url = https://github.com/azahar-emu/openal-soft
[submodule "glslang"] [submodule "glslang"]
path = externals/glslang path = externals/glslang
url = https://github.com/KhronosGroup/glslang url = https://github.com/KhronosGroup/glslang
@ -103,3 +97,12 @@
[submodule "externals/xxHash"] [submodule "externals/xxHash"]
path = externals/xxHash path = externals/xxHash
url = https://github.com/Cyan4973/xxHash.git 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
[submodule "externals/cryptopp"]
path = externals/cryptopp
url = https://github.com/cryptopp-modern/cryptopp-modern.git

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) cmake_policy(SET CMP0127 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0063 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}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
include(DownloadExternals) include(DownloadExternals)
include(CMakeDependentOption) include(CMakeDependentOption)
include(FindPkgConfig)
project(citra LANGUAGES C CXX ASM) 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") if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
enable_language(OBJC OBJCXX) enable_language(OBJC OBJCXX)
endif() 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. # 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. # 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) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif() endif()
if (APPLE) if (APPLE AND NOT ENABLE_LIBRETRO)
# Silence warnings on empty objects, for example when platform-specific code is #ifdef'd out. # 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_C_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>") set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> Scr <TARGET> <LINK_FLAGS> <OBJECTS>")
@ -90,13 +105,23 @@ else()
set(DEFAULT_ENABLE_OPENGL ON) set(DEFAULT_ENABLE_OPENGL ON)
endif() 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) 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) option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF)
# Set bundled qt as dependent options. # Set bundled qt as dependent options.
option(ENABLE_QT "Enable the Qt frontend" ON) option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" ON)
option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF) option(ENABLE_QT_UPDATE_CHECKER "Enable built-in update checker for the Qt frontend" OFF)
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
@ -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_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" 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) CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON) option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
@ -113,9 +139,10 @@ 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_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) 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) option(ENABLE_DISCORD_RPC "Enables Discord Rich Presence" OFF)
option(ENABLE_MICROPROFILE "Enables microprofile capabilities" OFF) option(ENABLE_MICROPROFILE "Enables microprofile capabilities" 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_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 # Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) 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}) 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_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
option(CITRA_WARNINGS_AS_ERRORS "Enable warnings as errors" 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 # Pass the following values to C++ land
if (ENABLE_QT) if (ENABLE_QT)
add_definitions(-DENABLE_QT) add_definitions(-DENABLE_QT)
@ -143,9 +197,6 @@ endif()
if (ENABLE_SDL2) if (ENABLE_SDL2)
add_definitions(-DENABLE_SDL2) add_definitions(-DENABLE_SDL2)
endif() endif()
if (ENABLE_SDL2_FRONTEND)
add_definitions(-DENABLE_SDL2_FRONTEND)
endif()
if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")) if(ENABLE_SSE42 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64"))
message(STATUS "SSE4.2 enabled for x86_64") message(STATUS "SSE4.2 enabled for x86_64")
@ -223,7 +274,7 @@ function(check_submodules_present)
foreach(module ${gitmodules}) foreach(module ${gitmodules})
string(REGEX REPLACE "path *= *" "" module ${module}) string(REGEX REPLACE "path *= *" "" module ${module})
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") 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") "Please run: git submodule update --init --recursive")
endif() endif()
endforeach() endforeach()
@ -300,6 +351,9 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN NO)
# set up output paths for executable binaries # set up output paths for executable binaries
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/$<CONFIG>)
if (ENABLE_LIBRETRO)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
# System imported libraries # System imported libraries
# ====================== # ======================
@ -310,7 +364,7 @@ find_package(Threads REQUIRED)
if (ENABLE_QT) if (ENABLE_QT)
if (NOT USE_SYSTEM_QT) if (NOT USE_SYSTEM_QT)
download_qt(6.9.2) download_qt(6.9.3)
endif() endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
@ -357,13 +411,21 @@ if (APPLE)
endif() endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED) find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED)
find_library(IOSURFACE_LIBRARY IOSurface REQUIRED) find_library(IOSURFACE_LIBRARY IOSurface REQUIRED)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY} ${MOLTENVK_LIBRARY}) set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOSURFACE_LIBRARY})
if (ENABLE_VULKAN) if (ENABLE_VULKAN AND NOT ENABLE_LIBRETRO)
if (NOT USE_SYSTEM_MOLTENVK) if (USE_SYSTEM_MOLTENVK)
download_moltenvk()
endif()
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
else()
download_moltenvk()
if (IOS)
set(MOLTENVK_RELATIVE_LIBPATH "static/MoltenVK.xcframework/ios-arm64/libMoltenVK.a")
else()
set(MOLTENVK_RELATIVE_LIBPATH "dynamic/dylib/macOS/libMoltenVK.dylib")
endif()
set(MOLTENVK_LIBRARY "${CMAKE_BINARY_DIR}/externals/MoltenVK/MoltenVK/${MOLTENVK_RELATIVE_LIBPATH}")
endif()
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY}) set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif() endif()
@ -513,8 +575,6 @@ if (NOT ANDROID AND NOT IOS)
include(BundleTarget) include(BundleTarget)
if (ENABLE_QT) if (ENABLE_QT)
qt_bundle_target(citra_meta) qt_bundle_target(citra_meta)
elseif (ENABLE_SDL2_FRONTEND)
bundle_target(citra_meta)
endif() endif()
if (ENABLE_ROOM_STANDALONE) if (ENABLE_ROOM_STANDALONE)
bundle_target(citra_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. # On Linux, always bundle an AppImage.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IS_MINGW)
return()
endif()
if (IN_PLACE) if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.") message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif() endif()
@ -273,6 +277,13 @@ else()
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs. # On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") 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( add_custom_command(
TARGET bundle TARGET bundle
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
@ -283,6 +294,7 @@ else()
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
POST_BUILD) POST_BUILD)
endif() endif()
endif()
endfunction() endfunction()
# Adds a target to the bundle target, packing in required libraries. # Adds a target to the bundle target, packing in required libraries.
@ -293,6 +305,11 @@ else()
create_base_bundle_target() create_base_bundle_target()
endif() 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}>") set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (bundle_qt AND APPLE) if (bundle_qt AND APPLE)
# For Qt targets on Apple, expect an app bundle. # For Qt targets on Apple, expect an app bundle.
@ -331,6 +348,7 @@ else()
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\"" "-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}" "-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}" "-DIN_PLACE=${in_place}"
"-DIS_MINGW=${MINGW}"
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun" "-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") 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

@ -171,27 +171,16 @@ function(download_qt target)
endfunction() endfunction()
function(download_moltenvk) function(download_moltenvk)
if (IOS)
set(MOLTENVK_PLATFORM "static/MoltenVK.xcframework/ios-arm64")
else()
set(MOLTENVK_PLATFORM "dynamic/dylib/macOS")
endif()
set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK")
set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar")
if (NOT EXISTS ${MOLTENVK_DIR}) if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
if (NOT EXISTS ${MOLTENVK_TAR}) if (NOT EXISTS ${MOLTENVK_TAR})
file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-all.tar file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-all.tar
${MOLTENVK_TAR} SHOW_PROGRESS) ${MOLTENVK_TAR} SHOW_PROGRESS)
endif() endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif() endif()
# Add the MoltenVK library path to the prefix so find_library can locate it.
list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/${MOLTENVK_PLATFORM}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction() endfunction()
function(get_external_prefix lib_name prefix_var) function(get_external_prefix lib_name prefix_var)

View file

@ -1,3 +1,6 @@
macro(generate_build_info)
find_package(Git QUIET)
# Gets a UTC timstamp and sets the provided variable to it # Gets a UTC timstamp and sets the provided variable to it
function(get_timestamp _var) function(get_timestamp _var)
string(TIMESTAMP timestamp UTC) string(TIMESTAMP timestamp UTC)
@ -5,9 +8,14 @@ function(get_timestamp _var)
endfunction() endfunction()
get_timestamp(BUILD_DATE) 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 the package here with the known path so that the GetGit commands can find it as well
find_package(Git QUIET PATHS "${GIT_EXECUTABLE}") find_package(Git QUIET PATHS "${GIT_EXECUTABLE}")
@ -16,12 +24,6 @@ if (EXISTS "${SRC_DIR}/.git/objects")
get_git_head_revision(GIT_REF_SPEC GIT_REV) get_git_head_revision(GIT_REF_SPEC GIT_REV)
git_describe(GIT_DESC --always --long --dirty) git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH) 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? # self-packed archive?
set(GIT_REV "UNKNOWN") set(GIT_REV "UNKNOWN")
@ -38,8 +40,8 @@ if (DEFINED ENV{CI} AND DEFINED ENV{GITHUB_ACTIONS})
if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag") if ($ENV{GITHUB_REF_TYPE} STREQUAL "tag")
set(GIT_TAG $ENV{GITHUB_REF_NAME}) set(GIT_TAG $ENV{GITHUB_REF_NAME})
endif() endif()
elseif (EXISTS "${SRC_DIR}/GIT-COMMIT" AND EXISTS "${SRC_DIR}/GIT-TAG") elseif (EXISTS "${CMAKE_SOURCE_DIR}/GIT-COMMIT" AND EXISTS "${CMAKE_SOURCE_DIR}/GIT-TAG")
file(READ "${SRC_DIR}/GIT-TAG" GIT_TAG) file(READ "${CMAKE_SOURCE_DIR}/GIT-TAG" GIT_TAG)
string(STRIP ${GIT_TAG} GIT_TAG) string(STRIP ${GIT_TAG} GIT_TAG)
endif() endif()
@ -47,3 +49,4 @@ if (DEFINED GIT_TAG AND NOT "${GIT_TAG}" STREQUAL "unknown")
set(BUILD_VERSION "${GIT_TAG}") set(BUILD_VERSION "${GIT_TAG}")
set(BUILD_FULLNAME "${BUILD_VERSION}") 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) 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) # 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 set(HASH_FILES
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp" "${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h" "${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_opengl/gl_shader_util.h"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp" "${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h" "${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.cpp"
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h" "${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp" "${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/glsl_shader_gen.h"
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp" "${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
"${VIDEO_CORE}/shader/generator/pica_fs_config.h" "${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.cpp"
"${VIDEO_CORE}/shader/generator/shader_gen.h" "${VIDEO_CORE}/shader/generator/shader_gen.h"
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp" "${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
@ -41,4 +48,4 @@ foreach (F IN LISTS HASH_FILES)
set(COMBINED "${COMBINED}${TMP}") set(COMBINED "${COMBINED}${TMP}")
endforeach() endforeach()
string(MD5 SHADER_CACHE_VERSION "${COMBINED}") 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,283 @@
## 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"
"enable_secondary_display"
)
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) ![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) ![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) ![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) ![Flathub Downloads](https://img.shields.io/flathub/downloads/org.azahar_emu.Azahar?logo=flathub&label=Flathub%20Downloads)
@ -16,11 +18,14 @@ The goal of this project is to be the de-facto platform for future development.
### Windows ### Windows
Download the latest release from [Releases](https://github.com/azahar-emu/azahar/releases). Azahar is available as both an installer and a zip archive.
If you are unsure of whether you want to use MSYS2 or MSVC, use MSYS2. Download the latest release in your preferred format from the [Releases](https://github.com/azahar-emu/azahar/releases) page.
If you are unsure of whether you want to use MSVC or MSYS2, use MSYS2.
--- ---
### MacOS ### MacOS
To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page. To download a build that will work on all Macs, you can download the `macos-universal` build on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
@ -31,23 +36,30 @@ Alternatively, if you wish to download a build specifically for your Mac, you ca
- `macos-x86_64` for Intel Macs - `macos-x86_64` for Intel Macs
--- ---
### Android ### Android
The recommended method of downloading Azahar on Android is via the Google Play store:
There are two variants of Azahar available on Android, those being the Vanilla and Google Play builds.
The Vanilla build is technically superior, as it uses an alternative method of file management which is faster, but isn't permitted on the Google Play store.
For most users, we currently recommended downloading Azahar on Android via the Google Play Store for ease of accessibility:
<a href='https://play.google.com/store/apps/details?id=io.github.lime3ds.android'><img width='180' alt='Get it on Google Play' src='https://raw.githubusercontent.com/pioug/google-play-badges/06ccd9252af1501613da2ca28eaffe31307a4e6d/svg/English.svg'/></a> <a href='https://play.google.com/store/apps/details?id=io.github.lime3ds.android'><img width='180' alt='Get it on Google Play' src='https://raw.githubusercontent.com/pioug/google-play-badges/06ccd9252af1501613da2ca28eaffe31307a4e6d/svg/English.svg'/></a>
Alternatively, you can install the app using Obtainium: Alternatively, you can install the app using Obtainium, allowing you to use the Vanilla variant:
1. Download and install Obtainium from [here](https://github.com/ImranR98/Obtainium/releases) (use the file named `app-release.apk`) 1. Download and install Obtainium from [here](https://github.com/ImranR98/Obtainium/releases) (use the file named `app-release.apk`)
2. Open Obtainium and click 'Add App' 2. Open Obtainium and click 'Add App'
3. Type `https://github.com/azahar-emu/azahar` into the 'App Source URL' section 3. Type `https://github.com/azahar-emu/azahar` into the 'App Source URL' section
4. Click 'Add' 4. Click 'Add'
5. Click 'Install' 5. Click 'Install', and select the preferred variant
If you wish, you can also simply install the latest APK from the [Releases](https://github.com/azahar-emu/azahar/releases) page. If you wish, you can also simply install the latest APK from the [Releases](https://github.com/azahar-emu/azahar/releases) page.
Keep in mind that you will not recieve automatic updates when installing via the APK. Keep in mind that you will not recieve automatic updates when installing via the APK.
--- ---
### Linux ### Linux
The recommended format for using Azahar on Linux is the Flatpak available on Flathub: The recommended format for using Azahar on Linux is the Flatpak available on Flathub:
@ -56,11 +68,13 @@ The recommended format for using Azahar on Linux is the Flatpak available on Fla
Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page. Azahar is also available as an AppImage on the [Releases](https://github.com/azahar-emu/azahar/releases) page.
There are two variants of the AppImage available, those being `azahar.AppImage` and `azahar-wayland.AppImage`.
If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)). If you are unsure of which variant to use, we recommend using the default `azahar.AppImage`. This is because of upstream issues in the Wayland ecosystem which may cause problems when running the emulator (e.g. [#1162](https://github.com/azahar-emu/azahar/issues/1162)).
Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended. Unless you explicitly require native Wayland support (e.g. you are running a system with no Xwayland), the non-Wayland variant is recommended.
If you are using the Flatpak and run into issues with Wayland, you can disable Wayland support for the Azahar Flatpak using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal). The Flatpak build of Azahar also has native Wayland support disabled by default. If you require native Wayland support, it can be enabled using [Flatseal](https://flathub.org/en/apps/com.github.tchx84.Flatseal).
# Build instructions # Build instructions
@ -92,18 +106,23 @@ To do so, simply read https://github.com/azahar-emu/compatibility-list/blob/mast
Contributing compatibility data helps more accurately reflect the current capabilities of the emulator, so it would be highly appreciated if you could go through the reporting process after completing a game. Contributing compatibility data helps more accurately reflect the current capabilities of the emulator, so it would be highly appreciated if you could go through the reporting process after completing a game.
# Minimum requirements # Minimum requirements
Below are the minimum requirements to run Azahar: Below are the minimum requirements to run Azahar:
### Desktop ### Desktop
``` ```
Operating System: Windows 10 (64-bit), MacOS 13.4 (Ventura), or modern 64-bit Linux Operating System: Windows 10 (64-bit), MacOS 13.4 (Ventura), or modern 64-bit Linux
CPU: x86-64/ARM64 CPU (Windows for ARM not supported). Single core performance higher than 1,800 on Passmark CPU: x86-64/ARM64 CPU (Windows for ARM not supported).
Single core performance higher than 1,800 on Passmark.
SSE4.2 required on x86_64.
GPU: OpenGL 4.3 or Vulkan 1.1 support GPU: OpenGL 4.3 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended Memory: 2GB of RAM. 4GB is recommended
``` ```
### Android ### Android
``` ```
Operating System: Android 9.0+ (64-bit) Operating System: Android 10.0+ (64-bit)
CPU: Snapdragon 835 SoC or better CPU: Snapdragon 835 SoC or better
GPU: OpenGL ES 3.2 or Vulkan 1.1 support GPU: OpenGL ES 3.2 or Vulkan 1.1 support
Memory: 2GB of RAM. 4GB is recommended Memory: 2GB of RAM. 4GB is recommended
@ -116,6 +135,7 @@ We share public roadmaps for upcoming releases in the form of GitHub milestones.
You can find these at https://github.com/azahar-emu/azahar/milestones. You can find these at https://github.com/azahar-emu/azahar/milestones.
# Join the conversation # Join the conversation
We have a community Discord server where you can chat about the project, keep up to date with the latest announcements, or coordinate emulator development. We have a community Discord server where you can chat about the project, keep up to date with the latest announcements, or coordinate emulator development.
[![](https://dcbadge.vercel.app/api/server/4ZjMpAp3M6)](https://discord.gg/4ZjMpAp3M6) Join at https://discord.gg/4ZjMpAp3M6

View file

@ -35,6 +35,7 @@
<string>cci</string> <string>cci</string>
<string>cxi</string> <string>cxi</string>
<string>cia</string> <string>cia</string>
<string>3ds</string>
</array> </array>
<key>CFBundleTypeName</key> <key>CFBundleTypeName</key>
<string>Nintendo 3DS File</string> <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 file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID 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

1661
dist/languages/de.ts vendored

File diff suppressed because it is too large Load diff

2534
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

7918
dist/languages/es_419.ts vendored Normal file

File diff suppressed because it is too large Load diff

1533
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load diff

1637
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

1535
dist/languages/id.ts vendored

File diff suppressed because it is too large Load diff

1588
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

1949
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load diff

1537
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

1550
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

1522
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

1517
dist/languages/zh_CN.ts vendored

File diff suppressed because it is too large Load diff

1535
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> <expanded-acronym>CTR Cart Image</expanded-acronym>
<icon name="azahar"/> <icon name="azahar"/>
<glob pattern="*.cci"/> <glob pattern="*.cci"/>
<glob pattern="*.3ds"/>
<magic><match value="NCSD" type="string" offset="256"/></magic> <magic><match value="NCSD" type="string" offset="256"/></magic>
</mime-type> </mime-type>

View file

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

View file

@ -50,15 +50,18 @@ else()
endif() endif()
# Catch2 # Catch2
if (ENABLE_TESTS)
add_library(catch2 INTERFACE) add_library(catch2 INTERFACE)
if(USE_SYSTEM_CATCH2) if(USE_SYSTEM_CATCH2)
find_package(Catch2 3.0.0 REQUIRED) find_package(Catch2 3.0.0 REQUIRED)
else() else()
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "") set(CATCH_INSTALL_DOCS OFF CACHE BOOL "")
set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "") set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
add_subdirectory(catch2) add_subdirectory(catch2 EXCLUDE_FROM_ALL)
endif() endif()
target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain) target_link_libraries(catch2 INTERFACE Catch2::Catch2WithMain)
include(Catch)
endif()
# Crypto++ # Crypto++
if(USE_SYSTEM_CRYPTOPP) if(USE_SYSTEM_CRYPTOPP)
@ -66,17 +69,9 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE) add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else() else()
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
endif()
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "") set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "") add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
add_subdirectory(cryptopp-cmake)
endif() endif()
# dds-ktx # dds-ktx
@ -109,8 +104,14 @@ endif()
# Oaknut # Oaknut
if ("arm64" IN_LIST ARCHITECTURE) 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) add_subdirectory(oaknut EXCLUDE_FROM_ALL)
endif() endif()
endif()
# Dynarmic # Dynarmic
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE) if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
@ -133,7 +134,7 @@ endif()
# getopt # getopt
if (MSVC) if (MSVC)
add_subdirectory(getopt) add_subdirectory(getopt EXCLUDE_FROM_ALL)
endif() endif()
# inih # inih
@ -142,7 +143,7 @@ if(USE_SYSTEM_INIH)
add_library(inih INTERFACE) add_library(inih INTERFACE)
target_link_libraries(inih INTERFACE inih::inih inih::inir) target_link_libraries(inih INTERFACE inih::inih inih::inir)
else() else()
add_subdirectory(inih) add_subdirectory(inih EXCLUDE_FROM_ALL)
endif() endif()
# MicroProfile # MicroProfile
@ -165,7 +166,7 @@ if (NOT MSVC)
endif() endif()
# Open Source Archives # Open Source Archives
add_subdirectory(open_source_archives) add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
# faad2 # faad2
add_subdirectory(faad2 EXCLUDE_FROM_ALL) add_subdirectory(faad2 EXCLUDE_FROM_ALL)
@ -204,12 +205,12 @@ add_subdirectory(teakra EXCLUDE_FROM_ALL)
# SDL2 # SDL2
if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2) if (ENABLE_SDL2 AND NOT USE_SYSTEM_SDL2)
add_subdirectory(sdl2) add_subdirectory(sdl2 EXCLUDE_FROM_ALL)
endif() endif()
# libusb # libusb
if (ENABLE_LIBUSB AND NOT USE_SYSTEM_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_INCLUDE_DIR "" PARENT_SCOPE)
set(LIBUSB_LIBRARIES usb PARENT_SCOPE) set(LIBUSB_LIBRARIES usb PARENT_SCOPE)
endif() endif()
@ -253,7 +254,7 @@ if(USE_SYSTEM_ENET)
add_library(enet INTERFACE) add_library(enet INTERFACE)
target_link_libraries(enet INTERFACE libenet::libenet) target_link_libraries(enet INTERFACE libenet::libenet)
else() else()
add_subdirectory(enet) add_subdirectory(enet EXCLUDE_FROM_ALL)
target_include_directories(enet INTERFACE ./enet/include) target_include_directories(enet INTERFACE ./enet/include)
endif() endif()
@ -275,7 +276,7 @@ if (ENABLE_CUBEB)
endif() endif()
# DiscordRPC # DiscordRPC
if (USE_DISCORD_PRESENCE) if (ENABLE_DISCORD_RPC)
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms. # rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
include(TestBigEndian) include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN) test_big_endian(RAPIDJSON_BIG_ENDIAN)
@ -292,6 +293,15 @@ if (USE_DISCORD_PRESENCE)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif() 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 # JSON
add_library(json-headers INTERFACE) add_library(json-headers INTERFACE)
if (USE_SYSTEM_JSON) if (USE_SYSTEM_JSON)
@ -309,12 +319,8 @@ endif()
# OpenSSL # OpenSSL
if (USE_SYSTEM_OPENSSL) if (USE_SYSTEM_OPENSSL)
find_package(OpenSSL 1.1) find_package(OpenSSL 1.1)
if (OPENSSL_FOUND)
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
endif() else()
endif()
if (NOT OPENSSL_FOUND)
# LibreSSL # LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
set(OPENSSLDIR "/etc/ssl/") set(OPENSSLDIR "/etc/ssl/")
@ -356,7 +362,7 @@ target_compile_options(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
add_subdirectory(gamemode) add_subdirectory(gamemode EXCLUDE_FROM_ALL)
endif() endif()
# cpp-jwt # cpp-jwt
@ -379,13 +385,13 @@ if(USE_SYSTEM_LODEPNG)
find_package(lodepng REQUIRED) find_package(lodepng REQUIRED)
target_link_libraries(lodepng INTERFACE lodepng::lodepng) target_link_libraries(lodepng INTERFACE lodepng::lodepng)
else() else()
add_subdirectory(lodepng) add_subdirectory(lodepng EXCLUDE_FROM_ALL)
endif() endif()
# (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG # (xperia64): Only use libyuv on Android b/c of build issues on Windows and mandatory JPEG
if(ANDROID) if(ANDROID)
# libyuv # libyuv
add_subdirectory(libyuv) add_subdirectory(libyuv EXCLUDE_FROM_ALL)
target_include_directories(yuv INTERFACE ./libyuv/include) target_include_directories(yuv INTERFACE ./libyuv/include)
endif() endif()
@ -396,6 +402,9 @@ if (ENABLE_OPENAL)
find_package(OpenAL REQUIRED) find_package(OpenAL REQUIRED)
target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL) target_link_libraries(OpenAL INTERFACE OpenAL::OpenAL)
else() else()
if (BSD STREQUAL "OpenBSD")
set(ALSOFT_BACKEND_SOLARIS OFF CACHE BOOL "")
endif()
set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "") set(ALSOFT_EMBED_HRTF_DATA OFF CACHE BOOL "")
set(ALSOFT_EXAMPLES OFF CACHE BOOL "") set(ALSOFT_EXAMPLES OFF CACHE BOOL "")
set(ALSOFT_INSTALL OFF CACHE BOOL "") set(ALSOFT_INSTALL OFF CACHE BOOL "")
@ -411,7 +420,7 @@ endif()
# OpenGL dependencies # OpenGL dependencies
if (ENABLE_OPENGL) if (ENABLE_OPENGL)
# Glad # Glad
add_subdirectory(glad) add_subdirectory(glad EXCLUDE_FROM_ALL)
endif() endif()
# Vulkan dependencies # Vulkan dependencies
@ -451,7 +460,7 @@ if (ENABLE_VULKAN)
set(ENABLE_CTEST OFF CACHE BOOL "") set(ENABLE_CTEST OFF CACHE BOOL "")
set(ENABLE_HLSL OFF CACHE BOOL "") set(ENABLE_HLSL OFF CACHE BOOL "")
set(BUILD_EXTERNAL OFF CACHE BOOL "") set(BUILD_EXTERNAL OFF CACHE BOOL "")
add_subdirectory(glslang) add_subdirectory(glslang EXCLUDE_FROM_ALL)
endif() endif()
# sirit # sirit
@ -482,11 +491,24 @@ if (ENABLE_VULKAN)
else() else()
target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include) target_include_directories(vulkan-headers INTERFACE ./vulkan-headers/include)
target_disable_warnings(vulkan-headers) 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() endif()
# adrenotools # adrenotools
if (ANDROID AND "arm64" IN_LIST ARCHITECTURE) if (ANDROID AND "arm64" IN_LIST ARCHITECTURE)
add_subdirectory(libadrenotools) add_subdirectory(libadrenotools EXCLUDE_FROM_ALL)
endif() endif()
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_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_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_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_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_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) 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_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_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_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_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_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" 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 DYNARMIC
FMT FMT
XBYAK XBYAK
OAKNUT
INIH INIH
FFMPEG_HEADERS FFMPEG_HEADERS
GLSLANG GLSLANG

2
externals/cryptopp vendored

@ -1 +1 @@
Subproject commit 60f81a77e0c9a0e7ffc1ca1bc438ddfa2e43b78e Subproject commit 8d92d788421483a43e09acf1cd4a2861cb2b8cab

@ -1 +0,0 @@
Subproject commit 00a151f8489daaa32434ab1f340e6750793ddf0c

@ -1 +1 @@
Subproject commit cb50201fc09290cd078c7ab27917504491f7f96a Subproject commit 049826c3f42875c2f9599552f425e76435789cc7

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

20
src/android/.editorconfig Normal file
View file

@ -0,0 +1,20 @@
root = true
[*.{kt,kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ktlint_code_style = android_studio
# Disable wildcard imports
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
# Style configuration
indent_size = 4
indent_style = space
ktlint_standard_comment-wrapping = disabled
ktlint_standard_no-unused-imports = enabled
# ^- Reportedly finicky, use for now but maybe delete later if there are issues.
# See https://github.com/ktlint/ktlint/issues/3038
ktlint_standard_package-name = disabled
max_line_length = 100

View file

@ -34,7 +34,8 @@ captures/
# IntelliJ # IntelliJ
*.iml *.iml
.idea/ .idea/*
!.idea/inspectionProfiles
# Keystore files # Keystore files
# Uncomment the following line if you do not want to check your keystore files in. # Uncomment the following line if you do not want to check your keystore files in.

View file

@ -0,0 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GrazieInspection" enabled="false" level="GRAMMAR_ERROR" enabled_by_default="false" />
<inspection_tool class="GrazieStyle" enabled="false" level="STYLE_SUGGESTION" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LiftReturnOrAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View file

@ -12,6 +12,7 @@ plugins {
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "2.0.20" kotlin("plugin.serialization") version "2.0.20"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint")
} }
/** /**
@ -24,12 +25,11 @@ val abiFilter = listOf("arm64-v8a", "x86_64")
val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs" val downloadedJniLibsPath = "${layout.buildDirectory.get().asFile.path}/downloadedJniLibs"
@Suppress("UnstableApiUsage")
android { android {
namespace = "org.citra.citra_emu" namespace = "org.citra.citra_emu"
compileSdkVersion = "android-35" compileSdkVersion = "android-35"
ndkVersion = "27.1.12297006" ndkVersion = "27.3.13750724"
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
@ -63,7 +63,7 @@ android {
defaultConfig { defaultConfig {
// The application ID refers to Lime3DS to allow for // The application ID refers to Lime3DS to allow for
// the Play Store listing, which was originally set up for Lime3DS, to still be used. // 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 minSdk = 29
targetSdk = 35 targetSdk = 35
versionCode = autoVersion versionCode = autoVersion
@ -80,7 +80,9 @@ android {
"-DENABLE_QT=0", // Don't use QT "-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL "-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-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
) )
} }
} }
@ -125,7 +127,8 @@ android {
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
versionNameSuffix = "-debug" versionNameSuffix = "-debug"
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
isShrinkResources = true // TODO: Does this actually do anything when isDebuggable is enabled? -OS isShrinkResources = true
// TODO: ^- Does this actually do anything when isDebuggable is enabled? -OS
isDebuggable = true isDebuggable = true
isJniDebuggable = true isJniDebuggable = true
proguardFiles( proguardFiles(
@ -136,8 +139,10 @@ android {
} }
// Same as above, but with isDebuggable disabled. // Same as above, but with isDebuggable disabled.
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS) without constantly tripping over years-old and seemingly harmless memory bugs. // Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS)
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done. // without constantly tripping over years-old and seemingly harmless memory bugs.
// We should fix those bugs eventually, but for now this exists as a workaround to
// allow other work to be done on these devices.
register("relWithDebInfoLite") { register("relWithDebInfoLite") {
initWith(getByName("relWithDebInfo")) initWith(getByName("relWithDebInfo"))
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
@ -147,7 +152,7 @@ android {
} }
lint { lint {
checkReleaseBuilds = false // Ditto checkReleaseBuilds = false // Ditto
// The name of this property is misleading, this doesn't actually disable linting for the `release` build. // ^- The name of this property is misleading, this doesn't actually disable linting for the `release` build.
} }
} }
@ -173,6 +178,7 @@ android {
register("googlePlay") { register("googlePlay") {
dimension = "version" dimension = "version"
versionNameSuffix = "-googleplay" versionNameSuffix = "-googleplay"
applicationId = "io.github.lime3ds.android"
} }
} }
@ -214,7 +220,9 @@ dependencies {
// Download Vulkan Validation Layers from the KhronosGroup GitHub. // Download Vulkan Validation Layers from the KhronosGroup GitHub.
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") { val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip") src(
"https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip"
)
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip")) dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
onlyIfModified(true) onlyIfModified(true)
} }
@ -234,6 +242,11 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
tasks.named("preBuild") { tasks.named("preBuild") {
dependsOn(unzipVulkanValidationLayers) dependsOn(unzipVulkanValidationLayers)
dependsOn("ktlintCheck")
}
ktlint {
version = "1.8.0"
} }
fun getGitVersion(): String { fun getGitVersion(): String {
@ -291,7 +304,7 @@ android.applicationVariants.configureEach {
val variant = this val variant = this
val capitalizedName = variant.name.capitalizeUS() val capitalizedName = variant.name.capitalizeUS()
val copyTask = tasks.register("copyBundle${capitalizedName}") { val copyTask = tasks.register("copyBundle$capitalizedName") {
doLast { doLast {
project.copy { project.copy {
from(variant.outputs.first().outputFile.parentFile) from(variant.outputs.first().outputFile.parentFile)
@ -305,5 +318,5 @@ android.applicationVariants.configureEach {
} }
} }
} }
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) } tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
} }

View file

@ -12,10 +12,10 @@ import android.content.Context
import android.os.Build import android.os.Build
import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GpuDriverHelper import org.citra.citra_emu.utils.GraphicsUtil
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil import org.citra.citra_emu.utils.MemoryUtil
import org.citra.citra_emu.utils.PermissionsHandler
class CitraApplication : Application() { class CitraApplication : Application() {
private fun createNotificationChannel() { private fun createNotificationChannel() {
@ -69,6 +69,7 @@ class CitraApplication : Application() {
Log.info("SoC Model - ${Build.SOC_MODEL}") Log.info("SoC Model - ${Build.SOC_MODEL}")
} }
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}") Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
Log.info("OpenGL ES Renderer - ${GraphicsUtil.openGLRendererString}")
} }
companion object { companion object {

View file

@ -19,19 +19,22 @@ import android.view.Surface
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import java.util.Date
import org.citra.citra_emu.activities.EmulationActivity 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.BuildUtil
import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GraphicsUtil
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RemovableStorageHelper import org.citra.citra_emu.utils.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference
import java.util.Date
/** /**
* Class which contains methods that interact * Class which contains methods that interact
@ -41,7 +44,7 @@ object NativeLibrary {
/** /**
* Default touchscreen device * Default touchscreen device
*/ */
const val TouchScreenDevice = "Touchscreen" const val TOUCHSCREEN_DEVICE = "Touchscreen"
@JvmField @JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null) var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@ -132,7 +135,24 @@ object NativeLibrary {
* If not set, it auto-detects a location * If not set, it auto-detects a location
*/ */
external fun setUserDirectory(directory: String) 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. // Create the config.ini file.
external fun createConfigFile() external fun createConfigFile()
@ -230,6 +250,15 @@ object NativeLibrary {
external fun playTimeManagerGetPlayTime(titleId: Long): Long external fun playTimeManagerGetPlayTime(titleId: Long): Long
external fun playTimeManagerGetCurrentTitleId(): Long external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
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 var coreErrorAlertResult = false
private val coreErrorAlertLock = Object() private val coreErrorAlertLock = Object()
@ -286,6 +315,12 @@ object NativeLibrary {
canContinue = false canContinue = false
} }
CoreError.ErrorCoreExceptionRaised -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = false
}
CoreError.ErrorUnknown -> { CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error) title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message) message = emulationActivity.getString(R.string.fatal_error_message)
@ -310,11 +345,12 @@ object NativeLibrary {
return coreErrorAlertResult return coreErrorAlertResult
} }
@get:Keep @Keep
@get:JvmStatic @JvmStatic
val isPortraitMode: Boolean fun isPortraitMode(): Boolean = (
get() = CitraApplication.appContext.resources.configuration.orientation == CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
)
@Keep @Keep
@JvmStatic @JvmStatic
@ -408,7 +444,7 @@ object NativeLibrary {
return return
} }
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) { if (resultCode == CoreError.ShutdownRequested.value) {
emulationActivity.finish() emulationActivity.finish()
return return
} }
@ -427,23 +463,57 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
var captionId = R.string.loader_error_invalid_format val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
val result = requireArguments().getInt(RESULT_CODE) val title: String
if (result == ErrorLoader_ErrorEncrypted) { val message: String
captionId = R.string.loader_error_encrypted when (coreError) {
CoreError.ErrorGetLoader,
CoreError.ErrorLoaderErrorInvalidFormat,
CoreError.ErrorSystemMode -> {
title = getString(R.string.loader_error_invalid_format)
message = getString(R.string.loader_error_invalid_format_description)
}
CoreError.ErrorLoaderErrorEncrypted -> {
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.ErrorLoaderErrorPatches -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_applying_patches_description)
}
CoreError.ErrorLoaderErrorPatchesInvalidTitle -> {
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()) val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(captionId) .setTitle(title)
.setMessage( .setMessage(
Html.fromHtml( Html.fromHtml(
if (result == ErrorArticDisconnected) message,
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
else
CitraApplication.appContext.resources.getString(R.string.redump_games),
Html.FROM_HTML_MODE_LEGACY Html.FROM_HTML_MODE_LEGACY
) )
) )
@ -465,20 +535,6 @@ object NativeLibrary {
const val RESULT_CODE = "resultcode" 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 { fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
val args = Bundle() val args = Bundle()
args.putInt(RESULT_CODE, resultCode) args.putInt(RESULT_CODE, resultCode)
@ -627,19 +683,17 @@ object NativeLibrary {
// Compression / Decompression // Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus { fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
return CompressStatus.fromValue( CompressStatus.fromValue(
compressFileNative(inputPath, outputPath) compressFileNative(inputPath, outputPath)
) )
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus { fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
return CompressStatus.fromValue( CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath) decompressFileNative(inputPath, outputPath)
) )
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@ -673,8 +727,7 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun openContentUri(path: String, openMode: String): Int = fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.openContentUri(path, openMode) CitraApplication.documentsTree.openContentUri(path, openMode)
} else { } else {
FileUtil.openContentUri(path, openMode) FileUtil.openContentUri(path, openMode)
@ -682,8 +735,7 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getFilesName(path: String): Array<String?> = fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFilesName(path) CitraApplication.documentsTree.getFilesName(path)
} else { } else {
FileUtil.getFilesName(path) FileUtil.getFilesName(path)
@ -691,40 +743,56 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun getUserDirectory(uriOverride: Uri? = null): String { fun getNativePath(uri: Uri): String {
BuildUtil.assertNotGooglePlay() BuildUtil.assertNotGooglePlay()
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val dirSep = "/" 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 val primaryStoragePath = Environment.getExternalStorageDirectory().absolutePath
return primaryStoragePath + dirSep + udVirtualPath + dirSep return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device } else { // User directory probably located on a removable storage device
val storageIdString = udPathSegment.substringBefore(":") val storageIdString = pathSegment.substringBefore(":")
val udRemovablePath = RemovableStorageHelper.getRemovableStoragePath(storageIdString) val removablePath = RemovableStorageHelper.getRemovableStoragePath(
CitraApplication.appContext,
storageIdString
)
if (udRemovablePath == null) { if (removablePath == null) {
android.util.Log.e("NativeLibrary", android.util.Log.e(
"Unknown mount location for storage device '$storageIdString' (URI: $udUri)" "NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
) )
return "" return ""
} }
return udRemovablePath + dirSep + udVirtualPath + dirSep return removablePath + dirSep + virtualPath
} }
} }
@Keep @Keep
@JvmStatic @JvmStatic
fun getSize(path: String): Long = fun getUserDirectory(): String {
if (FileUtil.isNativePath(path)) { val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val userDirectoryUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri()
return getNativePath(userDirectoryUri)
}
@Keep
@JvmStatic
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFileSize(path) CitraApplication.documentsTree.getFileSize(path)
} else { } else {
FileUtil.getFileSize(path) FileUtil.getFileSize(path)
@ -736,8 +804,11 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun fileExists(path: String): Boolean = fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
if (FileUtil.isNativePath(path)) {
@Keep
@JvmStatic
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path) CitraApplication.documentsTree.exists(path)
} else { } else {
FileUtil.exists(path) FileUtil.exists(path)
@ -745,8 +816,7 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun isDirectory(path: String): Boolean = fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path) CitraApplication.documentsTree.isDirectory(path)
} else { } else {
FileUtil.isDirectory(path) FileUtil.isDirectory(path)
@ -758,8 +828,7 @@ object NativeLibrary {
sourcePath: String, sourcePath: String,
destinationParentPath: String, destinationParentPath: String,
destinationFilename: String destinationFilename: String
): Boolean = ): Boolean = if (FileUtil.isNativePath(sourcePath) &&
if (FileUtil.isNativePath(sourcePath) &&
FileUtil.isNativePath(destinationParentPath) FileUtil.isNativePath(destinationParentPath)
) { ) {
CitraApplication.documentsTree CitraApplication.documentsTree
@ -805,19 +874,35 @@ object NativeLibrary {
@Keep @Keep
@JvmStatic @JvmStatic
fun deleteDocument(path: String): Boolean = fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path) CitraApplication.documentsTree.deleteDocument(path)
} else { } else {
FileUtil.deleteDocument(path) FileUtil.deleteDocument(path)
} }
enum class CoreError { enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
ErrorSystemFiles, Success(0, R.string.core_error_success),
ErrorSavestate, ErrorNotInitialized(1, R.string.core_error_not_initialized),
ErrorArticDisconnected, ErrorGetLoader(2, R.string.core_error_get_loader),
ErrorN3DSApplication, ErrorSystemMode(3, R.string.core_error_system_mode),
ErrorUnknown ErrorLoader(4, R.string.core_error_loader),
ErrorLoaderErrorEncrypted(5, R.string.core_error_loader_encrypted),
ErrorLoaderErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
ErrorLoaderErrorGBATitle(7, R.string.core_error_loader_gba_title),
ErrorLoaderErrorPatches(8, R.string.core_error_loader_error_patches),
ErrorLoaderErrorPatchesInvalidTitle(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 = entries.find { it.value == value } ?: ErrorUnknown
}
} }
enum class InstallStatus { enum class InstallStatus {
@ -868,7 +953,11 @@ object NativeLibrary {
const val MESSAGE = "message" const val MESSAGE = "message"
const val CAN_CONTINUE = "canContinue" const val CAN_CONTINUE = "canContinue"
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment { fun newInstance(
title: String,
message: String,
canContinue: Boolean
): CoreErrorDialogFragment {
val frag = CoreErrorDialogFragment() val frag = CoreErrorDialogFragment()
val args = Bundle() val args = Bundle()
args.putString(TITLE, title) args.putString(TITLE, title)

View file

@ -8,9 +8,9 @@ import android.Manifest.permission
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.InputDevice import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
@ -21,6 +21,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.core.os.BundleCompat import androidx.core.os.BundleCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -30,7 +31,7 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult import org.citra.citra_emu.camera.StillImageCameraHelper.onFilePickerResult
import org.citra.citra_emu.contracts.OpenFileResultContract import org.citra.citra_emu.contracts.OpenFileResultContract
import org.citra.citra_emu.databinding.ActivityEmulationBinding import org.citra.citra_emu.databinding.ActivityEmulationBinding
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
@ -43,10 +44,11 @@ import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.fragments.EmulationFragment import org.citra.citra_emu.fragments.EmulationFragment
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game 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.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.utils.ThemeUtil
@ -62,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility private lateinit var hotkeyUtility: HotkeyUtility
private lateinit var secondaryDisplay: SecondaryDisplay lateinit var secondaryDisplayManager: SecondaryDisplay
private val onShutdown = Runnable { private val onShutdown = Runnable {
if (intent.getBooleanExtra("launched_from_shortcut", false)) { if (intent.getBooleanExtra("launched_from_shortcut", false)) {
@ -79,7 +81,9 @@ class EmulationActivity : AppCompatActivity() {
return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment return navHostFragment.getChildFragmentManager().fragments.last() as EmulationFragment
} }
private var isRotationBlocked: Boolean = true
private var isEmulationRunning: Boolean = false private var isEmulationRunning: Boolean = false
private var isEmulationReady: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
@ -88,12 +92,20 @@ class EmulationActivity : AppCompatActivity() {
ThemeUtil.setTheme(this) ThemeUtil.setTheme(this)
settingsViewModel.settings.loadSettings() 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) super.onCreate(savedInstanceState)
secondaryDisplay = SecondaryDisplay(this)
secondaryDisplay.updateDisplay() secondaryDisplayManager = SecondaryDisplay(this)
secondaryDisplayManager.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
setContentView(binding.root) setContentView(binding.root)
@ -118,8 +130,6 @@ class EmulationActivity : AppCompatActivity() {
isEmulationRunning = true isEmulationRunning = true
instance = this instance = this
applyOrientationSettings() // Check for orientation settings at startup
val game = try { val game = try {
intent.extras?.let { extras -> intent.extras?.let { extras ->
BundleCompat.getParcelable(extras, "game", Game::class.java) BundleCompat.getParcelable(extras, "game", Game::class.java)
@ -135,39 +145,76 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStart(game.titleId) 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 // 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 // rotations. Here we set full screen immersive repeatedly in onResume and in
// onWindowFocusChanged to prevent the unwanted status bar state. // onWindowFocusChanged to prevent the unwanted status bar state.
override fun onResume() { override fun onResume() {
super.onResume()
enableFullscreenImmersive() 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() { override fun onStop() {
secondaryDisplay.releasePresentation() secondaryDisplayManager.releasePresentation()
super.onStop() super.onStop()
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
enableFullscreenImmersive() enableFullscreenImmersive()
super.onWindowFocusChanged(hasFocus)
} }
public override fun onRestart() { public override fun onRestart() {
super.onRestart() super.onRestart()
secondaryDisplay.updateDisplay() secondaryDisplayManager.updateDisplay()
NativeLibrary.reloadCameraDevices() NativeLibrary.reloadCameraDevices()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putBoolean("isEmulationRunning", isEmulationRunning) outState.putBoolean("isEmulationRunning", isEmulationRunning)
outState.putBoolean("isEmulationReady", isEmulationReady)
outState.putBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) { override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false) isEmulationRunning = savedInstanceState.getBoolean("isEmulationRunning", false)
isEmulationReady = savedInstanceState.getBoolean("isEmulationReady", false)
isRotationBlocked = savedInstanceState.getBoolean("isRotationBlocked", isRotationBlocked)
} }
override fun onDestroy() { override fun onDestroy() {
@ -175,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStop() NativeLibrary.playTimeManagerStop()
isEmulationRunning = false isEmulationRunning = false
instance = null instance = null
secondaryDisplay.releasePresentation() secondaryDisplayManager.releasePresentation()
secondaryDisplay.releaseVD() secondaryDisplayManager.releaseVD()
super.onDestroy() super.onDestroy()
} }
@ -221,6 +268,11 @@ class EmulationActivity : AppCompatActivity() {
fun onEmulationStarted() { fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true) emulationViewModel.setEmulationStarted(true)
isEmulationReady = true
if (isRotationBlocked) {
isRotationBlocked = false
applyOrientationSettings()
}
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
getString(R.string.emulation_menu_help), getString(R.string.emulation_menu_help),
@ -267,41 +319,36 @@ class EmulationActivity : AppCompatActivity() {
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
} }
val button = when (event.action) {
preferences.getInt(InputBindingSetting.getInputButtonKey(event.keyCode), event.keyCode)
val action: Int = when (event.action) {
KeyEvent.ACTION_DOWN -> { KeyEvent.ACTION_DOWN -> {
hotkeyUtility.handleHotkey(button)
// On some devices, the back gesture / button press is not intercepted by androidx // 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 // 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) // cover for either a fault on androidx's side or in OEM skins (MIUI at least)
if (event.keyCode == KeyEvent.KEYCODE_BACK) { if (event.keyCode == KeyEvent.KEYCODE_BACK) {
// If the hotkey is pressed, we don't want to open the drawer // If the hotkey is pressed, we don't want to open the drawer
if (!hotkeyUtility.HotkeyIsPressed) { if (!hotkeyUtility.hotkeyIsPressed) {
onBackPressed() onBackPressed()
return true
} }
} }
return hotkeyUtility.handleKeyPress(event)
// Normal key events.
NativeLibrary.ButtonState.PRESSED
} }
KeyEvent.ACTION_UP -> { KeyEvent.ACTION_UP -> {
hotkeyUtility.HotkeyIsPressed = false return hotkeyUtility.handleKeyRelease(event)
NativeLibrary.ButtonState.RELEASED
} }
else -> return false
} else -> {
val input = event.device
?: // Controller was disconnected
return false return false
return NativeLibrary.onGamePadEvent(input.descriptor, button, action) }
}
} }
private fun onAmiiboSelected(selectedFile: String) { private fun onAmiiboSelected(selectedFile: String) {
val success = NativeLibrary.loadAmiibo(selectedFile) val success = NativeLibrary.loadAmiibo(selectedFile)
if (!success) { if (!success) {
Log.error("[EmulationActivity] Failed to load Amiibo file: $selectedFile")
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
R.string.amiibo_load_error, R.string.amiibo_load_error,
R.string.amiibo_load_error_message R.string.amiibo_load_error_message
@ -313,7 +360,8 @@ class EmulationActivity : AppCompatActivity() {
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation // TODO: Move this check into native code - prevents crash if input pressed before starting emulation
if (!NativeLibrary.isRunning() || if (!NativeLibrary.isRunning() ||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) || (event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
emulationFragment.isDrawerOpen()) { emulationFragment.isDrawerOpen()
) {
return super.dispatchGenericMotionEvent(event) return super.dispatchGenericMotionEvent(event)
} }
@ -342,16 +390,19 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation = val guestOrientation =
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1) preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false); val inverted = preferences.getBoolean(
InputBindingSetting.getInputAxisInvertedKey(axis),
false
)
if (nextMapping == -1 || guestOrientation == -1) { if (nextMapping == -1 || guestOrientation == -1) {
// Axis is unmapped // Axis is unmapped
continue continue
} }
if (value > 0f && value < 0.1f || value < 0f && value > -0.1f) { if ((value > 0f && value < 0.1f) || (value < 0f && value > -0.1f)) {
// Skip joystick wobble // Skip joystick wobble
value = 0f value = 0f
} }
if (inverted) value = -value; if (inverted) value = -value
when (nextMapping) { when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> { NativeLibrary.ButtonType.STICK_LEFT -> {
@ -405,7 +456,7 @@ class EmulationActivity : AppCompatActivity() {
// Triggers L/R and ZL/ZR // Triggers L/R and ZL/ZR
if (isTriggerPressedLMapped) { if (isTriggerPressedLMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.TRIGGER_L, NativeLibrary.ButtonType.TRIGGER_L,
if (isTriggerPressedL) { if (isTriggerPressedL) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -416,7 +467,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedRMapped) { if (isTriggerPressedRMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.TRIGGER_R, NativeLibrary.ButtonType.TRIGGER_R,
if (isTriggerPressedR) { if (isTriggerPressedR) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -427,7 +478,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedZLMapped) { if (isTriggerPressedZLMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.BUTTON_ZL, NativeLibrary.ButtonType.BUTTON_ZL,
if (isTriggerPressedZL) { if (isTriggerPressedZL) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -438,7 +489,7 @@ class EmulationActivity : AppCompatActivity() {
} }
if (isTriggerPressedZRMapped) { if (isTriggerPressedZRMapped) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.BUTTON_ZR, NativeLibrary.ButtonType.BUTTON_ZR,
if (isTriggerPressedZR) { if (isTriggerPressedZR) {
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
@ -451,72 +502,72 @@ class EmulationActivity : AppCompatActivity() {
// Work-around to allow D-pad axis to be bound to emulated buttons // Work-around to allow D-pad axis to be bound to emulated buttons
if (axisValuesDPad[0] == 0f) { if (axisValuesDPad[0] == 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[0] < 0f) { if (axisValuesDPad[0] < 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[0] > 0f) { if (axisValuesDPad[0] > 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
} }
if (axisValuesDPad[1] == 0f) { if (axisValuesDPad[1] == 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[1] < 0f) { if (axisValuesDPad[1] < 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
} }
if (axisValuesDPad[1] > 0f) { if (axisValuesDPad[1] > 0f) {
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP, NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED NativeLibrary.ButtonState.RELEASED
) )
NativeLibrary.onGamePadEvent( NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice, NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.PRESSED NativeLibrary.ButtonState.PRESSED
) )
@ -524,13 +575,21 @@ class EmulationActivity : AppCompatActivity() {
return true return true
} }
val openFileLauncher = val openAmiiboFileLauncher =
registerForActivityResult(OpenFileResultContract()) { result: Intent? -> registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles( val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, applicationContext, listOf<String>("bin") result,
applicationContext,
listOf<String>("bin")
) ?: return@registerForActivityResult ) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) {
onAmiiboSelected(selectedFiles[0]) onAmiiboSelected(selectedFiles[0])
} else {
val fileUri = selectedFiles[0].toUri()
val nativePath = "!" + NativeLibrary.getNativePath(fileUri)
onAmiiboSelected(nativePath)
}
} }
val openImageLauncher = val openImageLauncher =
@ -539,14 +598,12 @@ class EmulationActivity : AppCompatActivity() {
return@registerForActivityResult return@registerForActivityResult
} }
OnFilePickerResult(result.toString()) onFilePickerResult(result.toString())
} }
companion object { companion object {
private var instance: EmulationActivity? = null private var instance: EmulationActivity? = null
fun isRunning(): Boolean { fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
return instance?.isEmulationRunning ?: false
}
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardDriverOptionBinding import org.citra.citra_emu.databinding.CardDriverOptionBinding
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.GpuDriverMetadata import org.citra.citra_emu.utils.GpuDriverMetadata
import org.citra.citra_emu.viewmodel.DriverViewModel import org.citra.citra_emu.viewmodel.DriverViewModel
import org.citra.citra_emu.utils.GpuDriverHelper
class DriverAdapter(private val driverViewModel: DriverViewModel) : class DriverAdapter(private val driverViewModel: DriverViewModel) :
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>( ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
@ -105,15 +105,11 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>, oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata> newItem: Pair<Uri, GpuDriverMetadata>
): Boolean { ): Boolean = oldItem.first == newItem.first
return oldItem.first == newItem.first
}
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>, oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata> newItem: Pair<Uri, GpuDriverMetadata>
): Boolean { ): Boolean = oldItem.second == newItem.second
return oldItem.second == newItem.second
}
} }
} }

View file

@ -4,24 +4,25 @@
package org.citra.citra_emu.adapters package org.citra.citra_emu.adapters
import android.graphics.drawable.Icon import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
import android.net.Uri import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.content.Context
import android.content.SharedPreferences
import android.widget.TextView
import android.widget.ImageView import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.BitmapFactory
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
@ -29,23 +30,23 @@ import androidx.core.graphics.scale
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.citra.citra_emu.HomeNavigationDirections import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
@ -54,18 +55,22 @@ import org.citra.citra_emu.databinding.DialogShortcutBinding
import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections import org.citra.citra_emu.features.cheats.ui.CheatsFragmentDirections
import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment import org.citra.citra_emu.fragments.IndeterminateProgressDialogFragment
import org.citra.citra_emu.model.Game 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.FileUtil
import org.citra.citra_emu.utils.GameIconUtils import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.viewmodel.GamesViewModel import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter( class GameAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val inflater: LayoutInflater, private val inflater: LayoutInflater,
private val openImageLauncher: ActivityResultLauncher<String>?, private val openImageLauncher: ActivityResultLauncher<String>?,
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null private val onRequestCompressOrDecompress: (
) : (inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), )? = null
View.OnClickListener, View.OnLongClickListener { ) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener,
View.OnLongClickListener {
private var lastClickTime = 0L private var lastClickTime = 0L
private var imagePath: String? = null private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null private var dialogShortcutBinding: DialogShortcutBinding? = null
@ -136,7 +141,7 @@ class GameAdapter(
val holder = view.tag as GameViewHolder val holder = view.tag as GameViewHolder
gameExists(holder) gameExists(holder)
if (holder.game.titleId == 0L) { if (!holder.game.valid) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(R.string.properties) .setTitle(R.string.properties)
.setMessage(R.string.properties_not_loaded) .setMessage(R.string.properties_not_loaded)
@ -153,12 +158,21 @@ class GameAdapter(
if (holder.game.isInstalled) { if (holder.game.isInstalled) {
return true return true
} }
val path = holder.game.path
val gameExists = DocumentFile.fromSingleUri( val pathUri = path.toUri()
var gameExists: Boolean
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(path)) {
gameExists =
DocumentFile.fromSingleUri(
CitraApplication.appContext, CitraApplication.appContext,
Uri.parse(holder.game.path) pathUri
)?.exists() == true )?.exists() == true
} else {
val nativePath = NativeLibrary.getNativePath(pathUri)
gameExists = NativeLibrary.nativeFileExists(nativePath)
}
return if (!gameExists) { return if (!gameExists) {
Log.error("[GameAdapter] ROM file does not exist: $path")
Toast.makeText( Toast.makeText(
CitraApplication.appContext, CitraApplication.appContext,
R.string.loader_error_file_not_found, R.string.loader_error_file_not_found,
@ -200,7 +214,8 @@ class GameAdapter(
binding.textGameTitle.text = game.title binding.textGameTitle.text = game.title
binding.textCompany.text = game.company binding.textCompany.text = game.company
binding.textGameRegion.text = game.regions binding.textGameRegion.text = game.regions
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) { binding.imageCartridge.visibility =
if (preferences.getString("insertedCartridge", "") != game.path) {
View.GONE View.GONE
} else { } else {
View.VISIBLE View.VISIBLE
@ -208,7 +223,9 @@ class GameAdapter(
val backgroundColorId = val backgroundColorId =
if ( if (
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()) isValidGame(
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
)
) { ) {
R.attr.colorSurface R.attr.colorSurface
} else { } else {
@ -248,16 +265,45 @@ class GameAdapter(
val extraDir: String val extraDir: String
) )
private fun getGameDirectories(game: Game): GameDirectories { private fun getGameDirectories(game: Game): GameDirectories {
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000" val basePath =
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
return GameDirectories( return GameDirectories(
gameDir = game.path.substringBeforeLast("/"), gameDir = game.path.substringBeforeLast("/"),
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001", saveDir =
basePath +
"/title/${String.format(
"%016x",
game.titleId
).lowercase().substring(
0,
8
)}/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/data/00000001",
modsDir = "load/mods/${String.format("%016X", game.titleId)}", modsDir = "load/mods/${String.format("%016X", game.titleId)}",
texturesDir = "load/textures/${String.format("%016X", game.titleId)}", texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"), appDir = game.path.substringBeforeLast("/").split("/").filter {
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content", it.isNotEmpty()
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content", }.joinToString("/"),
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}" dlcDir =
basePath +
"/title/0004008c/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
updatesDir =
basePath +
"/title/0004000e/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
extraDir =
basePath +
"/extdata/00000000/${String.format(
"%016X",
game.titleId
).substring(8, 14).padStart(8, '0')}"
) )
} }
@ -287,13 +333,36 @@ class GameAdapter(
.setType("*/*") .setType("*/*")
val uri = when (menuItem.itemId) { val uri = when (menuItem.itemId) {
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir) R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir) dirs.appDir
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir) )
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir) R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true) dirs.saveDir
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true) )
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
dirs.updatesDir
)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
dirs.dlcDir
)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
dirs.extraDir
)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
dirs.texturesDir,
true
)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
dirs.modsDir,
true
)
else -> null else -> null
} }
@ -307,7 +376,11 @@ class GameAdapter(
popup.show() popup.show()
} }
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) { private fun showUninstallContextMenu(
view: View,
game: Game,
bottomSheetDialog: BottomSheetDialog
) {
val dirs = getGameDirectories(game) val dirs = getGameDirectories(game)
val popup = PopupMenu(view.context, view).apply { val popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu) menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
@ -323,21 +396,45 @@ class GameAdapter(
} }
} }
val titleId = game.titleId
val dlcTitleId = titleId or 0x8C00000000L
val updateTitleId = titleId or 0xE00000000L
popup.setOnMenuItemClickListener { menuItem -> popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = { val uninstallAction: () -> Unit = {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.game_context_uninstall -> CitraApplication.documentsTree.deleteDocument(dirs.gameDir) R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
R.id.game_context_uninstall_dlc -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir) titleId,
.toString()) game.mediaType
R.id.game_context_uninstall_updates -> FileUtil.deleteDocument(CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir) )
.toString())
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) ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) { if (menuItem.itemId in
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction) listOf(
R.id.game_context_uninstall,
R.id.game_context_uninstall_dlc,
R.id.game_context_uninstall_updates
)
) {
IndeterminateProgressDialogFragment.newInstance(
activity,
R.string.uninstalling,
false,
uninstallAction
)
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG) .show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
true true
} else { } else {
@ -348,7 +445,12 @@ class GameAdapter(
popup.show() popup.show()
} }
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) { private fun showAboutGameDialog(
context: Context,
game: Game,
holder: GameViewHolder,
view: View
) {
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null) val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
val bottomSheetDialog = BottomSheetDialog(context) val bottomSheetDialog = BottomSheetDialog(context)
@ -360,12 +462,22 @@ class GameAdapter(
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId) bottomSheetView.findViewById<TextView>(R.id.about_game_id).text =
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text =
context.getString(R.string.game_context_file) + " " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text =
context.getString(R.string.game_context_type) + " " + game.fileType
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button) val insertButton = bottomSheetView.findViewById<MaterialButton>(
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) } R.id.insert_cartridge_button
)
insertButton.text =
if (inserted) {
context.getString(R.string.game_context_eject)
} else {
context.getString(R.string.game_context_insert)
}
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
insertButton.setOnClickListener { insertButton.setOnClickListener {
if (inserted) { if (inserted) {
@ -406,7 +518,7 @@ class GameAdapter(
val preferences = PreferenceManager.getDefaultSharedPreferences(context) val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Default to false for zoomed in shortcut icons // Default to false for zoomed in shortcut icons
preferences.edit() { preferences.edit {
putBoolean( putBoolean(
"shouldStretchIcon", "shouldStretchIcon",
false false
@ -438,10 +550,15 @@ class GameAdapter(
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString() val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
if (shortcutName.isEmpty()) { if (shortcutName.isEmpty()) {
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show() Toast.makeText(
context,
R.string.shortcut_name_empty,
Toast.LENGTH_LONG
).show()
return@setPositiveButton return@setPositiveButton
} }
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap val iconBitmap =
(dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
val shortcutManager = activity.getSystemService(ShortcutManager::class.java) val shortcutManager = activity.getSystemService(ShortcutManager::class.java)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@ -449,9 +566,11 @@ class GameAdapter(
val shortcut = ShortcutInfo.Builder(context, shortcutName) val shortcut = ShortcutInfo.Builder(context, shortcutName)
.setShortLabel(shortcutName) .setShortLabel(shortcutName)
.setIcon(icon) .setIcon(icon)
.setIntent(game.launchIntent.apply { .setIntent(
game.launchIntent.apply {
putExtra("launchedFromShortcut", true) putExtra("launchedFromShortcut", true)
}) }
)
.build() .build()
shortcutManager?.requestPinShortcut(shortcut, null) shortcutManager?.requestPinShortcut(shortcut, null)
@ -472,7 +591,9 @@ class GameAdapter(
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress) val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
R.id.compress_decompress
)
if (game.isInstalled) { if (game.isInstalled) {
compressDecompressButton.setOnClickListener { compressDecompressButton.setOnClickListener {
Toast.makeText( Toast.makeText(
@ -485,22 +606,90 @@ class GameAdapter(
} else { } else {
compressDecompressButton.setOnClickListener { compressDecompressButton.setOnClickListener {
val shouldCompress = !game.isCompressed val shouldCompress = !game.isCompressed
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress) val recommendedExt = NativeLibrary.getRecommendedExtension(
holder.game.path,
shouldCompress
)
val baseName = holder.game.filename.substringBeforeLast('.') val baseName = holder.game.filename.substringBeforeLast('.')
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress) onRequestCompressOrDecompress?.invoke(
holder.game.path,
"$baseName.$recommendedExt",
shouldCompress
)
bottomSheetDialog.dismiss() bottomSheetDialog.dismiss()
} }
} }
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress) compressDecompressButton.text =
context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game) showOpenContextMenu(it, game)
} }
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener { bottomSheetView.findViewById<MaterialButton>(
R.id.menu_button_uninstall
).setOnClickListener {
showUninstallContextMenu(it, game, bottomSheetDialog) 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() val bottomSheetBehavior = bottomSheetDialog.getBehavior()
bottomSheetBehavior.skipCollapsed = true bottomSheetBehavior.skipCollapsed = true
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -549,18 +738,16 @@ class GameAdapter(
} }
} }
private fun isValidGame(extension: String): Boolean { private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
return Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() } .noneMatch { extension == it.lowercase() }
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() { private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { 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 { override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
return oldItem == newItem
}
} }
} }

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -9,27 +9,24 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardHomeOptionBinding import org.citra.citra_emu.databinding.CardHomeOptionBinding
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.HomeSetting import org.citra.citra_emu.model.HomeSetting
import org.citra.citra_emu.viewmodel.GamesViewModel
class HomeSettingAdapter( class HomeSettingAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner, private val viewLifecycle: LifecycleOwner,
var options: List<HomeSetting> var options: List<HomeSetting>
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener { ) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
val binding = val binding =
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -37,9 +34,7 @@ class HomeSettingAdapter(
return HomeOptionViewHolder(binding) return HomeOptionViewHolder(binding)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int = options.size
return options.size
}
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) { override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position]) holder.bind(options[position])

View file

@ -8,18 +8,17 @@ import android.content.res.ColorStateList
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.PageSetupBinding import org.citra.citra_emu.databinding.PageSetupBinding
import org.citra.citra_emu.model.ButtonState import org.citra.citra_emu.model.ButtonState
import org.citra.citra_emu.model.PageState import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.ViewUtils import org.citra.citra_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
@ -35,7 +34,8 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position]) holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) : inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root), SetupCallback { RecyclerView.ViewHolder(binding.root),
SetupCallback {
lateinit var page: SetupPage lateinit var page: SetupPage
init { init {
@ -49,7 +49,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
onStepCompleted(0, pageFullyCompleted = true) onStepCompleted(0, pageFullyCompleted = true)
} }
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) { if (page.pageButtons != null &&
page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE
) {
for (pageButton in page.pageButtons) { for (pageButton in page.pageButtons) {
val pageButtonView = LayoutInflater.from(activity) val pageButtonView = LayoutInflater.from(activity)
.inflate( .inflate(
@ -108,9 +110,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
.alpha(0.38f) .alpha(0.38f)
.setDuration(200) .setDuration(200)
.start() .start()
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled)) button.setTextColor(
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
button.iconTint = button.iconTint =
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled)) ColorStateList.valueOf(
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
} }
} }
} }

View file

@ -1,20 +1,20 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
package org.citra.citra_emu.applets package org.citra.citra_emu.applets
import androidx.annotation.Keep import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
import java.io.Serializable
@Keep @Keep
object MiiSelector { object MiiSelector {
lateinit var data: MiiSelectorData lateinit var data: MiiSelectorData
val finishLock = Object() val finishLock = Object()
private fun ExecuteImpl(config: MiiSelectorConfig) { private fun executeImpl(config: MiiSelectorConfig) {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
data = MiiSelectorData(0, 0) data = MiiSelectorData(0, 0)
val fragment = MiiSelectorDialogFragment.newInstance(config) val fragment = MiiSelectorDialogFragment.newInstance(config)
@ -22,8 +22,8 @@ object MiiSelector {
} }
@JvmStatic @JvmStatic
fun Execute(config: MiiSelectorConfig): MiiSelectorData { fun execute(config: MiiSelectorConfig): MiiSelectorData {
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) } NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
synchronized(finishLock) { synchronized(finishLock) {
try { try {
finishLock.wait() finishLock.wait()

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -7,27 +7,27 @@ package org.citra.citra_emu.applets
import android.text.InputFilter import android.text.InputFilter
import android.text.Spanned import android.text.Spanned
import androidx.annotation.Keep import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.CitraApplication.Companion.appContext import org.citra.citra_emu.CitraApplication.Companion.appContext
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.fragments.KeyboardDialogFragment import org.citra.citra_emu.fragments.KeyboardDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.utils.Log import org.citra.citra_emu.utils.Log
import java.io.Serializable
@Keep @Keep
object SoftwareKeyboard { object SoftwareKeyboard {
lateinit var data: KeyboardData lateinit var data: KeyboardData
val finishLock = Object() val finishLock = Object()
private fun ExecuteImpl(config: KeyboardConfig) { private fun executeImpl(config: KeyboardConfig) {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
data = KeyboardData(0, "") data = KeyboardData(0, "")
KeyboardDialogFragment.newInstance(config) KeyboardDialogFragment.newInstance(config)
.show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG) .show(emulationActivity!!.supportFragmentManager, KeyboardDialogFragment.TAG)
} }
fun HandleValidationError(config: KeyboardConfig, error: ValidationError) { fun handleValidationError(config: KeyboardConfig, error: ValidationError) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()!! val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
val message: String = when (error) { val message: String = when (error) {
ValidationError.FixedLengthRequired -> emulationActivity.getString( ValidationError.FixedLengthRequired -> emulationActivity.getString(
@ -54,12 +54,12 @@ object SoftwareKeyboard {
} }
@JvmStatic @JvmStatic
fun Execute(config: KeyboardConfig): KeyboardData { fun execute(config: KeyboardConfig): KeyboardData {
if (config.buttonConfig == ButtonConfig.None) { if (config.buttonConfig == ButtonConfig.NONE) {
Log.error("Unexpected button config None") Log.error("Unexpected button config None")
return KeyboardData(0, "") return KeyboardData(0, "")
} }
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) } NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
synchronized(finishLock) { synchronized(finishLock) {
try { try {
finishLock.wait() finishLock.wait()
@ -69,8 +69,9 @@ object SoftwareKeyboard {
return data return data
} }
@Suppress("unused")
@JvmStatic @JvmStatic
fun ShowError(error: String) { fun showError(error: String) {
NativeLibrary.displayAlertMsg( NativeLibrary.displayAlertMsg(
appContext.resources.getString(R.string.software_keyboard), appContext.resources.getString(R.string.software_keyboard),
error, error,
@ -78,16 +79,19 @@ object SoftwareKeyboard {
) )
} }
@Suppress("FunctionName")
private external fun ValidateFilters(text: String): ValidationError private external fun ValidateFilters(text: String): ValidationError
@Suppress("FunctionName")
external fun ValidateInput(text: String): ValidationError external fun ValidateInput(text: String): ValidationError
// / Corresponds to Frontend::ButtonConfig // / Corresponds to Frontend::ButtonConfig
interface ButtonConfig { interface ButtonConfig {
companion object { companion object {
const val Single = 0 /// Ok button const val SINGLE = 0 // / Ok button
const val Dual = 1 /// Cancel | Ok buttons const val DUAL = 1 // / Cancel | Ok buttons
const val Triple = 2 /// Cancel | I Forgot | Ok buttons const val TRIPLE = 2 // / Cancel | I Forgot | Ok buttons
const val None = 3 /// No button (returned by swkbdInputText in special cases) const val NONE = 3 // / No button (returned by swkbdInputText in special cases)
} }
} }

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -21,9 +21,10 @@ object StillImageCameraHelper {
private var filePickerPath: String? = null private var filePickerPath: String? = null
// Opens file picker for camera. // Opens file picker for camera.
@Suppress("unused")
@Keep @Keep
@JvmStatic @JvmStatic
fun OpenFilePicker(): String? { fun openFilePicker(): String? {
val emulationActivity = NativeLibrary.sEmulationActivity.get() val emulationActivity = NativeLibrary.sEmulationActivity.get()
// At this point, we are assuming that we already have permissions as they are // At this point, we are assuming that we already have permissions as they are
@ -44,19 +45,21 @@ object StillImageCameraHelper {
// Called from EmulationActivity. // Called from EmulationActivity.
@JvmStatic @JvmStatic
fun OnFilePickerResult(result: String) { fun onFilePickerResult(result: String) {
filePickerPath = result filePickerPath = result
synchronized(filePickerLock) { filePickerLock.notifyAll() } synchronized(filePickerLock) { filePickerLock.notifyAll() }
} }
// Blocking call. Load image from file and crop/resize it to fit in width x height. // Blocking call. Load image from file and crop/resize it to fit in width x height.
@Suppress("unused")
@Keep @Keep
@JvmStatic @JvmStatic
fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? { fun loadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
val context = CitraApplication.appContext val context = CitraApplication.appContext
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
.data(uri) .data(uri)
.size(width, height) .size(width, height)
.allowHardware(false)
.build() .build()
return context.imageLoader.executeBlocking(request).drawable?.toBitmap( return context.imageLoader.executeBlocking(request).drawable?.toBitmap(
width, width,

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -9,11 +9,10 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() { class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
override fun createIntent(context: Context, input: Boolean?): Intent { override fun createIntent(context: Context, input: Boolean?): Intent =
return Intent(Intent.ACTION_OPEN_DOCUMENT) Intent(Intent.ACTION_OPEN_DOCUMENT)
.setType("application/octet-stream") .setType("application/octet-stream")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input) .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
} }

View file

@ -4,13 +4,13 @@
package org.citra.citra_emu.display package org.citra.citra_emu.display
import android.content.Context
import android.content.pm.ActivityInfo
import android.app.Activity import android.app.Activity
import android.content.Context
import android.view.WindowManager import android.view.WindowManager
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
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.IntSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
@ -19,7 +19,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
class ScreenAdjustmentUtil( class ScreenAdjustmentUtil(
private val context: Context, private val context: Context,
private val windowManager: WindowManager, private val windowManager: WindowManager,
private val settings: Settings, private val settings: Settings
) { ) {
fun swapScreen() { fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens val isEnabled = !EmulationMenuSettings.swapScreens
@ -31,11 +31,20 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
} }
fun cycleLayouts() { 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) val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) { if (NativeLibrary.isPortraitMode()) {
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
val pos = portraitValues.indexOf(currentLayout) val pos = portraitValues.indexOf(currentLayout)
val layoutOption = portraitValues[(pos + 1) % portraitValues.size] val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
@ -52,14 +61,32 @@ class ScreenAdjustmentUtil(
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
} }
fun changeScreenOrientation(layoutOption: Int) { fun changeScreenOrientation(layoutOption: Int) {
IntSetting.SCREEN_LAYOUT.int = layoutOption IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
}
fun changeSecondaryOrientation(layoutOption: Int) {
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
}
fun enableSecondaryDisplay(layoutOption: Int) {
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = true
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
changeSecondaryOrientation(layoutOption)
}
fun disableSecondaryDisplay() {
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
} }
fun changeActivityOrientation(orientationOption: Int) { fun changeActivityOrientation(orientationOption: Int) {
@ -74,7 +101,6 @@ class ScreenAdjustmentUtil(
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode) NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
} }
} }

View file

@ -13,11 +13,8 @@ enum class ScreenLayout(val int: Int) {
HYBRID_SCREEN(4), HYBRID_SCREEN(4),
CUSTOM_LAYOUT(5); CUSTOM_LAYOUT(5);
companion object { companion object {
fun from(int: Int): ScreenLayout { fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
} }
} }
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
BELOW(7); BELOW(7);
companion object { companion object {
fun from(int: Int): SmallScreenPosition { fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
} }
} }
@ -45,23 +40,27 @@ enum class PortraitScreenLayout(val int: Int) {
ORIGINAL(2); ORIGINAL(2);
companion object { companion object {
fun from(int: Int): PortraitScreenLayout { fun from(int: Int): PortraitScreenLayout =
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
} }
} }
enum class SecondaryDisplayLayout(val int: Int) { enum class SecondaryDisplayLayout(val int: Int) {
// These must match what is defined in src/common/settings.h // These must match what is defined in src/common/settings.h
// NONE is no longer selectable in the interface, having been replaced with
// the boolean ENABLE_SECONDARY_DISPLAY setting, but is left here for backwards compatibility
NONE(0), NONE(0),
TOP_SCREEN(1), TOP_SCREEN(1),
BOTTOM_SCREEN(2), BOTTOM_SCREEN(2),
SIDE_BY_SIDE(3); SIDE_BY_SIDE(3),
REVERSE_PRIMARY(4),
ORIGINAL(5),
HYBRID(6),
LARGE_SCREEN(7)
;
companion object { companion object {
fun from(int: Int): SecondaryDisplayLayout { fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
return entries.firstOrNull { it.int == int } ?: NONE
}
} }
} }
@ -74,9 +73,7 @@ enum class StereoWhichDisplay(val int: Int) {
SECONDARY_ONLY(3); SECONDARY_ONLY(3);
companion object { companion object {
fun from(int: Int): StereoWhichDisplay { fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
return entries.firstOrNull { it.int == int } ?: NONE
}
} }
} }
@ -92,8 +89,6 @@ enum class StereoMode(val int: Int) {
CARDBOARD_VR(6); CARDBOARD_VR(6);
companion object { companion object {
fun from(int: Int): StereoMode { fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
return entries.firstOrNull { it.int == int } ?: OFF
}
} }
} }

View file

@ -6,24 +6,29 @@ package org.citra.citra_emu.display
import android.app.Presentation import android.app.Presentation
import android.content.Context import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Display import android.view.Display
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceHolder import android.view.SurfaceHolder
import android.view.SurfaceView import android.view.SurfaceView
import android.view.WindowManager 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 import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.utils.Log
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener { class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
private var pres: SecondaryDisplayPresentation? = null private var pres: SecondaryDisplayPresentation? = null
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val vd: VirtualDisplay private val vd: VirtualDisplay
var preferredDisplayId = -1
var currentDisplayId = -1
val availableDisplays: List<Display>
get() = getSecondaryDisplays()
init { init {
vd = displayManager.createVirtualDisplay( vd = displayManager.createVirtualDisplay(
@ -38,21 +43,34 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
} }
fun updateSurface() { fun updateSurface() {
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface) val surface = pres?.getSurfaceHolder()?.surface
if (surface != null && surface.isValid) {
NativeLibrary.secondarySurfaceChanged(surface)
} else {
Log.warning("SecondaryDisplay Attempted to update null or invalid surface")
}
} }
fun destroySurface() { fun destroySurface() {
NativeLibrary.secondarySurfaceDestroyed() NativeLibrary.secondarySurfaceDestroyed()
} }
private fun getExternalDisplay(context: Context): Display? { private fun getSecondaryDisplays(): List<Display> {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = context.display.displayId val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display.displayId
} else {
@Suppress("DEPRECATION")
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.displayId
}
val displays = dm.displays val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return displays.firstOrNull { return displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId } val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable val isNotDefaultOrPresentable =
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
isNotDefaultOrPresentable && isNotDefaultOrPresentable &&
it.displayId != currentDisplayId && it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" && it.name != "HiddenDisplay" &&
@ -62,24 +80,61 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
} }
fun updateDisplay() { fun updateDisplay() {
// decide if we are going to the external display or the internal one // return early if the parent context is dead or dying
var display = getExternalDisplay(context) if (context is android.app.Activity && (context.isFinishing || context.isDestroyed)) {
if (display == null || return
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) { }
display = vd.display
val displayToUse = if (availableDisplays.isEmpty() ||
// Theoretically, the NONE option is no longer selectable, but
// I am leaving this in for backwards compatibility
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
) {
currentDisplayId = -1
vd.display
} else if (preferredDisplayId >= 0 &&
availableDisplays.any { it.displayId == preferredDisplayId }
) {
currentDisplayId = preferredDisplayId
availableDisplays.first { it.displayId == preferredDisplayId }
} else {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val default = dm.displays.first { it.displayId == Display.DEFAULT_DISPLAY }
// prioritize displays that have a different name from the default display, as
// some devices such as the Odin 2 create a permanent virtual display with the same
// name as the default display that should be skipped in most cases
currentDisplayId = availableDisplays.firstOrNull {
it.name != default.name && !it.name.contains("Built", true)
}?.displayId
?: availableDisplays[0].displayId
availableDisplays.first { it.displayId == currentDisplayId }
} }
// if our presentation is already on the right display, ignore // if our presentation is already on the right display, ignore
if (pres?.display == display) return if (pres?.display == displayToUse) return
// otherwise, make a new presentation // otherwise, make a new presentation
releasePresentation() releasePresentation()
pres = SecondaryDisplayPresentation(context, display!!, this)
try {
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
pres?.show() 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() { fun releasePresentation() {
try {
pres?.dismiss() pres?.dismiss()
} catch (_: Exception) { }
pres = null pres = null
} }
@ -100,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
} }
} }
class SecondaryDisplayPresentation( class SecondaryDisplayPresentation(
context: Context, display: Display, val parent: SecondaryDisplay context: Context,
display: Display,
val parent: SecondaryDisplay
) : Presentation(context, display) { ) : Presentation(context, display) {
private lateinit var surfaceView: SurfaceView private lateinit var surfaceView: SurfaceView
private var touchscreenPointerId = -1 private var touchscreenPointerId = -1
@ -118,16 +175,21 @@ class SecondaryDisplayPresentation(
surfaceView = SurfaceView(context) surfaceView = SurfaceView(context)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback { surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) { override fun surfaceCreated(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface created")
} }
override fun surfaceChanged( override fun surfaceChanged(
holder: SurfaceHolder, format: Int, width: Int, height: Int holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) { ) {
Log.debug("SecondaryDisplay Surface changed: ${width}x$height")
parent.updateSurface() parent.updateSurface()
} }
override fun surfaceDestroyed(holder: SurfaceHolder) { override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface destroyed")
parent.destroySurface() parent.destroySurface()
} }
}) })
@ -172,7 +234,5 @@ class SecondaryDisplayPresentation(
} }
// Publicly accessible method to get the SurfaceHolder // Publicly accessible method to get the SurfaceHolder
fun getSurfaceHolder(): SurfaceHolder { fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
return surfaceView.holder
}
} }

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
private var selectedCheatPosition = -1 private var selectedCheatPosition = -1
fun initialize(titleId_: Long) { fun initialize(titleId_: Long) {
titleId = titleId_; titleId = titleId_
load() load()
} }

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -170,8 +170,7 @@ class CheatDetailsFragment : Fragment() {
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
} }
private fun setInsets() = private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
ViewCompat.setOnApplyWindowInsetsListener(
binding.root binding.root
) { _: View?, windowInsets: WindowInsetsCompat -> ) { _: View?, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -41,7 +41,8 @@ class CheatsAdapter(
} }
inner class CheatViewHolder(private val binding: ListItemCheatBinding) : inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
RecyclerView.ViewHolder(binding.root), View.OnClickListener, RecyclerView.ViewHolder(binding.root),
View.OnClickListener,
CompoundButton.OnCheckedChangeListener { CompoundButton.OnCheckedChangeListener {
private lateinit var viewModel: CheatsViewModel private lateinit var viewModel: CheatsViewModel
private lateinit var cheat: Cheat private lateinit var cheat: Cheat

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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -32,7 +32,9 @@ import org.citra.citra_emu.ui.TwoPaneOnBackPressedCallback
import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.viewmodel.HomeViewModel import org.citra.citra_emu.viewmodel.HomeViewModel
class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener { class CheatsFragment :
Fragment(),
SlidingPaneLayout.PanelSlideListener {
private var cheatListLastFocus: View? = null private var cheatListLastFocus: View? = null
private var cheatDetailsLastFocus: View? = null private var cheatDetailsLastFocus: View? = null
@ -157,12 +159,15 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
} }
private fun onSelectedCheatChanged(selectedCheat: Cheat?) { private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value!! val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value
if (!cheatSelected && binding.slidingPaneLayout.isOpen) { if (!cheatSelected && binding.slidingPaneLayout.isOpen) {
binding.slidingPaneLayout.close() binding.slidingPaneLayout.close()
} }
binding.slidingPaneLayout.lockMode = binding.slidingPaneLayout.lockMode = if (cheatSelected) {
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED SlidingPaneLayout.LOCK_MODE_UNLOCKED
} else {
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
}
} }
fun onListViewFocusChange(hasFocus: Boolean) { fun onListViewFocusChange(hasFocus: Boolean) {
@ -203,7 +208,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
// Set keyboard insets if the system supports smooth keyboard animations // Set keyboard insets if the system supports smooth keyboard animations
val mlpDetails = binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams val mlpDetails = binding.cheatDetailsContainer.layoutParams
as ViewGroup.MarginLayoutParams
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (keyboardInsets.bottom > 0) { if (keyboardInsets.bottom > 0) {
mlpDetails.bottomMargin = keyboardInsets.bottom mlpDetails.bottomMargin = keyboardInsets.bottom
@ -231,14 +237,16 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
runningAnimations: List<WindowInsetsAnimationCompat> runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat { ): WindowInsetsCompat {
val mlpDetails = val mlpDetails =
binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams binding.cheatDetailsContainer.layoutParams
as ViewGroup.MarginLayoutParams
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets) mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
binding.cheatDetailsContainer.layoutParams = mlpDetails binding.cheatDetailsContainer.layoutParams = mlpDetails
return insets return insets
} }
}) }
)
} }
} }
} }

View file

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

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