Compare commits

...

285 Commits

Author SHA1 Message Date
向日葵 725270e46a Translation: Improve Chinese Simplified translation 2023-12-27 19:22:08 +08:00
liamwhite 12178c694a Merge pull request #12455 from liamwhite/end-wait
kernel: use simple mutex for object list container
2023-12-26 11:46:19 -05:00
liamwhite de1e5584b3 Merge pull request #12465 from liamwhite/proper-handle-table
service: fetch objects from the client handle table
2023-12-26 11:46:11 -05:00
liamwhite 1559984f77 Merge pull request #12471 from FearlessTobi/port-7146
Port citra-emu/citra#7146: "assert/logging: Stop the logging thread and flush the backends before crashing"
2023-12-26 11:46:04 -05:00
liamwhite 467ac4fdfe Merge pull request #12472 from FearlessTobi/port-7239
Port citra-emu/citra#7239: "common: Miscellaneous cleanups"
2023-12-26 11:45:57 -05:00
liamwhite 69b7100dac Merge pull request #12449 from liamwhite/debug-utils
renderer_vulkan: skip SetObjectNameEXT on unsupported driver
2023-12-26 11:45:39 -05:00
liamwhite 14dc41d4b3 Merge pull request #12448 from liamwhite/format-assert
renderer_vulkan: demote format assert to error log
2023-12-26 11:45:33 -05:00
liamwhite ad049f13aa Merge pull request #12415 from ameerj/ogl-draw-auto
gl_rasterizer: Implement DrawTransformFeedback macro
2023-12-26 11:45:25 -05:00
FearlessTobi 4f569fd568 assert/logging: Stop the logging thread and flush the backends before crashing
Co-Authored-By: SachinVin <26602104+SachinVin@users.noreply.github.com>
2023-12-26 10:35:14 +01:00
FearlessTobi 553dac2ae0 ring_buffer: Use feature macro
Co-Authored-By: GPUCode <47210458+GPUCode@users.noreply.github.com>
2023-12-25 14:10:40 +01:00
FearlessTobi 96abe0d7d3 main: Remove unused enum
Co-Authored-By: GPUCode <47210458+GPUCode@users.noreply.github.com>
2023-12-25 14:10:05 +01:00
Liam 47e44a6693 am/jit: reference memory instance from context 2023-12-24 19:36:42 -05:00
Liam cf8c7d4ed3 kernel: remove unecessary process member from handle table 2023-12-24 19:23:03 -05:00
Liam 5165ed9efd service: fetch objects from the client handle table 2023-12-24 19:20:43 -05:00
Fernando S 05e3db3ac9 Merge pull request #12394 from liamwhite/per-process-memory
general: properly support multiple memory instances
2023-12-24 16:23:14 +01:00
Liam e3491a9ee8 kernel: use simple mutex for object list container 2023-12-23 16:26:07 -05:00
Liam 6a1ddc5028 renderer_vulkan: skip SetObjectNameEXT on unsupported driver 2023-12-23 11:08:02 -05:00
Liam b1d4804c07 renderer_vulkan: demote format assert to error log 2023-12-23 11:04:02 -05:00
Liam c57ae803a6 kernel: fix resource limit imbalance 2023-12-22 21:52:49 -05:00
Liam db7b2bc8f1 kernel: restrict nce to applications 2023-12-22 21:52:49 -05:00
Liam 31bf57a310 general: properly support multiple memory instances 2023-12-22 21:52:49 -05:00
Liam cae675343c k_server_session: remove scratch buffer usage in favor of direct copy 2023-12-22 21:52:49 -05:00
Liam 35501ba41c k_server_session: process for guest servers 2023-12-22 21:52:49 -05:00
Liam 419055e484 kernel: instantiate memory separately for each guest process 2023-12-22 21:52:49 -05:00
liamwhite 91290b9be4 Merge pull request #12412 from ameerj/gl-query-prims
OpenGL: Add GL_PRIMITIVES_GENERATED and GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN queries
2023-12-22 11:42:05 -05:00
Fernando S 820f113d9e Merge pull request #12435 from liamwhite/type-check
shader_recompiler: ensure derivatives for textureGrad are f32
2023-12-22 17:41:13 +01:00
Fernando S 373a1ff2ce Merge pull request #12410 from liamwhite/more-mali-null
renderer_vulkan: don't pass null view when nullDescriptor is not supported
2023-12-22 17:40:47 +01:00
Fernando S 4d6b6ba76c Merge pull request #12432 from liamwhite/float-write
shader_recompiler: use float image operations on load/store when required
2023-12-22 17:40:26 +01:00
Liam 4aa713e861 shader_recompiler: ensure derivatives for textureGrad are f32 2023-12-21 19:06:33 -05:00
Liam 9e9aed41be shader_recompiler: use float image operations on load/store when required 2023-12-21 14:34:46 -05:00
liamwhite 3d268b8480 Merge pull request #12424 from t895/vsync-per-game-qt
qt: settings: Fix per-game vsync combobox
2023-12-21 10:53:06 -05:00
liamwhite ad7445d4cc Merge pull request #12425 from german77/temp-fix
service: hid: Fix crash on InitializeVibrationDevice
2023-12-21 10:50:22 -05:00
liamwhite 3a30271219 Merge pull request #12426 from t895/reload-text-fix
android: Fix "No games found" text appearing on load
2023-12-21 10:50:11 -05:00
t895 bb5196aaae qt: settings: Fix per-game vsync combobox 2023-12-21 01:15:05 -05:00
t895 d3070cafa7 android: Fix "No games found" text appearing on load 2023-12-21 00:49:22 -05:00
Narr the Reg 5cd3b6f58c service: hid: Fix crash on InitializeVibrationDevice 2023-12-20 22:52:36 -06:00
liamwhite bedc758fe7 Merge pull request #12414 from jbeich/vk274
externals: update Vulkan-Headers to v1.3.274
2023-12-20 12:46:50 -05:00
liamwhite 76701185ad Merge pull request #12400 from ameerj/vk-query-prefix-fix
vk_query_cache: Fix prefix sum max_accumulation_limit logic
2023-12-20 12:46:41 -05:00
Fernando S f1cb14eb54 Merge pull request #12417 from liamwhite/arm64-gcc-fix
nce: hide shadowing warnings from dynarmic headers
2023-12-20 18:46:08 +01:00
Fernando S f4f4a469a9 Merge pull request #12409 from liamwhite/bits-and-bytes
nce: fix read size in simd immediate emulation
2023-12-20 18:45:44 +01:00
Fernando S 9e5b4052ed Merge pull request #12403 from liamwhite/clipdistance
shader_recompiler: use minimal clip distance array
2023-12-20 18:45:20 +01:00
Fernando S 234867b84d Merge pull request #12390 from liamwhite/binding-insanity
renderer_vulkan: work around turnip binding bug in a610
2023-12-20 18:44:47 +01:00
Ameer J 61e8c5f798 gl_rasterizer: Less spammy log for unimplemented resets 2023-12-20 11:51:44 -05:00
Liam 4b60aec190 nce: hide shadowing warnings from dynarmic headers 2023-12-20 11:07:50 -05:00
Ameer J bbc0ed118d gl_rasterizer: Implement DrawTransformFeedback macro 2023-12-19 19:54:57 -05:00
Jan Beich ecfba79d98 externals: update Vulkan-Headers to v1.3.274 2023-12-20 01:13:09 +01:00
Jan Beich 310834aea2 vulkan_common: unbreak build with Vulkan-Headers 1.3.274
src/video_core/vulkan_common/vulkan_wrapper.cpp:293:13: error: enumeration value 'VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR' not handled in switch [-Werror,-Wswitch]
    switch (result) {
            ^~~~~~
2023-12-20 01:12:41 +01:00
liamwhite 6a1fa9bb17 Merge pull request #12411 from ameerj/gl-nv-tfb-fixups
gl_buffer_cache: Reintroduce NV_vertex_buffer_unified_memory
2023-12-19 18:36:50 -05:00
Ameer J db8a601cf8 OpenGL: Add GL_PRIMITIVES_GENERATED and GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN queries 2023-12-19 17:32:31 -05:00
Ameer J 1bb76201e6 gl_rasterizer: Silence spammy logs 2023-12-19 17:13:23 -05:00
Ameer J 372bca5945 gl_buffer_cache: Reintroduce NV_vertex_buffer_unified_memory
Workaround Nvidia drivers complaining when a buffer is bound as both a vertex buffer and transform feedback buffer
2023-12-19 17:13:23 -05:00
Liam 93c19a40bf nce: increase handler stack size 2023-12-19 15:24:13 -05:00
Liam d0a75580da renderer_vulkan: don't pass null view when nullDescriptor is not supported 2023-12-19 15:13:10 -05:00
Charles Lombardo 345ec25532 Merge pull request #12408 from german77/lang
yuzu: Read/Save category Paths
2023-12-19 14:40:10 -05:00
Liam a94721fde0 nce: fix read size in simd immediate emulation 2023-12-19 12:51:19 -05:00
Narr the Reg 816c7a8d1f yuzu: Read/Save category Paths 2023-12-19 11:34:53 -06:00
Fernando S efe52db690 Merge pull request #12382 from liamwhite/image-limit
renderer_vulkan: allow up to 7 swapchain images
2023-12-19 16:15:40 +01:00
Fernando S d61df0f400 Merge pull request #12387 from liamwhite/oboe
android: add oboe audio sink
2023-12-19 16:15:07 +01:00
Fernando S b14547b8b6 Merge pull request #12392 from liamwhite/mode
fs: implement OpenDirectoryMode
2023-12-19 16:14:29 +01:00
Fernando S 97ad3e7530 Merge pull request #12391 from yuzu-emu/revert-12344-its-free-real-estate
Revert "video_core: use interval map for page count tracking"
2023-12-19 16:14:09 +01:00
Fernando S 0589a32f75 Merge pull request #12304 from liamwhite/flinger-wtf
nvnflinger: mark buffer as acquired when acquired
2023-12-19 16:12:56 +01:00
liamwhite 617dc0f822 Merge pull request #12402 from german77/lang
yuzu: Make language persistent and remove symbols_path
2023-12-18 23:10:59 -05:00
Liam fcfa8b680b shader_recompiler: use minimal clip distance array 2023-12-18 22:25:14 -05:00
Liam 94244437de shader_recompiler: ignore clip distances beyond driver support level 2023-12-18 22:25:14 -05:00
Narr the Reg 53956a2990 yuzu: Make language persistent and remove symbols_path 2023-12-18 20:28:55 -06:00
Liam a7731abb72 oboe_sink: specify additional required parameters 2023-12-18 17:27:32 -05:00
liamwhite 50fd029eaa Merge pull request #12349 from Kelebek1/return_system_channels_active
Have GetActiveChannelCount return the system channels instead of host device channels
2023-12-18 15:06:16 -05:00
Ameer J a2b567dfd6 vk_query_cache: Fix prefix sum max_accumulation_limit logic 2023-12-18 12:37:55 -05:00
Liam b770f6a985 fs: implement OpenDirectoryMode 2023-12-18 00:12:38 -05:00
Liam 797e8fdbc3 oboe_sink: set low latency performance mode 2023-12-17 21:05:00 -05:00
liamwhite b8c5027686 Merge pull request #12389 from liamwhite/string-copy
path_util: copy output for GetParentPath
2023-12-17 19:01:04 -05:00
liamwhite 65e646eeba Revert "video_core: use interval map for page count tracking" 2023-12-17 18:59:49 -05:00
Liam fba3fa705d renderer_vulkan: work around turnip binding bug in a610 2023-12-17 15:45:09 -05:00
Liam 09e8fb75ce path_util: copy output for GetParentPath 2023-12-17 14:25:42 -05:00
Liam 6ca530a721 android: add oboe to audio configuration 2023-12-17 11:44:49 -05:00
Liam e01c535178 oboe_sink: implement channel count querying 2023-12-17 10:10:14 -05:00
Liam 7239547ead android: add oboe audio sink 2023-12-17 01:42:59 -05:00
Liam 7fc06260d1 renderer_vulkan: allow up to 7 swapchain images 2023-12-16 18:59:44 -05:00
liamwhite e357896674 Merge pull request #12378 from liamwhite/offsetof
set: add cstddef for offsetof macro
2023-12-16 13:58:13 -05:00
liamwhite 225f4f40cb Merge pull request #12377 from ameerj/tfb-batch-oopsie
gl_buffer_cache: Fix tfb binding typo
2023-12-16 13:58:06 -05:00
liamwhite 927be75616 Merge pull request #12345 from liamwhite/a-flock-of-seagulls
renderer_vulkan: cap async presentation frame count
2023-12-16 13:58:00 -05:00
liamwhite 00965e6c34 Merge pull request #12335 from t895/per-game-settings
android: Game Properties
2023-12-16 13:57:54 -05:00
liamwhite 4bf1f217ae Merge pull request #12331 from liamwhite/layer-confusion
vi: fix confusion between closing and destroying layers
2023-12-16 13:57:42 -05:00
Liam fcc85abe27 nvnflinger: mark buffer as acquired when acquired 2023-12-16 13:40:04 -05:00
Liam 6851e93296 audio: skip coefficient normalization for downmix 2023-12-16 13:05:55 -05:00
Liam 67660972c9 set: add cstddef for offsetof macro 2023-12-16 12:57:37 -05:00
Kelebek1 ffbba74c91 Have GetActiveChannelCount return the system channels instead of host device channels 2023-12-16 12:49:28 -05:00
Ameer J 2b0cf73bf0 gl_buffer_cache: Fix tfb binding typo 2023-12-16 12:48:21 -05:00
liamwhite a093f3d47a Merge pull request #12184 from Kelebek1/system_settings
Make system settings persistent across boots
2023-12-16 11:47:52 -05:00
liamwhite 4f600f746a Merge pull request #12237 from liamwhite/nce-sigtrap
nce: implement instruction emulation for misaligned memory accesses
2023-12-16 11:47:35 -05:00
liamwhite 360418f1a1 Merge pull request #12290 from liamwhite/deferred-path-split
Improve path splitting speed
2023-12-16 11:47:29 -05:00
liamwhite 3bc7575c47 Merge pull request #12344 from liamwhite/its-free-real-estate
video_core: use interval map for page count tracking
2023-12-16 11:47:21 -05:00
liamwhite fde8dc1652 Merge pull request #12358 from liamwhite/optimized-alloc
common: use memory holepunching when clearing memory
2023-12-16 11:47:03 -05:00
liamwhite b8f83aa4bf Merge pull request #12359 from german77/real_shared
service: hid: Allow to create multiple instances of shared memory
2023-12-16 11:46:51 -05:00
liamwhite 85b1e17df6 ssl: fix output byte buffer size issue (#12372) 2023-12-16 17:42:33 +01:00
Kelebek1 4144c517a5 Make system settings persistent across boots 2023-12-16 06:01:54 +00:00
Liam 8ad5f2c506 common: use memory holepunching when clearing memory 2023-12-14 23:44:33 -05:00
Liam 2a3f84aaf2 video_core: lock interval map update 2023-12-14 22:10:21 -05:00
Liam 030e6b3980 video_core: use interval map for page count tracking 2023-12-14 21:54:36 -05:00
Narr the Reg e8ad603cd9 core: Make sure npad is initialized 2023-12-14 20:04:38 -06:00
Liam b560ade663 renderer_vulkan: bound async presentation queue growth 2023-12-14 15:54:56 -05:00
Narr the Reg d10464de30 core: hid: Clean up headers 2023-12-14 09:36:33 -06:00
Narr the Reg 64f68e9635 service: hid: Allow to create multiple instances of shared memory 2023-12-13 23:24:28 -06:00
liamwhite 462ba1b360 Merge pull request #12354 from liamwhite/mackage-panager
ci: fix homebrew installation issue in actions runner images
2023-12-13 23:15:43 -05:00
Liam 4a86a55174 ci: fix homebrew installation issue in actions runner images 2023-12-13 14:03:51 -05:00
t895 86d26914a2 android: Rework InstallableProperty view with icon 2023-12-12 17:25:37 -05:00
t895 6ae4177b25 android: Prevent editing non-savable settings in per-game settings 2023-12-12 17:25:37 -05:00
t895 f6bf8b3ed3 android: Pre-select custom config in game launch dialog 2023-12-12 17:25:37 -05:00
t895 345fb6b226 android: Use confirmation dialog when deleting shader cache 2023-12-12 17:25:37 -05:00
t895 87a9dc9489 android: Always use custom settings when launched from intent 2023-12-12 17:25:37 -05:00
t895 6c6e8b8de0 settings: Allow vsync to be changed per-game 2023-12-12 17:25:37 -05:00
t895 5acffe75df android: Adjust variable name format for native config 2023-12-12 17:25:37 -05:00
t895 ac222ceba2 android: Add game dir entries to FilesystemProvider
Allows us to correctly parse update metadata
2023-12-12 17:25:37 -05:00
t895 f9d4827102 android: Fix games list loading thread safety
Previously we relied on a stateflow for reloading state. Now we use an atomic boolean.
2023-12-12 17:25:37 -05:00
t895 7ea7c72dde android: Collect latest information for games list 2023-12-12 17:25:37 -05:00
t895 809230f634 android: Remove global save import/exporter UI
The original implementation exposed here was fundamentally broken where it would not export or import all of your saves depending on your user profile configuration
2023-12-12 17:25:37 -05:00
t895 698c854d5b android: Compare all properties between games in DiffCallback 2023-12-12 17:25:37 -05:00
t895 ca5b135ddf android: Expose MemoryUtil size formatting function 2023-12-12 17:25:37 -05:00
t895 dbddc627d4 android: Add JNI initialization information for Game class
Unused in this PR, but will be useful later
2023-12-12 17:25:37 -05:00
t895 62fc386bb4 settings: Allow CPU Debug and Fastmem to be changed per-game 2023-12-12 17:25:37 -05:00
t895 f2eb3c579f android: Add per-game drivers 2023-12-12 17:25:37 -05:00
t895 2fce812026 android: Add per-game settings 2023-12-12 17:25:36 -05:00
t895 e975f3cde9 android: Add Game properties
This commit has the UI for viewing a game's properties on long-press and some links to useful tools like
- Game info
- Shortcut to settings (global in this commit)
- Addon manager with installer
- Save data manager
- Option to clear all save data
- Option to clear shader cache
2023-12-12 17:25:36 -05:00
t895 6b5fb2063f frontend_common: Fix settings reload bug
This clears the touch_from_button_maps array before we read new data into it because this read duplicate data on a reload otherwise.
2023-12-12 17:25:36 -05:00
t895 70c3d36536 android: Refactor settings to expose more options
In AbstractSetting, this removes the category, androidDefault, and valueAsString properties as they are no longer needed and have replacements. isSwitchable, global, and getValueAsString are all exposed and give better options for working with global/per-game settings.
2023-12-12 17:25:36 -05:00
liamwhite d590cfb9d0 Merge pull request #12342 from FearlessTobi/fix-msvc
vcpkg: Update fmt to 10.1.1
2023-12-12 15:32:09 -05:00
liamwhite ded419ef2b Merge pull request #12343 from FearlessTobi/fix-typo
configure_debug: Fix small typo
2023-12-12 15:32:00 -05:00
FearlessTobi 4c3f898789 configure_debug: Fix small typo 2023-12-12 20:38:54 +01:00
FearlessTobi 46c259bb20 vcpkg: Update fmt to 10.1.1
Should fix compiling on the latest version of MSVC.
2023-12-12 19:27:20 +01:00
Liam adc3079613 vi: fix confusion between closing and destroying layers 2023-12-12 12:14:23 -05:00
liamwhite 15bebf1695 Merge pull request #12328 from german77/profile_manager
core: Use single instance of profile manager
2023-12-12 11:06:37 -05:00
liamwhite 5c840334b8 Merge pull request #12333 from german77/aruid_free
service: hid: Improve CreateAppletResource implementation and free resources
2023-12-12 11:06:24 -05:00
Liam a05c242429 nce: adjust initialization for repeated calls 2023-12-11 23:02:01 -05:00
Liam bd59934350 nce: implement instruction emulation for misaligned memory accesses 2023-12-10 18:23:42 -05:00
liamwhite 11b123ba01 Merge pull request #12322 from liamwhite/savedata-absurdity
fs: don't enumerate hidden savedata size file
2023-12-10 18:17:11 -05:00
liamwhite 24e7ace876 Merge pull request #12327 from liamwhite/tipc
sm:: fix tipc deserialization
2023-12-10 18:17:04 -05:00
liamwhite 62586c1676 Merge pull request #12317 from liamwhite/sc-fix
kernel: fix single core
2023-12-10 18:16:58 -05:00
liamwhite 108737fcc6 Merge pull request #12321 from liamwhite/ro2
ro: add separate ro service
2023-12-10 18:16:50 -05:00
german77 abfebe5cc4 service: hid: Improve CreateAppletResource implementation and free resources 2023-12-10 16:17:51 -06:00
german77 a22a025c5b core: Use single instance of profile manager 2023-12-10 11:29:43 -06:00
Liam a529ef4c09 sm:: fix tipc deserialization 2023-12-09 22:56:21 -05:00
liamwhite 875568bb3e Merge pull request #12296 from liamwhite/client-session
kernel: implement remaining IPC syscalls
2023-12-09 19:03:56 -05:00
liamwhite 988e557ec8 Merge pull request #12299 from liamwhite/light-ipc
kernel: implement light IPC
2023-12-09 19:03:50 -05:00
liamwhite 6d2af32f29 Merge pull request #12323 from liamwhite/buffer-format
fbshare: set external format correctly
2023-12-09 19:00:52 -05:00
liamwhite 8f9d5c3143 Merge pull request #12325 from GPUCode/better-nce-message
settings: Clearer NCE error messages
2023-12-09 19:00:44 -05:00
GPUCode dc0fb56f3a settings: Clearer NCE error messages 2023-12-09 23:37:14 +02:00
Liam 7ba4a8f4a3 ro: add separate ro service 2023-12-09 15:50:34 -05:00
Liam 8ef1fdafa2 fbshare: set external format correctly 2023-12-09 14:51:15 -05:00
Liam d597383ab2 fs: don't enumerate hidden savedata size file 2023-12-09 14:05:16 -05:00
Liam 5feda37688 service: populate pid and handle table from client 2023-12-09 13:45:25 -05:00
Liam 34e4012998 service: use interface factory in server manager 2023-12-09 13:45:25 -05:00
liamwhite c1924951ad Merge pull request #12289 from german77/aruid
service: hid: Introduce proper AppletResource emulation
2023-12-09 13:41:06 -05:00
liamwhite 5646e313a0 Merge pull request #12320 from liamwhite/debug-fix
debug: fix reading of module names
2023-12-09 13:38:39 -05:00
liamwhite f447996080 Merge pull request #12319 from t895/refresh-list-bug
android: Listen for directory selection in viewmodel
2023-12-09 13:38:34 -05:00
liamwhite 42b34a0dc5 Merge pull request #12318 from t895/nce-default
settings: Enable NCE by default on capable systems
2023-12-09 13:38:27 -05:00
Liam fe5e4bd846 debug: fix reading of module names 2023-12-09 11:18:10 -05:00
t895 a53cd2854e android: Listen for directory selection in viewmodel
Fixes broken game directory selection setup flow
2023-12-08 22:02:04 -05:00
Liam 1d731dd1ff kernel: fix single core 2023-12-08 20:31:18 -05:00
t895 8225ac004e settings: Enable NCE by default on capable systems 2023-12-08 20:25:07 -05:00
liamwhite 52e6b8a2d3 Merge pull request #12274 from liamwhite/srgb-nonsense
renderer_vulkan: do not recreate swapchain for srgb
2023-12-08 12:26:13 -05:00
liamwhite 13131e602f Merge pull request #12208 from liamwhite/romfs
romfs: optimize parsing and building
2023-12-08 12:25:58 -05:00
liamwhite 7761f29892 Merge pull request #11214 from lat9nq/ff-deprecated
codec: Update to use av frame flags
2023-12-07 23:13:13 -05:00
HurricanePootis e92b10f971 dist: add udev rule to enable user hidraw access (#12292)
* dist: add udev rule to enable user hidraw access

* dist: amend install instructions for udev rules

* dist: change udev prefix prefix from 99 to 72

* dist: fix header typo for udev rule

* Update dist/72-yuzu-input.rules

shoutout to @liamwhite

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* Update dist/72-yuzu-input.rules

shout out to @liamwhite

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* Update dist/72-yuzu-input.rules

shout out to @liamwhite

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* Update dist/72-yuzu-input.rules

shout out to @liamwhite

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* Update dist/72-yuzu-input.rules

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* dist: add spdx header to udev rules

* dist: change udev modes to 0660

* Update dist/72-yuzu-input.rules

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

* Update dist/72-yuzu-input.rules

Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>

---------

Co-authored-by: HurricanePootis <hurricanepootis@protonmail.com>
Co-authored-by: liamwhite <liamwhite@users.noreply.github.com>
2023-12-07 23:12:56 -05:00
Liam 9268f265a1 kernel: implement light IPC 2023-12-07 09:13:43 -05:00
Narr the Reg e445ef9d60 service: hid: Introduce proper AppletResource emulation 2023-12-06 20:24:04 -06:00
Liam 40bb176c39 kernel: implement remaining IPC syscalls 2023-12-06 17:33:00 -05:00
Fernando S 8a79dd2d6c Merge pull request #12236 from liamwhite/cpu-refactor
core: refactor emulated cpu core activation
2023-12-06 14:19:17 +01:00
BreadFish64 d5de9402ee Improve path splitting speed 2023-12-05 23:17:19 -05:00
lat9nq 4cd3f9f4f9 codec: Update to use av frame flags
Resolves Clang -Wdeprecated-declarations warning from interlaced_frame
2023-12-05 21:10:38 -05:00
liamwhite 167efb2d2b Merge pull request #12271 from liamwhite/pretext-fix
nce: fix pre-text patch for single modules
2023-12-05 07:51:53 -05:00
liamwhite 8e0e066c3f Merge pull request #12283 from t895/language-troubles
frontend_common: Use optional for language default
2023-12-04 22:45:08 -05:00
Liam f0ee3e29cb arm: fix context save of vector regs 2023-12-04 22:19:11 -05:00
t895 5d4da07943 frontend_common: Use optional for language default 2023-12-04 19:49:01 -05:00
Liam 45c87c7e6e core: refactor emulated cpu core activation 2023-12-04 10:37:16 -05:00
liamwhite 90e87c40e8 Merge pull request #12235 from liamwhite/flip-clip
renderer_vulkan: adjust window origin and swizzle independently
2023-12-03 21:59:11 -05:00
Liam 6b7dc587cf texture_cache: fix max_element comparison function 2023-12-03 18:40:19 -05:00
Liam f05cb69d4f renderer_opengl: remove srgb conversion logic 2023-12-03 17:08:25 -05:00
Liam 382cf087a0 renderer_vulkan: do not recreate swapchain for srgb 2023-12-03 16:43:54 -05:00
Liam 0751488727 fsmitm_romfsbuild: optimize for data locality 2023-12-03 16:29:57 -05:00
Liam 4bc932261b romfs: cache file and directory metadata tables 2023-12-03 16:29:57 -05:00
liamwhite 5fb1a83e4c Merge pull request #12094 from ameerj/gl-buffer-cache-batch-vtx
gl_buffer_cache: Batch vertex/tfb buffer binding
2023-12-03 16:27:22 -05:00
liamwhite 6da8301773 Merge pull request #12196 from ameerj/glsl-cbuf-sizes
GLSL: Use known cbuf sizes when possible
2023-12-03 16:27:07 -05:00
liamwhite fedeff7a89 Merge pull request #12263 from liamwhite/null-romfs
file_sys: handle null romfs
2023-12-03 16:26:14 -05:00
Liam 9de99839bd nce: fix pre-text patch for single modules 2023-12-02 16:14:52 -05:00
liamwhite 69529a748c Merge pull request #12261 from liamwhite/fruit-company
texture_cache: use pedantic type names
2023-12-02 09:28:43 -05:00
liamwhite 0ed292568f Merge pull request #12260 from t895/install-reload-fix
android: Don't reload filesystem on update install
2023-12-02 09:28:38 -05:00
liamwhite 7fb6fbcd87 Merge pull request #12259 from jbeich/freebsd-mmap
host_memory: unbreak build on FreeBSD x86_64
2023-12-02 09:28:30 -05:00
liamwhite 82ac3003a6 Merge pull request #12256 from GPUCode/fxaa
vk_blit_screen: Use correct format for fxaa renderpass
2023-12-02 09:28:18 -05:00
Liam 45b6161582 file_sys: handle null romfs 2023-12-01 23:39:48 -05:00
Liam 345ea568ba texture_cache: use pedantic type names 2023-12-01 22:46:33 -05:00
t895 08be9cff0f android: Don't reload filesystem on update install 2023-12-01 20:19:28 -05:00
liamwhite e9a43bae6f Merge pull request #12255 from german77/amiibo
service: nfc: Don't tag amiibos as corrupted if they are loaded as read only
2023-12-01 18:39:38 -05:00
Jan Beich 01d3e250ab host_memory: move MAP_ALIGNED_SUPER attempt after 448d4815de
src/common/host_memory.cpp:410:14: error: unused function 'ChooseVirtualBase' [-Werror,-Wunused-function]
  410 | static void* ChooseVirtualBase(size_t virtual_size) {
      |              ^~~~~~~~~~~~~~~~~
2023-12-02 00:25:50 +01:00
Jan Beich 270d290e65 host_memory: allow missing MAP_NORESERVE on FreeBSD after 448d4815de
src/common/host_memory.cpp:408:47: error: use of undeclared identifier 'MAP_NORESERVE'
                MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
                                              ^
2023-12-02 00:25:50 +01:00
GPUCode d74dd4faf9 vk_blit_screen: Use correct format for fxaa renderpass 2023-12-01 22:55:50 +02:00
Narr the Reg e40a2d2fd2 service: nfc: Don't tag amiibos as corrupted if they are loaded as read only 2023-12-01 14:21:55 -06:00
liamwhite f6fdacd935 Merge pull request #12243 from zhaobot/tx-update-20231201021513
Update translations (2023-12-01)
2023-12-01 09:17:06 -05:00
liamwhite 1c21d6c2c2 Merge pull request #12056 from ameerj/opengl-neglect
OpenGL: Implement async downloads in buffer and texture caches
2023-12-01 09:16:56 -05:00
The yuzu Community 7dd78e8dcc Update translations (2023-12-01) 2023-12-01 02:15:30 +00:00
Liam e7dd968ac4 renderer_vulkan: adjust window origin and swizzle independently 2023-11-30 12:33:26 -05:00
liamwhite 3c45ba1c22 Merge pull request #12234 from abouvier/unbundle-gamemode
cmake: prefer system gamemode library
2023-11-30 11:52:03 -05:00
Alexandre Bouvier d2bb9e9729 cmake: prefer system gamemode library 2023-11-30 16:54:00 +01:00
liamwhite 127bfb81d5 Merge pull request #12229 from liamwhite/qcom-wtf
renderer_vulkan: exclude more qcom drivers from extensions
2023-11-30 09:21:27 -05:00
liamwhite 3a12fe5d13 Merge pull request #12227 from jbeich/gamemode
cmake: unbreak build on FreeBSD by re-enabling gamemode
2023-11-30 09:21:19 -05:00
liamwhite 5345ab40eb Merge pull request #12225 from liamwhite/mac-ci
Add mac workflow
2023-11-30 09:21:12 -05:00
liamwhite 57a391e71d Merge pull request #12074 from GPUCode/yuwu-on-the-metal
Implement Native Code Execution (NCE)
2023-11-30 09:20:55 -05:00
Liam 8f62e8e63f renderer_vulkan: exclude more qcom drivers from extensions 2023-11-29 21:06:06 -05:00
Jan Beich df96caec79 cmake: sync gamemode conditionals with code after 5eec980a2d
FAILED: bin/yuzu
ld: error: unable to find library -lgamemode

FAILED: bin/yuzu-cmd
ld: error: undefined symbol: Common::Linux::StartGamemode()
>>> referenced by yuzu.cpp
>>>               src/yuzu_cmd/CMakeFiles/yuzu-cmd.dir/yuzu.cpp.o:(main)

ld: error: undefined symbol: Common::Linux::StopGamemode()
>>> referenced by yuzu.cpp
>>>               src/yuzu_cmd/CMakeFiles/yuzu-cmd.dir/yuzu.cpp.o:(main)
2023-11-30 00:05:11 +01:00
GPUCode 4a3abba16d core: Rename patcher file 2023-11-29 23:49:16 +02:00
Liam d21305c2e7 add mac workflow 2023-11-29 16:02:37 -05:00
Narr the Reg 9dc9aaf4af Merge pull request #12223 from liamwhite/fruit-company
general: conditionally compile gamemode on linux only
2023-11-29 14:28:37 -06:00
Liam 5eec980a2d general: conditionally compile gamemode on linux only 2023-11-29 14:26:11 -05:00
liamwhite aded28f276 Merge pull request #12204 from t895/config-migration
android: Multi directory UI
2023-11-29 12:34:09 -05:00
liamwhite 80c4743754 Merge pull request #12203 from liamwhite/crash-fix
set: don't load version nca with null romfs
2023-11-29 12:34:02 -05:00
liamwhite 498159d719 Merge pull request #12201 from Macj0rdan/controller-applet-fix
Fixed controller applet crashing when on FW17+
2023-11-29 12:33:50 -05:00
liamwhite 91ad6b7098 Merge pull request #12194 from liamwhite/fruit-company
video_core: fix mac compile
2023-11-29 12:33:41 -05:00
liamwhite df49795bcb Merge pull request #12154 from liamwhite/more-drivers
vulkan_device: add names for more driverID enumeration values
2023-11-29 12:33:34 -05:00
liamwhite 337e37f91d Merge pull request #11946 from flodavid/gamemode
Enable (Feral Interactive) Gamemode on Linux
2023-11-29 12:33:09 -05:00
liamwhite 992ca8c358 Merge pull request #11902 from ameerj/ssbo-align
shader_recompiler: Align SSBO offsets to meet host requirements
2023-11-29 12:32:52 -05:00
GPUCode 340548aba7 cmake: Move HAS_NCE to root cmake
* So we can use it in common
2023-11-29 01:35:06 +02:00
t895 7dddf5cb3c android: Save global settings in onStop 2023-11-27 23:46:30 -05:00
t895 b8f66c9412 android: Multi directory UI 2023-11-27 23:46:29 -05:00
Liam fd29227bc4 set: don't load version nca with null romfs 2023-11-27 15:20:18 -05:00
FrozenAra e21f96ffde Fixed controller applet crashing when on FW17+ 2023-11-27 20:01:30 +01:00
liamwhite c7649a0cdb Merge pull request #12183 from german77/justmii
service: nfc: Validate mii data
2023-11-27 11:56:37 -05:00
liamwhite 5a96c525e3 Merge pull request #12160 from german77/mouse_constrain
yuzu: Constrain mouse in render window when emulated
2023-11-27 11:56:24 -05:00
Ameer J db1d32485e GLSL: Prefer known used cbuf sizes 2023-11-26 23:25:29 -05:00
Ameer J 1d11fe00a3 Merge branch 'master' into ssbo-align 2023-11-26 21:08:53 -05:00
amazingfate a76a8fb5fe qt: add cpu_backend configuration 2023-11-26 20:44:07 -05:00
Liam 7c1cb5e8c9 video_core: fix mac compile 2023-11-26 20:04:06 -05:00
Liam 15f35b8657 general: fix mac compile 2023-11-26 19:50:10 -05:00
Liam 7482e03c77 loader: fix gcc compile 2023-11-26 19:34:07 -05:00
Fernando S f21340f7aa Merge pull request #11535 from GPUCode/upload_cmdbuf
renderer_vulkan: Introduce separate cmd buffer for uploads
2023-11-26 18:33:05 +01:00
liamwhite e0c894408a Merge pull request #12180 from german77/cabinetmii
service: am: Create random mii for cabinet applet
2023-11-26 09:50:08 -05:00
liamwhite 257a1c884d Merge pull request #12156 from german77/firmwarevs
yuzu: Display firmware version
2023-11-26 09:49:53 -05:00
liamwhite c100d7e802 Merge pull request #12175 from abouvier/unbundle-simpleini
cmake: prefer system simpleini library
2023-11-26 09:49:43 -05:00
german77 281eb020ea service: nfc: Validate mii data 2023-11-25 23:40:01 -06:00
german77 4ce6762945 service: am: Create random mii for cabinet applet 2023-11-25 21:31:05 -06:00
Alexandre Bouvier fe3702223f cmake: prefer system simpleini library 2023-11-26 03:45:10 +01:00
liamwhite 83aa66b17d Merge pull request #12166 from german77/redmagic
android: input: Flip abxy on redmagic controllers
2023-11-25 20:56:40 -05:00
liamwhite 8d7a55be5b Merge pull request #12169 from liamwhite/its-time-to-stop
time: undef GetCurrentTime for Windows
2023-11-25 20:56:31 -05:00
german77 de58618421 android: input: Flip abxy on backbone labs controllers 2023-11-25 18:22:03 -06:00
Liam e6847c65a8 time: undef GetCurrentTime for Windows 2023-11-25 16:18:02 -05:00
german77 b5bde8451c android: input: Flip abxy on redmagic controllers 2023-11-25 10:53:59 -06:00
liamwhite 090ea0281c Merge pull request #12153 from liamwhite/deck2
renderer_vulkan: exclude steam deck oled from force max clock setting
2023-11-25 10:17:18 -05:00
liamwhite bc4818b058 Merge pull request #12151 from german77/no-crash
core: hid: Ensure output devices are initialized
2023-11-25 10:17:11 -05:00
liamwhite 2993d3bb49 Merge pull request #12110 from liamwhite/mali-nullview
vk_texture_cache: add workaround for nullDescriptor on Mali
2023-11-25 10:17:00 -05:00
GPUCode 6432508740 oaknut: Address warnings 2023-11-25 00:47:43 -05:00
GPUCode 5a9ffa81a6 host_memory: Simplify randomness generation 2023-11-25 00:47:43 -05:00
GPUCode 9ff8d0f3e6 Address more review comments 2023-11-25 00:47:43 -05:00
Liam d040b27a35 loader: apply nso patch to offset program image 2023-11-25 00:47:36 -05:00
Liam cf534f5149 arm_nce: skip data aborts for crash handling parity 2023-11-25 00:47:36 -05:00
Liam 20de0ddf1f android: show current backend in fps overlay 2023-11-25 00:47:36 -05:00
GPUCode 1cde01c8c8 arm: Print backtrace on data abort 2023-11-25 00:47:36 -05:00
Liam f542a3bb7a patch: check offsets from first code word 2023-11-25 00:47:36 -05:00
GPUCode 3ec3cca4d8 core: Define HAS_NCE macro 2023-11-25 00:47:36 -05:00
GPUCode c37b5f431f common: Enforce fastmem for nce usage 2023-11-25 00:47:36 -05:00
Liam 263b7a44f9 arm_nce: skip dc cvac on possibly write-protected areas 2023-11-25 00:47:36 -05:00
GPUCode 6de2edcca1 Address some review comments 2023-11-25 00:47:35 -05:00
GPUCode 8fab363237 android: Add cpu bakend gui toggle 2023-11-25 00:47:28 -05:00
Liam 9f91ba1f73 arm: Implement native code execution backend 2023-11-25 00:46:47 -05:00
GPUCode 4838837620 device_memory: Enable direct mapped addresses for nce 2023-11-25 00:46:47 -05:00
GPUCode 2e02efbdd0 externals: Add oaknut submodule 2023-11-25 00:46:47 -05:00
GPUCode 15331c2a60 settings: Add cpu backend setting 2023-11-25 00:46:15 -05:00
Liam f2a8409083 kernel: Manually specify aslr region start 2023-11-25 00:46:15 -05:00
GPUCode 5938a9582a core: Respect memory permissions in Map 2023-11-25 00:46:15 -05:00
Liam 4766baddf3 host_memory: Switch to FreeRegionManager 2023-11-25 00:46:15 -05:00
Liam 448d4815de host_memory: ensure map base is between 36 and 39 bits 2023-11-25 00:46:15 -05:00
--author=Liam 29e7d79a86 common: Add free region manager
* Abstraction for placeholder region tracking in host_memory
2023-11-25 00:46:15 -05:00
GPUCode 20011dfeb8 common: Add libc sigaction hook 2023-11-25 00:46:15 -05:00
Narr the Reg f61cf14646 yuzu: Constrain mouse in render window when emulated 2023-11-24 19:32:35 -06:00
Narr the Reg 2d4e7c8264 yuzu: Display firmware version 2023-11-24 12:58:07 -06:00
Liam dee792937f vulkan_device: add names for more driverID enumeration values 2023-11-24 12:00:41 -05:00
Liam 4a278b69b1 renderer_vulkan: exclude steam deck oled from force max clock setting 2023-11-24 11:38:39 -05:00
Narr the Reg 02a0b41a15 core: hid: Ensure output devices are initialized 2023-11-24 10:18:16 -06:00
Liam e7878e3cf8 vk_texture_cache: add workaround for nullDescriptor on Mali 2023-11-20 20:09:12 -05:00
Ameer J a595ed499d gl_buffer_cache: Batch vertex/tfb buffer binding 2023-11-19 17:17:16 -05:00
Fernando Sahmkow 50bcfa5fb9 Vulkan: Add a final barrier to the upload command buffer 2023-11-12 20:58:30 +01:00
GPUCode efc50485b8 renderer_vulkan: Introduce separate cmd buffer for uploads 2023-11-12 20:27:39 +01:00
Ameer J 75c5be55af shader_recompiler: Align SSBO offsets in GlobalMemory functions 2023-10-31 20:14:18 -04:00
Ameer J 735612c9b3 buffer_cache: Apply storage buffer alignment only to the offset 2023-10-31 20:10:54 -04:00
Ameer J 7d34800531 shader_recompiler: Align SSBO offsets to meet host requirements
Co-Authored-By: Billy Laws <blaws05@gmail.com>
2023-10-31 20:10:54 -04:00
Ameer J d1c878fb41 gl_texture_cache: Enable async downloads 2023-08-13 23:17:59 -05:00
Ameer J 9b112107e2 gl_buffer_cache: Enable async downloads 2023-08-13 23:17:54 -05:00
Ameer J 48b87d64de gl_staging_buffer_pool: Refactor allocation variables into a struct 2023-08-13 23:17:47 -05:00
491 changed files with 72303 additions and 29054 deletions
+1 -1
View File
@@ -3,4 +3,4 @@
[codespell]
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES,./src/android/app/src/main/res
ignore-words-list = aci,allright,ba,canonicalizations,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink
ignore-words-list = aci,allright,ba,canonicalizations,deques,fpr,froms,hda,inout,lod,masia,nam,nax,nce,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,vas,zink
+20
View File
@@ -68,6 +68,26 @@ jobs:
with:
name: ${{ matrix.type }}
path: artifacts/
build-mac:
name: 'test build (macos)'
needs: format
runs-on: macos-13
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Install dependencies
run: |
# workaround for https://github.com/actions/setup-python/issues/577
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite python@3.12
- name: Build
run: |
mkdir build
cd build
export Qt5_DIR="/usr/local/opt/qt@5/lib/cmake"
cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF
ninja
build-msvc:
name: 'test build (windows, msvc)'
needs: format
+3
View File
@@ -61,3 +61,6 @@
[submodule "simpleini"]
path = externals/simpleini
url = https://github.com/brofield/simpleini.git
[submodule "oaknut"]
path = externals/oaknut
url = https://github.com/merryhime/oaknut
+4
View File
@@ -151,3 +151,7 @@ License: GPL-3.0-or-later
Files: externals/stb/*
Copyright: Sean Barrett
License: MIT
Files: externals/gamemode/*
Copyright: Copyright 2017-2019 Feral Interactive
License: BSD-3-Clause
+14 -1
View File
@@ -142,6 +142,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (ENABLE_WEB_SERVICE)
list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
endif()
if (ANDROID)
list(APPEND VCPKG_MANIFEST_FEATURES "android")
endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@@ -260,6 +263,11 @@ if (UNIX)
add_definitions(-DYUZU_UNIX=1)
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR ${CMAKE_SYSTEM_NAME} STREQUAL "Linux"))
set(HAS_NCE 1)
add_definitions(-DHAS_NCE=1)
endif()
# Configure C++ standard
# ===========================
@@ -290,13 +298,14 @@ find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
find_package(RenderDoc MODULE)
find_package(SimpleIni MODULE)
find_package(stb MODULE)
find_package(VulkanMemoryAllocator CONFIG)
find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED)
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
find_package(Vulkan 1.3.256 REQUIRED)
find_package(Vulkan 1.3.274 REQUIRED)
endif()
if (ENABLE_LIBUSB)
@@ -337,6 +346,10 @@ if(ENABLE_OPENSSL)
find_package(OpenSSL 1.1.1 REQUIRED)
endif()
if (UNIX AND NOT APPLE)
find_package(gamemode 1.7 MODULE)
endif()
# Please consider this as a stub
if(ENABLE_QT6 AND Qt6_LOCATION)
list(APPEND CMAKE_PREFIX_PATH "${Qt6_LOCATION}")
+19
View File
@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
#
# SPDX-License-Identifier: GPL-3.0-or-later
find_path(SimpleIni_INCLUDE_DIR SimpleIni.h)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SimpleIni
REQUIRED_VARS SimpleIni_INCLUDE_DIR
)
if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
add_library(SimpleIni::SimpleIni INTERFACE IMPORTED)
set_target_properties(SimpleIni::SimpleIni PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SimpleIni_INCLUDE_DIR}"
)
endif()
mark_as_advanced(SimpleIni_INCLUDE_DIR)
+15
View File
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
find_package(PkgConfig QUIET)
pkg_search_module(GAMEMODE QUIET IMPORTED_TARGET gamemode)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(gamemode
REQUIRED_VARS GAMEMODE_INCLUDEDIR
VERSION_VAR GAMEMODE_VERSION
)
if (gamemode_FOUND AND NOT TARGET gamemode::headers)
add_library(gamemode::headers ALIAS PkgConfig::GAMEMODE)
endif()
+19
View File
@@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Allow systemd-logind to manage user access to hidraw with this file
# On most systems, this file should be installed to /etc/udev/rules.d/72-yuzu-input.rules
# Consult your distro if this is not the case
# Switch Pro Controller (USB/Bluetooth)
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", MODE="0660", TAG+="uaccess"
KERNEL=="hidraw*", KERNELS=="*057e:2009*", MODE="0660", TAG+="uaccess"
# Joy-Con L (Bluetooth)
KERNEL=="hidraw*", KERNELS=="*057e:2006*", MODE="0660", TAG+="uaccess"
# Joy-Con R (Bluetooth)
KERNEL=="hidraw*", KERNELS=="*057e:2007*", MODE="0660", TAG+="uaccess"
# Joy-Con Charging Grip (USB)
KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="200e", MODE="0660", TAG+="uaccess"
+1973 -892
View File
File diff suppressed because it is too large Load Diff
+1991 -901
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1877 -791
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1866 -782
View File
File diff suppressed because it is too large Load Diff
+1868 -781
View File
File diff suppressed because it is too large Load Diff
+1941 -852
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1890 -805
View File
File diff suppressed because it is too large Load Diff
+1942 -858
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1861 -780
View File
File diff suppressed because it is too large Load Diff
+1861 -780
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1860 -779
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1863 -779
View File
File diff suppressed because it is too large Load Diff
+1858 -772
View File
File diff suppressed because it is too large Load Diff
+2025 -939
View File
File diff suppressed because it is too large Load Diff
+11 -3
View File
@@ -20,6 +20,10 @@ if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
endif()
# Dynarmic
if (ARCHITECTURE_arm64 AND NOT TARGET merry::oaknut)
add_subdirectory(oaknut)
endif()
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
set(DYNARMIC_IGNORE_ASSERTS ON)
add_subdirectory(dynarmic)
@@ -189,8 +193,10 @@ if (ANDROID)
endif()
endif()
if (UNIX)
add_subdirectory(gamemode)
if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers)
add_library(gamemode INTERFACE)
target_include_directories(gamemode INTERFACE gamemode)
add_library(gamemode::headers ALIAS gamemode)
endif()
# Breakpad
@@ -296,4 +302,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
endif()
# SimpleIni
add_subdirectory(simpleini)
if (NOT TARGET SimpleIni::SimpleIni)
add_subdirectory(simpleini)
endif()
-11
View File
@@ -1,11 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
project(gamemode LANGUAGES CXX C)
add_library(gamemode include/gamemode_client.h)
target_link_libraries(gamemode PRIVATE common)
target_include_directories(gamemode PUBLIC include)
set_target_properties(gamemode PROPERTIES LINKER_LANGUAGE C)
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2017-2019 Feral Interactive
// SPDX-License-Identifier: BSD-3-Clause
/*
Copyright (c) 2017-2019, Feral Interactive
Vendored Submodule
+1
Submodule externals/oaknut added at 918bd94f02
+2 -1
View File
@@ -174,7 +174,8 @@ android {
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_BUNDLED_VCPKG=ON",
"-DYUZU_USE_BUNDLED_FFMPEG=ON",
"-DYUZU_ENABLE_LTO=ON"
"-DYUZU_ENABLE_LTO=ON",
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
)
abiFilters("arm64-v8a", "x86_64")
@@ -230,8 +230,6 @@ object NativeLibrary {
*/
external fun onTouchReleased(finger_id: Int)
external fun initGameIni(gameID: String?)
external fun setAppDirectory(directory: String)
/**
@@ -241,6 +239,8 @@ object NativeLibrary {
*/
external fun installFileToNand(filename: String, extension: String): Int
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
external fun initializeGpuDriver(
hookLibDir: String?,
customDriverDir: String?,
@@ -252,18 +252,11 @@ object NativeLibrary {
external fun initializeSystem(reload: Boolean)
external fun defaultCPUCore(): Int
/**
* Begins emulation.
*/
external fun run(path: String?)
/**
* Begins emulation from the specified savestate.
*/
external fun run(path: String?, savestatePath: String?, deleteSavestate: Boolean)
// Surface Handling
external fun surfaceChanged(surf: Surface?)
@@ -300,9 +293,13 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray
/**
* Notifies the core emulation that the orientation has changed.
* Returns the current CPU backend.
*/
external fun notifyOrientationChange(layout_option: Int, rotation: Int)
external fun getCpuBackend(): String
external fun applySettings()
external fun logSettings()
enum class CoreError {
ErrorSystemFiles,
@@ -533,6 +530,35 @@ object NativeLibrary {
*/
external fun isFirmwareAvailable(): Boolean
/**
* Checks the PatchManager for any addons that are available
*
* @param path Path to game file. Can be a [Uri].
* @param programId String representation of a game's program ID
* @return Array of pairs where the first value is the name of an addon and the second is the version
*/
external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>?
/**
* Gets the save location for a specific game
*
* @param programId String representation of a game's program ID
* @return Save data path that may not exist yet
*/
external fun getSavePath(programId: String): String
/**
* Adds a file to the manual filesystem provider in our EmulationSession instance
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or
* a normal path
*/
external fun addFileToFilesystemProvider(path: String)
/**
* Clears all files added to the manual filesystem provider in our EmulationSession instance
*/
external fun clearFilesystemProvider()
/**
* Button type for use in onTouchEvent
*/
@@ -172,7 +172,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
@@ -284,7 +284,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
PictureInPictureParams.Builder {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) {
0 -> Rational(16, 9)
1 -> Rational(4, 3)
2 -> Rational(21, 9)
@@ -331,7 +331,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
pictureInPictureActions.add(pauseRemoteAction)
}
if (BooleanSetting.AUDIO_MUTED.boolean) {
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
val unmuteIcon = Icon.createWithResource(
this@EmulationActivity,
R.drawable.ic_pip_unmute
@@ -376,7 +376,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val isEmulationActive = emulationViewModel.emulationStarted.value &&
!emulationViewModel.isEmulationStopping.value
pictureInPictureParamsBuilder.setAutoEnterEnabled(
BooleanSetting.PICTURE_IN_PICTURE.boolean && isEmulationActive
BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive
)
}
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
@@ -390,9 +390,13 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
}
if (intent.action == actionUnmute) {
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
} else if (intent.action == actionMute) {
if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
if (!BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(true)
}
}
buildPictureInPictureParams()
}
@@ -423,7 +427,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} catch (ignored: Exception) {
}
// Always resume audio, since there is no UI button
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
if (BooleanSetting.AUDIO_MUTED.getBoolean()) {
BooleanSetting.AUDIO_MUTED.setBoolean(false)
}
}
}
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Addon
class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return AddonViewHolder(it) }
}
override fun getItemCount(): Int = currentList.size
override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
holder.bind(currentList[position])
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(addon: Addon) {
binding.root.setOnClickListener {
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
}
binding.title.text = addon.title
binding.version.text = addon.version
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
addon.enabled = checked
}
binding.addonSwitch.isChecked = addon.enabled
}
}
private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
return oldItem == newItem
}
}
}
@@ -15,7 +15,7 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.Applet
import org.yuzu.yuzu_emu.model.AppletInfo
import org.yuzu.yuzu_emu.model.Game
@@ -28,7 +28,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
parent: ViewGroup,
viewType: Int
): AppletAdapter.AppletViewHolder {
CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.apply { root.setOnClickListener(this@AppletAdapter) }
.also { return AppletViewHolder(it) }
}
@@ -65,7 +65,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
view.findNavController().navigate(action)
}
inner class AppletViewHolder(val binding: CardAppletOptionBinding) :
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var applet: Applet
@@ -42,7 +42,7 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
if (driverViewModel.selectedDriver > position) {
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
}
if (GpuDriverHelper.customDriverData == driverData.second) {
if (GpuDriverHelper.customDriverSettingData == driverData.second) {
driverViewModel.setSelectedDriverIndex(0)
}
driverViewModel.driversToDelete.add(driverData.first)
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
AsyncDifferConfig.Builder(DiffCallback()).build()
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FolderAdapter.FolderViewHolder {
CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
.also { return FolderViewHolder(it) }
}
override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
holder.bind(currentList[position])
inner class FolderViewHolder(val binding: CardFolderBinding) :
RecyclerView.ViewHolder(binding.root) {
private lateinit var gameDir: GameDir
fun bind(gameDir: GameDir) {
this.gameDir = gameDir
binding.apply {
path.text = Uri.parse(gameDir.uriString).path
path.postDelayed(
{
path.isSelected = true
path.ellipsize = TextUtils.TruncateAt.MARQUEE
},
3000
)
buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
.show(
activity.supportFragmentManager,
GameFolderPropertiesDialogFragment.TAG
)
}
buttonDelete.setOnClickListener {
gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
}
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
return oldItem == newItem
}
}
}
@@ -44,19 +44,20 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
class GameAdapter(private val activity: AppCompatActivity) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener {
View.OnClickListener,
View.OnLongClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
// Create a new view.
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding.cardGame.setOnClickListener(this)
binding.cardGame.setOnLongClickListener(this)
// Use that view to create a ViewHolder.
return GameViewHolder(binding)
}
override fun onBindViewHolder(holder: GameViewHolder, position: Int) {
override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
holder.bind(currentList[position])
}
override fun getItemCount(): Int = currentList.size
@@ -125,10 +126,17 @@ class GameAdapter(private val activity: AppCompatActivity) :
}
}
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
view.findNavController().navigate(action)
}
override fun onLongClick(view: View): Boolean {
val holder = view.tag as GameViewHolder
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
view.findNavController().navigate(action)
return true
}
inner class GameViewHolder(val binding: CardGameBinding) :
RecyclerView.ViewHolder(binding.root) {
lateinit var game: Game
@@ -157,7 +165,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem.programId == newItem.programId
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
@@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
class GamePropertiesAdapter(
private val viewLifecycle: LifecycleOwner,
private var properties: List<GameProperty>
) :
RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): GamePropertyViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
PropertyType.Submenu.ordinal -> {
SubmenuPropertyViewHolder(
CardSimpleOutlinedBinding.inflate(
inflater,
parent,
false
)
)
}
else -> InstallablePropertyViewHolder(
CardInstallableIconBinding.inflate(
inflater,
parent,
false
)
)
}
}
override fun getItemCount(): Int = properties.size
override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
holder.bind(properties[position])
override fun getItemViewType(position: Int): Int {
return when (properties[position]) {
is SubmenuProperty -> PropertyType.Submenu.ordinal
else -> PropertyType.Installable.ordinal
}
}
sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(property: GameProperty)
}
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
GamePropertyViewHolder(binding.root) {
override fun bind(property: GameProperty) {
val submenuProperty = property as SubmenuProperty
binding.root.setOnClickListener {
submenuProperty.action.invoke()
}
binding.title.setText(submenuProperty.titleId)
binding.description.setText(submenuProperty.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
submenuProperty.iconId,
binding.icon.context.theme
)
)
binding.details.postDelayed({
binding.details.isSelected = true
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
}, 3000)
if (submenuProperty.details != null) {
binding.details.visibility = View.VISIBLE
binding.details.text = submenuProperty.details.invoke()
} else if (submenuProperty.detailsFlow != null) {
binding.details.visibility = View.VISIBLE
viewLifecycle.lifecycleScope.launch {
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
submenuProperty.detailsFlow.collect { binding.details.text = it }
}
}
} else {
binding.details.visibility = View.GONE
}
}
}
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
GamePropertyViewHolder(binding.root) {
override fun bind(property: GameProperty) {
val installableProperty = property as InstallableProperty
binding.title.setText(installableProperty.titleId)
binding.description.setText(installableProperty.descriptionId)
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
binding.icon.context.resources,
installableProperty.iconId,
binding.icon.context.theme
)
)
if (installableProperty.install != null) {
binding.buttonInstall.visibility = View.VISIBLE
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
}
if (installableProperty.export != null) {
binding.buttonExport.visibility = View.VISIBLE
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
}
}
}
enum class PropertyType {
Submenu,
Installable
}
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractBooleanSetting : AbstractSetting {
val boolean: Boolean
fun getBoolean(needsGlobal: Boolean = false): Boolean
fun setBoolean(value: Boolean)
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractByteSetting : AbstractSetting {
val byte: Byte
fun getByte(needsGlobal: Boolean = false): Byte
fun setByte(value: Byte)
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractFloatSetting : AbstractSetting {
val float: Float
fun getFloat(needsGlobal: Boolean = false): Float
fun setFloat(value: Float)
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractIntSetting : AbstractSetting {
val int: Int
fun getInt(needsGlobal: Boolean = false): Int
fun setInt(value: Int)
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractLongSetting : AbstractSetting {
val long: Long
fun getLong(needsGlobal: Boolean = false): Long
fun setLong(value: Long)
}
@@ -7,12 +7,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
interface AbstractSetting {
val key: String
val category: Settings.Category
val defaultValue: Any
val androidDefault: Any?
get() = null
val valueAsString: String
get() = ""
val isRuntimeModifiable: Boolean
get() = NativeConfig.getIsRuntimeModifiable(key)
@@ -20,5 +15,17 @@ interface AbstractSetting {
val pairedSettingKey: String
get() = NativeConfig.getPairedSettingKey(key)
val isSwitchable: Boolean
get() = NativeConfig.getIsSwitchable(key)
var global: Boolean
get() = NativeConfig.usingGlobal(key)
set(value) = NativeConfig.setGlobal(key, value)
val isSaveable: Boolean
get() = NativeConfig.getIsSaveable(key)
fun getValueAsString(needsGlobal: Boolean = false): String
fun reset()
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractShortSetting : AbstractSetting {
val short: Short
fun getShort(needsGlobal: Boolean = false): Short
fun setShort(value: Short)
}
@@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model
interface AbstractStringSetting : AbstractSetting {
val string: String
fun getString(needsGlobal: Boolean = false): String
fun setString(value: String)
}
@@ -5,36 +5,34 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class BooleanSetting(
override val key: String,
override val category: Settings.Category,
override val androidDefault: Boolean? = null
) : AbstractBooleanSetting {
AUDIO_MUTED("audio_muted", Settings.Category.Audio),
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
RENDERER_DEBUG("debug", Settings.Category.Renderer),
PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
AUDIO_MUTED("audio_muted"),
CPU_DEBUG_MODE("cpu_debug_mode"),
FASTMEM("cpuopt_fastmem"),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_DOCKED_MODE("use_docked_mode"),
RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"),
RENDERER_FORCE_MAX_CLOCK("force_max_clock"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
RENDERER_DEBUG("debug"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled");
override val boolean: Boolean
get() = NativeConfig.getBoolean(key, false)
override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal)
override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
override val defaultValue: Boolean by lazy {
androidDefault ?: NativeConfig.getBoolean(key, true)
override fun setBoolean(value: Boolean) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setBoolean(key, value)
}
override val valueAsString: String
get() = if (boolean) "1" else "0"
override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() }
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString()
override fun reset() = NativeConfig.setBoolean(key, defaultValue)
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ByteSetting(
override val key: String,
override val category: Settings.Category
) : AbstractByteSetting {
AUDIO_VOLUME("volume", Settings.Category.Audio);
enum class ByteSetting(override val key: String) : AbstractByteSetting {
AUDIO_VOLUME("volume");
override val byte: Byte
get() = NativeConfig.getByte(key, false)
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
override fun setByte(value: Byte) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setByte(key, value)
}
override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
override val defaultValue: Byte by lazy { NativeConfig.getDefaultToString(key).toByte() }
override val valueAsString: String
get() = byte.toString()
override fun getValueAsString(needsGlobal: Boolean): String = getByte(needsGlobal).toString()
override fun reset() = NativeConfig.setByte(key, defaultValue)
}
@@ -5,22 +5,22 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class FloatSetting(
override val key: String,
override val category: Settings.Category
) : AbstractFloatSetting {
enum class FloatSetting(override val key: String) : AbstractFloatSetting {
// No float settings currently exist
EMPTY_SETTING("", Settings.Category.UiGeneral);
EMPTY_SETTING("");
override val float: Float
get() = NativeConfig.getFloat(key, false)
override fun getFloat(needsGlobal: Boolean): Float = NativeConfig.getFloat(key, false)
override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
override fun setFloat(value: Float) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setFloat(key, value)
}
override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
override val defaultValue: Float by lazy { NativeConfig.getDefaultToString(key).toFloat() }
override val valueAsString: String
get() = float.toString()
override fun getValueAsString(needsGlobal: Boolean): String = getFloat(needsGlobal).toString()
override fun reset() = NativeConfig.setFloat(key, defaultValue)
}
@@ -5,35 +5,33 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class IntSetting(
override val key: String,
override val category: Settings.Category,
override val androidDefault: Int? = null
) : AbstractIntSetting {
CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
REGION_INDEX("region_index", Settings.Category.System),
LANGUAGE_INDEX("language_index", Settings.Category.System),
RENDERER_BACKEND("backend", Settings.Category.Renderer),
RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
enum class IntSetting(override val key: String) : AbstractIntSetting {
CPU_BACKEND("cpu_backend"),
CPU_ACCURACY("cpu_accuracy"),
REGION_INDEX("region_index"),
LANGUAGE_INDEX("language_index"),
RENDERER_BACKEND("backend"),
RENDERER_ACCURACY("gpu_accuracy"),
RENDERER_RESOLUTION("resolution_setup"),
RENDERER_VSYNC("use_vsync"),
RENDERER_SCALING_FILTER("scaling_filter"),
RENDERER_ANTI_ALIASING("anti_aliasing"),
RENDERER_SCREEN_LAYOUT("screen_layout"),
RENDERER_ASPECT_RATIO("aspect_ratio"),
AUDIO_OUTPUT_ENGINE("output_engine");
override val int: Int
get() = NativeConfig.getInt(key, false)
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
override fun setInt(value: Int) = NativeConfig.setInt(key, value)
override val defaultValue: Int by lazy {
androidDefault ?: NativeConfig.getInt(key, true)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setInt(key, value)
}
override val valueAsString: String
get() = int.toString()
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setInt(key, defaultValue)
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class LongSetting(
override val key: String,
override val category: Settings.Category
) : AbstractLongSetting {
CUSTOM_RTC("custom_rtc", Settings.Category.System);
enum class LongSetting(override val key: String) : AbstractLongSetting {
CUSTOM_RTC("custom_rtc");
override val long: Long
get() = NativeConfig.getLong(key, false)
override fun getLong(needsGlobal: Boolean): Long = NativeConfig.getLong(key, needsGlobal)
override fun setLong(value: Long) = NativeConfig.setLong(key, value)
override fun setLong(value: Long) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setLong(key, value)
}
override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
override val defaultValue: Long by lazy { NativeConfig.getDefaultToString(key).toLong() }
override val valueAsString: String
get() = long.toString()
override fun getValueAsString(needsGlobal: Boolean): String = getLong(needsGlobal).toString()
override fun reset() = NativeConfig.setLong(key, defaultValue)
}
@@ -3,89 +3,14 @@
package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils
import android.widget.Toast
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.NativeConfig
object Settings {
private val context get() = YuzuApplication.appContext
fun saveSettings(gameId: String = "") {
if (TextUtils.isEmpty(gameId)) {
Toast.makeText(
context,
context.getString(R.string.ini_saved),
Toast.LENGTH_SHORT
).show()
NativeConfig.saveSettings()
} else {
// TODO: Save custom game settings
Toast.makeText(
context,
context.getString(R.string.gameid_saved, gameId),
Toast.LENGTH_SHORT
).show()
}
}
enum class Category {
Android,
Audio,
Core,
Cpu,
CpuDebug,
CpuUnsafe,
Renderer,
RendererAdvanced,
RendererDebug,
System,
SystemAudio,
DataStorage,
Debugging,
DebuggingGraphics,
Miscellaneous,
Network,
WebService,
AddOns,
Controls,
Ui,
UiGeneral,
UiLayout,
UiGameList,
Screenshots,
Shortcuts,
Multiplayer,
Services,
Paths,
MaxEnum
}
val settingsList = listOf<AbstractSetting>(
*BooleanSetting.values(),
*ByteSetting.values(),
*ShortSetting.values(),
*IntSetting.values(),
*FloatSetting.values(),
*LongSetting.values(),
*StringSetting.values()
)
const val SECTION_GENERAL = "General"
const val SECTION_SYSTEM = "System"
const val SECTION_RENDERER = "Renderer"
const val SECTION_AUDIO = "Audio"
const val SECTION_CPU = "Cpu"
const val SECTION_THEME = "Theme"
const val SECTION_DEBUG = "Debug"
enum class MenuTag(val titleId: Int) {
SECTION_ROOT(R.string.advanced_settings),
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_CPU(R.string.cpu),
SECTION_THEME(R.string.preferences_theme),
SECTION_DEBUG(R.string.preferences_debug);
}
@@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(
override val key: String,
override val category: Settings.Category
) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit");
override val short: Short
get() = NativeConfig.getShort(key, false)
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
override fun setShort(value: Short) = NativeConfig.setShort(key, value)
override fun setShort(value: Short) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setShort(key, value)
}
override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
override val defaultValue: Short by lazy { NativeConfig.getDefaultToString(key).toShort() }
override val valueAsString: String
get() = short.toString()
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}
@@ -5,22 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class StringSetting(
override val key: String,
override val category: Settings.Category
) : AbstractStringSetting {
// No string settings currently exist
EMPTY_SETTING("", Settings.Category.UiGeneral);
enum class StringSetting(override val key: String) : AbstractStringSetting {
DRIVER_PATH("driver_path");
override val string: String
get() = NativeConfig.getString(key, false)
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
override fun setString(value: String) = NativeConfig.setString(key, value)
override fun setString(value: String) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setString(key, value)
}
override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
override val defaultValue: String by lazy { NativeConfig.getDefaultToString(key) }
override val valueAsString: String
get() = string
override fun getValueAsString(needsGlobal: Boolean): String = getString(needsGlobal)
override fun reset() = NativeConfig.setString(key, defaultValue)
}
@@ -12,7 +12,6 @@ class DateTimeSetting(
) : SettingsItem(longSetting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING
var value: Long
get() = longSetting.long
set(value) = (setting as AbstractLongSetting).setLong(value)
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
fun setValue(value: Long) = (setting as AbstractLongSetting).setLong(value)
}
@@ -11,8 +11,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -30,10 +30,26 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
// Can't edit settings that aren't saveable in per-game config even if they are switchable
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
return false
}
if (!NativeLibrary.isRunning()) return true
// Prevent editing settings that were modified in per-game config while editing global
// config
if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
return false
}
return setting.isRuntimeModifiable
}
val needsRuntimeGlobal: Boolean
get() = NativeLibrary.isRunning() && !setting.global &&
!NativeConfig.isPerGameConfigLoaded()
companion object {
const val TYPE_HEADER = 0
const val TYPE_SWITCH = 1
@@ -48,8 +64,9 @@ abstract class SettingsItem(
val emptySetting = object : AbstractSetting {
override val key: String = ""
override val category: Settings.Category = Settings.Category.Ui
override val defaultValue: Any = false
override val isSaveable = true
override fun getValueAsString(needsGlobal: Boolean): String = ""
override fun reset() {}
}
@@ -77,6 +94,15 @@ abstract class SettingsItem(
"%"
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_BACKEND,
R.string.cpu_backend,
0,
R.array.cpuBackendArm64Names,
R.array.cpuBackendArm64Values
)
)
put(
SingleChoiceSetting(
IntSetting.CPU_ACCURACY,
@@ -261,9 +287,9 @@ abstract class SettingsItem(
)
val fastmem = object : AbstractBooleanSetting {
override val boolean: Boolean
get() =
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
override fun getBoolean(needsGlobal: Boolean): Boolean =
BooleanSetting.FASTMEM.getBoolean() &&
BooleanSetting.FASTMEM_EXCLUSIVES.getBoolean()
override fun setBoolean(value: Boolean) {
BooleanSetting.FASTMEM.setBoolean(value)
@@ -271,9 +297,24 @@ abstract class SettingsItem(
}
override val key: String = FASTMEM_COMBINED
override val category = Settings.Category.Cpu
override val isRuntimeModifiable: Boolean = false
override val defaultValue: Boolean = true
override val isSwitchable: Boolean = true
override var global: Boolean
get() {
return BooleanSetting.FASTMEM.global &&
BooleanSetting.FASTMEM_EXCLUSIVES.global
}
set(value) {
BooleanSetting.FASTMEM.global = value
BooleanSetting.FASTMEM_EXCLUSIVES.global = value
}
override val isSaveable = true
override fun getValueAsString(needsGlobal: Boolean): String =
getBoolean().toString()
override fun reset() = setBoolean(defaultValue)
}
put(SwitchSetting(fastmem, R.string.fastmem, 0))
@@ -15,16 +15,11 @@ class SingleChoiceSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE
var selectedValue: Int
get() {
return when (setting) {
is AbstractIntSetting -> setting.int
else -> -1
}
}
set(value) {
when (setting) {
is AbstractIntSetting -> setting.setInt(value)
}
fun getSelectedValue(needsGlobal: Boolean = false) =
when (setting) {
is AbstractIntSetting -> setting.getInt(needsGlobal)
else -> -1
}
fun setSelectedValue(value: Int) = (setting as AbstractIntSetting).setInt(value)
}
@@ -20,22 +20,20 @@ class SliderSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
var selectedValue: Int
get() {
return when (setting) {
is AbstractByteSetting -> setting.byte.toInt()
is AbstractShortSetting -> setting.short.toInt()
is AbstractIntSetting -> setting.int
is AbstractFloatSetting -> setting.float.roundToInt()
else -> -1
}
fun getSelectedValue(needsGlobal: Boolean = false) =
when (setting) {
is AbstractByteSetting -> setting.getByte(needsGlobal).toInt()
is AbstractShortSetting -> setting.getShort(needsGlobal).toInt()
is AbstractIntSetting -> setting.getInt(needsGlobal)
is AbstractFloatSetting -> setting.getFloat(needsGlobal).roundToInt()
else -> -1
}
set(value) {
when (setting) {
is AbstractByteSetting -> setting.setByte(value.toByte())
is AbstractShortSetting -> setting.setShort(value.toShort())
is AbstractIntSetting -> setting.setInt(value)
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
}
fun setSelectedValue(value: Int) =
when (setting) {
is AbstractByteSetting -> setting.setByte(value.toByte())
is AbstractShortSetting -> setting.setShort(value.toShort())
is AbstractFloatSetting -> setting.setFloat(value.toFloat())
else -> (setting as AbstractIntSetting).setInt(value)
}
}
@@ -17,14 +17,13 @@ class StringSingleChoiceSetting(
fun getValueAt(index: Int): String =
if (index >= 0 && index < values.size) values[index] else ""
var selectedValue: String
get() = stringSetting.string
set(value) = stringSetting.setString(value)
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
fun setSelectedValue(value: String) = stringSetting.setString(value)
val selectValueIndex: Int
get() {
for (i in values.indices) {
if (values[i] == selectedValue) {
if (values[i] == getSelectedValue()) {
return i
}
}
@@ -14,18 +14,18 @@ class SwitchSetting(
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH
var checked: Boolean
get() {
return when (setting) {
is AbstractIntSetting -> setting.int == 1
is AbstractBooleanSetting -> setting.boolean
else -> false
}
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
return when (setting) {
is AbstractIntSetting -> setting.getInt(needsGlobal) == 1
is AbstractBooleanSetting -> setting.getBoolean(needsGlobal)
else -> false
}
set(value) {
when (setting) {
is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
is AbstractBooleanSetting -> setting.setBoolean(value)
}
}
fun setChecked(value: Boolean) {
when (setting) {
is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
is AbstractBooleanSetting -> setting.setBoolean(value)
}
}
}
@@ -21,10 +21,10 @@ import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel
@@ -45,6 +45,9 @@ class SettingsActivity : AppCompatActivity() {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
SettingsFile.loadCustomConfig(args.game!!)
}
settingsViewModel.game = args.game
val navHostFragment =
@@ -53,10 +56,6 @@ class SettingsActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (savedInstanceState != null) {
settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
}
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
@@ -127,49 +126,44 @@ class SettingsActivity : AppCompatActivity() {
}
}
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
}
override fun onStart() {
super.onStart()
// TODO: Load custom settings contextually
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
}
/**
* If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted. So we kick off an
* IntentService which will do so on a background thread.
*/
override fun onStop() {
super.onStop()
if (isFinishing && settingsViewModel.shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
Settings.saveSettings()
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
if (isFinishing) {
NativeLibrary.applySettings()
if (args.game == null) {
NativeConfig.saveGlobalConfig()
} else if (NativeConfig.isPerGameConfigLoaded()) {
NativeLibrary.logSettings()
NativeConfig.savePerGameConfig()
NativeConfig.unloadPerGameConfig()
}
}
}
override fun onDestroy() {
settingsViewModel.clear()
super.onDestroy()
}
fun onSettingsReset() {
// Prevents saving to a non-existent settings file
settingsViewModel.shouldSave = false
// Delete settings file because the user may have changed values that do not exist in the UI
NativeConfig.unloadConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
if (args.game == null) {
NativeConfig.unloadGlobalConfig()
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
NativeConfig.initializeGlobalConfig()
} else {
NativeConfig.unloadPerGameConfig()
val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
if (!settingsFile.delete()) {
throw IOException("Failed to delete $settingsFile")
}
}
NativeConfig.initializeConfig()
Toast.makeText(
applicationContext,
@@ -194,8 +188,4 @@ class SettingsActivity : AppCompatActivity() {
windowInsets
}
}
companion object {
private const val KEY_SHOULD_SAVE = "should_save"
}
}
@@ -102,10 +102,10 @@ class SettingsAdapter(
return currentList[position].type
}
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.checked = checked
fun onBooleanClick(item: SwitchSetting, checked: Boolean, position: Int) {
item.setChecked(checked)
notifyItemChanged(position)
settingsViewModel.setShouldReloadSettingsList(true)
settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@@ -127,7 +127,7 @@ class SettingsAdapter(
}
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
val storedTime = item.value * 1000
val storedTime = item.getValue() * 1000
// Helper to extract hour and minute from epoch time
val calendar: Calendar = Calendar.getInstance()
@@ -160,10 +160,9 @@ class SettingsAdapter(
var epochTime: Long = datePicker.selection!! / 1000
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
if (item.value != epochTime) {
settingsViewModel.shouldSave = true
if (item.getValue() != epochTime) {
notifyItemChanged(position)
item.value = epochTime
item.setValue(epochTime)
}
}
datePicker.show(
@@ -197,6 +196,12 @@ class SettingsAdapter(
return true
}
fun onClearClick(item: SettingsItem, position: Int) {
item.setting.global = true
notifyItemChanged(position)
settingsViewModel.setShouldReloadSettingsList(true)
}
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
return oldItem.setting.key == newItem.setting.key
@@ -66,7 +66,13 @@ class SettingsFragment : Fragment() {
args.menuTag
)
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
args.game != null
) {
args.game!!.title
} else {
getString(args.menuTag.titleId)
}
binding.listSettings.apply {
adapter = settingsAdapter
layoutManager = LinearLayoutManager(requireContext())
@@ -7,6 +7,7 @@ import android.content.SharedPreferences
import android.os.Build
import android.widget.Toast
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@@ -31,12 +32,27 @@ class SettingsFragmentPresenter(
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
// Extension for populating settings list based on paired settings
// Extension for altering settings list based on each setting's properties
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
if (settingsViewModel.game != null && !item.setting.isSwitchable) {
return
}
if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) {
item.setting.global = true
}
val pairedSettingKey = item.setting.pairedSettingKey
if (pairedSettingKey.isNotEmpty()) {
val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
val pairedSettingValue = NativeConfig.getBoolean(
pairedSettingKey,
if (NativeLibrary.isRunning() && !NativeConfig.isPerGameConfigLoaded()) {
!NativeConfig.usingGlobal(pairedSettingKey)
} else {
NativeConfig.usingGlobal(pairedSettingKey)
}
)
if (!pairedSettingValue) return
}
add(item)
@@ -153,8 +169,8 @@ class SettingsFragmentPresenter(
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
sl.apply {
val theme: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME, 0)
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_THEME, 0)
override fun setInt(value: Int) {
preferences.edit()
@@ -164,8 +180,8 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_THEME
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override val defaultValue: Int = 0
override fun reset() {
preferences.edit()
@@ -197,8 +213,8 @@ class SettingsFragmentPresenter(
}
val themeMode: AbstractIntSetting = object : AbstractIntSetting {
override val int: Int
get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_THEME_MODE, -1)
override fun setInt(value: Int) {
preferences.edit()
@@ -208,8 +224,8 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_THEME_MODE
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
override val defaultValue: Int = -1
override fun reset() {
preferences.edit()
@@ -230,8 +246,8 @@ class SettingsFragmentPresenter(
)
val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
override val boolean: Boolean
get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
override fun getBoolean(needsGlobal: Boolean): Boolean =
preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
override fun setBoolean(value: Boolean) {
preferences.edit()
@@ -241,8 +257,10 @@ class SettingsFragmentPresenter(
}
override val key: String = Settings.PREF_BLACK_BACKGROUNDS
override val category = Settings.Category.UiGeneral
override val isRuntimeModifiable: Boolean = false
override fun getValueAsString(needsGlobal: Boolean): String =
getBoolean().toString()
override val defaultValue: Boolean = false
override fun reset() {
preferences.edit()
@@ -269,6 +287,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
add(IntSetting.CPU_BACKEND.key)
add(IntSetting.CPU_ACCURACY.key)
add(BooleanSetting.CPU_DEBUG_MODE.key)
add(SettingsItem.FASTMEM_COMBINED)
@@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -29,12 +30,23 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
}
binding.textSettingValue.visibility = View.VISIBLE
val epochTime = setting.value
val epochTime = setting.getValue()
val instant = Instant.ofEpochMilli(epochTime * 1000)
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime)
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -38,6 +38,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
binding.buttonClear.visibility = View.GONE
setStyle(setting.isEditable, binding)
}
@@ -41,6 +41,7 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.textSettingValue.alpha = opacity
binding.buttonClear.isEnabled = isEditable
}
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
@@ -48,5 +49,6 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
val opacity = if (isEditable) 1.0f else 0.5f
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.buttonClear.isEnabled = isEditable
}
}
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -29,20 +30,31 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
val resMgr = binding.textSettingValue.context.resources
val values = resMgr.getIntArray(item.valuesId)
for (i in values.indices) {
if (values[i] == item.selectedValue) {
if (values[i] == item.getSelectedValue()) {
binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
break
}
}
} else if (item is StringSingleChoiceSetting) {
for (i in item.values.indices) {
if (item.values[i] == item.selectedValue) {
if (item.values[i] == item.getSelectedValue()) {
binding.textSettingValue.text = item.choices[i]
break
}
}
}
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -26,10 +27,21 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = String.format(
binding.textSettingValue.context.getString(R.string.value_with_units),
setting.selectedValue,
setting.getSelectedValue(),
setting.units
)
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
}
@@ -37,6 +37,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
binding.buttonClear.visibility = View.GONE
}
override fun onClick(clicked: View) {
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.NativeConfig
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -27,9 +28,20 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
}
binding.switchWidget.setOnCheckedChangeListener(null)
binding.switchWidget.isChecked = setting.checked
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
adapter.onBooleanClick(item, binding.switchWidget.isChecked)
adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
}
binding.buttonClear.visibility = if (setting.setting.global ||
!NativeConfig.isPerGameConfigLoaded()
) {
View.GONE
} else {
View.VISIBLE
}
binding.buttonClear.setOnClickListener {
adapter.onClearClick(setting, bindingAdapterPosition)
}
setStyle(setting.isEditable, binding)
@@ -3,15 +3,27 @@
package org.yuzu.yuzu_emu.features.settings.utils
import android.net.Uri
import org.yuzu.yuzu_emu.model.Game
import java.io.*
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
*/
object SettingsFile {
const val FILE_NAME_CONFIG = "config"
const val FILE_NAME_CONFIG = "config.ini"
fun getSettingsFile(fileName: String): File =
File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
File(DirectoryInitialization.userDirectory + "/config/" + fileName)
fun getCustomSettingsFile(game: Game): File =
File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini")
fun loadCustomConfig(game: Game) {
val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.programId, fileName)
}
}
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
class AddGameFolderDialogFragment : DialogFragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogAddFolderBinding.inflate(layoutInflater)
val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
if (folderUriString == null) {
dismiss()
}
binding.path.text = Uri.parse(folderUriString).path
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.add_game_folder)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
homeViewModel.setGamesDirSelected(true)
gamesViewModel.addFolder(newGameDir)
}
.setNegativeButton(android.R.string.cancel, null)
.setView(binding.root)
.show()
}
companion object {
const val TAG = "AddGameFolderDialogFragment"
private const val FOLDER_URI_STRING = "FolderUriString"
fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
val args = Bundle()
args.putString(FOLDER_URI_STRING, folderUriString)
val fragment = AddGameFolderDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -0,0 +1,214 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.AddonAdapter
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.AddonUtil
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
import java.io.File
class AddonsFragment : Fragment() {
private var _binding: FragmentAddonsBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val addonViewModel: AddonViewModel by activityViewModels()
private val args by navArgs<AddonsFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addonViewModel.onOpenAddons(args.game)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAddonsBinding.inflate(inflater)
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.toolbarAddons.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title)
binding.listAddons.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = AddonAdapter()
}
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.addonList.collect {
(binding.listAddons.adapter as AddonAdapter).submitList(it)
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.showModInstallPicker.collect {
if (it) {
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
addonViewModel.showModInstallPicker(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addonViewModel.showModNoticeDialog.collect {
if (it) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.addon_notice,
descriptionId = R.string.addon_notice_description,
positiveAction = { addonViewModel.showModInstallPicker(true) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
addonViewModel.showModNoticeDialog(false)
}
}
}
}
}
binding.buttonInstall.setOnClickListener {
ContentTypeSelectionDialogFragment().show(
parentFragmentManager,
ContentTypeSelectionDialogFragment.TAG
)
}
setInsets()
}
override fun onResume() {
super.onResume()
addonViewModel.refreshAddons()
}
override fun onDestroy() {
super.onDestroy()
addonViewModel.onCloseAddons()
}
val installAddon =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) {
return@registerForActivityResult
}
val externalAddonDirectory = DocumentFile.fromTreeUri(requireContext(), result)
if (externalAddonDirectory == null) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.invalid_directory,
descriptionId = R.string.invalid_directory_description
).show(parentFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
val isValid = externalAddonDirectory.listFiles()
.any { AddonUtil.validAddonDirectories.contains(it.name) }
val errorMessage = MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.invalid_directory,
descriptionId = R.string.invalid_directory_description
)
if (isValid) {
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_game_content,
false
) {
val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName)
try {
externalAddonDirectory.copyFilesTo(internalAddonDirectory)
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
} else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarAddons.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarAddons.layoutParams = mlpToolbar
val mlpAddonsList = binding.listAddons.layoutParams as ViewGroup.MarginLayoutParams
mlpAddonsList.leftMargin = leftInsets
mlpAddonsList.rightMargin = rightInsets
binding.listAddons.layoutParams = mlpAddonsList
binding.listAddons.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonInstall.layoutParams = mlpFab
windowInsets
}
}
@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class ContentTypeSelectionDialogFragment : DialogFragment() {
private val addonViewModel: AddonViewModel by activityViewModels()
private val preferences get() =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
private var selectedItem = 0
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val launchOptions =
arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats))
if (savedInstanceState != null) {
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
}
val mainActivity = requireActivity() as MainActivity
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.select_content_type)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (selectedItem) {
0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*"))
else -> {
if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) {
preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply()
addonViewModel.showModNoticeDialog(true)
return@setPositiveButton
}
addonViewModel.showModInstallPicker(true)
}
}
}
.setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int ->
selectedItem = i
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SELECTED_ITEM, selectedItem)
}
companion object {
const val TAG = "ContentTypeSelectionDialogFragment"
private const val SELECTED_ITEM = "SelectedItem"
private const val MOD_NOTICE_SEEN = "ModNoticeSeen"
}
}
@@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.flow.collectLatest
@@ -36,6 +37,8 @@ class DriverManagerFragment : Fragment() {
private val homeViewModel: HomeViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<DriverManagerFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
@@ -57,7 +60,9 @@ class DriverManagerFragment : Fragment() {
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
if (!driverViewModel.isInteractionAllowed) {
driverViewModel.onOpenDriverManager(args.game)
if (!driverViewModel.isInteractionAllowed.value) {
DriversLoadingDialogFragment().show(
childFragmentManager,
DriversLoadingDialogFragment.TAG
@@ -102,10 +107,9 @@ class DriverManagerFragment : Fragment() {
setInsets()
}
// Start installing requested driver
override fun onStop() {
super.onStop()
driverViewModel.onCloseDriverManager()
override fun onDestroy() {
super.onDestroy()
driverViewModel.onCloseDriverManager(args.game)
}
private fun setInsets() =
@@ -47,25 +47,9 @@ class DriversLoadingDialogFragment : DialogFragment() {
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.areDriversLoading.collect { checkForDismiss() }
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDriverReady.collect { checkForDismiss() }
}
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
}
}
}
}
private fun checkForDismiss() {
if (driverViewModel.isInteractionAllowed) {
dismiss()
}
}
@@ -52,6 +52,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel
@@ -127,6 +128,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return
}
// Always load custom settings when launching a game from an intent
if (args.custom || intentGame != null) {
SettingsFile.loadCustomConfig(game)
NativeConfig.unloadPerGameConfig()
} else {
NativeConfig.reloadGlobalConfig()
}
// Install the selected driver asynchronously as the game starts
driverViewModel.onLaunchGame()
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -217,6 +229,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
R.id.menu_settings_per_game -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
args.game,
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
true
}
R.id.menu_overlay_controls -> {
showOverlayOptions()
true
@@ -332,15 +353,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
driverViewModel.isDriverReady.collect {
if (it && !emulationState.isRunning) {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
driverViewModel.isInteractionAllowed.collect {
if (it) {
onEmulationStart()
}
}
}
@@ -348,6 +363,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private fun onEmulationStart() {
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (_binding == null) {
@@ -414,8 +441,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats()
val cpuBackend = NativeLibrary.getCpuBackend()
if (_binding != null) {
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
binding.showFpsText.text =
String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
@@ -433,7 +462,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() {
emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) {
Settings.LayoutOption_MobileLandscape ->
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
@@ -615,7 +644,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@SuppressLint("SourceLockedOrientationActivity")
private fun startConfiguringControls() {
// Lock the current orientation to prevent editing inconsistencies
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -633,7 +662,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.doneControlConfig.visibility = View.GONE
binding.surfaceInputOverlay.setIsInEditMode(false)
// Unlock the orientation if it was locked for editing
if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) {
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) {
emulationActivity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class GameFolderPropertiesDialogFragment : DialogFragment() {
private val gamesViewModel: GamesViewModel by activityViewModels()
private var deepScan = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
// Restore checkbox state
binding.deepScanSwitch.isChecked =
savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
// Ensure that we can get the checkbox state even if the view is destroyed
deepScan = binding.deepScanSwitch.isChecked
binding.deepScanSwitch.setOnClickListener {
deepScan = binding.deepScanSwitch.isChecked
}
return MaterialAlertDialogBuilder(requireContext())
.setView(binding.root)
.setTitle(R.string.game_folder_properties)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
if (folderIndex != -1) {
gamesViewModel.folders.value[folderIndex].deepScan =
binding.deepScanSwitch.isChecked
gamesViewModel.updateGameDirs()
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onStop() {
super.onStop()
NativeConfig.saveGlobalConfig()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(DEEP_SCAN, deepScan)
}
companion object {
const val TAG = "GameFolderPropertiesDialogFragment"
private const val GAME_DIR = "GameDir"
private const val DEEP_SCAN = "DeepScan"
fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
val args = Bundle()
args.putParcelable(GAME_DIR, gameDir)
val fragment = GameFolderPropertiesDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FolderAdapter
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
class GameFoldersFragment : Fragment() {
private var _binding: FragmentFoldersBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
gamesViewModel.onOpenGameFoldersFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFoldersBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarFolders.setNavigationOnClickListener {
binding.root.findNavController().popBackStack()
}
binding.listFolders.apply {
layoutManager = GridLayoutManager(
requireContext(),
resources.getInteger(R.integer.grid_columns)
)
adapter = FolderAdapter(requireActivity(), gamesViewModel)
}
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.folders.collect {
(binding.listFolders.adapter as FolderAdapter).submitList(it)
}
}
}
val mainActivity = requireActivity() as MainActivity
binding.buttonAdd.setOnClickListener {
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
setInsets()
}
override fun onStop() {
super.onStop()
gamesViewModel.onCloseGameFoldersFragment()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarFolders.layoutParams = mlpToolbar
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonAdd.layoutParams = mlpFab
val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
mlpListFolders.leftMargin = leftInsets
mlpListFolders.rightMargin = rightInsets
binding.listFolders.layoutParams = mlpListFolders
binding.listFolders.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
}
@@ -0,0 +1,148 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.GameMetadata
class GameInfoFragment : Fragment() {
private var _binding: FragmentGameInfoBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val args by navArgs<GameInfoFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
// Check for an up-to-date version string
args.game.version = GameMetadata.getVersion(args.game.path, true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentGameInfoBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = false)
homeViewModel.setStatusBarShadeVisibility(false)
binding.apply {
toolbarInfo.title = args.game.title
toolbarInfo.setNavigationOnClickListener {
view.findNavController().popBackStack()
}
val pathString = Uri.parse(args.game.path).path ?: ""
path.setHint(R.string.path)
pathField.setText(pathString)
pathField.setOnClickListener { copyToClipboard(getString(R.string.path), pathString) }
programId.setHint(R.string.program_id)
programIdField.setText(args.game.programIdHex)
programIdField.setOnClickListener {
copyToClipboard(getString(R.string.program_id), args.game.programIdHex)
}
if (args.game.developer.isNotEmpty()) {
developer.setHint(R.string.developer)
developerField.setText(args.game.developer)
developerField.setOnClickListener {
copyToClipboard(getString(R.string.developer), args.game.developer)
}
} else {
developer.visibility = View.GONE
}
version.setHint(R.string.version)
versionField.setText(args.game.version)
versionField.setOnClickListener {
copyToClipboard(getString(R.string.version), args.game.version)
}
buttonCopy.setOnClickListener {
val details = """
${args.game.title}
${getString(R.string.path)} - $pathString
${getString(R.string.program_id)} - ${args.game.programIdHex}
${getString(R.string.developer)} - ${args.game.developer}
${getString(R.string.version)} - ${args.game.version}
""".trimIndent()
copyToClipboard(args.game.title, details)
}
}
setInsets()
}
private fun copyToClipboard(label: String, body: String) {
val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(label, body)
clipBoard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
Toast.makeText(
requireContext(),
R.string.copied_to_clipboard,
Toast.LENGTH_SHORT
).show()
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpToolbar = binding.toolbarInfo.layoutParams as ViewGroup.MarginLayoutParams
mlpToolbar.leftMargin = leftInsets
mlpToolbar.rightMargin = rightInsets
binding.toolbarInfo.layoutParams = mlpToolbar
val mlpScrollAbout = binding.scrollInfo.layoutParams as ViewGroup.MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.scrollInfo.layoutParams = mlpScrollAbout
binding.contentInfo.updatePadding(bottom = barInsets.bottom)
windowInsets
}
}
@@ -0,0 +1,456 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter
import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GameProperty
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallableProperty
import org.yuzu.yuzu_emu.model.SubmenuProperty
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
class GamePropertiesFragment : Fragment() {
private var _binding: FragmentGamePropertiesBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private val gamesViewModel: GamesViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private val args by navArgs<GamePropertiesFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentGamePropertiesBinding.inflate(layoutInflater)
return binding.root
}
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = false, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
binding.buttonBack.setOnClickListener {
view.findNavController().popBackStack()
}
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
binding.title.text = args.game.title
binding.title.postDelayed(
{
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.title.isSelected = true
},
3000
)
binding.buttonStart.setOnClickListener {
LaunchGameDialogFragment.newInstance(args.game)
.show(childFragmentManager, LaunchGameDialogFragment.TAG)
}
reloadList()
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
homeViewModel.openImportSaves.collect {
if (it) {
importSaves.launch(arrayOf("application/zip"))
homeViewModel.setOpenImportSaves(false)
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
homeViewModel.reloadPropertiesList.collect {
if (it) {
reloadList()
homeViewModel.reloadPropertiesList(false)
}
}
}
}
}
setInsets()
}
override fun onDestroy() {
super.onDestroy()
gamesViewModel.reloadGames(true)
}
private fun reloadList() {
_binding ?: return
driverViewModel.updateDriverNameForGame(args.game)
val properties = mutableListOf<GameProperty>().apply {
add(
SubmenuProperty(
R.string.info,
R.string.info_description,
R.drawable.ic_info_outline
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToGameInfoFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
add(
SubmenuProperty(
R.string.preferences_settings,
R.string.per_game_settings_description,
R.drawable.ic_settings
) {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
args.game,
Settings.MenuTag.SECTION_ROOT
)
binding.root.findNavController().navigate(action)
}
)
if (GpuDriverHelper.supportsCustomDriverLoading()) {
add(
SubmenuProperty(
R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
R.drawable.ic_build,
detailsFlow = driverViewModel.selectedDriverTitle
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToDriverManagerFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
}
if (!args.game.isHomebrew) {
add(
SubmenuProperty(
R.string.add_ons,
R.string.add_ons_description,
R.drawable.ic_edit
) {
val action = GamePropertiesFragmentDirections
.actionPerGamePropertiesFragmentToAddonsFragment(args.game)
binding.root.findNavController().navigate(action)
}
)
add(
InstallableProperty(
R.string.save_data,
R.string.save_data_description,
R.drawable.ic_save,
{
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.import_save_warning,
descriptionId = R.string.import_save_warning_description,
positiveAction = { homeViewModel.setOpenImportSaves(true) }
).show(parentFragmentManager, MessageDialogFragment.TAG)
},
if (File(args.game.saveDir).exists()) {
{ exportSaves.launch(args.game.saveZipName) }
} else {
null
}
)
)
val saveDirFile = File(args.game.saveDir)
if (saveDirFile.exists()) {
add(
SubmenuProperty(
R.string.delete_save_data,
R.string.delete_save_data_description,
R.drawable.ic_delete,
action = {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.delete_save_data,
descriptionId = R.string.delete_save_data_warning_description,
positiveAction = {
File(args.game.saveDir).deleteRecursively()
Toast.makeText(
YuzuApplication.appContext,
R.string.save_data_deleted_successfully,
Toast.LENGTH_SHORT
).show()
homeViewModel.reloadPropertiesList(true)
}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
)
)
}
val shaderCacheDir = File(
DirectoryInitialization.userDirectory +
"/shader/" + args.game.settingsName.lowercase()
)
if (shaderCacheDir.exists()) {
add(
SubmenuProperty(
R.string.clear_shader_cache,
R.string.clear_shader_cache_description,
R.drawable.ic_delete,
{
if (shaderCacheDir.exists()) {
val bytes = shaderCacheDir.walkTopDown().filter { it.isFile }
.map { it.length() }.sum()
MemoryUtil.bytesToSizeUnit(bytes.toFloat())
} else {
MemoryUtil.bytesToSizeUnit(0f)
}
}
) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.clear_shader_cache,
descriptionId = R.string.clear_shader_cache_warning_description,
positiveAction = {
shaderCacheDir.deleteRecursively()
Toast.makeText(
YuzuApplication.appContext,
R.string.cleared_shaders_successfully,
Toast.LENGTH_SHORT
).show()
homeViewModel.reloadPropertiesList(true)
}
).show(parentFragmentManager, MessageDialogFragment.TAG)
}
)
}
}
}
binding.listProperties.apply {
layoutManager =
GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns))
adapter = GamePropertiesAdapter(viewLifecycleOwner, properties)
}
}
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(args.game)
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val smallLayout = resources.getBoolean(R.bool.small_layout)
if (smallLayout) {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.leftMargin = leftInsets
mlpListAll.rightMargin = rightInsets
binding.listAll.layoutParams = mlpListAll
} else {
if (ViewCompat.getLayoutDirection(binding.root) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.rightMargin = rightInsets
binding.listAll.layoutParams = mlpListAll
val mlpIconLayout =
binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams
mlpIconLayout.topMargin = barInsets.top
mlpIconLayout.leftMargin = leftInsets
binding.iconLayout!!.layoutParams = mlpIconLayout
} else {
val mlpListAll =
binding.listAll.layoutParams as ViewGroup.MarginLayoutParams
mlpListAll.leftMargin = leftInsets
binding.listAll.layoutParams = mlpListAll
val mlpIconLayout =
binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams
mlpIconLayout.topMargin = barInsets.top
mlpIconLayout.rightMargin = rightInsets
binding.iconLayout!!.layoutParams = mlpIconLayout
}
}
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonStart.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonStart.layoutParams = mlpFab
binding.layoutAll.updatePadding(
top = barInsets.top,
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
private val importSaves =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null
if (files != null) {
val savesFolderName = args.game.programIdHex
for (file in files) {
if (file.isDirectory && file.name == savesFolderName) {
savesFolderFile = file
break
}
}
}
if (savesFolderFile != null) {
savesFolder.deleteRecursively()
savesFolder.mkdir()
savesFolderFile.copyRecursively(savesFolder)
savesFolderFile.deleteRecursively()
}
withContext(Dispatchers.Main) {
if (savesFolderFile == null) {
MessageDialogFragment.newInstance(
requireActivity(),
titleId = R.string.save_file_invalid_zip_structure,
descriptionId = R.string.save_file_invalid_zip_structure_description
).show(parentFragmentManager, MessageDialogFragment.TAG)
return@withContext
}
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.save_file_imported_success),
Toast.LENGTH_LONG
).show()
homeViewModel.reloadPropertiesList(true)
}
cacheSaveDir.deleteRecursively()
} catch (e: Exception) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
/**
* Exports the save file located in the given folder path by creating a zip file and opening a
* file picker to save.
*/
private val exportSaves = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/zip")
) { result ->
if (result == null) {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation),
saveLocation.replaceAfterLast("/", ""),
BufferedOutputStream(requireContext().contentResolver.openOutputStream(result))
)
return@newInstance when (zipResult) {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
}
@@ -68,6 +68,9 @@ class HomeSettingsFragment : Fragment() {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true)
mainActivity = requireActivity() as MainActivity
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
@@ -91,13 +94,14 @@ class HomeSettingsFragment : Fragment() {
R.string.install_gpu_driver_description,
R.drawable.ic_build,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
val action = HomeSettingsFragmentDirections
.actionHomeSettingsFragmentToDriverManagerFragment(null)
binding.root.findNavController().navigate(action)
},
{ GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported,
R.string.custom_driver_not_supported_description,
driverViewModel.selectedDriverMetadata
driverViewModel.selectedDriverTitle
)
)
add(
@@ -127,18 +131,13 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
R.string.select_games_folder,
R.string.manage_game_folders,
R.string.select_games_folder_description,
R.drawable.ic_add,
{
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
},
{ true },
0,
0,
homeViewModel.gamesDir
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
}
)
)
add(
@@ -217,8 +216,11 @@ class HomeSettingsFragment : Fragment() {
override fun onStart() {
super.onStart()
exitTransition = null
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(visible = true)
}
override fun onResume() {
super.onResume()
driverViewModel.updateDriverNameForGame(null)
}
override fun onDestroyView() {
@@ -122,7 +122,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
task: () -> Any
task: suspend () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
val args = Bundle()
@@ -21,8 +21,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.Installable
import org.yuzu.yuzu_emu.ui.main.MainActivity
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class InstallableFragment : Fragment() {
private var _binding: FragmentInstallablesBinding? = null
@@ -75,28 +73,6 @@ class InstallableFragment : Fragment() {
R.string.install_firmware_description,
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
),
if (mainActivity.savesFolderRoot != "") {
Installable(
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) },
export = {
mainActivity.exportSaves.launch(
"yuzu saves - ${
LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
)
}.zip"
)
}
)
} else {
Installable(
R.string.manage_save_data,
R.string.import_export_saves_description,
install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }
)
},
Installable(
R.string.install_prod_keys,
R.string.install_prod_keys_description,
@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class LaunchGameDialogFragment : DialogFragment() {
private var selectedItem = 1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val game = requireArguments().parcelable<Game>(GAME)
val launchOptions = arrayOf(getString(R.string.global), getString(R.string.custom))
if (savedInstanceState != null) {
selectedItem = savedInstanceState.getInt(SELECTED_ITEM)
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.launch_options)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val action = HomeNavigationDirections
.actionGlobalEmulationActivity(game, selectedItem != 0)
requireParentFragment().findNavController().navigate(action)
}
.setSingleChoiceItems(launchOptions, 1) { _: DialogInterface, i: Int ->
selectedItem = i
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SELECTED_ITEM, selectedItem)
}
companion object {
const val TAG = "LaunchGameDialogFragment"
const val GAME = "Game"
const val SELECTED_ITEM = "SelectedItem"
fun newInstance(game: Game): LaunchGameDialogFragment {
val args = Bundle()
args.putParcelable(GAME, game)
val fragment = LaunchGameDialogFragment()
fragment.arguments = args
return fragment
}
}
}
@@ -27,30 +27,31 @@ class MessageDialogFragment : DialogFragment() {
val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
val helpLinkId = requireArguments().getInt(HELP_LINK)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setPositiveButton(R.string.close, null)
val builder = MaterialAlertDialogBuilder(requireContext())
if (titleId != 0) dialog.setTitle(titleId)
if (titleString.isNotEmpty()) dialog.setTitle(titleString)
if (messageDialogViewModel.positiveAction == null) {
builder.setPositiveButton(R.string.close, null)
} else {
builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
messageDialogViewModel.positiveAction?.invoke()
}.setNegativeButton(android.R.string.cancel, null)
}
if (titleId != 0) builder.setTitle(titleId)
if (titleString.isNotEmpty()) builder.setTitle(titleString)
if (descriptionId != 0) {
dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
}
if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString)
if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString)
if (helpLinkId != 0) {
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
builder.setNeutralButton(R.string.learn_more) { _, _ ->
openLink(getString(helpLinkId))
}
}
return dialog.show()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
messageDialogViewModel.dismissAction.invoke()
messageDialogViewModel.clear()
return builder.show()
}
private fun openLink(link: String) {
@@ -74,7 +75,7 @@ class MessageDialogFragment : DialogFragment() {
descriptionId: Int = 0,
descriptionString: String = "",
helpLinkId: Int = 0,
dismissAction: () -> Unit = {}
positiveAction: (() -> Unit)? = null
): MessageDialogFragment {
val dialog = MessageDialogFragment()
val bundle = Bundle()
@@ -85,8 +86,10 @@ class MessageDialogFragment : DialogFragment() {
putString(DESCRIPTION_STRING, descriptionString)
putInt(HELP_LINK, helpLinkId)
}
ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction =
dismissAction
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
clear()
this.positiveAction = positiveAction
}
dialog.arguments = bundle
return dialog
}
@@ -24,6 +24,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.Locale
import org.yuzu.yuzu_emu.R
@@ -60,7 +61,9 @@ class SearchFragment : Fragment() {
// This is using the correct scope, lint is just acting up
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
homeViewModel.setNavigationVisibility(visible = true, animated = false)
super.onViewCreated(view, savedInstanceState)
homeViewModel.setNavigationVisibility(visible = true, animated = true)
homeViewModel.setStatusBarShadeVisibility(true)
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (savedInstanceState != null) {
@@ -99,7 +102,7 @@ class SearchFragment : Fragment() {
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
gamesViewModel.games.collect { filterAndSearch() }
gamesViewModel.games.collectLatest { filterAndSearch() }
}
}
launch {

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