Compare commits

...

77 Commits

Author SHA1 Message Date
Ryan McClelland
0c19c33bd3
libretro: scope Vulkan case to fix MSVC C2360 (#2267)
MSVC with /permissive- /WX rejects the switch in retro_load_game:
the static vk_negotiation initializer in the Vulkan case is crossed
by the next case label (C2360, initialization skipped by case). Wrap
the Vulkan case body in a block so its scope ends before that label,
and add a comment noting why the braces are required there. The other
cases are wrapped in blocks too for visual consistency, though only
the Vulkan case strictly needs it. GCC/clang and the MXE cross-build
accept the original; this only affects native MSVC builds. No
behaviour change.
2026-07-09 14:44:37 +02:00
PabloMK7
bda4671be6
core: gsp: General improvements to GSP service accuracy (#2273) 2026-07-09 14:19:36 +02:00
PabloMK7
c2dd5f3bea
core: Change default value of simulate GPU timings to false (#2274) 2026-07-09 13:58:07 +02:00
PabloMK7
31e4ecea58 core: apt: Restore captured framebuffers on libapplet closure 2026-07-09 09:07:02 +02:00
PabloMK7
49ba2e8d5f core: apt: Implement GetAppletProgramInfo 2026-07-09 09:07:02 +02:00
PabloMK7
b64040b5fa
video_core: Fix fill surface downloading (#2270) 2026-07-08 23:44:03 +02:00
ApfelTeeSaft
64a967b4a8
core/arm: model ARM11 MPCore timing per TRM (#2262)
* feat: make per instruction cycle table optional

makes the nov 2022 change regarding the instruction cycle table thing toggleable inside Debug->CPU Settings.

* core/arm: model ARM11 MPCore timing per TRM

This Commit also reverts my initial "reimplementation / revertion" of the legacy "1 tick : 1 instruction" cpu cycling.

- Fix a bug related to the Get<field> helper. It performed a parallel bit deposit where an extract was required

- Branch prediction stuff

- Charge now loads/stores their 1-cycle issue cost instead of flat interlock penalty

Add VFP11 costs (was VFP -> decay to 1 cycle generic entry)

- Implement Thumb dispatch

* Revert accidental push of diagnosig logic.

* Fix license and format

---------

Co-authored-by: PabloMK7 <hackyglitch2@gmail.com>
2026-07-08 20:23:22 +02:00
OpenSauce04
7e976e852c android: Flag AndroidLintUseKtx lint as style suggestion instead of warning 2026-07-08 15:24:01 +01:00
PabloMK7
ad0dde0725
common: Detect and print AppData folder get failures (#2259) 2026-07-05 00:00:57 +02:00
PabloMK7
d61801ad76
qt: gdb: Do not change GDB settings when the gdbport arg is used (#2257) 2026-07-05 00:00:32 +02:00
OpenSauce04
9a0f0e3caf citra_qt.cpp: Deduplicate 3DS system file setup dialog strings 2026-07-04 18:43:23 +01:00
OpenSauce04
4f63caf5fc android: Fix misplaced Inappropriate Scale messages + Fix crash with inappropriate values 2026-07-04 18:35:18 +01:00
OpenSauce04
2a39345e4b android: Fix Turbo button not appearing in Adjust Scale menu 2026-07-04 18:35:18 +01:00
ADAS2024
209173fa0c
[Android] Add Combo Button (#1244)
* Base starting commit for Feature Impl

* Base commit 2 (off of closed PR, impl details of the actual button will likely be different.)

* Added Combo Button Bool setting to use for later implementation.

* Added Combo Button Submenu and associated strings. Need to implement button displaying for user to interact with.

* Added layout file for combo settings for potential impl

* Removed combo settings as it wasn't liked. Starting Multi Choice impl

* Added Multi Choice Impl. Need to Refactor OnClick for MultiChoice and debug Impl when adding combo button.

* Change List<> in Multi Files to be MutableSet<> to allow adds while prevent potential duplicates. Finished primary multi choice backend impl.

* used correct lisencing procedure for multi files

* Added code in arrays.xml to display the buttons.
This code has the buttons display but clicking on them crashes the app. Will debug further.
Also replaced setSelectedValues with add and remove SelectedValue for clarity purposes.

* Deleted Int Combo Value array in arrays.xml as it was not needed. Able to Click multiple buttons at once but unchecking them causes app to crash. (within the MultiChoice OnClick for SettingsAdapter.kt)

Also debating whether to remove the normal MultiChoice addition as it wasn't added.

* Attempt to deal with merge conflicts

* Moved OnSettingChange after I add or remove selected vals to fix crashing issue.

* Displayed combo button in both landscape and portrait

* WIP on Button Impl, made combo button setting run time editable

* Changed the combo_key string into button_combo, added functionality (impl could be better), added combo key as an input bindable setting within the buttons tab

* Moved Combo Settings into just controls instead of having its own submenu

* Made ComboHelper code more readable and added the key setting to be toggleable in game

* Edited Layout integers of combo button. Made Combo Button display properly in normal portrait mode.

* Code Cleanup and Start of Reimplementation based off #1430

* Progress commit for better impl, need to have the pop up menu show up to select buttons

* Code cleanup and bug fix to properly map values.

* Deleted Multi String Setting, added basic error handling, added comment explaining hardcoded size

* Changed Combo Button to be a HotKey, changed impl to be default multi choice, and removed StringMultiChoice details

* Remove Commented Code

* Changed ComboHelper.kt to use Native Button ints, removed rest of initial implementation traces

* Commented out debug logging, added back missing TODO statement regarding magic numbers

* Cleanup

* Changed Combo Key to Combo Button in kotlin code (will need to test)

* Trying to integrate combo button as a SettingsKey setting. Running into issues with malformed/missing key

* latest changes (added setting to default ini). Still debugging file issue

* added possible fix to touchscreen latency issue, will need to test

* Reverted MultiChoice code to original, removed unused strings and variables and ensured consistent naming, fixed latency issues with button,

* reverted int list setting change as its no longer needed.

* `combo_buttons` --> `combo_button_buttons`

* default_ini.h: Adjust ini comment for combo_button_buttons

* Fix "Reset Overlay" not affecting combo button

* Better center default position of combo button in portrait layout

* `combo_button_options` --> `combo_button_settings`

Better reflects string content

* Last-minute minor string nitpicks

---------

Co-authored-by: OpenSauce <opensauce04@gmail.com>
2026-07-04 15:11:06 +01:00
KojoZero
8c4e8b77b2
Add Skip Presenting Duplicate Frames Feature (#1867)
* added basic skip duplicate frames implementation

* fix secondary display and attempt to fix lcd color fill
2026-07-03 12:37:27 +02:00
PabloMK7
fcb10fb899
core: Make PICA debugging a toggleable feature (#2254) 2026-07-03 11:00:56 +02:00
PabloMK7
77cb5cecd8 Remove unintended leftover scripts 2026-07-02 21:40:19 +02:00
Malachi
6b197e2c04
renderer_vulkan: Fix screenshot color channels on RGBA swapchain formats (#2170) 2026-07-02 09:40:33 +02:00
Filipe Fortunato
384913be70
video_core: vulkan: Fix crash on color fill before framebuffer setup (#2241) 2026-07-01 20:32:38 +02:00
Andrey Kurlin
307f558d28
Refresh game list after uninstalling installed titles (#2237)
Signed-off-by: Andrey Kurlin <superkurlin2013@yandex.ru>
2026-07-01 20:20:56 +02:00
Wunk
d93adebb16
renderer_gl: Fix OpenGL texture unit state tracking (#2242)
* video_core: Implement texture-layer clears

Allow specific texture-layers to be targetted for clears rather than just defaulting to the first layer, or clearing all layers after in the case of Vulkan. Allows specific faces of a cubemap to be cleared.

* video_core: Initialize `NULL_SURFACE_CUBE_ID`

Properly initialize the `NULL_SURFACE_CUBE_ID` texture to ensure that all 6 of its faces are black-transparent.

* renderer_gl: Add explicit texture-unit target state tracking

The `.target` field can be either a `GL_TEXTURE_2D` or a
`GL_TEXTURE_CUBE_MAP` and must be explicitly set when assigning the
`.texture_2d` or there is a risk of leaking a previous texture state
into the current one in the case that a texture-cube was binded into
slot 0 and it becomes a texture-2d sometime after.
2026-07-01 17:25:03 +02:00
OpenSauce04
7c225c6abb qt: Fix macOS freeze-on-boot caused by unprocessed events
As far as I can tell the line added here really shouldn't be necessary and wasn't until a relatively recent change in either Qt or macOS. The exact reason behind the original issue is unclear, but by educated guess it could have been caused by some kind of race condition within Qt?
2026-06-29 14:34:22 +01:00
asyync1024
93143bcb15
audio_core: Add '#include <cstring>' to cubeb_sink and cubeb_input.cpp (#2232)
* audio_core: Add '#include <cstring>' to cubeb_sink and cubeb_input.cpp

This fixes a build failure encountered when compiling: https://pastebin.com/JQ4mbhyu

* Fix: License header
2026-06-24 20:54:06 +02:00
PabloMK7
32fd435928 core: Add HLE HTTP URL replacement functionality 2026-06-24 17:43:04 +02:00
PabloMK7
1d59c9a0fc core: Fix HTTPC behaviour when post data is pending 2026-06-24 17:43:04 +02:00
PabloMK7
9293767e1d
dynarmic: Update to latest commit (#2236) 2026-06-24 16:11:31 +02:00
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
296 changed files with 12509 additions and 9984 deletions

View File

@ -12,9 +12,9 @@ fi
echo "Tag name is: $TAG_NAME"
docker build -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
docker build --no-cache -f docker/azahar-room/Dockerfile -t azahar-room:$TAG_NAME .
mkdir -p build
FILENAME="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_OSX_ARCHITECTURES=arm64 \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
ninja
ccache -s -v

View File

@ -25,9 +25,8 @@ cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \
-DENABLE_DISCORD_RPC=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
strip -s bin/Release/*

View File

@ -8,9 +8,8 @@ mkdir -p build/$BUILD_ARCH && cd build/$BUILD_ARCH
cmake ../.. -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="$BUILD_ARCH" \
-DENABLE_QT_TRANSLATION=ON \
-DENABLE_ROOM_STANDALONE=OFF \
-DUSE_DISCORD_PRESENCE=ON \
-DENABLE_DISCORD_RPC=ON \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
ninja bundle

View File

@ -14,8 +14,7 @@ x86_64-w64-mingw32.shared-cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_DISCORD_PRESENCE=ON \
-DENABLE_DISCORD_RPC=ON \
-DUSE_SYSTEM_BOOST=ON \
-DUSE_SYSTEM_CRYPTOPP=ON \
"${EXTRA_CMAKE_FLAGS[@]}"

View File

@ -6,7 +6,7 @@ gcc -v
tx --version
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
cd ..

View File

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

View File

@ -17,7 +17,7 @@ jobs:
if: ${{ !github.head_ref }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Pack
@ -31,7 +31,7 @@ jobs:
output-file: artifacts/source.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: source
path: artifacts/
@ -61,13 +61,13 @@ jobs:
SHOULD_RUN: ${{ (matrix.target != 'appimage-wayland' || github.ref_type == 'tag') }}
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
@ -95,7 +95,7 @@ jobs:
upload-artifact: false
- name: Upload
if: ${{ contains(matrix.target, 'appimage') && env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ github.job }}-${{ matrix.target }}
path: artifacts/
@ -123,11 +123,11 @@ jobs:
OS: linux
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ github.job }}-${{ matrix.target }}-${{ github.sha }}
@ -145,12 +145,12 @@ jobs:
CACHE_ENABLED: ${{ github.ref_type != 'tag' }}
OS: macos
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ github.sha }}
@ -183,7 +183,7 @@ jobs:
output-file: artifacts/macos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}
path: artifacts/
@ -222,12 +222,12 @@ jobs:
OS: windows
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up cache
if: ${{ env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -235,7 +235,7 @@ jobs:
${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC
if: ${{ matrix.target == 'msvc' }}
uses: ilammy/msvc-dev-cmd@v1
uses: azahar-emu/msvc-dev-cmd@v1
- name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget
@ -256,7 +256,7 @@ jobs:
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v3
uses: crazy-max/ghaction-chocolatey@v4
with:
args: install ptime wget
- name: Install NSIS
@ -300,7 +300,7 @@ jobs:
output-file: artifacts/windows-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
@ -328,13 +328,13 @@ jobs:
TARGET: ${{ matrix.target }}
SHOULD_RUN: ${{ (matrix.target == 'vanilla' || github.ref_type == 'tag') }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ env.SHOULD_RUN == 'true' }}
with:
submodules: recursive
- name: Set up cache
if: ${{ env.SHOULD_RUN == 'true' && env.CACHE_ENABLED == 'true' }}
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
~/.gradle/caches
@ -382,7 +382,7 @@ jobs:
upload-artifact: false
- name: Upload
if: ${{ env.SHOULD_RUN == 'true' }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/
@ -396,15 +396,24 @@ jobs:
sbom-path: src/android/app/artifacts/android-${{ matrix.target }}.spdx.json
docker:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64
os: ubuntu-24.04
- target: arm64
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
container:
image: docker:dind
# Can't use docker:dind for ARM64 because it's Alpine-based, see https://github.com/actions/upload-artifact/issues/739
image: earthbuild/dind:ubuntu-24.04-docker-28.5.2-1
env:
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
run: apk add bash
- name: Fix git ownership
run: git config --global --add safe.directory .
- name: Build Docker image
@ -422,9 +431,9 @@ jobs:
output-file: artifacts/docker-room.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: docker
name: docker-${{ env.TARGET }}
path: artifacts/
- name: Attest artifacts
if: ${{ github.ref_type == 'tag' }}

View File

@ -20,7 +20,7 @@ jobs:
(github.event.pull_request.author_association != 'OWNER')
steps:
- name: Detect PR if author is first-time contributor
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;

View File

@ -14,7 +14,7 @@ jobs:
if: github.event.issue.pull_request && contains(github.event.issue.labels.*.name, 'needs verification')
steps:
- name: Verify and reopen PR
uses: actions/github-script@v7
uses: actions/github-script@v9
with:
script: |
const { owner, repo } = context.repo;

View File

@ -13,10 +13,22 @@ jobs:
image: opensauce04/azahar-build-environment:latest
options: -u 1001
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Build
env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
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

View File

@ -17,7 +17,7 @@ permissions:
attestations: write
jobs:
android:
libretro-android:
runs-on: ubuntu-22.04
env:
OS: android
@ -28,7 +28,7 @@ jobs:
BUILD_DIR: build/android-arm64-v8a
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set tag name
@ -62,7 +62,7 @@ jobs:
output-file: libretro-android.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
@ -76,7 +76,7 @@ jobs:
./*.zip
sbom-path: libretro-android.spdx.json
linux:
libretro-linux:
runs-on: ubuntu-22.04
env:
OS: linux
@ -85,7 +85,7 @@ jobs:
EXTRA_PATH: bin/Release
EXTRA_CORE_ARGS: -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DENABLE_LTO=OFF
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
@ -108,7 +108,7 @@ jobs:
output-file: libretro-linux.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
@ -122,7 +122,7 @@ jobs:
./*.zip
sbom-path: libretro-linux.spdx.json
windows:
libretro-windows:
runs-on: ubuntu-latest
env:
OS: windows
@ -133,7 +133,7 @@ jobs:
IMAGE: reallibretroretroarch/libretro-build-mxe-win-cross-cores:mingw12
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build in cross-container
@ -158,7 +158,7 @@ jobs:
output-file: libretro-windows.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
@ -171,9 +171,10 @@ jobs:
subject-path: |
./*.zip
sbom-path: libretro-windows.spdx.json
macos:
libretro-macos:
runs-on: macos-26
strategy:
fail-fast: false
matrix:
target: ["x86_64", "arm64"]
env:
@ -183,7 +184,7 @@ jobs:
BUILD_DIR: build/osx-${{ matrix.target }}
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install tools
@ -204,7 +205,7 @@ jobs:
output-file: libretro-macos-${{ matrix.target }}.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
@ -218,7 +219,7 @@ jobs:
./*.zip
sbom-path: libretro-macos-${{ matrix.target }}.spdx.json
ios:
libretro-ios:
runs-on: macos-26
env:
OS: ios
@ -227,7 +228,7 @@ jobs:
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@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@ -246,7 +247,7 @@ jobs:
output-file: libretro-ios.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |
@ -260,7 +261,7 @@ jobs:
./*.zip
sbom-path: libretro-ios.spdx.json
tvos:
libretro-tvos:
runs-on: macos-26
env:
OS: tvos
@ -269,7 +270,7 @@ jobs:
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@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@ -288,7 +289,7 @@ jobs:
output-file: libretro-tvos.spdx.json
upload-artifact: false
- name: Upload
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: |

View File

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

View File

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

View File

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

3
.gitignore vendored
View File

@ -17,7 +17,8 @@ src/common/scm_rev.cpp
*.swp
*.kdev4
.markdown-preview.html
.idea/
.idea/*
!.idea/inspectionProfiles
.vs/
.vscode/
.cache/

9
.gitmodules vendored
View File

@ -55,12 +55,6 @@
[submodule "sdl2"]
path = externals/sdl2/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"]
path = externals/dds-ktx
url = https://github.com/septag/dds-ktx
@ -109,3 +103,6 @@
[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

View File

@ -121,7 +121,7 @@ option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OF
# Set bundled qt as dependent options.
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)
CMAKE_DEPENDENT_OPTION(ENABLE_TESTS "Enable generating tests executable" ON "NOT IOS" OFF)
@ -142,7 +142,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_OPENGL "Enables the OpenGL renderer" ${DEFAULT_ENA
# 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)
@ -411,13 +411,21 @@ if (APPLE)
endif()
find_library(AVFOUNDATION_LIBRARY AVFoundation 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 AND NOT ENABLE_LIBRETRO)
if (NOT USE_SYSTEM_MOLTENVK)
download_moltenvk()
endif()
if (USE_SYSTEM_MOLTENVK)
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}.")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${MOLTENVK_LIBRARY})
endif()

View File

@ -171,27 +171,16 @@ function(download_qt target)
endfunction()
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")
if (NOT EXISTS ${MOLTENVK_DIR})
if (NOT EXISTS "${CMAKE_BINARY_DIR}/externals/MoltenVK")
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)
endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
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()
function(get_external_prefix lib_name prefix_var)

View File

@ -32,6 +32,7 @@ foreach(KEY IN ITEMS
"physical_device"
"use_gles"
"renderer_debug"
"pica_debugging"
"dump_command_buffers"
"spirv_shader_gen"
"disable_spirv_optimizer"
@ -41,6 +42,7 @@ foreach(KEY IN ITEMS
"use_disk_shader_cache"
"shaders_accurate_mul"
"use_vsync"
"use_skip_duplicate_frames"
"use_display_refresh_rate_detection"
"use_shader_jit"
"resolution_factor"
@ -238,6 +240,8 @@ if (ANDROID)
"android_hide_images"
"screen_orientation"
"performance_overlay_position"
"enable_secondary_display"
"combo_button_buttons"
)
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")

View File

@ -1,299 +0,0 @@
set PREVIEW_MODE "Key Dumper\n \nWorking..."
# Boot9
set BOOT9_BIN "M:/boot9.bin"
fget $[BOOT9_BIN]@D9D0:10 KEYX_2C
set KEYX_2D $[KEYX_2C]
set KEYX_2E $[KEYX_2C]
set KEYX_2F $[KEYX_2C]
fget $[BOOT9_BIN]@D9E0:10 KEYX_30
set KEYX_31 $[KEYX_30]
set KEYX_32 $[KEYX_30]
set KEYX_33 $[KEYX_30]
fget $[BOOT9_BIN]@D9F0:10 KEYX_34
set KEYX_35 $[KEYX_34]
set KEYX_36 $[KEYX_34]
set KEYX_37 $[KEYX_34]
fget $[BOOT9_BIN]@DA00:10 KEYX_38
set KEYX_39 $[KEYX_38]
set KEYX_3A $[KEYX_38]
set KEYX_3B $[KEYX_38]
fget $[BOOT9_BIN]@DA10:10 KEYX_3C
fget $[BOOT9_BIN]@DA20:10 KEYX_3D
fget $[BOOT9_BIN]@DA30:10 KEYX_3E
fget $[BOOT9_BIN]@DA40:10 KEYX_3F
fget $[BOOT9_BIN]@DA50:10 KEYY_04
fget $[BOOT9_BIN]@DA60:10 KEYY_05
fget $[BOOT9_BIN]@DA70:10 KEYY_06
fget $[BOOT9_BIN]@DA80:10 KEYY_07
fget $[BOOT9_BIN]@DA90:10 KEYY_08
fget $[BOOT9_BIN]@DAA0:10 KEYY_09
fget $[BOOT9_BIN]@DAB0:10 KEYY_0A
fget $[BOOT9_BIN]@DAC0:10 KEYY_0B
fget $[BOOT9_BIN]@DAD0:10 KEYN_0D
fget $[BOOT9_BIN]@DBA0:10 KEYN_2D
fget $[BOOT9_BIN]@DBB0:10 KEYN_32
fget $[BOOT9_BIN]@DBC0:10 KEYN_36
fget $[BOOT9_BIN]@DBD0:10 KEYN_38
# NATIVE_FIRM
if chk $[ONTYPE] "N3DS"
if not find 1:/title/00040138/20000002/content/????????.app NATIVE_FIRM_APP
echo "New 3DS NATIVE_FIRM not found."
goto Exit
end
set EXPECTED_NATIVE_FIRM_VER "1F00"
set KEYY_2E_OFFSET "66504"
set KEYY_39_NFC_OFFSET "66524"
set COMMON_0_OFFSET "6CE51"
set COMMON_1_OFFSET "6CE65"
set COMMON_2_OFFSET "6CE79"
set COMMON_3_OFFSET "6CE8D"
set COMMON_4_OFFSET "6CEA1"
set COMMON_5_OFFSET "6CEB5"
else
if not find 1:/title/00040138/00000002/content/????????.app NATIVE_FIRM_APP
echo "Old 3DS NATIVE_FIRM not found."
goto Exit
end
set EXPECTED_NATIVE_FIRM_VER "1F00"
set KEYY_2E_OFFSET "66488"
set KEYY_39_NFC_OFFSET "664A8"
set COMMON_0_OFFSET "6CDC1"
set COMMON_1_OFFSET "6CDD5"
set COMMON_2_OFFSET "6CDE9"
set COMMON_3_OFFSET "6CDFD"
set COMMON_4_OFFSET "6CE11"
set COMMON_5_OFFSET "6CE25"
end
set NATIVE_FIRM_EXTHEADER "G:/extheader.bin"
set NATIVE_FIRM_ENCRYPTED "G:/exefs/.firm"
set NATIVE_FIRM_DECRYPTED "$[GM9OUT]/native.firm"
set PROCESS9_CODE "G:/0004013000003000.Process9/exefs/.code"
imgmount $[NATIVE_FIRM_APP]
fget $[NATIVE_FIRM_EXTHEADER]@E:2 NATIVE_FIRM_VER
if not chk $[NATIVE_FIRM_VER] $[EXPECTED_NATIVE_FIRM_VER]
echo "Unsupported NATIVE_FIRM version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NATIVE_FIRM_VER], got $[NATIVE_FIRM_VER]"
goto Exit
end
cp -w $[NATIVE_FIRM_ENCRYPTED] $[NATIVE_FIRM_DECRYPTED]
decrypt $[NATIVE_FIRM_DECRYPTED]
imgumount
imgmount $[NATIVE_FIRM_DECRYPTED]
fget $[PROCESS9_CODE]@$[KEYY_2E_OFFSET]:10 KEYY_2E
set KEYY_31 $[KEYY_2E]
set KEYY_39_DLP $[KEYY_2E]
fget $[PROCESS9_CODE]@$[KEYY_39_NFC_OFFSET]:10 KEYY_39_NFC
fget $[PROCESS9_CODE]@$[COMMON_0_OFFSET]:10 COMMON_0
fget $[PROCESS9_CODE]@$[COMMON_1_OFFSET]:10 COMMON_1
fget $[PROCESS9_CODE]@$[COMMON_2_OFFSET]:10 COMMON_2
fget $[PROCESS9_CODE]@$[COMMON_3_OFFSET]:10 COMMON_3
fget $[PROCESS9_CODE]@$[COMMON_4_OFFSET]:10 COMMON_4
fget $[PROCESS9_CODE]@$[COMMON_5_OFFSET]:10 COMMON_5
imgumount
rm -o -s $[NATIVE_FIRM_DECRYPTED]
# NFC
if chk $[ONTYPE] "N3DS"
if not find 1:/title/00040130/20004002/content/????????.app NFC_APP
echo "New 3DS NFC not found."
goto Exit
end
set EXPECTED_NFC_VER "0700"
set NFC_PHRASE_0_OFFSET "355EE"
set NFC_SEED_0_OFFSET "355FC"
set NFC_HMAC_KEY_0_OFFSET "3560A"
set NFC_PHRASE_1_OFFSET "3561A"
set NFC_SEED_1_OFFSET "35628"
set NFC_HMAC_KEY_1_OFFSET "35638"
set NFC_IV_OFFSET "35648"
else
if not find 1:/title/00040130/00004002/content/????????.app NFC_APP
echo "Old 3DS NFC not found."
goto Exit
end
set EXPECTED_NFC_VER "0800"
set NFC_PHRASE_0_OFFSET "17382"
set NFC_SEED_0_OFFSET "17390"
set NFC_HMAC_KEY_0_OFFSET "1739E"
set NFC_PHRASE_1_OFFSET "173AE"
set NFC_SEED_1_OFFSET "173BC"
set NFC_HMAC_KEY_1_OFFSET "173CC"
set NFC_IV_OFFSET "173DC"
end
set NFC_EXTHEADER "G:/extheader.bin"
set NFC_CODE "$[GM9OUT]/nfc_code.bin"
imgmount $[NFC_APP]
fget $[NFC_EXTHEADER]@E:2 NFC_VER
if not chk $[NFC_VER] $[EXPECTED_NFC_VER]
echo "Unsupported NFC module version.\nThis script requires the latest 3DS firmware.\n\nExpected $[EXPECTED_NFC_VER], got $[NFC_VER]"
goto Exit
end
imgumount
extrcode $[NFC_APP] $[NFC_CODE]
fget $[NFC_CODE]@$[NFC_PHRASE_0_OFFSET]:E NFC_PHRASE_0
fget $[NFC_CODE]@$[NFC_SEED_0_OFFSET]:E NFC_SEED_0
fget $[NFC_CODE]@$[NFC_HMAC_KEY_0_OFFSET]:10 NFC_HMAC_KEY_0
fget $[NFC_CODE]@$[NFC_PHRASE_1_OFFSET]:E NFC_PHRASE_1
fget $[NFC_CODE]@$[NFC_SEED_1_OFFSET]:10 NFC_SEED_1
fget $[NFC_CODE]@$[NFC_HMAC_KEY_1_OFFSET]:10 NFC_HMAC_KEY_1
fget $[NFC_CODE]@$[NFC_IV_OFFSET]:10 NFC_IV
rm -o -s $[NFC_CODE]
# GodMode9 Key Database
set KEY_DB "V:/aeskeydb.bin"
set KEY_DB_18X "K:/slot0x18KeyX.ret.bin"
set KEY_DB_19X "K:/slot0x19KeyX.ret.bin"
set KEY_DB_1AX "K:/slot0x1AKeyX.ret.bin"
set KEY_DB_1BX "K:/slot0x1BKeyX.ret.bin"
set KEY_DB_1CX "K:/slot0x1CKeyX.ret.bin"
set KEY_DB_1DX "K:/slot0x1DKeyX.ret.bin"
set KEY_DB_1EX "K:/slot0x1EKeyX.ret.bin"
set KEY_DB_1FX "K:/slot0x1FKeyX.ret.bin"
set KEY_DB_25X "K:/slot0x25KeyX.ret.bin"
set KEY_DB_24Y "K:/slot0x24KeyY.bin"
set KEY_DB_2FY "K:/slot0x2FKeyY.ret.bin"
imgmount $[KEY_DB]
fget $[KEY_DB_18X]@0:10 KEYX_18
fget $[KEY_DB_19X]@0:10 KEYX_19
fget $[KEY_DB_1AX]@0:10 KEYX_1A
fget $[KEY_DB_1BX]@0:10 KEYX_1B
fget $[KEY_DB_1CX]@0:10 KEYX_1C
fget $[KEY_DB_1DX]@0:10 KEYX_1D
fget $[KEY_DB_1EX]@0:10 KEYX_1E
fget $[KEY_DB_1FX]@0:10 KEYX_1F
fget $[KEY_DB_25X]@0:10 KEYX_25
fget $[KEY_DB_24Y]@0:10 KEYY_24
fget $[KEY_DB_2FY]@0:10 KEYY_2F
imgumount
# Write Keys To File
set OUT "0:/gm9/aes_keys.txt"
dumptxt $[OUT] "# KeyX"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x18KeyX=$[KEYX_18]"
dumptxt -p $[OUT] "slot0x19KeyX=$[KEYX_19]"
dumptxt -p $[OUT] "slot0x1AKeyX=$[KEYX_1A]"
dumptxt -p $[OUT] "slot0x1BKeyX=$[KEYX_1B]"
dumptxt -p $[OUT] "slot0x1CKeyX=$[KEYX_1C]"
dumptxt -p $[OUT] "slot0x1DKeyX=$[KEYX_1D]"
dumptxt -p $[OUT] "slot0x1EKeyX=$[KEYX_1E]"
dumptxt -p $[OUT] "slot0x1FKeyX=$[KEYX_1F]"
dumptxt -p $[OUT] "slot0x25KeyX=$[KEYX_25]"
dumptxt -p $[OUT] "slot0x2CKeyX=$[KEYX_2C]"
dumptxt -p $[OUT] "slot0x2DKeyX=$[KEYX_2D]"
dumptxt -p $[OUT] "slot0x2EKeyX=$[KEYX_2E]"
dumptxt -p $[OUT] "slot0x2FKeyX=$[KEYX_2F]"
dumptxt -p $[OUT] "slot0x30KeyX=$[KEYX_30]"
dumptxt -p $[OUT] "slot0x31KeyX=$[KEYX_31]"
dumptxt -p $[OUT] "slot0x32KeyX=$[KEYX_32]"
dumptxt -p $[OUT] "slot0x33KeyX=$[KEYX_33]"
dumptxt -p $[OUT] "slot0x34KeyX=$[KEYX_34]"
dumptxt -p $[OUT] "slot0x35KeyX=$[KEYX_35]"
dumptxt -p $[OUT] "slot0x36KeyX=$[KEYX_36]"
dumptxt -p $[OUT] "slot0x37KeyX=$[KEYX_37]"
dumptxt -p $[OUT] "slot0x38KeyX=$[KEYX_38]"
dumptxt -p $[OUT] "slot0x39KeyX=$[KEYX_39]"
dumptxt -p $[OUT] "slot0x3AKeyX=$[KEYX_3A]"
dumptxt -p $[OUT] "slot0x3BKeyX=$[KEYX_3B]"
dumptxt -p $[OUT] "slot0x3CKeyX=$[KEYX_3C]"
dumptxt -p $[OUT] "slot0x3DKeyX=$[KEYX_3D]"
dumptxt -p $[OUT] "slot0x3EKeyX=$[KEYX_3E]"
dumptxt -p $[OUT] "slot0x3FKeyX=$[KEYX_3F]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# KeyY"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x04KeyY=$[KEYY_04]"
dumptxt -p $[OUT] "slot0x05KeyY=$[KEYY_05]"
dumptxt -p $[OUT] "slot0x06KeyY=$[KEYY_06]"
dumptxt -p $[OUT] "slot0x07KeyY=$[KEYY_07]"
dumptxt -p $[OUT] "slot0x08KeyY=$[KEYY_08]"
dumptxt -p $[OUT] "slot0x09KeyY=$[KEYY_09]"
dumptxt -p $[OUT] "slot0x0AKeyY=$[KEYY_0A]"
dumptxt -p $[OUT] "slot0x0BKeyY=$[KEYY_0B]"
dumptxt -p $[OUT] "slot0x24KeyY=$[KEYY_24]"
dumptxt -p $[OUT] "slot0x2EKeyY=$[KEYY_2E]"
dumptxt -p $[OUT] "slot0x2FKeyY=$[KEYY_2F]"
dumptxt -p $[OUT] "slot0x31KeyY=$[KEYY_31]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# DLP/NFC KeyY (slot 0x39)"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "dlpKeyY=$[KEYY_39_DLP]"
dumptxt -p $[OUT] "nfcKeyY=$[KEYY_39_NFC]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# Ticket Common KeyY (slot 0x3D)"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "common0=$[COMMON_0]"
dumptxt -p $[OUT] "common1=$[COMMON_1]"
dumptxt -p $[OUT] "common2=$[COMMON_2]"
dumptxt -p $[OUT] "common3=$[COMMON_3]"
dumptxt -p $[OUT] "common4=$[COMMON_4]"
dumptxt -p $[OUT] "common5=$[COMMON_5]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# KeyN"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "slot0x0DKeyN=$[KEYN_0D]"
dumptxt -p $[OUT] "slot0x2DKeyN=$[KEYN_2D]"
dumptxt -p $[OUT] "slot0x32KeyN=$[KEYN_32]"
dumptxt -p $[OUT] "slot0x36KeyN=$[KEYN_36]"
dumptxt -p $[OUT] "slot0x38KeyN=$[KEYN_38]"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "# NFC Secrets"
dumptxt -p $[OUT] ""
dumptxt -p $[OUT] "nfcSecret0Phrase=$[NFC_PHRASE_0]"
dumptxt -p $[OUT] "nfcSecret0Seed=$[NFC_SEED_0]"
dumptxt -p $[OUT] "nfcSecret0HmacKey=$[NFC_HMAC_KEY_0]"
dumptxt -p $[OUT] "nfcSecret1Phrase=$[NFC_PHRASE_1]"
dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
# Dump seeddb.bin as well
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
set SEEDDB_OUT "0:/gm9/seeddb.bin"
sdump -w seeddb.bin
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
@Exit

View File

@ -1,10 +0,0 @@
# DumpKeys
This is a GodMode9 script that dumps all the keys and other related secrets that Azahar needs from a real 3DS.
Usage:
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
3. Wait for the script to complete and return you to the GodMode9 main menu.
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Azahar directory)/sysdata/".

View File

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

612
dist/languages/de.ts vendored

File diff suppressed because it is too large Load Diff

582
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

File diff suppressed because it is too large Load Diff

582
dist/languages/fi.ts vendored

File diff suppressed because it is too large Load Diff

582
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

582
dist/languages/id.ts vendored

File diff suppressed because it is too large Load Diff

582
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

606
dist/languages/nb.ts vendored

File diff suppressed because it is too large Load Diff

582
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

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

594
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -69,17 +69,9 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
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_INSTALL OFF CACHE BOOL "")
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
add_subdirectory(cryptopp-cmake EXCLUDE_FROM_ALL)
add_subdirectory(cryptopp EXCLUDE_FROM_ALL)
endif()
# dds-ktx
@ -284,7 +276,7 @@ if (ENABLE_CUBEB)
endif()
# 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.
include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN)

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

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 526227eebe1efff3fb14dbf494b9c5b44c2e9c1f
Subproject commit e77b1ba0b7da7cbe93021b01a663acfe7c4dd516

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
*.iml
.idea/
.idea/*
!.idea/inspectionProfiles
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.

View File

@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintUseKtx" enabled="true" level="STYLE_SUGGESTION" enabled_by_default="true" editorAttributes="TEXT_STYLE_SUGGESTION" />
<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")
kotlin("plugin.serialization") version "2.0.20"
id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint")
}
/**
@ -79,8 +80,9 @@ android {
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
"-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"
versionNameSuffix = "-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
isJniDebuggable = true
proguardFiles(
@ -136,8 +139,10 @@ android {
}
// 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.
// We should fix those bugs eventually, but for now this exists as a workaround to allow other work to be done.
// Primarily exists to allow development on hardened_malloc systems (e.g. GrapheneOS)
// 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") {
initWith(getByName("relWithDebInfo"))
signingConfig = signingConfigs.getByName("debug")
@ -147,7 +152,7 @@ android {
}
lint {
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.
}
}
@ -215,7 +220,9 @@ dependencies {
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
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"))
onlyIfModified(true)
}
@ -235,6 +242,11 @@ val unzipVulkanValidationLayers = tasks.register<Copy>("unzipVulkanValidationLay
tasks.named("preBuild") {
dependsOn(unzipVulkanValidationLayers)
dependsOn("ktlintCheck")
}
ktlint {
version = "1.8.0"
}
fun getGitVersion(): String {
@ -266,7 +278,7 @@ fun getGitHash(): String =
fun getBranch(): String =
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
fun runGitCommand(command: ProcessBuilder) : String? {
fun runGitCommand(command: ProcessBuilder): String? {
try {
command.directory(project.rootDir)
val process = command.start()
@ -292,7 +304,7 @@ android.applicationVariants.configureEach {
val variant = this
val capitalizedName = variant.name.capitalizeUS()
val copyTask = tasks.register("copyBundle${capitalizedName}") {
val copyTask = tasks.register("copyBundle$capitalizedName") {
doLast {
project.copy {
from(variant.outputs.first().outputFile.parentFile)
@ -306,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 org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.GraphicsUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil
import org.citra.citra_emu.utils.PermissionsHandler
class CitraApplication : Application() {
private fun createNotificationChannel() {
@ -69,6 +69,7 @@ class CitraApplication : Application() {
Log.info("SoC Model - ${Build.SOC_MODEL}")
}
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
Log.info("OpenGL ES Renderer - ${GraphicsUtil.openGLRendererString}")
}
companion object {

View File

@ -25,15 +25,16 @@ import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
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.model.Game
import org.citra.citra_emu.utils.BuildUtil
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.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference
import java.util.Date
/**
* Class which contains methods that interact
@ -43,7 +44,7 @@ object NativeLibrary {
/**
* Default touchscreen device
*/
const val TouchScreenDevice = "Touchscreen"
const val TOUCHSCREEN_DEVICE = "Touchscreen"
@JvmField
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
@ -135,10 +136,7 @@ object NativeLibrary {
*/
external fun setUserDirectory(directory: String)
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
data class InstalledGame(val path: String, val mediaType: Game.MediaType)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
@ -253,9 +251,8 @@ object NativeLibrary {
external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
uninstallTitle(titleId, mediaType.value)
external fun nativeFileExists(path: String): Boolean
@ -348,11 +345,12 @@ object NativeLibrary {
return coreErrorAlertResult
}
@get:Keep
@get:JvmStatic
val isPortraitMode: Boolean
get() = CitraApplication.appContext.resources.configuration.orientation ==
@Keep
@JvmStatic
fun isPortraitMode(): Boolean = (
CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
)
@Keep
@JvmStatic
@ -465,16 +463,18 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity
var coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
val coreError = CoreError.fromInt(requireArguments().getInt(RESULT_CODE))
val title: String
val message: String
when (coreError) {
CoreError.ErrorGetLoader, CoreError.ErrorLoader_ErrorInvalidFormat, CoreError.ErrorSystemMode -> {
CoreError.ErrorGetLoader,
CoreError.ErrorLoaderErrorInvalidFormat,
CoreError.ErrorSystemMode -> {
title = getString(R.string.loader_error_invalid_format)
message = getString(R.string.loader_error_invalid_format_description)
}
CoreError.ErrorLoader_ErrorEncrypted -> {
CoreError.ErrorLoaderErrorEncrypted -> {
title = getString(R.string.loader_error_encrypted)
message = getString(R.string.loader_error_encrypted_description)
}
@ -489,27 +489,31 @@ object NativeLibrary {
message = getString(R.string.loader_error_invalid_system_mode_description)
}
CoreError.ErrorLoader_ErrorPatches -> {
CoreError.ErrorLoaderErrorPatches -> {
title = getString(R.string.loader_error_applying_patches)
message = getString(R.string.loader_error_applying_patches_description)
}
CoreError.ErrorLoader_ErrorPatchesInvalidTitle -> {
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)
message = getString(
R.string.loader_error_generic,
getString(coreError.stringRes),
coreError.value
)
}
}
val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(
Html.fromHtml(message,
Html.fromHtml(
message,
Html.FROM_HTML_MODE_LEGACY
)
)
@ -641,7 +645,7 @@ object NativeLibrary {
fun loadStateIfAvailable(slot: Int): Boolean {
var available = false
getSavestateInfo()?.forEach {
if (it.slot == slot){
if (it.slot == slot) {
available = true
return@forEach
}
@ -679,19 +683,17 @@ object NativeLibrary {
// Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
compressFileNative(inputPath, outputPath)
)
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath)
)
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@ -725,8 +727,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun openContentUri(path: String, openMode: String): Int =
if (FileUtil.isNativePath(path)) {
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.openContentUri(path, openMode)
} else {
FileUtil.openContentUri(path, openMode)
@ -734,8 +735,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getFilesName(path: String): Array<String?> =
if (FileUtil.isNativePath(path)) {
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFilesName(path)
} else {
FileUtil.getFilesName(path)
@ -765,10 +765,14 @@ object NativeLibrary {
return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device
val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
CitraApplication.appContext,
storageIdString
)
if (removablePath == null) {
android.util.Log.e("NativeLibrary",
android.util.Log.e(
"NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
)
return ""
@ -788,8 +792,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getSize(path: String): Long =
if (FileUtil.isNativePath(path)) {
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFileSize(path)
} else {
FileUtil.getFileSize(path)
@ -801,8 +804,11 @@ object NativeLibrary {
@Keep
@JvmStatic
fun fileExists(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
fun isUsingAngleForOpenGL(): Boolean = GraphicsUtil.isUsingAngleForOpenGL()
@Keep
@JvmStatic
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path)
} else {
FileUtil.exists(path)
@ -810,8 +816,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun isDirectory(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path)
} else {
FileUtil.isDirectory(path)
@ -823,8 +828,7 @@ object NativeLibrary {
sourcePath: String,
destinationParentPath: String,
destinationFilename: String
): Boolean =
if (FileUtil.isNativePath(sourcePath) &&
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
FileUtil.isNativePath(destinationParentPath)
) {
CitraApplication.documentsTree
@ -870,8 +874,7 @@ object NativeLibrary {
@Keep
@JvmStatic
fun deleteDocument(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path)
} else {
FileUtil.deleteDocument(path)
@ -883,11 +886,11 @@ object NativeLibrary {
ErrorGetLoader(2, R.string.core_error_get_loader),
ErrorSystemMode(3, R.string.core_error_system_mode),
ErrorLoader(4, R.string.core_error_loader),
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
ErrorLoader_ErrorPatches(8, R.string.core_error_loader_error_patches),
ErrorLoader_ErrorPatchesInvalidTitle(9, R.string.core_error_loader_patches_invalid_title),
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),
@ -898,9 +901,7 @@ object NativeLibrary {
ErrorUnknown(17, R.string.core_error_unknown);
companion object {
fun fromInt(value: Int): CoreError {
return entries.find { it.value == value } ?: ErrorUnknown
}
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
}
}
@ -952,7 +953,11 @@ object NativeLibrary {
const val MESSAGE = "message"
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 args = Bundle()
args.putString(TITLE, title)

View File

@ -31,7 +31,7 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.camera.StillImageCameraHelper.OnFilePickerResult
import org.citra.citra_emu.camera.StillImageCameraHelper.onFilePickerResult
import org.citra.citra_emu.contracts.OpenFileResultContract
import org.citra.citra_emu.databinding.ActivityEmulationBinding
import org.citra.citra_emu.display.ScreenAdjustmentUtil
@ -46,9 +46,9 @@ import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
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.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil
@ -64,7 +64,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility
private lateinit var secondaryDisplay: SecondaryDisplay
lateinit var secondaryDisplayManager: SecondaryDisplay
private val onShutdown = Runnable {
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
@ -102,8 +102,8 @@ class EmulationActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
secondaryDisplay = SecondaryDisplay(this)
secondaryDisplay.updateDisplay()
secondaryDisplayManager = SecondaryDisplay(this)
secondaryDisplayManager.updateDisplay()
binding = ActivityEmulationBinding.inflate(layoutInflater)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
@ -188,7 +188,7 @@ class EmulationActivity : AppCompatActivity() {
}
override fun onStop() {
secondaryDisplay.releasePresentation()
secondaryDisplayManager.releasePresentation()
super.onStop()
}
@ -199,7 +199,7 @@ class EmulationActivity : AppCompatActivity() {
public override fun onRestart() {
super.onRestart()
secondaryDisplay.updateDisplay()
secondaryDisplayManager.updateDisplay()
NativeLibrary.reloadCameraDevices()
}
@ -222,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
NativeLibrary.playTimeManagerStop()
isEmulationRunning = false
instance = null
secondaryDisplay.releasePresentation()
secondaryDisplay.releaseVD()
secondaryDisplayManager.releasePresentation()
secondaryDisplayManager.releaseVD()
super.onDestroy()
}
@ -334,11 +334,13 @@ class EmulationActivity : AppCompatActivity() {
}
return hotkeyUtility.handleKeyPress(event)
}
KeyEvent.ACTION_UP -> {
return hotkeyUtility.handleKeyRelease(event)
}
else -> {
return false;
return false
}
}
}
@ -358,7 +360,8 @@ class EmulationActivity : AppCompatActivity() {
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
if (!NativeLibrary.isRunning() ||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
emulationFragment.isDrawerOpen()) {
emulationFragment.isDrawerOpen()
) {
return super.dispatchGenericMotionEvent(event)
}
@ -387,16 +390,19 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation =
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) {
// Axis is unmapped
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
value = 0f
}
if (inverted) value = -value;
if (inverted) value = -value
when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> {
@ -450,7 +456,7 @@ class EmulationActivity : AppCompatActivity() {
// Triggers L/R and ZL/ZR
if (isTriggerPressedLMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.TRIGGER_L,
if (isTriggerPressedL) {
NativeLibrary.ButtonState.PRESSED
@ -461,7 +467,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedRMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.TRIGGER_R,
if (isTriggerPressedR) {
NativeLibrary.ButtonState.PRESSED
@ -472,7 +478,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedZLMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.BUTTON_ZL,
if (isTriggerPressedZL) {
NativeLibrary.ButtonState.PRESSED
@ -483,7 +489,7 @@ class EmulationActivity : AppCompatActivity() {
}
if (isTriggerPressedZRMapped) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.BUTTON_ZR,
if (isTriggerPressedZR) {
NativeLibrary.ButtonState.PRESSED
@ -496,72 +502,72 @@ class EmulationActivity : AppCompatActivity() {
// Work-around to allow D-pad axis to be bound to emulated buttons
if (axisValuesDPad[0] == 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[0] < 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.PRESSED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[0] > 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_LEFT,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_RIGHT,
NativeLibrary.ButtonState.PRESSED
)
}
if (axisValuesDPad[1] == 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[1] < 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.PRESSED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.RELEASED
)
}
if (axisValuesDPad[1] > 0f) {
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_UP,
NativeLibrary.ButtonState.RELEASED
)
NativeLibrary.onGamePadEvent(
NativeLibrary.TouchScreenDevice,
NativeLibrary.TOUCHSCREEN_DEVICE,
NativeLibrary.ButtonType.DPAD_DOWN,
NativeLibrary.ButtonState.PRESSED
)
@ -573,7 +579,9 @@ class EmulationActivity : AppCompatActivity() {
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, applicationContext, listOf<String>("bin")
result,
applicationContext,
listOf<String>("bin")
) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) {
onAmiiboSelected(selectedFiles[0])
@ -590,14 +598,12 @@ class EmulationActivity : AppCompatActivity() {
return@registerForActivityResult
}
OnFilePickerResult(result.toString())
onFilePickerResult(result.toString())
}
companion object {
private var instance: EmulationActivity? = null
fun isRunning(): Boolean {
return instance?.isEmulationRunning ?: false
}
fun isRunning(): Boolean = 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
// Refer to the license.txt file included.
@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.citra.citra_emu.R
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.viewmodel.DriverViewModel
import org.citra.citra_emu.utils.GpuDriverHelper
class DriverAdapter(private val driverViewModel: DriverViewModel) :
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
@ -105,15 +105,11 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
override fun areItemsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean {
return oldItem.first == newItem.first
}
): Boolean = oldItem.first == newItem.first
override fun areContentsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean {
return oldItem.second == newItem.second
}
): Boolean = oldItem.second == newItem.second
}
}

View File

@ -4,24 +4,25 @@
package org.citra.citra_emu.adapters
import android.graphics.drawable.Icon
import android.content.Context
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.os.SystemClock
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.content.Context
import android.content.SharedPreferences
import android.widget.TextView
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
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.appcompat.app.AppCompatActivity
import androidx.core.content.edit
@ -29,24 +30,23 @@ import androidx.core.graphics.scale
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
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.HomeNavigationDirections
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
@ -65,10 +65,12 @@ class GameAdapter(
private val activity: AppCompatActivity,
private val inflater: LayoutInflater,
private val openImageLauncher: ActivityResultLauncher<String>?,
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener, View.OnLongClickListener {
private val onRequestCompressOrDecompress: (
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
)? = null
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener,
View.OnLongClickListener {
private var lastClickTime = 0L
private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null
@ -212,7 +214,8 @@ class GameAdapter(
binding.textGameTitle.text = game.title
binding.textCompany.text = game.company
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
} else {
View.VISIBLE
@ -220,7 +223,9 @@ class GameAdapter(
val backgroundColorId =
if (
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
isValidGame(
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
)
) {
R.attr.colorSurface
} else {
@ -260,16 +265,45 @@ class GameAdapter(
val extraDir: String
)
private fun getGameDirectories(game: Game): GameDirectories {
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
val basePath =
"sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
return GameDirectories(
gameDir = game.path.substringBeforeLast("/"),
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
saveDir =
basePath +
"/title/${String.format(
"%016x",
game.titleId
).lowercase().substring(
0,
8
)}/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/data/00000001",
modsDir = "load/mods/${String.format("%016X", game.titleId)}",
texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
appDir = game.path.substringBeforeLast("/").split("/").filter {
it.isNotEmpty()
}.joinToString("/"),
dlcDir =
basePath +
"/title/0004008c/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
updatesDir =
basePath +
"/title/0004000e/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
extraDir =
basePath +
"/extdata/00000000/${String.format(
"%016X",
game.titleId
).substring(8, 14).padStart(8, '0')}"
)
}
@ -299,13 +333,36 @@ class GameAdapter(
.setType("*/*")
val uri = when (menuItem.itemId) {
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
dirs.appDir
)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(
dirs.saveDir
)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
dirs.updatesDir
)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
dirs.dlcDir
)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
dirs.extraDir
)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
dirs.texturesDir,
true
)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
dirs.modsDir,
true
)
else -> null
}
@ -319,7 +376,11 @@ class GameAdapter(
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 popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
@ -342,16 +403,38 @@ class GameAdapter(
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
titleId,
game.mediaType
)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(
dlcTitleId,
Game.MediaType.SDMC
)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(
updateTitleId,
Game.MediaType.SDMC
)
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()
}
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
if (menuItem.itemId in
listOf(
R.id.game_context_uninstall,
R.id.game_context_uninstall_dlc,
R.id.game_context_uninstall_updates
)
) {
IndeterminateProgressDialogFragment.newInstance(
activity,
R.string.uninstalling,
false,
uninstallAction
)
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
true
} else {
@ -362,7 +445,12 @@ class GameAdapter(
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 bottomSheetDialog = BottomSheetDialog(context)
@ -374,12 +462,22 @@ class GameAdapter(
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_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_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
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_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)
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
val insertButton = bottomSheetView.findViewById<MaterialButton>(
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.setOnClickListener {
if (inserted) {
@ -420,7 +518,7 @@ class GameAdapter(
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Default to false for zoomed in shortcut icons
preferences.edit() {
preferences.edit {
putBoolean(
"shouldStretchIcon",
false
@ -452,10 +550,15 @@ class GameAdapter(
.setPositiveButton(android.R.string.ok) { _, _ ->
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
if (shortcutName.isEmpty()) {
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
Toast.makeText(
context,
R.string.shortcut_name_empty,
Toast.LENGTH_LONG
).show()
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)
CoroutineScope(Dispatchers.IO).launch {
@ -463,9 +566,11 @@ class GameAdapter(
val shortcut = ShortcutInfo.Builder(context, shortcutName)
.setShortLabel(shortcutName)
.setIcon(icon)
.setIntent(game.launchIntent.apply {
.setIntent(
game.launchIntent.apply {
putExtra("launchedFromShortcut", true)
})
}
)
.build()
shortcutManager?.requestPinShortcut(shortcut, null)
@ -486,7 +591,9 @@ class GameAdapter(
bottomSheetDialog.dismiss()
}
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
R.id.compress_decompress
)
if (game.isInstalled) {
compressDecompressButton.setOnClickListener {
Toast.makeText(
@ -499,31 +606,42 @@ class GameAdapter(
} else {
compressDecompressButton.setOnClickListener {
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('.')
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
onRequestCompressOrDecompress?.invoke(
holder.game.path,
"$baseName.$recommendedExt",
shouldCompress
)
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 {
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)
}
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
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) {_, _ ->
.setPositiveButton(android.R.string.ok) { _, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
@ -532,11 +650,11 @@ class GameAdapter(
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
@ -620,10 +738,8 @@ class GameAdapter(
}
}
private fun isValidGame(extension: String): Boolean {
return Game.badExtensions.stream()
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }
}
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
@ -632,8 +748,6 @@ class GameAdapter(
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = 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
// Refer to the license.txt file included.
@ -9,27 +9,24 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardHomeOptionBinding
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.HomeSetting
import org.citra.citra_emu.viewmodel.GamesViewModel
class HomeSettingAdapter(
private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner,
var options: List<HomeSetting>
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
val binding =
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -37,9 +34,7 @@ class HomeSettingAdapter(
return HomeOptionViewHolder(binding)
}
override fun getItemCount(): Int {
return options.size
}
override fun getItemCount(): Int = options.size
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position])

View File

@ -8,18 +8,17 @@ import android.content.res.ColorStateList
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
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.model.ButtonState
import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.ViewUtils
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])
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root), SetupCallback {
RecyclerView.ViewHolder(binding.root),
SetupCallback {
lateinit var page: SetupPage
init {
@ -49,7 +49,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
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) {
val pageButtonView = LayoutInflater.from(activity)
.inflate(
@ -108,9 +110,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
.alpha(0.38f)
.setDuration(200)
.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 =
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
// Refer to the license.txt file included.
package org.citra.citra_emu.applets
import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
import java.io.Serializable
@Keep
object MiiSelector {
lateinit var data: MiiSelectorData
val finishLock = Object()
private fun ExecuteImpl(config: MiiSelectorConfig) {
private fun executeImpl(config: MiiSelectorConfig) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
data = MiiSelectorData(0, 0)
val fragment = MiiSelectorDialogFragment.newInstance(config)
@ -22,8 +22,8 @@ object MiiSelector {
}
@JvmStatic
fun Execute(config: MiiSelectorConfig): MiiSelectorData {
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { ExecuteImpl(config) }
fun execute(config: MiiSelectorConfig): MiiSelectorData {
NativeLibrary.sEmulationActivity.get()!!.runOnUiThread { executeImpl(config) }
synchronized(finishLock) {
try {
finishLock.wait()
@ -43,5 +43,5 @@ object MiiSelector {
lateinit var miiNames: Array<String>
}
class MiiSelectorData (var returnCode: Long, var index: Int)
class MiiSelectorData(var returnCode: Long, var index: Int)
}

View File

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

View File

@ -21,9 +21,10 @@ object StillImageCameraHelper {
private var filePickerPath: String? = null
// Opens file picker for camera.
@Suppress("unused")
@Keep
@JvmStatic
fun OpenFilePicker(): String? {
fun openFilePicker(): String? {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
// At this point, we are assuming that we already have permissions as they are
@ -44,15 +45,16 @@ object StillImageCameraHelper {
// Called from EmulationActivity.
@JvmStatic
fun OnFilePickerResult(result: String) {
fun onFilePickerResult(result: String) {
filePickerPath = result
synchronized(filePickerLock) { filePickerLock.notifyAll() }
}
// Blocking call. Load image from file and crop/resize it to fit in width x height.
@Suppress("unused")
@Keep
@JvmStatic
fun LoadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
fun loadImageFromFile(uri: String?, width: Int, height: Int): Bitmap? {
val context = CitraApplication.appContext
val request = ImageRequest.Builder(context)
.data(uri)

View File

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

View File

@ -4,15 +4,14 @@
package org.citra.citra_emu.display
import android.content.Context
import android.content.pm.ActivityInfo
import android.app.Activity
import android.content.Context
import android.view.WindowManager
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings
@ -20,7 +19,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
class ScreenAdjustmentUtil(
private val context: Context,
private val windowManager: WindowManager,
private val settings: Settings,
private val settings: Settings
) {
fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens
@ -34,17 +33,18 @@ class ScreenAdjustmentUtil(
}
fun cycleLayouts() {
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty())
if (landscapeLayoutsToCycle.isNotEmpty()) {
landscapeLayoutsToCycle.toIntArray()
else context.resources.getIntArray(
} else {
context.resources.getIntArray(
R.array.landscapeValues
)
}
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) {
if (NativeLibrary.isPortraitMode()) {
val currentLayout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
val pos = portraitValues.indexOf(currentLayout)
val layoutOption = portraitValues[(pos + 1) % portraitValues.size]
@ -61,14 +61,32 @@ class ScreenAdjustmentUtil(
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
}
fun changeScreenOrientation(layoutOption: Int) {
IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
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) {
@ -83,7 +101,6 @@ class ScreenAdjustmentUtil(
BooleanSetting.UPRIGHT_SCREEN.boolean = !uprightBoolean
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
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),
CUSTOM_LAYOUT(5);
companion object {
fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
}
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
BELOW(7);
companion object {
fun from(int: Int): SmallScreenPosition {
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
}
@ -45,23 +40,27 @@ enum class PortraitScreenLayout(val int: Int) {
ORIGINAL(2);
companion object {
fun from(int: Int): PortraitScreenLayout {
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
fun from(int: Int): PortraitScreenLayout =
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
}
enum class SecondaryDisplayLayout(val int: Int) {
// 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),
TOP_SCREEN(1),
BOTTOM_SCREEN(2),
SIDE_BY_SIDE(3);
SIDE_BY_SIDE(3),
REVERSE_PRIMARY(4),
ORIGINAL(5),
HYBRID(6),
LARGE_SCREEN(7)
;
companion object {
fun from(int: Int): SecondaryDisplayLayout {
return entries.firstOrNull { it.int == int } ?: NONE
}
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
}
}
@ -74,9 +73,7 @@ enum class StereoWhichDisplay(val int: Int) {
SECONDARY_ONLY(3);
companion object {
fun from(int: Int): StereoWhichDisplay {
return entries.firstOrNull { it.int == int } ?: NONE
}
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
}
}
@ -88,12 +85,10 @@ enum class StereoMode(val int: Int) {
SIDE_BY_SIDE_FULL(2),
ANAGLYPH(3),
INTERLACED(4),
REVERSE_INTERLACED (5),
CARDBOARD_VR (6);
REVERSE_INTERLACED(5),
CARDBOARD_VR(6);
companion object {
fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
}
}

View File

@ -8,19 +8,27 @@ import android.app.Presentation
import android.content.Context
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle
import android.view.Display
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.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 {
private var pres: SecondaryDisplayPresentation? = null
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val vd: VirtualDisplay
var preferredDisplayId = -1
var currentDisplayId = -1
val availableDisplays: List<Display>
get() = getSecondaryDisplays()
init {
vd = displayManager.createVirtualDisplay(
@ -35,31 +43,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
}
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() {
NativeLibrary.secondarySurfaceDestroyed()
}
private fun getExternalDisplay(context: Context): Display? {
private fun getSecondaryDisplays(): List<Display> {
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 presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
val extDisplays = displays.filter {
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return displays.filter {
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 &&
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
}
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
?: extDisplays.firstOrNull()
return selected
}
fun updateDisplay() {
@ -68,21 +85,40 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
return
}
// decide if we are going to the external display or the internal one
var display = getExternalDisplay(context)
if (display == null ||
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 (pres?.display == display) return
if (pres?.display == displayToUse) return
// otherwise, make a new presentation
releasePresentation()
try {
pres = SecondaryDisplayPresentation(context, display!!, this)
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
pres?.show()
}
// catch BadTokenException and InvalidDisplayException,
@ -119,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
}
}
class SecondaryDisplayPresentation(
context: Context, display: Display, val parent: SecondaryDisplay
context: Context,
display: Display,
val parent: SecondaryDisplay
) : Presentation(context, display) {
private lateinit var surfaceView: SurfaceView
private var touchscreenPointerId = -1
@ -137,16 +175,21 @@ class SecondaryDisplayPresentation(
surfaceView = SurfaceView(context)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface created")
}
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()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.debug("SecondaryDisplay Surface destroyed")
parent.destroySurface()
}
})
@ -191,7 +234,5 @@ class SecondaryDisplayPresentation(
}
// Publicly accessible method to get the SurfaceHolder
fun getSurfaceHolder(): SurfaceHolder {
return surfaceView.holder
}
fun getSurfaceHolder(): SurfaceHolder = 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
// Refer to the license.txt file included.
@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
private var selectedCheatPosition = -1
fun initialize(titleId_: Long) {
titleId = titleId_;
titleId = titleId_
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
// Refer to the license.txt file included.
@ -170,8 +170,7 @@ class CheatDetailsFragment : Fragment() {
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View?, windowInsets: WindowInsetsCompat ->
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
// 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
// Refer to the license.txt file included.
@ -41,7 +41,8 @@ class CheatsAdapter(
}
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
RecyclerView.ViewHolder(binding.root), View.OnClickListener,
RecyclerView.ViewHolder(binding.root),
View.OnClickListener,
CompoundButton.OnCheckedChangeListener {
private lateinit var viewModel: CheatsViewModel
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
// 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.viewmodel.HomeViewModel
class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
class CheatsFragment :
Fragment(),
SlidingPaneLayout.PanelSlideListener {
private var cheatListLastFocus: View? = null
private var cheatDetailsLastFocus: View? = null
@ -157,12 +159,15 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
}
private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value!!
val cheatSelected = selectedCheat != null || cheatsViewModel.isEditing.value
if (!cheatSelected && binding.slidingPaneLayout.isOpen) {
binding.slidingPaneLayout.close()
}
binding.slidingPaneLayout.lockMode =
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
binding.slidingPaneLayout.lockMode = if (cheatSelected) {
SlidingPaneLayout.LOCK_MODE_UNLOCKED
} else {
SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
}
}
fun onListViewFocusChange(hasFocus: Boolean) {
@ -203,7 +208,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
// 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 (keyboardInsets.bottom > 0) {
mlpDetails.bottomMargin = keyboardInsets.bottom
@ -231,14 +237,16 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
val mlpDetails =
binding.cheatDetailsContainer.layoutParams as ViewGroup.MarginLayoutParams
binding.cheatDetailsContainer.layoutParams
as ViewGroup.MarginLayoutParams
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
binding.cheatDetailsContainer.layoutParams = mlpDetails
return insets
}
})
}
)
}
}
}

View File

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

View File

@ -11,11 +11,12 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.ComboHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.Settings
class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
@ -41,7 +42,7 @@ class HotkeyUtility(
// Now process all internal buttons associated with this keypress
for (button in buttonSet) {
currentlyPressedButtons.add(button)
//option 1 - this is the enable command, which was already handled
// option 1 - this is the enable command, which was already handled
if (button == Hotkey.ENABLE.button) {
handled = true
}
@ -71,10 +72,16 @@ class HotkeyUtility(
var handled = false
val buttonSet = InputBindingSetting.getButtonSet(keyEvent)
val thisKeyIsEnableButton = buttonSet.contains(Hotkey.ENABLE.button)
val thisKeyIsComboButton = buttonSet.contains(Hotkey.COMBO_BUTTON.button)
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) {
handled = true; hotkeyIsEnabled = false
handled = true
hotkeyIsEnabled = false
}
if (thisKeyIsComboButton) {
ComboHelper.comboActivate(NativeLibrary.ButtonState.RELEASED)
handled = true
}
for (button in buttonSet) {
@ -109,10 +116,15 @@ class HotkeyUtility(
fun handleHotkey(bindedButton: Int): Boolean {
when (bindedButton) {
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(
@ -136,6 +148,10 @@ class HotkeyUtility(
).show()
}
Hotkey.COMBO_BUTTON.button -> {
ComboHelper.comboActivate(NativeLibrary.ButtonState.PRESSED)
}
else -> {}
}
hotkeyIsPressed = true

View File

@ -39,6 +39,7 @@ object SettingKeys {
external fun use_disk_shader_cache(): String
external fun shaders_accurate_mul(): String
external fun use_vsync(): String
external fun use_skip_duplicate_frames(): String
external fun use_shader_jit(): String
external fun resolution_factor(): String
external fun frame_limit(): String
@ -141,4 +142,6 @@ object SettingKeys {
external fun android_hide_images(): String
external fun screen_orientation(): String
external fun performance_overlay_position(): String
external fun enable_secondary_display(): String
external fun combo_button_buttons(): String
}

View File

@ -20,19 +20,63 @@ enum class BooleanSetting(
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(
SettingKeys.toggle_unique_data_console_type(),
Settings.SECTION_DEBUG,
false
),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(), Settings.SECTION_RENDERER, false),
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
PERF_OVERLAY_SHOW_FPS(
SettingKeys.performance_overlay_show_fps(),
Settings.SECTION_LAYOUT,
true
),
PERF_OVERLAY_SHOW_FRAMETIME(
SettingKeys.performance_overlay_show_frame_time(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_SPEED(
SettingKeys.performance_overlay_show_speed(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(
SettingKeys.performance_overlay_show_app_ram_usage(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(
SettingKeys.performance_overlay_show_available_ram(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_BATTERY_TEMP(
SettingKeys.performance_overlay_show_battery_temp(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_BACKGROUND(
SettingKeys.performance_overlay_background(),
Settings.SECTION_LAYOUT,
false
),
DELAY_START_LLE_MODULES(
SettingKeys.delay_start_for_lle_modules(),
Settings.SECTION_DEBUG,
true
),
DETERMINISTIC_ASYNC_OPERATIONS(
SettingKeys.deterministic_async_operations(),
Settings.SECTION_DEBUG,
false
),
REQUIRED_ONLINE_LLE_MODULES(
SettingKeys.enable_required_online_lle_modules(),
Settings.SECTION_SYSTEM,
false
),
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
@ -44,22 +88,48 @@ enum class BooleanSetting(
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(
SettingKeys.simulate_headphones_plugged(),
Settings.SECTION_AUDIO,
false
),
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
USE_SKIP_DUPLICATE_FRAMES(
SettingKeys.use_skip_duplicate_frames(),
Settings.SECTION_RENDERER,
false
),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
DISABLE_RIGHT_EYE_RENDER(
SettingKeys.disable_right_eye_render(),
Settings.SECTION_RENDERER,
false
),
USE_ARTIC_BASE_CONTROLLER(
SettingKeys.use_artic_base_controller(),
Settings.SECTION_CONTROLS,
false
),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
COMPRESS_INSTALLED_CIA_CONTENT(
SettingKeys.compress_cia_installs(),
Settings.SECTION_STORAGE,
false
),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
ENABLE_SECONDARY_DISPLAY(SettingKeys.enable_secondary_display(), Settings.SECTION_LAYOUT, true),
SIMULATE_3DS_GPU_TIMINGS(
SettingKeys.simulate_3ds_gpu_timings(),
Settings.SECTION_RENDERER,
false
);
override var boolean: Boolean = defaultValue

View File

@ -11,8 +11,12 @@ enum class FloatSetting(
override val section: String,
override val defaultValue: Float
) : AbstractFloatSetting {
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(), Settings.SECTION_LAYOUT, 2.25f),
SECOND_SCREEN_OPACITY(
SettingKeys.custom_second_layer_opacity(),
Settings.SECTION_RENDERER,
100f
),
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);

View File

@ -4,6 +4,8 @@
package org.citra.citra_emu.features.settings.model
import org.citra.citra_emu.features.settings.SettingKeys
enum class IntListSetting(
override val key: String,
override val section: String,
@ -11,10 +13,25 @@ enum class IntListSetting(
val canBeEmpty: Boolean = true
) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE("layouts_to_cycle", Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
LAYOUTS_TO_CYCLE(
SettingKeys.layouts_to_cycle(),
Settings.SECTION_LAYOUT,
listOf(0, 1, 2, 3, 4, 5),
canBeEmpty = false
),
/**
* Combo Buttons will be given options on MultiChoice setting initialization. This just makes selections empty by default.
*/
COMBO_BUTTON_BUTTONS(
SettingKeys.combo_button_buttons(),
Settings.SECTION_CONTROLS,
listOf(),
canBeEmpty = true
);
private var backingList: List<Int> = defaultValue
private var lastValidList : List<Int> = defaultValue
private var lastValidList: List<Int> = defaultValue
override var list: List<Int>
get() = backingList
@ -30,7 +47,6 @@ enum class IntListSetting(
override val valueAsString: String
get() = list.joinToString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
@ -44,8 +60,7 @@ enum class IntListSetting(
companion object {
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue }
}

View File

@ -26,26 +26,30 @@ enum class IntSetting(
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(), Settings.SECTION_LAYOUT, 800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(), Settings.SECTION_LAYOUT, 480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(), Settings.SECTION_LAYOUT, 80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(), Settings.SECTION_LAYOUT, 480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(), Settings.SECTION_LAYOUT, 640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(), Settings.SECTION_LAYOUT, 480),
SCREEN_GAP(SettingKeys.screen_gap(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(), Settings.SECTION_LAYOUT, 0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 4),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(), Settings.SECTION_LAYOUT, 800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(), Settings.SECTION_LAYOUT, 480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(), Settings.SECTION_LAYOUT, 80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(), Settings.SECTION_LAYOUT, 480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(), Settings.SECTION_LAYOUT, 640),
PORTRAIT_BOTTOM_HEIGHT(
SettingKeys.custom_portrait_bottom_height(),
Settings.SECTION_LAYOUT,
480
),
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
@ -54,8 +58,12 @@ enum class IntSetting(
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
PERFORMANCE_OVERLAY_POSITION(
SettingKeys.performance_overlay_position(),
Settings.SECTION_LAYOUT,
0
),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(), Settings.SECTION_RENDERER, 0),
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue
@ -78,7 +86,7 @@ enum class IntSetting(
EMULATED_REGION,
INIT_CLOCK,
GRAPHICS_API,
AUDIO_INPUT_TYPE,
AUDIO_INPUT_TYPE
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -26,9 +26,7 @@ class SettingSection(val name: String) {
* @param key Used to retrieve the Setting.
* @return A Setting object (you should probably cast this before using)
*/
fun getSetting(key: String): AbstractSetting? {
return settings[key]
}
fun getSetting(key: String): AbstractSetting? = settings[key]
fun mergeSection(settingSection: SettingSection) {
for (setting in settingSection.settings.values) {

View File

@ -5,11 +5,11 @@
package org.citra.citra_emu.features.settings.model
import android.text.TextUtils
import java.util.TreeMap
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
import org.citra.citra_emu.features.settings.utils.SettingsFile
import java.util.TreeMap
class Settings {
private var gameId: String? = null
@ -33,9 +33,7 @@ class Settings {
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
fun getSection(sectionName: String): SettingSection? {
return sections[sectionName]
}
fun getSection(sectionName: String): SettingSection? = sections[sectionName]
val isEmpty: Boolean
get() = sections.isEmpty()
@ -142,8 +140,9 @@ class Settings {
const val HOTKEY_CLOSE_GAME = "hotkey_close_game"
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
const val HOTKEY_QUICKLOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_LIMIT = "hotkey_turbo_limit"
const val HOTKEY_BUTTON_COMBO = "hotkey_button_combo"
val buttonKeys = listOf(
KEY_BUTTON_A,
@ -210,8 +209,9 @@ class Settings {
HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD,
HOTKEY_TURBO_LIMIT
HOTKEY_QUICKLOAD,
HOTKEY_TURBO_LIMIT,
HOTKEY_BUTTON_COMBO
)
val hotkeyTitles = listOf(
R.string.controller_hotkey_enable_button,
@ -221,7 +221,8 @@ class Settings {
R.string.emulation_toggle_pause,
R.string.emulation_quicksave,
R.string.emulation_quickload,
R.string.turbo_limit_hotkey
R.string.turbo_limit_hotkey,
R.string.button_combo
)
// TODO: Move these in with the other setting keys in GenerateSettingKeys.cmake

View File

@ -15,9 +15,17 @@ enum class StringSetting(
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
CAMERA_OUTER_LEFT_CONFIG(
SettingKeys.camera_outer_left_config(),
Settings.SECTION_CAMERA,
"_back"
),
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
CAMERA_OUTER_RIGHT_CONFIG(
SettingKeys.camera_outer_right_config(),
Settings.SECTION_CAMERA,
"_back"
);
override var string: String = defaultValue

View File

@ -1,9 +1,11 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
@ -13,7 +15,9 @@ class DateTimeSetting(
descriptionId: Int,
val key: String? = null,
private val defaultValue: String? = null,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING

View File

@ -1,9 +1,9 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
class HeaderSetting(titleId: Int,descId: Int = 0) : SettingsItem(null, titleId, descId) {
class HeaderSetting(titleId: Int, descId: Int = 0) : SettingsItem(null, titleId, descId) {
override val type = TYPE_HEADER
}

View File

@ -20,10 +20,8 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.Settings
class InputBindingSetting(
val abstractSetting: AbstractSetting,
titleId: Int
) : SettingsItem(abstractSetting, titleId, 0) {
class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
SettingsItem(abstractSetting, titleId, 0) {
private val context: Context get() = CitraApplication.appContext
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(context)
@ -39,8 +37,7 @@ class InputBindingSetting(
/**
* Returns true if this key is for the 3DS Circle Pad
*/
fun isCirclePad(): Boolean =
when (abstractSetting.key) {
fun isCirclePad(): Boolean = when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
@ -50,8 +47,7 @@ class InputBindingSetting(
/**
* Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad
*/
fun isHorizontalOrientation(): Boolean =
when (abstractSetting.key) {
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
@ -62,8 +58,7 @@ class InputBindingSetting(
/**
* Returns true if this key is for the 3DS C-Stick
*/
fun isCStick(): Boolean =
when (abstractSetting.key) {
fun isCStick(): Boolean = when (abstractSetting.key) {
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
@ -73,19 +68,18 @@ class InputBindingSetting(
/**
* Returns true if this key is for the 3DS D-Pad
*/
fun isDPad(): Boolean =
when (abstractSetting.key) {
fun isDPad(): Boolean = when (abstractSetting.key) {
Settings.KEY_DPAD_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_VERTICAL -> true
else -> false
}
/**
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
* triggers on the 3DS, but we support them as such on a physical gamepad.
*/
fun isTrigger(): Boolean =
when (abstractSetting.key) {
fun isTrigger(): Boolean = when (abstractSetting.key) {
Settings.KEY_BUTTON_L,
Settings.KEY_BUTTON_R,
Settings.KEY_BUTTON_ZL,
@ -97,16 +91,12 @@ class InputBindingSetting(
/**
* Returns true if a gamepad axis can be used to map this key.
*/
fun isAxisMappingSupported(): Boolean {
return isCirclePad() || isCStick() || isDPad() || isTrigger()
}
fun isAxisMappingSupported(): Boolean = isCirclePad() || isCStick() || isDPad() || isTrigger()
/**
* Returns true if a gamepad button can be used to map this key.
*/
fun isButtonMappingSupported(): Boolean {
return !isAxisMappingSupported() || isTrigger()
}
fun isButtonMappingSupported(): Boolean = !isAxisMappingSupported() || isTrigger()
/**
* Returns the Citra button code for the settings key.
@ -135,8 +125,9 @@ class InputBindingSetting(
Settings.HOTKEY_CLOSE_GAME -> Hotkey.CLOSE_GAME.button
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_QUICKLOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_LIMIT -> Hotkey.TURBO_LIMIT.button
Settings.HOTKEY_BUTTON_COMBO -> Hotkey.COMBO_BUTTON.button
else -> -1
}
@ -177,10 +168,10 @@ class InputBindingSetting(
} catch (e: ClassCastException) {
// if this is an int pref, either old button or an axis, so just remove it
preferences.edit().remove(oldKey).apply()
return;
return
}
buttonCodes.remove(buttonCode.toString());
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
buttonCodes.remove(buttonCode.toString())
preferences.edit().putStringSet(oldKey, buttonCodes).apply()
}
}
@ -197,7 +188,7 @@ class InputBindingSetting(
// Cleanup old mapping for this setting
removeOldMapping()
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) { it.toString() })
// Write next reverse mapping for future cleanup
editor.putString(reverseKey, key)
@ -217,7 +208,7 @@ class InputBindingSetting(
preferences.edit()
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
.putInt(getInputAxisButtonKey(axis), value)
.putBoolean(getInputAxisInvertedKey(axis),inverted)
.putBoolean(getInputAxisInvertedKey(axis), inverted)
// Write next reverse mapping for future cleanup
.putString(reverseKey, getInputAxisKey(axis))
.apply()
@ -271,8 +262,7 @@ class InputBindingSetting(
companion object {
private const val INPUT_MAPPING_PREFIX = "InputMapping"
private fun toTitleCase(raw: String): String =
raw.replace("_", " ").lowercase()
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase()
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
private const val BUTTON_NAME_L3 = "Button L3"
@ -287,8 +277,7 @@ class InputBindingSetting(
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
)
fun getButtonName(keyCode: Int): String =
buttonNameOverrides[keyCode]
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode]
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
private data class DefaultButtonMapping(
@ -296,6 +285,7 @@ class InputBindingSetting(
val hostKeyCode: Int,
val guestButtonCode: Int
)
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
private data class DefaultAxisMapping(
val settingKey: String,
@ -306,45 +296,153 @@ class InputBindingSetting(
)
private val xboxFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_Y
)
)
private val nintendoFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
)
private val commonButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
DefaultButtonMapping(
Settings.KEY_BUTTON_L,
KeyEvent.KEYCODE_BUTTON_L1,
NativeLibrary.ButtonType.TRIGGER_L
),
DefaultButtonMapping(
Settings.KEY_BUTTON_R,
KeyEvent.KEYCODE_BUTTON_R1,
NativeLibrary.ButtonType.TRIGGER_R
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZL,
KeyEvent.KEYCODE_BUTTON_L2,
NativeLibrary.ButtonType.BUTTON_ZL
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZR,
KeyEvent.KEYCODE_BUTTON_R2,
NativeLibrary.ButtonType.BUTTON_ZR
),
DefaultButtonMapping(
Settings.KEY_BUTTON_SELECT,
KeyEvent.KEYCODE_BUTTON_SELECT,
NativeLibrary.ButtonType.BUTTON_SELECT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_START,
NativeLibrary.ButtonType.BUTTON_START
)
)
private val dpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
DefaultButtonMapping(
Settings.KEY_BUTTON_UP,
KeyEvent.KEYCODE_DPAD_UP,
NativeLibrary.ButtonType.DPAD_UP
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
KeyEvent.KEYCODE_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
KeyEvent.KEYCODE_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
)
private val stickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_X,
NativeLibrary.ButtonType.STICK_LEFT,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_Z,
NativeLibrary.ButtonType.STICK_C,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RZ,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
)
private val dpadAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
DefaultAxisMapping(
Settings.KEY_DPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_HAT_X,
NativeLibrary.ButtonType.DPAD,
0,
false
),
DefaultAxisMapping(
Settings.KEY_DPAD_AXIS_VERTICAL,
MotionEvent.AXIS_HAT_Y,
NativeLibrary.ButtonType.DPAD,
1,
false
)
)
// Nintendo Switch Joy-Con specific mappings.
@ -369,28 +467,84 @@ class InputBindingSetting(
// This is different from both the standard Xbox table (full swap) and the
// Nintendo table (no swap).
private val joyconFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
)
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
private val joyconDpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
DefaultButtonMapping(
Settings.KEY_BUTTON_UP,
LINUX_BTN_DPAD_UP,
NativeLibrary.ButtonType.DPAD_UP
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
LINUX_BTN_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
LINUX_BTN_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
LINUX_BTN_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
)
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
private val joyconStickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_X,
NativeLibrary.ButtonType.STICK_LEFT,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_RX,
NativeLibrary.ButtonType.STICK_C,
0,
true
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RY,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
)
/**
@ -417,9 +571,11 @@ class InputBindingSetting(
}
private val allBindingKeys: Set<String> by lazy {
(Settings.buttonKeys + Settings.triggerKeys +
(
Settings.buttonKeys + Settings.triggerKeys +
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
Settings.dPadButtonKeys).toSet()
Settings.dPadButtonKeys
).toSet()
}
fun clearAllBindings() {
@ -465,7 +621,11 @@ class InputBindingSetting(
* false if it should be mapped as individual button keycodes (DPAD_UP/DOWN/LEFT/RIGHT)
*/
fun applyAutoMapBindings(isNintendoLayout: Boolean, useAxisDpad: Boolean) {
val faceButtons = if (isNintendoLayout) nintendoFaceButtonMappings else xboxFaceButtonMappings
val faceButtons = if (isNintendoLayout) {
nintendoFaceButtonMappings
} else {
xboxFaceButtonMappings
}
val buttonMappings = if (useAxisDpad) {
faceButtons + commonButtonMappings
} else {
@ -502,15 +662,16 @@ class InputBindingSetting(
editor.putBoolean(getInputAxisInvertedKey(mapping.hostAxis), mapping.inverted)
val dir = if (mapping.orientation == 0) '+' else '-'
editor.putString(mapping.settingKey, "Axis ${mapping.hostAxis}$dir")
val reverseKey = "${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
@Suppress("ktlint:standard:max-line-length")
val reverseKey =
"${INPUT_MAPPING_PREFIX}_ReverseMapping_${mapping.settingKey}_${mapping.orientation}"
editor.putString(reverseKey, axisKey)
}
/**
* Returns the settings key for the specified Citra button code.
*/
private fun getButtonKey(buttonCode: Int): String =
when (buttonCode) {
private fun getButtonKey(buttonCode: Int): String = when (buttonCode) {
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
@ -528,16 +689,19 @@ class InputBindingSetting(
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
else -> ""
}
/**
* Get the mutable set of int button values this key should map to given an event
*/
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
fun getButtonSet(keyCode: KeyEvent): MutableSet<Int> {
val key = getInputButtonKey(keyCode)
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val preferences = PreferenceManager.getDefaultSharedPreferences(
CitraApplication.appContext
)
var buttonCodes = try {
preferences.getStringSet(key, mutableSetOf<String>())
} catch (e: ClassCastException) {
val prefInt = preferences.getInt(key, -1);
val prefInt = preferences.getInt(key, -1)
val migratedSet = if (prefInt != -1) {
mutableSetOf(prefInt.toString())
} else {
@ -549,15 +713,17 @@ class InputBindingSetting(
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
}
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
private fun getInputButtonKey(keyId: Int): String =
"${INPUT_MAPPING_PREFIX}_HostAxis_$keyId"
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
fun getInputButtonKey(event: KeyEvent): String =
getInputButtonKey(translateEventToKeyId(event))
/**
* Helper function to get the settings key for an gamepad axis.
*/
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${axis}"
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_$axis"
/**
* Helper function to get the settings key for an gamepad axis button (stick or trigger).
@ -575,7 +741,6 @@ class InputBindingSetting(
fun getInputAxisOrientationKey(axis: Int): String =
"${getInputAxisKey(axis)}_GuestOrientation"
/**
* This function translates a keyEvent into an "keyid"
* This key id is either the keyCode from the event, or
@ -585,12 +750,10 @@ class InputBindingSetting(
* This handles keys like the media-keys on google statia-controllers
* that don't have a conventional "mapping" and report as "unknown"
*/
fun translateEventToKeyId(event: KeyEvent): Int {
return if (event.keyCode == 0) {
fun translateEventToKeyId(event: KeyEvent): Int = if (event.keyCode == 0) {
event.scanCode
} else {
event.keyCode
}
}
}
}

View File

@ -3,6 +3,8 @@
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
class MultiChoiceSetting(
@ -13,7 +15,9 @@ class MultiChoiceSetting(
val valuesId: Int,
val key: String? = null,
val defaultValue: List<Int>? = null,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_MULTI_CHOICE
@ -25,7 +29,7 @@ class MultiChoiceSetting(
try {
val setting = setting as IntListSetting
return setting.list
}catch (_: ClassCastException) {
} catch (_: ClassCastException) {
}
return defaultValue!!
}
@ -42,5 +46,4 @@ class MultiChoiceSetting(
intSetting.list = selection
return intSetting
}
}

View File

@ -4,7 +4,8 @@
package org.citra.citra_emu.features.settings.model.view
import org.citra.citra_emu.NativeLibrary
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.features.settings.model.AbstractSetting
@ -30,6 +31,9 @@ abstract class SettingsItem(
open var isEnabled: Boolean = true
@StringRes open var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
val isActive: Boolean
get() {
return this.isEditable && this.isEnabled

View File

@ -1,9 +1,11 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
@ -16,7 +18,9 @@ class SingleChoiceSetting(
val valuesId: Int,
val key: String? = null,
val defaultValue: Int? = null,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE

View File

@ -4,6 +4,8 @@
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
@ -20,7 +22,9 @@ class SliderSetting(
val units: String,
val key: String? = null,
val defaultValue: Float? = null,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
val selectedFloat: Float
@ -29,8 +33,11 @@ class SliderSetting(
val ret = when (setting) {
is AbstractIntSetting -> setting.int.toFloat()
is FloatSetting -> setting.float
is ScaledFloatSetting -> setting.float
else -> {
Log.error("[SliderSetting] Error casting setting type.")
-1f
@ -38,6 +45,7 @@ class SliderSetting(
}
return ret.coerceIn(min.toFloat(), max.toFloat())
}
/**
* Write a value to the backing int. If that int was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.

View File

@ -1,9 +1,11 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
@ -13,7 +15,9 @@ class StringInputSetting(
descriptionId: Int,
val defaultValue: String,
val characterLimit: Int = 0,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_INPUT

View File

@ -1,9 +1,11 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
@ -16,7 +18,9 @@ class StringSingleChoiceSetting(
val values: Array<String>?,
val key: String? = null,
private val defaultValue: String? = null,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE

View File

@ -4,9 +4,9 @@
package org.citra.citra_emu.features.settings.model.view
import androidx.annotation.StringRes
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
class SwitchSetting(
setting: AbstractBooleanSetting,
@ -14,7 +14,9 @@ class SwitchSetting(
descriptionId: Int,
val key: String? = null,
val defaultValue: Boolean = false,
override var isEnabled: Boolean = true
override var isEnabled: Boolean = true,
@StringRes override var disabledMessage: Int =
R.string.setting_disabled_description_incompatible_setting
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH

View File

@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
@ -22,11 +21,11 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import java.io.IOException
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.ActivitySettingsBinding
import java.io.IOException
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntSetting
@ -35,13 +34,15 @@ import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.SettingsViewModel
import org.citra.citra_emu.features.settings.model.StringSetting
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.InsetsHelper
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.ThemeUtil
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
class SettingsActivity :
AppCompatActivity(),
SettingsActivityView {
private val presenter = SettingsActivityPresenter(this)
private lateinit var binding: ActivitySettingsBinding

View File

@ -12,11 +12,11 @@ import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.TurboHelper
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
@ -72,11 +72,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
val nomediaFileExists: Boolean
try {
dataDirTreeUri = PermissionsHandler.citraDirectory
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
dataDirDocument =
DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
nomediaFileExists = (nomediaFileDocument != null)
} catch (e: Exception) {
Log.error("[SettingsActivity]: Error occurred while trying to find .nomedia, error: " + e.message)
Log.error(
"[SettingsActivity]: Error occurred while trying to find .nomedia, error: " +
e.message
)
return
}
@ -95,9 +99,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
if (finishing && shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView)
//added to ensure that layout changes take effect as soon as settings window closes
// added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode())
updateAndroidImageVisibility()
TurboHelper.reloadTurbo(false) // TODO: Can this go somewhere else? -OS
}

View File

@ -16,6 +16,7 @@ import android.text.TextWatcher
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged
@ -29,6 +30,9 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.DialogSliderBinding
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
@ -39,16 +43,15 @@ import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SliderSetting
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
@ -69,14 +72,10 @@ import org.citra.citra_emu.fragments.AutoMapDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
import org.citra.citra_emu.utils.SystemSaveGame
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
class SettingsAdapter(private val fragmentView: SettingsFragmentView, public val context: Context) :
RecyclerView.Adapter<SettingViewHolder?>(),
DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
@ -148,17 +147,11 @@ class SettingsAdapter(
getItem(position)?.let { holder.bind(it) }
}
private fun getItem(position: Int): SettingsItem? {
return settings?.get(position)
}
private fun getItem(position: Int): SettingsItem? = settings?.get(position)
override fun getItemCount(): Int {
return settings?.size ?: 0
}
override fun getItemCount(): Int = settings?.size ?: 0
override fun getItemViewType(position: Int): Int {
return getItem(position)?.type ?: -1
}
override fun getItemViewType(position: Int): Int = getItem(position)?.type ?: -1
fun setSettingsList(newSettings: ArrayList<SettingsItem>?) {
if (settings == null) {
@ -196,22 +189,28 @@ class SettingsAdapter(
}
SettingsItem.TYPE_SINGLE_CHOICE -> {
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
(oldItem as SingleChoiceSetting).isEnabled ==
(newItem as SingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_MULTI_CHOICE -> {
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
(oldItem as MultiChoiceSetting).isEnabled ==
(newItem as MultiChoiceSetting).isEnabled
}
SettingsItem.TYPE_DATETIME_SETTING -> {
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
(oldItem as DateTimeSetting).isEnabled ==
(newItem as DateTimeSetting).isEnabled
}
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
(oldItem as StringSingleChoiceSetting).isEnabled ==
(newItem as StringSingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_STRING_INPUT -> {
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
(oldItem as StringInputSetting).isEnabled ==
(newItem as StringInputSetting).isEnabled
}
else -> {
@ -231,10 +230,11 @@ class SettingsAdapter(
fragmentView.onSettingChanged()
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
if (fragmentView.activityView != null)
if (fragmentView.activityView != null) {
// Reload the settings list to update the UI
fragmentView.loadSettingsList()
}
}
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
clickedItem = item
@ -253,7 +253,7 @@ class SettingsAdapter(
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
clickedItem = item
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
val value: BooleanArray = getSelectionForMultiChoiceValue(item)
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choicesId, value, this)
@ -298,7 +298,7 @@ class SettingsAdapter(
val time = item.value.substringAfter(" ")
val formatter = SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZZZZ")
val gmt = formatter.parse("${date}T${time}+0000")
val gmt = formatter.parse("${date}T$time+0000")
gmt!!.time
}
@ -354,7 +354,6 @@ class SettingsAdapter(
clickedPosition = position
sliderProgress = (item.selectedFloat * 100f).roundToInt() / 100f
val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
@ -376,9 +375,9 @@ class SettingsAdapter(
value = sliderProgress
textSliderValue?.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
var textValue = s.toString().toFloatOrNull();
var textValue = s.toString().toFloatOrNull()
if (item.setting !is FloatSetting) {
textValue = textValue?.roundToInt()?.toFloat();
textValue = textValue?.roundToInt()?.toFloat()
}
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout?.error = "Inappropriate value"
@ -425,6 +424,7 @@ class SettingsAdapter(
}
is FloatSetting -> (item.setting as FloatSetting).defaultValue
else -> item.defaultValue ?: 0f
}
onClick(dialog, which)
@ -495,7 +495,9 @@ class SettingsAdapter(
it.setSelectedValue(value)
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
else -> throw IllegalStateException(
"Unrecognized type used for SingleChoiceSetting!"
)
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
@ -518,7 +520,9 @@ class SettingsAdapter(
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
}
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
else -> throw IllegalStateException(
"Unrecognized type used for StringSingleChoiceSetting!"
)
}
fragmentView?.putSetting(setting)
@ -569,13 +573,22 @@ class SettingsAdapter(
textInputValue = ""
}
//onclick for multichoice
// onclick for multichoice
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
val mcsetting = clickedItem as? MultiChoiceSetting
mcsetting?.let {
val value = getValueForMultiChoiceSelection(it, which)
if (it.selectedValues.contains(value) != isChecked) {
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
val setting = it.setSelectedValue(
(
if (isChecked) {
it.selectedValues + value
} else {
it.selectedValues -
value
}
).sorted()
)
fragmentView?.putSetting(setting)
fragmentView?.onSettingChanged()
}
@ -583,13 +596,13 @@ class SettingsAdapter(
}
}
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (setting) {
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
is AbstractFloatSetting -> {
if (setting is ScaledFloatSetting) {
setting.float = setting.defaultValue * setting.scale
@ -599,7 +612,9 @@ class SettingsAdapter(
}
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
is AbstractStringSetting -> setting.string = setting.defaultValue as String
is AbstractShortSetting -> setting.short = setting.defaultValue as Short
}
notifyItemChanged(position)
@ -627,15 +642,17 @@ class SettingsAdapter(
return true
}
fun onClickDisabledSetting(isRuntimeDisabled: Boolean) {
val titleId = if (isRuntimeDisabled)
fun onClickDisabledSetting(isRuntimeDisabled: Boolean, @StringRes disabledMessage: Int) {
val titleId = if (isRuntimeDisabled) {
R.string.setting_not_editable
else
} else {
R.string.setting_disabled
val messageId = if (isRuntimeDisabled)
}
val messageId = if (isRuntimeDisabled) {
R.string.setting_not_editable_description
else
R.string.setting_disabled_description
} else {
disabledMessage
}
MessageDialogFragment.newInstance(
titleId,
@ -652,7 +669,10 @@ class SettingsAdapter(
}
fun onLongClickAutoMap(): Boolean {
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
showConfirmationDialog(
R.string.controller_clear_all,
R.string.controller_clear_all_confirm
) {
InputBindingSetting.clearAllBindings()
fragmentView.loadSettingsList()
fragmentView.onSettingChanged()
@ -661,14 +681,20 @@ class SettingsAdapter(
}
fun onClickRegenerateConsoleId() {
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
showConfirmationDialog(
R.string.regenerate_console_id,
R.string.regenerate_console_id_description
) {
SystemSaveGame.regenerateConsoleId()
notifyDataSetChanged()
}
}
fun onClickRegenerateMAC() {
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
showConfirmationDialog(
R.string.regenerate_mac_address,
R.string.regenerate_mac_address_description
) {
SystemSaveGame.regenerateMac()
notifyDataSetChanged()
}
@ -732,18 +758,18 @@ class SettingsAdapter(
}
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
val value = item.selectedValues;
val valuesId = item.valuesId;
val value = item.selectedValues
val valuesId = item.valuesId
if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId);
val res = BooleanArray(valuesArray.size){false}
val valuesArray = context.resources.getIntArray(valuesId)
val res = BooleanArray(valuesArray.size) { false }
for (index in valuesArray.indices) {
if (value.contains(valuesArray[index])) {
res[index] = true;
res[index] = true
}
}
return res;
return res
}
return BooleanArray(1){false};
return BooleanArray(1) { false }
}
}

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