Compare commits

...

162 Commits

Author SHA1 Message Date
CamilleLaVey 14cbcc2438 [vulkan] Removed LineRasterization mode for the removed LineLoop -> LineStrip convertion 2026-03-14 20:23:48 -04:00
CamilleLaVey 0a73fb5229 [vulkan] Changed handling of helper inside MaxwellToVK 2026-03-14 20:11:07 -04:00
CamilleLaVey 254250a83d Fix build 2026-03-14 19:57:14 -04:00
CamilleLaVey 83c727794e [vulkan] Adjust polygon mode handling + topology emulation resolve for line and point with polygon mode 2026-03-14 04:03:30 -04:00
CamilleLaVey a6c610cf26 [vulkan] Changed perspective of use for topologies strip/fan 2026-03-14 01:37:42 -04:00
CamilleLaVey 0dafab4053 [vulkam] Changed function location 2026-03-14 00:34:05 -04:00
CamilleLaVey 40b2fd0e6b [vulkan] Adjusted TFB Topology entry and use 2026-03-14 00:26:55 -04:00
CamilleLaVey 610e9eb516 [vulkan] Adjusted not indexed Quad's handling for QuadStrip + Adjusted Quad's calculations for LUT 2026-03-13 19:33:59 -04:00
CamilleLaVey d835cab32d [vulkan] Adjusting XFB configuration + StreamingByteCount adjustments 2026-03-13 19:17:50 -04:00
CamilleLaVey 46c2a81f4a [vulkan] Removed unneccesary UpdateBuffers from the end session of TFB/ Thanks Pavel 2026-03-13 14:35:26 -04:00
CamilleLaVey 1ebdef1a02 [vulkan] Adjusting how surface clip is working when viewport offset gets a scaling value 2026-03-13 02:54:14 -04:00
CamilleLaVey bd80d603e2 [vulkan] Corrected mishandling of render area + rescaling logic by using wrongly push constants 2026-03-13 01:55:02 -04:00
CamilleLaVey 76bb5fc18b [vulkan] Small patch for tesselation vs an already handling with topology patch list. 2026-03-13 01:17:57 -04:00
CamilleLaVey b1821cb60e [vulkan] Extend ConditionalRendering use vs old hacks for stability in some drivers 2026-03-12 23:30:22 -04:00
CamilleLaVey b1cc417c55 [vulkan] Removed QCOM and Turnip from an older handling in drivers for query pools handled in host-side vs gpu normal path 2026-03-12 21:45:30 -04:00
CamilleLaVey 70620c245c removed unused function 2026-03-12 15:20:43 -04:00
CamilleLaVey 8389a98648 [vulkan] First try to implement a LineLoop to LineStrip resolve via a software emulation for not equivalent lines available in Vulkan 2026-03-12 14:55:52 -04:00
CamilleLaVey 7fe40a37ef [vulkan] Removed bresenham line mode for driver issues + adjusted LineLoop to LineStrip handling 2026-03-12 14:15:07 -04:00
CamilleLaVey e05c74108f fix build 2026-03-12 01:31:30 -04:00
CamilleLaVey cd7414814a [vulkan] Removed handling for PixelFormatInterpretation 2026-03-12 01:13:50 -04:00
CamilleLaVey dfe1f39a4b Revert "[vulkan] Replaced old logic for DescriptorType for a numeric handling per type to avoid mismatches during format binding" 2026-03-12 00:55:49 -04:00
CamilleLaVey 3f1f20185c Revert "[vulkan] simplify numeric type determination" 2026-03-12 00:54:14 -04:00
CamilleLaVey 1845d5b222 Fix build 2026-03-10 05:25:45 -04:00
CamilleLaVey 1240268048 [vulkan] Fix primitive count calculation for Quads and QuadStrip in vk_query_cache 2026-03-10 05:03:45 -04:00
CamilleLaVey a134ad3fbd [vulkan] Added Line loop + topology emulation accuracy increased by changing triangle assumption 2026-03-10 04:58:11 -04:00
CamilleLaVey da30efbc55 [vulkan] Added primitive count calculation based on topology + patch vertices in PrimitivesSucceededStreamer 2026-03-10 04:14:07 -04:00
CamilleLaVey 2dbca791f6 [vulkan] Adjusted synchronization handling in QueryCacheRuntime + ConditionalRendering setting bug with syncing 2026-03-10 03:53:45 -04:00
CamilleLaVey 74248bd35a Removing remanents of NCE changes to previous state 2026-03-10 03:20:05 -04:00
CamilleLaVey e715925d52 Revert "[nce] Added rasterizer caching checks to memory management" 2026-03-10 03:13:21 -04:00
CamilleLaVey 852b8e176f Revert "[nce] Added rasterizer memory handling by nce page faults + intercepted memory access in nce with cached rasterizer data" 2026-03-10 03:12:19 -04:00
CamilleLaVey b2b07abbc8 Revert "[nce] Added "tainted" page fault handling inside dual channel" 2026-03-10 03:10:37 -04:00
CamilleLaVey ce2f2187bd Revert "[nce] Adjusted precise access fault window handling + decay mechanism" 2026-03-10 03:09:30 -04:00
CamilleLaVey 8c077fc4cd Revert "[nce] Added case for access fault handling to manage page edge cases" 2026-03-10 03:01:02 -04:00
CamilleLaVey b88ca5b635 Revert "[nce] Added dual channel handling for guest access faults" 2026-03-10 02:58:57 -04:00
CamilleLaVey 4755ec7a59 [vulkan] simplify numeric type determination 2026-03-10 02:46:35 -04:00
CamilleLaVey e20875d8fc fix build from nce changes 2026-03-09 01:57:29 -04:00
CamilleLaVey 58bff28b04 [vulkan] Reset query pool in Reset() method + removed bank reset 2026-03-09 01:40:26 -04:00
CamilleLaVey 570cd64c03 [nce] Added rasterizer memory handling by nce page faults + intercepted memory access in nce with cached rasterizer data 2026-03-09 01:16:07 -04:00
CamilleLaVey b0a607539a [nce] Added rasterizer caching checks to memory management 2026-03-09 00:34:12 -04:00
CamilleLaVey 64163f2d2a [vulkan] Adjusting few remanants of topologies + review of possible missing dynamic state handling 2026-03-08 22:42:22 -04:00
CamilleLaVey 631ad933b2 fix build 2026-03-08 22:25:56 -04:00
CamilleLaVey 6985aaf614 [vulkan] Adjusted dynamic state handling + update state tracker for extended dynamic states 2026-03-08 22:16:08 -04:00
CamilleLaVey d1466b0e9d Revert "[debug] Instrumentalization for EDS related pipeline worker failure." 2026-03-08 21:13:26 -04:00
CamilleLaVey a02e8a5679 [vulkan] Removed dynamic culling mode/ front face. 2026-03-08 19:52:20 -04:00
CamilleLaVey e98280bee9 [vulkan] Removal of dynamic viewport/scissor 2026-03-08 19:13:09 -04:00
CamilleLaVey 1d0a3e83fa [vulkan] Removed topologies 2026-03-08 17:23:46 -04:00
CamilleLaVey 4343874a69 [vulkan] Removal Primitive Topology 2026-03-08 16:30:03 -04:00
CamilleLaVey 11d5c0f96a [nce] Adjusted precise access fault window handling + decay mechanism 2026-03-08 16:07:05 -04:00
CamilleLaVey ca42578d4e [nce] Added "tainted" page fault handling inside dual channel 2026-03-08 15:55:06 -04:00
CamilleLaVey b7087a16ba [nce] Added dual channel handling for guest access faults 2026-03-08 15:37:29 -04:00
CamilleLaVey 1bdedc17ad [nce] Added case for access fault handling to manage page edge cases 2026-03-08 15:12:47 -04:00
CamilleLaVey 32e63b5fa2 fix build 2026-03-08 12:46:31 -04:00
CamilleLaVey 2e89bb890a [android] Modified compiliance of Android's Surface in older API's 2026-03-08 12:31:49 -04:00
CamilleLaVey 842acf2c86 fix build 2026-03-08 04:46:19 -04:00
CamilleLaVey 2d4c460011 [debug] Instrumentalization for EDS related pipeline worker failure. 2026-03-08 04:38:40 -04:00
CamilleLaVey c42c67ab85 [android] Refined cadence for low frame rate; chrono + duration = clamp/ ms response extended 2026-03-08 04:31:10 -04:00
CamilleLaVey 5bce1993e1 [android] Linked Frame Time Cadence to Quantized Frames 2026-03-08 04:17:58 -04:00
CamilleLaVey 8c79a3a8de [android] Adjusted heuristics hints for SpeedLimit x ScreenRefresh + Quantized FrameRate calculation 2026-03-08 04:05:42 -04:00
CamilleLaVey ebb3cda782 [android] Quantization for frames + hints Surface 2026-03-08 03:56:58 -04:00
CamilleLaVey daf972b517 [android] Replaced FramePacingMode path + Surface/EmuWindow added hints for FrameRate 2026-03-08 03:41:42 -04:00
CamilleLaVey cd25e61afa Fix license headers 2026-03-08 01:58:38 -04:00
CamilleLaVey 4a7aa1618a [vulkan] Add InvalidateState for old topology draw 2026-03-08 01:48:23 -04:00
CamilleLaVey 1aae75512b Revert "[vulkan] Added conservative path for RoundingModeRTZ + instrumentalization for shaders use" 2026-03-08 01:21:36 -04:00
CamilleLaVey 825b899bf8 Revert "fix build" 2026-03-08 01:18:06 -04:00
CamilleLaVey 73c16c3d45 Revert "[debug] Added extra logging/ address for shader info -> FP32Mul Optimize Path" 2026-03-08 01:17:13 -04:00
CamilleLaVey 124c97a88a Revert "[debug] fix logging entries for histogram" 2026-03-08 01:10:06 -04:00
CamilleLaVey 02f9c5ffd2 Revert "[test] Histogram debug - shader float control -> initial target: Adreno" 2026-03-08 01:07:59 -04:00
CamilleLaVey 042a10cf71 Revert "[test] shader float control returned to Adreno" 2026-03-08 01:04:14 -04:00
CamilleLaVey d6a889828f [vulkan] Invalidate scheduler state after rendering in multiple Vulkan draw functions 2026-03-08 01:02:37 -04:00
CamilleLaVey d06eb3f52f [vulkan] Changed info.color_output_type gate 2026-03-07 06:46:15 +00:00
CamilleLaVey 21c77d5dce [vulkan] Added conservative path for RoundingModeRTZ + instrumentalization for shaders use 2026-03-07 06:46:15 +00:00
CamilleLaVey a80e0f10ba Just meow 2026-03-07 06:46:15 +00:00
CamilleLaVey ccb518dc05 Changed logging context 2026-03-07 06:46:15 +00:00
CamilleLaVey bb768ad570 fix building 2 2026-03-07 06:46:15 +00:00
CamilleLaVey c028d925bc fix build 2026-03-07 06:46:15 +00:00
CamilleLaVey 3557ff28b7 [vulkan] Added no depth vs depth compare support 2026-03-07 06:46:15 +00:00
CamilleLaVey 108bb3d28b [debug] Added extra logging/ address for shader info -> FP32Mul Optimize Path 2026-03-07 06:46:15 +00:00
CamilleLaVey 5b12a7725d fix build 2026-03-07 06:46:15 +00:00
CamilleLaVey 15d575aa31 [test] Change forcerd order for CompareMask + forced refresh/ emit 2026-03-07 06:46:15 +00:00
CamilleLaVey 47fe86be7b [vulkan] Extended 3D image handling for subresource range calculations 2026-03-07 06:46:14 +00:00
CamilleLaVey 3db45f3c46 [vulkan] Implemented active color output tracking in runtime info and update fragment color handling 2026-03-07 06:46:14 +00:00
CamilleLaVey 4481391474 [vulkan] Maintenance9 removal 2026-03-07 06:46:14 +00:00
CamilleLaVey dee102cf92 [debug] Added extra parametters for histogram track info -> shader info for RZ 2026-03-07 06:46:14 +00:00
CamilleLaVey ad2f40b0e7 [debug] fix logging entries for histogram 2026-03-07 06:46:14 +00:00
CamilleLaVey d10080b757 [test] Histogram debug - shader float control -> initial target: Adreno 2026-03-07 06:46:14 +00:00
CamilleLaVey ce15cf7cd3 [vulkan] Adjusted image view usage flags to ensure compatibility with image format in TextureCache 2026-03-07 06:46:14 +00:00
CamilleLaVey 6b87b0052a [test] shader float control returned to Adreno 2026-03-07 06:46:14 +00:00
CamilleLaVey 9630da580d [vulkan] Removed counter enable for ZPassPixelCount64 in Clear method and added initial layout transition for images in RefreshContents 2026-03-07 06:46:14 +00:00
CamilleLaVey 12fdd88a58 smol fix for query enable 2026-03-07 06:46:14 +00:00
CamilleLaVey 646aea7fbf [vulkan] Removed unused helper in texture pass 2026-03-07 06:46:14 +00:00
CamilleLaVey 186c0b0cc7 [vulkan] Replaced old logic for DescriptorType for a numeric handling per type to avoid mismatches during format binding 2026-03-07 06:46:14 +00:00
CamilleLaVey ee1ffbaf2e [vulkan] Adjustments to wrong access of image-memory barrier on depth fragments + blending extended enabling method 2026-03-07 06:46:14 +00:00
CamilleLaVey c931de0570 [vulkan] Adjusted QueryReset's 2026-03-07 06:46:14 +00:00
CamilleLaVey 33f1fb1cf4 fix build 2026-03-07 06:46:14 +00:00
CamilleLaVey aabc470314 First meow in honor of meowly 2026-03-07 06:46:14 +00:00
CamilleLaVey d82a6a273d fix build 2026-03-07 06:46:14 +00:00
lizzie 4bb853d52a Merge fix 2026-03-07 06:46:14 +00:00
CamilleLaVey 43ebdb1ffc [vulkan] Adjusted DYNAMIC_STATES setting 2026-03-07 06:46:14 +00:00
CamilleLaVey e310f0b151 [vulkan] Indirect draw for dstStageMask 2026-03-07 06:46:14 +00:00
CamilleLaVey 9b915c8659 [vulkan] Adjusting re-cast for EDS support when bind happens 2026-03-07 06:46:14 +00:00
CamilleLaVey 35ab33de6a Fix build 2026-03-07 06:46:14 +00:00
CamilleLaVey 5615ea9ced [vulkan] re-cast vkCmdSet for dynamic states during binding 2026-03-07 06:46:14 +00:00
CamilleLaVey d273fc4ad6 [vulkan] Changed UpdateDynamicState order 2026-03-07 06:46:14 +00:00
CamilleLaVey e3e880e879 [vulkan] Added flag to detect last mode from provokingVertex 2026-03-07 06:46:14 +00:00
CamilleLaVey 6b8115f27a fix meow 2026-03-07 06:46:14 +00:00
CamilleLaVey a24e7e8143 [maxwell] Adding storage flags to some surface format 2026-03-07 06:46:14 +00:00
CamilleLaVey 88b9393b44 [vulkan] Adding guards per dynamic states setters 2026-03-07 06:46:14 +00:00
CamilleLaVey 2081d659d2 [vulkan] Dead code removal from VertexInputDynamicState 2026-03-07 06:46:14 +00:00
CamilleLaVey 9eacaf2444 [vulkan] Adjustment for Viewport and Scissor counts within EDS 2026-03-07 06:46:01 +00:00
CamilleLaVey c9eb764d2a [vulkan] Fixing some incongruences with pipeline keys and dynamic state flags 2026-03-07 06:46:01 +00:00
CamilleLaVey 72ced6b947 [vulkan] Set always vertex strides 2026-03-07 06:46:01 +00:00
CamilleLaVey ffae2350ca [vulkan] fix custom border color query struct 2026-03-07 06:46:01 +00:00
CamilleLaVey 646542a397 [vulkan] Query custom border color properties based on device report 2026-03-07 06:46:01 +00:00
CamilleLaVey 1ae76d44c1 [vulkan, rasterizer] Filling missing byte count handling when TFB is not available 2026-03-07 06:46:01 +00:00
CamilleLaVey 24d07ab28c fix build 2026-03-07 06:46:01 +00:00
CamilleLaVey 651a999017 [vulkan] Unique representation logic fix. 2026-03-07 06:46:01 +00:00
CamilleLaVey 5c0e12fb0e [vulkan] Rework line rasterization handle 2026-03-07 06:46:01 +00:00
CamilleLaVey 712c505cd1 [vulkan] Extending conversative rasterization detection and handling 2026-03-07 06:46:01 +00:00
CamilleLaVey 051522b54e [vulkan] Fix conditional rendering enable 2026-03-07 06:46:01 +00:00
CamilleLaVey a1fdbef129 [vulkan] removing dead code for driverID detection under EDS handling/ban 2026-03-07 06:46:01 +00:00
CamilleLaVey ecd5c751f8 fix license headers 2026-03-07 06:46:01 +00:00
CamilleLaVey 69678d02b6 [android] Removing unneeded setting 2026-03-07 06:46:01 +00:00
CamilleLaVey 8d031532d8 [vulkan] Dead code removal 2026-03-07 06:46:01 +00:00
CamilleLaVey c8e4818b01 [vulkan] Fixing inconsistences within VK_EXT_extended_dynamic_state1 handling 2026-03-07 06:46:01 +00:00
CamilleLaVey 2bac9cec32 [vulkan] Implenting layouts use for indexing descriptors 2026-03-07 06:46:01 +00:00
CamilleLaVey 6190fcaaef [vulkan] Changing ProvokingVertex enabling nature 2026-03-07 06:46:01 +00:00
CamilleLaVey 3272e1fcb5 [vulkan] adjusting BindVertexBuffer2EXT wrong calling in pipeline 2026-03-07 06:46:00 +00:00
CamilleLaVey 8faeffdc7e [vulkan] removal of EDS3 and VIDS 2026-03-07 06:44:48 +00:00
xbzk ddac8c8eb5 [vk] fix crash introduced in 9a07bd0570 (#3685)
Fix for current crash on master.
Just reverted only the necessary stuff so that PresentManager can hold a reference to khr and resist death upon application hold/restore.
@Lizzie shall judge.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3685
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-06 19:52:17 +01:00
lizzie c062931c9b [qt] add translation table entry for debug_knobs,serial_battery and serial_unit (#3682)
trivial qt change

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3682
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 16:38:39 +01:00
crueter e4122dae1d [desktop] addons: open mod folder in rc menu (#3662)
also fixed the multiselection being absolutely horrendous

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3662
2026-03-06 16:38:21 +01:00
lizzie b75e81af5e [video_core/engines] implement stub NV01 timer, inline other channel engines (#3640)
Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3640
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:39 +01:00
lizzie 2ed1328c93 [vk] use static_vector instead of small_vector for TFB and other bindings (#3641)
MK8D is a big offender, taking up lots of time memcpy'ing and memmov'ing small_vector<> AND to add salt to the wound it doesn't even do heap allocations (no game does I think) - so basically useless waste of compute time in hot path for NO reason :^)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3641
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:05:05 +01:00
lizzie c70b857c4f [video_core/engines] Macro HLE inline (#3653)
Should slightly boost perf on android, Desktop is mainly unaffected (for now)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3653
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-06 15:04:38 +01:00
MaranBr 23566a1f7d [prepo] Add support for missing PlayReport commands (#3674)
This fixes:

`[ 433.095195] Debug <Critical> core\hle\service\service.cpp:operator ():69: Assertion Failed!
Unknown / unimplemented function '10107': port='prepo:u' cmd_buf={[0]=0x110006, [1]=0x80000014, [2]=0x1, [3]=0x0, [4]=0x0, [5]=0x191080, [6]=0x5A7350F8, [7]=0x112, [8]=0x5A735158}`

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3674
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-06 15:02:59 +01:00
xbzk 529b069499 [android,ui] fixed top disalignment between buttons of each column in settings fragment (#3675)
this silly little thing tickles obsessive compulsive disturbed fellas a lot hu3
was shipped along PR 3660, which was rediscussed for other reason, hence this tiny lonely PR.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3675
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-05 13:58:46 +01:00
lizzie 9a07bd0570 [vk] unify VkSurfaceKHR with Android and the rest of platforms; remove technically incorrect nullptr() ctor for handles (#2971)
Removes some odd #ifdef-ing that just can use a shrimple opaque type.

Also removes nullptr() ctor'ing for vulkan handles and such; it's not incorrect per se like how `void *p = 0;` isn't incorrect, just that, y'know, any static analyzer will go "woah". Also there isn't any guarantee that handles `sizeof(Handle) == sizeof(void*)` so may as well :)

Signed-off-by: lizzie lizzie@eden-emu.dev

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2971
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-05 07:32:18 +01:00
xbzk 05f6942bef [android, ui] fixed setting reset to defaults infinite loop (#3659)
fixed a bug discovered by Pavel in which the settings' "reset to defaults" dialog would get stuck in a infinite loop, due to a recall prior to cleaning state.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3659
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-05 00:58:49 +01:00
nekle 70c1e9abed [android] Try and fix SD Card storage mount points for external paths (#3436)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3436
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: nekle <nekle@protonmail.com>
Co-committed-by: nekle <nekle@protonmail.com>
2026-03-05 00:56:25 +01:00
wildcard 69e47bd2c0 [vulkan] Fix wrong stage index in prepare_stage render area check (#3672)
The OpenGL correctly uses const auto& info{stage_infos[stage]};

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3672
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:48 +01:00
wildcard cdf9b556b2 [vulkan]fix vuid 02751 (#3573)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3573
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-05 00:54:26 +01:00
xbzk 7f5de6bcd6 [android/fs] external content loader + nca/xci patches (#3596)
Foreword: WHY DON'T EVERYBODY USE ONE FOLDER FOR EACH GAME+CONTENTS? AIN'T THIS THE FORMAT GAMES COME WHEN YOU BUE THEM? DO YOU LIVE WITH ALL YOUR FRIENDS AND HAVE A 2ND HOUSE FOR ALL THE CHILDREN?

Nice, i feel better now.

This feat extends Maufeat's work on external content loading. It harmonically additions:
"...also, if in each game folder X, you find a folder Y, and in this folder Y you detect ONLY a single game, then mount all external content for that game found in that folder Y and its subfolders."
Permanent (not toggleable). External Content folders are supported equally.

Also:
-Reworked several routines for preserving single source of truth between android and other systems;
-Fixed the annoying unknown format error for content files, by providing proper format detection.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3596
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-04 22:51:35 +01:00
wildcard c682306788 [vulkan] Implement push descriptors for compute pipelines (#3666)
Implements push descriptor for compute pipelines along with a bug fix, the increment logic was, offset += sizeof(DescriptorUpdateEntry);
This only advances the byte offset by a single descriptor slot, regardless of the array's size (descriptorCount).Now suppose if a shader utilized an array of descriptors (eg, layout(binding = 0) uniform sampler2D textures[4]) and if this happened to fit within the MaxPushDescriptors limit, the template would consume 4 * sizeof(DescriptorUpdateEntry) bytes, but the offset for the next binding would only advance by 1 slot.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3666
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2026-03-04 22:46:55 +01:00
PavelBARABANOV 9d2341eaea [vk] Disable float16 math on non-MESA AMD drivers as 2026+ versions are broken (#3661)
thanks MaranBR

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3661
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-03-03 14:27:47 +01:00
tarako d720a7b4b4 [vulkan] Fixed dual-source blending to correctly map shader outputs. (#3637)
Fixes Skyward Sword HD eye gitch and a related MoltenVK crash due to the incorrect output mapping. Verified working on mac and android.

The test in vk_pipeline_cache.cpp is a bit ugly, but it didn't seem worth it to go lambda/macro just to make it look cleaner. Could change if necessary.

Co-authored-by: tarako <none@none.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3637
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: tarako <r76036296@gmail.com>
Co-committed-by: tarako <r76036296@gmail.com>
2026-03-03 01:54:57 +01:00
crueter 464212393e [gitignore] add *.tar.zst (#3663)
the amount of times I've accidentally committed a tar.zst to this
repository...

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3663
2026-03-02 05:28:26 +01:00
MaranBr ea209e6dab [vulkan] Fine-tuning of frame pacing logic (#3628)
It makes fine adjustments to the frame pacing, ensuring better stability and precision.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3628
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-02 02:53:01 +01:00
MaranBr b5f9f8b743 [vulkan] Fix Vulkan rendering, image layout, and synchronization issues (#3511)
This fixes a visual corruption issue that occurred intermittently after loading screens, where some games would start the scene with vertex explosions, artifacts or with all colors blown out, resembling neon.

Among the known games affected by this bug are Mario Kart 8 Deluxe, The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom, Kirby and the Forgotten Land, Luigi's Mansion 3, Xenoblade Chronicles 3 and possibly others as well.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3511
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-03-02 02:51:50 +01:00
xbzk a8fc994c0b pr3655 gradientbordercardview fix (#3658)
This complements PR3655, where a change in theme setting caused gradientbordercardview theme color setting mismatch.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3658
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
2026-03-01 23:21:26 +01:00
lizzie 2991bd18ef [vk] Enable VK_IMG_filter_cubic on powervr (#3643)
in powervr, the enum for cubic filtering is an alias for an ext, however in true powervr fashion they want you to use their stupid VK_IMG

and knowing powevr it probably bugs out if you dont

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3643
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-01 01:53:15 +01:00
lizzie 281d7a468e [ports] Remove ENABLE_OPENSSL and mbedtls nixos remnants (#3638)
shell.nix still had references to mbedtls

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3638
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-01 01:53:04 +01:00
PavelBARABANOV 12f89745be [android] add IntSetting for static theme and set green as default (#3655)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3655
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-28 19:34:55 +01:00
xXJSONDeruloXx 7de5eb6884 [android] fix persist manual game pause after android sleep/wake (#3651)
if user invokes the "pause game" option from the menu while in game, as expected this suspends the process till user manually hits resume.. except for one case: Android sleep/wake lifecycle.

If user manually pauses a running game, then sleeps their device, then wakes their device; the game will self-resume without user pressing "resume game".

Expected behavior IMO is that if user left the game process in manually paused state, app should respect this and persist the pause on system wake, so that user may manually press "resume game" to unfreeze the process.

Simple fix is to have a few params for user initiated pause and resume, and update the pause and run methods to handle as described above.

Please let me know if there is a cleaner way to implement!

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3651
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: xXJSONDeruloXx <danielhimebauch@gmail.com>
Co-committed-by: xXJSONDeruloXx <danielhimebauch@gmail.com>
2026-02-28 16:05:06 +01:00
John ac181b756f [Config] Update Rescale Toggle Description to Include Intel GPUs (#3650)
The toggle hack has benefits for Intel GPU users in Luigi Mansion 3 as they also suffer from line artifacts.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3650
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-28 01:07:40 +01:00
smiRaphi 4a833e7206 [desktop] port IR Camera to Qt6 & fix camera saving on windows (#3630)
The camera was previously saved without escaping the name which made the values unusable after a settings load, for whatever reason replacing the backslashes when saving with / doesn't work but replacing them with | does.

Also note that the OBS virtual cam (and any other cameras that only have directshow drivers) won't work because Qt6 dropped support for that and the ffmpeg backend doesn't seem to support it either.

Closes #3468

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3630
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: smiRaphi <neogt404@gmail.com>
Co-committed-by: smiRaphi <neogt404@gmail.com>
2026-02-28 01:06:59 +01:00
lizzie 06a08de68a [core/loader] prevent program_image reallocations in NSO+KIP loading methods (#3639)
also changes some methods to std::span<> as well, but mainly std::vector<> in the NSO/KIP loading stuff is not needed to be memcpy'ed and memmove'd around
this should save a marginal amount of loading time (RDR1)

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3639
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-28 01:06:33 +01:00
JPikachu f8712e50e6 [audio] stub SetAudioDeviceOutputVolumeAutoTuneEnabled and IsAudioDeviceOutputVolumeAutoTuneEnabled (#3654)
For Pokémon LeafGreen Version and Pokémon FireRed Version

Co-authored-by: JPikachu <jpikachu.eden@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3654
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: JPikachu <jpikachu@eden-emu.dev>
Co-committed-by: JPikachu <jpikachu@eden-emu.dev>
2026-02-28 01:06:22 +01:00
crueter fc63de39dd [docs] Bring back the Options page, and clean up LLM policy (#3649)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3649
2026-02-27 03:33:36 +01:00
crueter bf25058dbd [docs] SIGNUP: Add spam/junk notice, email patch info, and clarify some things (#3648)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3648
2026-02-27 03:33:29 +01:00
lizzie 75104b6103 [hle] Implement IApplicationManagerInterface::GetTotalSpaceSize (#3642)
oops

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3642
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-26 16:48:04 +01:00
John 0d950195e9 [Settings] Add "Enable Legacy Rescale Pass" Toggle (#3582)
This PR introduces an optional Legacy Rescale Compatibility Mode that restores the previous rescale‑pass behavior for titles that rely on its quirks. While the new rescale logic is generally more correct, some games exhibit visual issues that the legacy behavior incidentally avoids.
Enabling this mode can mitigate line artifacts on AMD GPUs and reduce grey‑texture flickering on Nvidia GPUs in Luigi’s Mansion 3. This is a compatibility workaround rather than a full fix, and should only be used for titles affected by these rare edge‑case rendering problems.

Original Logic from MaranBR

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3582
Co-authored-by: John <john@eden-emu.dev>
Co-committed-by: John <john@eden-emu.dev>
2026-02-26 03:57:25 +01:00
PavelBARABANOV 04e88ab82c [qt] Remove QEvent::Polish from titlebar filter to fix Applets tab (#3644)
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3644
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
Co-committed-by: PavelBARABANOV <pavelbarabanov94@gmail.com>
2026-02-26 03:15:32 +01:00
168 changed files with 4442 additions and 3309 deletions
+1
View File
@@ -63,3 +63,4 @@ artifacts
*.AppImage*
/install*
vulkansdk*.exe
*.tar.zst
+7 -7
View File
@@ -70,7 +70,7 @@ endif()
option(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
# option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
cmake_dependent_option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
@@ -577,9 +577,9 @@ if (ENABLE_QT)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
# if (YUZU_USE_QT_MULTIMEDIA)
# find_package(Qt6 REQUIRED COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
endif()
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
# yes Qt, we get it
@@ -618,9 +618,9 @@ if (ENABLE_QT)
if (PLATFORM_LINUX)
list(APPEND YUZU_QT_COMPONENTS DBus)
endif()
# if (YUZU_USE_QT_MULTIMEDIA)
# list(APPEND YUZU_QT_COMPONENTS Multimedia)
# endif()
if (YUZU_USE_QT_MULTIMEDIA)
list(APPEND YUZU_QT_COMPONENTS Multimedia)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
endif()
+100
View File
@@ -0,0 +1,100 @@
# CMake Options
To change these options, add `-DOPTION_NAME=NEWVALUE` to the command line.
- On Qt Creator, go to Project -> Current Configuration
Notes:
- Defaults are marked per-platform.
- "Non-UNIX" just means Windows/MSVC and Android (yes, macOS is UNIX
- Android generally doesn't need to change anything; if you do, go to `src/android/app/build.gradle.kts`
- To set a boolean variable to on, use `ON` for the value; to turn it off, use `OFF`
- If a variable is mentioned as being e.g. "ON" for a specific platform(s), that means it is defaulted to OFF on others
- TYPE is always boolean unless otherwise specified
- Format:
- `OPTION_NAME` (TYPE DEFAULT) DESCRIPTION
## Options
### Dependencies
These options control dependencies.
- `YUZU_USE_BUNDLED_FFMPEG` (ON for non-UNIX) Download a pre-built and configured FFmpeg
- `YUZU_USE_EXTERNAL_FFMPEG` (ON for Solaris) Build FFmpeg from source
- `YUZU_DOWNLOAD_ANDROID_VVL` (ON) Download validation layer binary for Android
- `YUZU_DOWNLOAD_TIME_ZONE_DATA` (ON) Always download time zone binaries
- Currently, build fails without this
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo)
- `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build
- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
### Miscellaneous
- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service
- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental
- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend
- This option is subject for removal.
- `YUZU_TESTS` (ON) Compile tests - requires Catch2
- `ENABLE_LTO` (OFF) Enable link-time optimization
- Not recommended on Windows
- UNIX may be better off appending `-flto=thin` to compiler args
- `USE_FASTER_LINKER` (OFF) Check if a faster linker is available
- Not recommended outside of Linux
### Flavors
These options control executables and build flavors.
- `YUZU_LEGACY` (OFF): Apply patches to improve compatibility on some older GPUs at the cost of performance
- `NIGHTLY_BUILD` (OFF): This is only used by CI. Do not use this unless you're making your own distribution and know what you're doing.
- `YUZU_STATIC_BUILD` (OFF) Attempt to build using static libraries if possible
- Not supported on Linux
- Automatically set if `YUZU_USE_BUNDLED_QT` is on for non-Linux
- `ENABLE_UPDATE_CHECKER` (OFF) Enable update checking functionality
- `YUZU_DISABLE_LLVM` (OFF) Do not attempt to link to the LLVM demangler
- Really only useful for CI or distribution builds
**Desktop only**:
- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli)
- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable
- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality
- `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable
- This is only usable on Alpine Linux.
### Desktop
The following options are desktop only.
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
- Unavailable on Windows/ARM64
- You probably shouldn't turn this off.
### Qt
Also desktop-only, but apply strictly to Qt
- `ENABLE_QT` (ON) Enable the Qt frontend (recommended)
- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend
- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries
- Not recommended on Linux. For Windows and macOS, the provided build is statically linked.
- `YUZU_QT_MIRROR` (string) What mirror to use for downloading the bundled Qt libraries
- `YUZU_USE_QT_MULTIMEDIA` (OFF) Use QtMultimedia for camera support
- `YUZU_USE_QT_WEB_ENGINE` (OFF) Use QtWebEngine for web applet implementation (requires the huge QtWebEngine dependency; not recommended)
- `USE_DISCORD_PRESENCE` (OFF) Enables Discord Rich Presence (Qt frontend only)
### Retired Options
The following options were a part of Eden at one point, but have since been retired.
- `ENABLE_OPENSSL` - MbedTLS was fully replaced with OpenSSL in [#3606](https://git.eden-emu.dev/eden-emu/eden/pulls/3606), because OpenSSL straight-up performs better.
- `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort.
- `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now.
- If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`.
See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed
+1
View File
@@ -3,6 +3,7 @@
This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more.
- **[General Build Instructions](Build.md)**
- **[CMake Options](Options.md)**
- **[Cross Compiling](CrossCompile.md)**
- **[Development Guidelines](Development.md)**
- **[Dependencies](Deps.md)**
+23 -4
View File
@@ -18,7 +18,7 @@ First of all, you MUST have a valid reason to sign up for our Git. Valid reasons
The following are not valid reasons to sign up:
- I want to contribute to Eden.
* Be at least somewhat specific!
* Be at least somewhat specific! We always welcome contributors and developers, but generic "I want to contribute" messages don't give us enough information.
- I want to support Eden.
* If you wish to support us through development, be more specific; otherwise, to support us, check out our [donations page](https://eden-emu.dev/donations).
- I want to report issues.
@@ -41,17 +41,36 @@ Email: <Your Desired Email>
I wish to sign up because... <your reason here>
```
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email.
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email. Do note that the email you sign up with is used to accredit commits on the web UI, and *must* match your configured GPG key.
## Patches
In general, PRs are the preferred method of tracking patches, as they allow us to go through our standard triage, CI, and testing process without having to deal with the minutiae of incremental patches. However, we also understand that many people prefer to use raw patches, and that's totally okay! While we currently don't have a mailing list, we do accept email patches. To do so:
1. Make your changes on a clean copy of the master branch
2. Commit your changes with a descriptive, well-formed message (see the [commit message docs](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/Development.md#pull-requests)), and a proper description thoroughly explaining your changes.
* Note that we don't need to know all the individual details about your code. A description explaining the motivation and general implementation of your changes is enough, alongside caveats and any potential blockers.
3. Format your patch with `git format-patch -1 HEAD`.
4. Email us with the subject `[Eden] [PATCH] <brief patch description...>`, with a brief description of your patch, and the previously-formatted patch file as an attachment.
* If you don't include the first two bracketed parts, your email may be lost!
The following emails are currently set up to receive and process patches:
- [eden@eden-emu.dev](mailto:eden@eden-emu.dev]
- [crueter@eden-emu.dev](mailto:eden@eden-emu.dev)
## Instructions
If you have read everything above and affirm that you will not abuse your access, click the summary below to get the email to send your request to.
<details>
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam requests or the following email.</summary>
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam to the following email.</summary>
Email [crueter@crueter.xyz](mailto:crueter@crueter.xyz) with the format above.
Once your request is processed, you should receive a confirmation email from crueter with your password alongside a link to a repository containing instructions on SSH, etc. Note that you are required to change your password. If your request is rejected, you will receive a notice as such, asking for clarification if needed. If you do not receive a response in 48 hours, you may send another email.
</details>
> [!WARNING]
> Some email providers may place the response email in your spam/junk folder; notable offenders include Gmail and Outlook. *Always* ensure to check your Spam/Junk folder, until Google/Microsoft finally end their vendetta against the great evil of my `.xyz` domain.
</details>
+7 -2
View File
@@ -17,7 +17,9 @@ AI is notorious for hallucinating facts out of thin air and sometimes outright l
- Code that works, but is extraordinarily verbose or not nearly as efficient as it can be
- Code that works well and is written well, but solves a different problem than was intended, or solves the same problem but in a completely incorrect way that will break other things horribly.
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone both familiar with the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar. Keep in mind: all code is held under a standard of excellence. If your code sucks, it will be rejected. AI-generated code just so happens to be a particularly sucky genre of code, and will thus be held to this same standard.
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone familiar with *both* the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar.
**All code is held under a STRICT STANDARD OF EXCELLENCE**. AI code is no different, and since it often produces subpar or outright terrible code, it will often fail to meet this excellence standard.
On a lesser-known note, LLM outputs often contain unicode symbols such as emojis or the arrow symbol. Please don't put Unicode symbols in your code. It messes with many an IDE, and the three people viewing your code on Lynx will be very unhappy.
@@ -25,7 +27,8 @@ On a lesser-known note, LLM outputs often contain unicode symbols such as emojis
## Acceptable Use
- As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
- Anything directly outside of the realm of the code written in your PR or patch is none of our business.
- This primarily covers research.
- However, we *still* strongly discourage this for the reasons mentioned above.
@@ -104,4 +107,6 @@ This consolidates profile removal behavior, fixes potential race conditions in t
This has all of the same problems as the other one. Needlessly verbose, doesn't address *what* it actually fixes ("consolidates profile removal behavior"... okay, why? What does it fix?), etc. It even has the bonus of totally hallucinating the addition of a method!
On a more "philosophical" note, LLMs tend to be geared towards *corporate language*, as that's what they're trained on. This is why AI-generated commit messages feel like "word salad", and typically pad out the commit message to make it *look* like a lot of things were changed (trust me, it's like that in the corporate world). They typically also drift towards unneeded buzzwords and useless implementation details.
**Don't use AI for commit messages**.
+1 -1
View File
@@ -9,7 +9,7 @@ pkgs.mkShellNoCC {
# libraries
openssl boost fmt nlohmann_json lz4 zlib zstd
enet libopus vulkan-headers vulkan-utility-libraries
spirv-tools spirv-headers vulkan-loader unzip mbedtls
spirv-tools spirv-headers vulkan-loader unzip
glslang python3 httplib cpp-jwt ffmpeg-headless
libusb1 cubeb
# eden
-1
View File
@@ -80,7 +80,6 @@ android {
listOf(
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_WEB_SERVICE=1", // Enable web service
"-DENABLE_OPENSSL=ON",
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DYUZU_USE_CPM=ON",
"-DCPMUTIL_FORCE_BUNDLED=ON",
@@ -152,6 +152,10 @@ object NativeLibrary {
external fun surfaceDestroyed()
external fun getAppletCaptureBuffer(): ByteArray
external fun getAppletCaptureWidth(): Int
external fun getAppletCaptureHeight(): Int
/**
* Unpauses emulation from a paused state.
*/
@@ -603,6 +607,12 @@ object NativeLibrary {
*/
external fun addFileToFilesystemProvider(path: String)
/**
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
* game-folder external-content mounting.
*/
external fun addGameFolderFileToFilesystemProvider(path: String)
/**
* Clears all files added to the manual filesystem provider in our EmulationSession instance
*/
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
}
override fun onPause() {
super.onPause()
nfcReader.stopScanning()
stopMotionSensorListener()
super.onPause()
}
override fun onDestroy() {
@@ -339,6 +339,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
}
override fun onSensorChanged(event: SensorEvent) {
if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
return
}
val rotation = this.display?.rotation
if (rotation == Surface.ROTATION_90) {
flipMotionOrientation = true
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.fetcher
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
@@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
state: RecyclerView.State
) {
outRect.bottom = spacing
if (parent.getChildAdapterPosition(view) == 0) {
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
if (position == 0) {
outRect.top = spacing
return
}
// If the item is in the first row, but NOT in first column add top spacing as well
val layoutManager = parent.layoutManager
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
outRect.top = spacing
return
}
}
}
@@ -30,8 +30,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
RENDERER_DEBUG("debug"),
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
RENDERER_SAMPLE_SHADING("sample_shading"),
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
@@ -34,6 +34,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
MAX_ANISOTROPY("max_anisotropy"),
THEME("theme"),
THEME_MODE("theme_mode"),
STATIC_THEME_COLOR("static_theme_color"),
APP_LANGUAGE("app_language"),
OVERLAY_SCALE("control_scale"),
OVERLAY_OPACITY("control_opacity"),
@@ -141,20 +141,6 @@ abstract class SettingsItem(
valuesId = R.array.dynaStateValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_PROVOKING_VERTEX,
titleId = R.string.provoking_vertex,
descriptionId = R.string.provoking_vertex_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE,
titleId = R.string.vertex_input_dynamic_state,
descriptionId = R.string.vertex_input_dynamic_state_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_DESCRIPTOR_INDEXING,
@@ -349,15 +335,6 @@ abstract class SettingsItem(
valuesId = R.array.astcDecodingMethodValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_ASTC_RECOMPRESSION,
titleId = R.string.astc_recompression,
descriptionId = R.string.astc_recompression_description,
choicesId = R.array.astcRecompressionMethodNames,
valuesId = R.array.astcRecompressionMethodValues
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_VRAM_USAGE_MODE,
@@ -670,15 +647,6 @@ abstract class SettingsItem(
valuesId = R.array.dmaAccuracyValues
)
)
put(
SingleChoiceSetting(
IntSetting.FRAME_PACING_MODE,
titleId = R.string.frame_pacing_mode,
descriptionId = R.string.frame_pacing_mode_description,
choicesId = R.array.framePacingModeNames,
valuesId = R.array.framePacingModeValues
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (val item = settingsViewModel.clickedItem) {
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
clearDialogState()
when (item) {
is AnalogInputSetting -> {
val stickParam = NativeInput.getStickParam(
item.playerIndex,
@@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
}
else -> {
settingsViewModel.clickedItem!!.setting.reset()
item.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
clearDialogState()
}
.setOnCancelListener {
clearDialogState()
}
.create()
}
@@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
updateButtonState(isValid)
}
/*
* xbzk: these two events, along with attachRepeat feature,
* were causing spinbox buttons to respond twice per press
* cutting these out to retain accelerated press functionality
* TODO: clean this out later if no issues arise
*
spinboxBinding.buttonDecrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current - 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
spinboxBinding.buttonIncrement.setOnClickListener {
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
val newValue = current + 1
spinboxBinding.editValue.setText(newValue.toString())
updateValidity(newValue)
}
*/
fun attachRepeat(button: View, delta: Int) {
val handler = Handler(Looper.getMainLooper())
var runnable: Runnable? = null
@@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private fun closeDialog() {
settingsViewModel.setAdapterItemChanged(position)
clearDialogState()
dismiss()
}
private fun clearDialogState() {
settingsViewModel.clickedItem = null
settingsViewModel.setSliderProgress(-1f)
dismiss()
}
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
@@ -267,11 +267,9 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_ACCURACY.key)
add(IntSetting.DMA_ACCURACY.key)
add(IntSetting.FRAME_PACING_MODE.key)
add(IntSetting.MAX_ANISOTROPY.key)
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
add(IntSetting.RENDERER_ASTC_RECOMPRESSION.key)
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
@@ -291,8 +289,6 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.extensions))
add(IntSetting.RENDERER_DYNA_STATE.key)
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
@@ -1066,7 +1062,10 @@ class SettingsFragmentPresenter(
IntSetting.THEME.getValueAsString()
override val defaultValue: Int = IntSetting.THEME.defaultValue
override fun reset() = IntSetting.THEME.setInt(defaultValue)
override fun reset() {
IntSetting.THEME.setInt(defaultValue)
settingsViewModel.setShouldRecreate(true)
}
}
add(HeaderSetting(R.string.app_settings))
@@ -1124,23 +1123,24 @@ class SettingsFragmentPresenter(
}
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
val preferences = PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal)
override fun setInt(value: Int) {
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, value) }
IntSetting.STATIC_THEME_COLOR.setInt(value)
settingsViewModel.setShouldRecreate(true)
}
override val key: String = Settings.PREF_STATIC_THEME_COLOR
override val key: String = IntSetting.STATIC_THEME_COLOR.key
override val isRuntimeModifiable: Boolean = true
override fun getValueAsString(needsGlobal: Boolean): String =
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString()
override val defaultValue: Any = 0
IntSetting.STATIC_THEME_COLOR.getValueAsString(needsGlobal)
override val defaultValue: Any = IntSetting.STATIC_THEME_COLOR.defaultValue
override fun reset() {
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, 0) }
IntSetting.STATIC_THEME_COLOR.reset()
settingsViewModel.setShouldRecreate(true)
}
}
@@ -15,6 +15,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
import android.os.BatteryManager
import android.os.BatteryManager.*
@@ -97,6 +98,7 @@ import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.or
@@ -141,6 +143,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var wasInputOverlayAutoHidden = false
private var overlayTouchActive = false
private var pausedFrameBitmap: Bitmap? = null
var shouldUseCustom = false
private var isQuickSettingsMenuOpen = false
@@ -703,6 +706,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
binding.pausedIcon.setOnClickListener {
if (this::emulationState.isInitialized && emulationState.isPaused) {
resumeEmulationFromUi()
}
}
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val lockMode = IntSetting.LOCK_DRAWER.getInt()
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
@@ -728,11 +737,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
when (it.itemId) {
R.id.menu_pause_emulation -> {
if (emulationState.isPaused) {
emulationState.run(false)
updatePauseMenuEntry(false)
resumeEmulationFromUi()
} else {
emulationState.pause()
updatePauseMenuEntry(true)
pauseEmulationAndCaptureFrame()
}
binding.inGameMenu.requestFocus()
true
@@ -826,6 +833,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
R.id.menu_exit -> {
clearPausedFrame()
emulationState.stop()
NativeConfig.reloadGlobalConfig()
emulationViewModel.setIsEmulationStopping(true)
@@ -1197,6 +1205,71 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private fun pauseEmulationAndCaptureFrame() {
emulationState.pause()
updatePauseMenuEntry(true)
capturePausedFrameFromCore()
updatePausedFrameVisibility()
}
private fun capturePausedFrameFromCore() {
lifecycleScope.launch(Dispatchers.Default) {
val frameData = NativeLibrary.getAppletCaptureBuffer()
val width = NativeLibrary.getAppletCaptureWidth()
val height = NativeLibrary.getAppletCaptureHeight()
if (frameData.isEmpty() || width <= 0 || height <= 0) {
Log.warning(
"[EmulationFragment] Paused frame capture returned empty/invalid data. " +
"size=${frameData.size}, width=$width, height=$height"
)
return@launch
}
val expectedSize = width * height * 4
if (frameData.size < expectedSize) {
Log.warning(
"[EmulationFragment] Paused frame buffer smaller than expected. " +
"size=${frameData.size}, expected=$expectedSize"
)
return@launch
}
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize))
withContext(Dispatchers.Main) {
pausedFrameBitmap?.recycle()
pausedFrameBitmap = bitmap
updatePausedFrameVisibility()
}
}
}
private fun updatePausedFrameVisibility() {
val b = _binding ?: return
val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused
b.pausedIcon.setVisible(showPausedUi)
val bitmap = if (showPausedUi) pausedFrameBitmap else null
b.pausedFrameImage.setImageBitmap(bitmap)
b.pausedFrameImage.setVisible(bitmap != null)
}
private fun resumeEmulationFromUi() {
clearPausedFrame()
emulationState.resume()
updatePauseMenuEntry(emulationState.isPaused)
updatePausedFrameVisibility()
}
private fun clearPausedFrame() {
val b = _binding
b?.pausedFrameImage?.setVisible(false)
b?.pausedFrameImage?.setImageDrawable(null)
pausedFrameBitmap?.recycle()
pausedFrameBitmap = null
}
private fun handleLoadAmiiboSelection(): Boolean {
val binding = _binding ?: return true
@@ -1290,8 +1363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun onPause() {
if (this::emulationState.isInitialized) {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
updatePauseMenuEntry(true)
pauseEmulationAndCaptureFrame()
} else {
updatePausedFrameVisibility()
}
}
super.onPause()
@@ -1301,6 +1375,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDestroyView()
amiiboLoadJob?.cancel()
amiiboLoadJob = null
clearPausedFrame()
_binding?.surfaceInputOverlay?.touchEventListener = null
_binding = null
isAmiiboPickerOpen = false
@@ -1321,6 +1396,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
b.inGameMenu.post {
if (!this::emulationState.isInitialized || _binding == null) return@post
updatePauseMenuEntry(emulationState.isPaused)
updatePausedFrameVisibility()
}
}
@@ -1760,6 +1836,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
// Only update surface reference, don't trigger state changes
emulationState.updateSurfaceReference(holder.surface)
}
updatePausedFrameVisibility()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
@@ -2090,6 +2167,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
@Synchronized
fun resume() {
if (state != State.PAUSED) {
Log.warning("[EmulationFragment] Resume called while emulation is not paused.")
return
}
if (!emulationCanStart.invoke()) {
Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.")
return
}
val currentSurface = surface
if (currentSurface == null || !currentSurface.isValid) {
Log.debug("[EmulationFragment] Resume requested with invalid surface.")
return
}
NativeLibrary.surfaceChanged(currentSurface)
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.unpauseEmulation()
NativeLibrary.playTimeManagerStart()
state = State.RUNNING
}
@Synchronized
fun changeProgram(programIndex: Int) {
emulationThread.join()
@@ -2111,7 +2211,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun updateSurface() {
if (surface != null) {
if (surface != null && state == State.RUNNING) {
NativeLibrary.surfaceChanged(surface)
}
}
@@ -2127,20 +2227,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@Synchronized
fun clearSurface() {
if (surface == null) {
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
Log.debug("[EmulationFragment] clearSurface called, but surface already null.")
} else {
if (state == State.RUNNING) {
pause()
}
NativeLibrary.surfaceDestroyed()
surface = null
Log.debug("[EmulationFragment] Surface destroyed.")
when (state) {
State.RUNNING -> {
state = State.PAUSED
}
State.PAUSED -> Log.warning(
State.PAUSED -> Log.debug(
"[EmulationFragment] Surface cleared while emulation paused."
)
else -> Log.warning(
else -> Log.debug(
"[EmulationFragment] Surface cleared while emulation stopped."
)
}
@@ -2148,29 +2248,35 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
private fun runWithValidSurface(programIndex: Int = 0) {
NativeLibrary.surfaceChanged(surface)
if (!emulationCanStart.invoke()) {
return
}
val currentSurface = surface
if (currentSurface == null || !currentSurface.isValid) {
Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.")
return
}
when (state) {
State.STOPPED -> {
NativeLibrary.surfaceChanged(currentSurface)
emulationThread = Thread({
Log.debug("[EmulationFragment] Starting emulation thread.")
NativeLibrary.run(gamePath, programIndex, true)
}, "NativeEmulation")
emulationThread.start()
state = State.RUNNING
}
State.PAUSED -> {
Log.debug("[EmulationFragment] Resuming emulation.")
NativeLibrary.unpauseEmulation()
NativeLibrary.playTimeManagerStart()
Log.debug(
"[EmulationFragment] Surface restored while emulation paused; " +
"waiting for explicit resume."
)
}
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
}
state = State.RUNNING
}
private enum class State {
@@ -127,10 +127,6 @@ class AddonViewModel : ViewModel() {
return
}
// Check if there are multiple update versions
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
val hasMultipleUpdates = updates.size > 1
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
@@ -140,7 +136,7 @@ class AddonViewModel : ViewModel() {
if (PatchType.from(it.type) == PatchType.Update) {
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
it.name
} else if (hasMultipleUpdates) {
} else if (it.numericVersion != 0L) {
"Update@${it.numericVersion}"
} else {
it.name
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.overlay
@@ -20,7 +20,6 @@ import android.os.Looper
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.SurfaceView
import android.view.View
import android.view.View.OnTouchListener
import android.view.WindowInsets
@@ -42,10 +41,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Draws the interactive input overlay on top of the
* [SurfaceView] that is rendering emulation.
* emulation rendering surface.
*/
class InputOverlay(context: Context, attrs: AttributeSet?) :
SurfaceView(context, attrs),
View(context, attrs),
OnTouchListener {
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@@ -94,8 +94,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityMainBinding.inflate(layoutInflater)
// Since Android 15, google automatically forces "games" to be 60 hrz
// This ensures the display's max refresh rate is actually used
display?.let {
val supportedModes = it.supportedModes
val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate }
@@ -424,7 +422,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
val folder = gamesViewModel.folders.value.firstOrNull {
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
}
if (folder != null) {
Toast.makeText(
applicationContext,
@@ -61,6 +61,12 @@ object DirectoryInitialization {
saveConfig = true
}
val staticThemeColor = preferences.migratePreference<Int>(Settings.PREF_STATIC_THEME_COLOR)
if (staticThemeColor != null) {
IntSetting.STATIC_THEME_COLOR.setInt(staticThemeColor)
saveConfig = true
}
val blackBackgrounds =
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
if (blackBackgrounds != null) {
@@ -51,11 +51,24 @@ object GameHelper {
// Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs()
for (externalDir in externalContentDirs) {
val uniqueExternalContentDirs = linkedSetOf<String>()
externalContentDirs.forEach { externalDir ->
if (externalDir.isNotEmpty()) {
uniqueExternalContentDirs.add(externalDir)
}
}
val mountedContainerUris = mutableSetOf<String>()
for (externalDir in uniqueExternalContentDirs) {
if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
val containerUri = it.uri.toString()
if (mountedContainerUris.add(containerUri)) {
NativeLibrary.addFileToFilesystemProvider(containerUri)
}
}
}
}
}
@@ -65,10 +78,13 @@ object GameHelper {
val gameDirUri = gameDir.uriString.toUri()
val isValid = FileUtil.isTreeUriValid(gameDirUri)
if (isValid) {
val scanDepth = if (gameDir.deepScan) 3 else 1
addGamesRecursive(
games,
FileUtil.listFiles(gameDirUri),
if (gameDir.deepScan) 3 else 1
scanDepth,
mountedContainerUris
)
} else {
badDirs.add(index)
@@ -103,9 +119,10 @@ object GameHelper {
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
private fun scanExternalContentRecursive(
private fun scanContentContainersRecursive(
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
onContainerFound: (MinimalDocumentFile) -> Unit
) {
if (depth <= 0) {
return
@@ -113,14 +130,15 @@ object GameHelper {
files.forEach {
if (it.isDirectory) {
scanExternalContentRecursive(
scanContentContainersRecursive(
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
onContainerFound
)
} else {
val extension = FileUtil.getExtension(it.uri).lowercase()
if (externalContentExtensions.contains(extension)) {
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
onContainerFound(it)
}
}
}
@@ -129,7 +147,8 @@ object GameHelper {
private fun addGamesRecursive(
games: MutableList<Game>,
files: Array<MinimalDocumentFile>,
depth: Int
depth: Int,
mountedContainerUris: MutableSet<String>
) {
if (depth <= 0) {
return
@@ -140,11 +159,20 @@ object GameHelper {
addGamesRecursive(
games,
FileUtil.listFiles(it.uri),
depth - 1
depth - 1,
mountedContainerUris
)
} else {
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
val game = getGame(it.uri, true)
val extension = FileUtil.getExtension(it.uri).lowercase()
val filePath = it.uri.toString()
if (externalContentExtensions.contains(extension) &&
mountedContainerUris.add(filePath)) {
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
}
if (Game.extensions.contains(extension)) {
val game = getGame(it.uri, true, false)
if (game != null) {
games.add(game)
}
@@ -153,14 +181,20 @@ object GameHelper {
}
}
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
fun getGame(
uri: Uri,
addedToLibrary: Boolean,
registerFilesystemProvider: Boolean = true
): Game? {
val filePath = uri.toString()
if (!GameMetadata.getIsValid(filePath)) {
return null
}
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
if (registerFilesystemProvider) {
// Needed to update installed content information
NativeLibrary.addFileToFilesystemProvider(filePath)
}
var name = GameMetadata.getTitle(filePath)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -80,16 +80,14 @@ object PathUtil {
}
}
// This really shouldn't be necessary, but the Android API seemingly
// doesn't have a way of doing this?
// Apparently, on certain devices the mount location can vary, so add
// extra cases here if we discover any new ones.
fun getRemovableStoragePath(idString: String): String? {
var pathFile: File
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
pathFile = File("/mnt/media_rw/$idString");
if (pathFile.exists()) {
return pathFile.absolutePath
for (mountPath in possibleMountPaths) {
val pathFile = File(mountPath);
if (pathFile.exists()) {
return pathFile.absolutePath
}
}
return null
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -52,7 +52,7 @@ object ThemeHelper {
}
private fun getSelectedStaticThemeColor(): Int {
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
val themes = arrayOf(
R.style.Theme_Eden_Main,
R.style.Theme_Yuzu_Main_Violet,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -11,8 +11,7 @@ import android.graphics.*
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.settings.model.Settings
import androidx.preference.PreferenceManager
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
class GradientBorderCardView @JvmOverloads constructor(
context: Context,
@@ -44,12 +43,7 @@ class GradientBorderCardView @JvmOverloads constructor(
}
private fun updateThemeState() {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val themeIndex = try {
prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
} catch (e: Exception) {
0 // Default to Eden theme if error
}
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
isEdenTheme = themeIndex == 0
invalidate()
}
+2 -5
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -27,10 +27,7 @@ if (ARCHITECTURE_arm64)
target_link_libraries(yuzu-android PRIVATE adrenotools)
endif()
if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
endif()
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
if (ENABLE_UPDATE_CHECKER)
target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER)
endif()
@@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() {
if (global) {
ReadAndroidUIValues();
ReadUIValues();
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
std::string("ext_content_from_game_dirs"),
std::make_optional(
Settings::values.ext_content_from_game_dirs.GetDefault()));
EndGroup();
ReadOverlayValues();
}
ReadDriverValues();
@@ -56,6 +56,7 @@ namespace AndroidSettings {
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
Settings::Setting<s32> static_theme_color{linkage, 5, "static_theme_color", Settings::Category::Android};
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
Settings::Category::Android};
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
@@ -1,10 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <android/native_window_jni.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <dlfcn.h>
#include "common/android/id_cache.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/drivers/android.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/virtual_amiibo.h"
@@ -14,6 +24,20 @@
#include "jni/native.h"
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
if (!surface) {
LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface");
m_window_width = 0;
m_window_height = 0;
window_info.render_surface = nullptr;
m_last_frame_rate_hint = -1.0f;
m_pending_frame_rate_hint = -1.0f;
m_pending_frame_rate_hint_votes = 0;
m_smoothed_present_rate = 0.0f;
m_last_frame_display_time = {};
m_pending_frame_rate_since = {};
return;
}
m_window_width = ANativeWindow_getWidth(surface);
m_window_height = ANativeWindow_getHeight(surface);
@@ -21,6 +45,7 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
window_info.render_surface = reinterpret_cast<void*>(surface);
UpdateFrameRateHint();
}
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
@@ -40,6 +65,9 @@ void EmuWindow_Android::OnTouchReleased(int id) {
}
void EmuWindow_Android::OnFrameDisplayed() {
UpdateObservedFrameRate();
UpdateFrameRateHint();
if (!m_first_frame) {
Common::Android::RunJNIOnFiber<void>(
[&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); });
@@ -47,6 +75,175 @@ void EmuWindow_Android::OnFrameDisplayed() {
}
}
void EmuWindow_Android::UpdateObservedFrameRate() {
const auto now = Clock::now();
if (m_last_frame_display_time.time_since_epoch().count() != 0) {
const auto frame_time = std::chrono::duration<float>(now - m_last_frame_display_time);
const float seconds = frame_time.count();
if (seconds > 0.0f) {
const float instantaneous_rate = 1.0f / seconds;
if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 1.0f &&
instantaneous_rate <= 240.0f) {
constexpr float SmoothingFactor = 0.15f;
if (m_smoothed_present_rate <= 0.0f) {
m_smoothed_present_rate = instantaneous_rate;
} else {
m_smoothed_present_rate +=
(instantaneous_rate - m_smoothed_present_rate) * SmoothingFactor;
}
}
}
}
m_last_frame_display_time = now;
}
float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) {
if (!std::isfinite(frame_rate) || frame_rate <= 0.0f) {
return 0.0f;
}
frame_rate = std::clamp(frame_rate, 1.0f, 240.0f);
constexpr float Step = 0.5f;
return std::round(frame_rate / Step) * Step;
}
float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
if (!EmulationSession::GetInstance().IsRunning()) {
return 0.0f;
}
const double frame_time_scale =
EmulationSession::GetInstance().System().GetPerfStats().GetLastFrameTimeScale();
if (!std::isfinite(frame_time_scale) || frame_time_scale <= 0.0) {
return 0.0f;
}
const float verified_rate =
std::clamp(60.0f / static_cast<float>(frame_time_scale), 0.0f, 240.0f);
return QuantizeFrameRateHint(verified_rate);
}
float EmuWindow_Android::GetFrameRateHint() const {
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
if (std::fabs(observed_rate - m_last_frame_rate_hint) <= tolerance) {
return m_last_frame_rate_hint;
}
}
const float observed_hint = QuantizeFrameRateHint(observed_rate);
if (observed_hint > 0.0f) {
if (frame_time_verified_hint > 0.0f) {
const float tolerance = std::max(observed_hint * 0.20f, 3.0f);
if (std::fabs(observed_hint - frame_time_verified_hint) <= tolerance) {
return QuantizeFrameRateHint((observed_hint + frame_time_verified_hint) * 0.5f);
}
}
return observed_hint;
}
if (frame_time_verified_hint > 0.0f) {
return frame_time_verified_hint;
}
constexpr float NominalFrameRate = 60.0f;
if (!Settings::values.use_speed_limit.GetValue()) {
return NominalFrameRate;
}
const u16 speed_limit = Settings::SpeedLimit();
if (speed_limit == 0) {
return 0.0f;
}
const float speed_limited_rate =
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
return QuantizeFrameRateHint(speed_limited_rate);
}
void EmuWindow_Android::UpdateFrameRateHint() {
auto* const surface = reinterpret_cast<ANativeWindow*>(window_info.render_surface);
if (!surface) {
return;
}
const auto now = Clock::now();
const float frame_rate_hint = GetFrameRateHint();
if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) {
m_pending_frame_rate_hint = frame_rate_hint;
m_pending_frame_rate_hint_votes = 0;
m_pending_frame_rate_since = {};
return;
}
if (frame_rate_hint == 0.0f) {
m_pending_frame_rate_hint = frame_rate_hint;
m_pending_frame_rate_hint_votes = 0;
m_pending_frame_rate_since = now;
} else if (m_last_frame_rate_hint >= 0.0f) {
if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) {
m_pending_frame_rate_hint = frame_rate_hint;
m_pending_frame_rate_hint_votes = 1;
m_pending_frame_rate_since = now;
return;
}
++m_pending_frame_rate_hint_votes;
if (m_pending_frame_rate_since.time_since_epoch().count() == 0) {
m_pending_frame_rate_since = now;
}
const auto stable_for = now - m_pending_frame_rate_since;
const float reference_rate = std::max(frame_rate_hint, 1.0f);
const auto stable_duration = std::chrono::duration_cast<Clock::duration>(
std::chrono::duration<float>(std::clamp(3.0f / reference_rate, 0.15f, 0.40f)));
constexpr std::uint32_t MinStableVotes = 3;
if (m_pending_frame_rate_hint_votes < MinStableVotes || stable_for < stable_duration) {
return;
}
} else {
m_pending_frame_rate_since = now;
}
using SetFrameRateWithChangeStrategyFn =
int32_t (*)(ANativeWindow*, float, int8_t, int8_t);
using SetFrameRateFn = int32_t (*)(ANativeWindow*, float, int8_t);
static const auto set_frame_rate_with_change_strategy =
reinterpret_cast<SetFrameRateWithChangeStrategyFn>(
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRateWithChangeStrategy"));
static const auto set_frame_rate = reinterpret_cast<SetFrameRateFn>(
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRate"));
constexpr int8_t FrameRateCompatibilityDefault = 0;
constexpr int8_t ChangeFrameRateOnlyIfSeamless = 0;
int32_t result = -1;
if (set_frame_rate_with_change_strategy) {
result = set_frame_rate_with_change_strategy(surface, frame_rate_hint,
FrameRateCompatibilityDefault,
ChangeFrameRateOnlyIfSeamless);
} else if (set_frame_rate) {
result = set_frame_rate(surface, frame_rate_hint, FrameRateCompatibilityDefault);
} else {
return;
}
if (result != 0) {
LOG_DEBUG(Frontend, "Failed to update Android surface frame rate hint: {}", result);
return;
}
m_last_frame_rate_hint = frame_rate_hint;
m_pending_frame_rate_hint = frame_rate_hint;
m_pending_frame_rate_hint_votes = 0;
m_pending_frame_rate_since = {};
}
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library)
: m_driver_library{driver_library} {
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <chrono>
#include <cstdint>
#include <memory>
#include <span>
@@ -50,10 +55,24 @@ public:
};
private:
using Clock = std::chrono::steady_clock;
void UpdateFrameRateHint();
void UpdateObservedFrameRate();
[[nodiscard]] float GetFrameRateHint() const;
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
float m_window_width{};
float m_window_height{};
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
bool m_first_frame = false;
float m_last_frame_rate_hint = -1.0f;
float m_pending_frame_rate_hint = -1.0f;
float m_smoothed_present_rate = 0.0f;
Clock::time_point m_last_frame_display_time{};
Clock::time_point m_pending_frame_rate_since{};
std::uint32_t m_pending_frame_rate_hint_votes = 0;
};
@@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
return false;
}
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return false;
}
u64 program_id = 0;
Loader::ResultStatus res = loader->ReadProgramId(program_id);
if (res != Loader::ResultStatus::Success) {
+54 -103
View File
@@ -89,6 +89,8 @@
#include "jni/native.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/capture.h"
#include "video_core/textures/decoders.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/shader_notify.h"
@@ -215,107 +217,8 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
return;
}
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
if (extension == "nsp") {
auto nsp = std::make_shared<FileSys::NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings;
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == FileSys::ContentRecordType::Meta) {
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
const auto section0 = meta_nca->GetSubdirectories();
if (!section0.empty()) {
for (const auto& meta_file : section0[0]->GetFiles()) {
if (meta_file->GetExtension() == "cnmt") {
FileSys::CNMT cnmt(meta_file);
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
}
}
}
}
}
if (content_type == FileSys::ContentRecordType::Control &&
title_type == FileSys::TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = FileSys::ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
FileSys::NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type == FileSys::TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
std::string version_string;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
version_string = str_it->second;
}
m_manual_provider->AddEntryWithVersion(
title_type, content_type, title_id, version, version_string,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
title_id, version, version_string);
} else {
// Use regular AddEntry for non-updates
m_manual_provider->AddEntry(title_type, content_type, title_id,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
return;
}
}
// Handle XCI files
if (extension == "xci") {
FileSys::XCI xci{file};
if (xci.GetStatus() == Loader::ResultStatus::Success) {
const auto nsp = xci.GetSecurePartitionNSP();
if (nsp) {
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
return;
}
if (m_manual_provider->AddEntriesFromContainer(file)) {
return;
}
auto loader = Loader::GetLoader(m_system, file);
@@ -337,6 +240,13 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
}
}
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
return;
}
ConfigureFilesystemProvider(filepath);
}
void EmulationSession::InitializeSystem(bool reload) {
if (!reload) {
// Initialize logging system
@@ -780,9 +690,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) {
ANativeWindow_release(native_window);
}
EmulationSession::GetInstance().SetNativeWindow(nullptr);
EmulationSession::GetInstance().SurfaceChanged();
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
@@ -969,6 +880,40 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
using namespace VideoCore::Capture;
if (!EmulationSession::GetInstance().IsRunning()) {
return env->NewByteArray(0);
}
const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer();
if (tiled.size() < TiledSize) {
return env->NewByteArray(0);
}
std::vector<u8> linear(LinearWidth * LinearHeight * BytesPerPixel);
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight,
LinearDepth, BlockHeight, BlockDepth);
auto buffer = env->NewByteArray(static_cast<jsize>(linear.size()));
if (!buffer) {
return env->NewByteArray(0);
}
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(linear.size()),
reinterpret_cast<const jbyte*>(linear.data()));
return buffer;
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::Capture::LinearWidth);
}
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VideoCore::Capture::LinearHeight);
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
jboolean reload) {
// Initialize the emulated system.
@@ -1572,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
JNIEnv* env, jobject jobj, jstring jpath) {
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
Common::Android::GetJString(env, jpath));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
}
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -46,6 +49,7 @@ public:
const Core::PerfStatsResults& PerfStats();
int ShadersBuilding();
void ConfigureFilesystemProvider(const std::string& filepath);
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
void InitializeSystem(bool reload);
void SetAppletId(int applet_id);
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#E6FFFFFF" />
</shape>
@@ -108,6 +108,22 @@
</FrameLayout>
<FrameLayout
android:id="@+id/paused_frame_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<ImageView
android:id="@+id/paused_frame_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null"
android:scaleType="fitCenter"
android:visibility="gone" />
</FrameLayout>
<FrameLayout
android:id="@+id/input_container"
android:layout_width="match_parent"
@@ -142,6 +158,18 @@
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<ImageView
android:id="@+id/paused_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
android:background="@drawable/circle_white"
android:contentDescription="@string/emulation_unpause"
android:padding="14dp"
android:src="@drawable/ic_play"
android:visibility="gone"
app:tint="@android:color/black" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/show_stats_overlay_text"
style="@style/TextAppearance.Material3.BodySmall"
@@ -506,8 +506,6 @@
<string name="dyna_state">الحالة الديناميكية الموسعة</string>
<string name="dyna_state_description">يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في حالة الديناميكية الموسعة. تسمح الأرقام الأعلى بمزيد من الميزات ويمكن أن تزيد من الأداء، ولكنها قد تسبب مشاكل مع بعض برامج التشغيل والأجهزة.</string>
<string name="disabled">معطل</string>
<string name="vertex_input_dynamic_state">حالة ديناميكية لإدخال الرأس</string>
<string name="vertex_input_dynamic_state_description">يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء.</string>
<string name="provoking_vertex">الرأس المثير</string>
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات Vulkan 1.0+.</string>
<string name="descriptor_indexing">فهرسة الوصف</string>
@@ -488,8 +488,6 @@
<string name="dyna_state">Úroveň EDS</string>
<string name="dyna_state_description">Určuje počet funkcí využívaných v rámci rozšířeného dynamického stavu API Vulkan (Extended Dynamic State). Vyšší hodnoty umožňují využít více funkcí a mohou zvýšit výkon, ale u některých ovladačů a výrobců grafických karet mohou způsobovat problémy s kompatibilitou.</string>
<string name="disabled">Vypnuto</string>
<string name="vertex_input_dynamic_state">Dynamický stav vstupu vrcholů (Vertex Input)</string>
<string name="vertex_input_dynamic_state_description">Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon.</string>
<string name="provoking_vertex">Určující vrchol</string>
<string name="provoking_vertex_description">Zlepšuje osvětlení a zpracování vrcholů v některých hrách. Podporováno pouze na GPU s API Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexování deskriptorů</string>
@@ -486,8 +486,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
<string name="dyna_state">Erweiterter dynamischer Status</string>
<string name="dyna_state_description">Steuert die Anzahl der Funktionen, die im \"Vertex Input Dynamic State\" werden können. Höhere Werte ermöglichen mehr Funktionen und können die Leistung steigern, können aber bei einigen Treibern und Anbietern zu Problemen führen.</string>
<string name="disabled">Deaktiviert</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung.</string>
<string name="provoking_vertex">Provokanter Vertex</string>
<string name="provoking_vertex_description">Verbessert die Beleuchtung und die Vertex-Verarbeitung in einigen Spielen. Wird nur von GPUs mit Vulkan 1.0+ unterstützt.</string>
<string name="descriptor_indexing">Deskriptor-Indizierung</string>
@@ -436,8 +436,6 @@
<string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone. Cela peut réduire les saccades mais peut aussi provoquer des problèmes graphiques.</string>
<string name="dyna_state">État dynamique étendu</string>
<string name="disabled">Désactivé</string>
<string name="vertex_input_dynamic_state">État dynamique d\'entrée de sommet</string>
<string name="vertex_input_dynamic_state_description">Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances.</string>
<string name="provoking_vertex">Provoque des Vertex</string>
<string name="provoking_vertex_description">Améliore l`éclairage et la gestion des vertex dans certains jeux. Pris en charge uniquement par les GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexation des descripteurs</string>
@@ -488,8 +488,6 @@
<string name="dyna_state">Rozszerzony stan dynamiczny</string>
<string name="dyna_state_description">Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe wartości pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować problemy z niektórymi sterownikami i u niektórych producentów.</string>
<string name="disabled">Wyłączone</string>
<string name="vertex_input_dynamic_state">Dynamiczny stan wejścia wierzchołków</string>
<string name="vertex_input_dynamic_state_description">Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność.</string>
<string name="provoking_vertex">Wierzchołek prowokujący</string>
<string name="provoking_vertex_description">Poprawia oświetlenie i obsługę wierzchołków w niektórych grach. Obsługiwane tylko przez GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indeksowanie deskryptorów</string>
@@ -471,8 +471,6 @@
<string name="renderer_asynchronous_shaders_description">Compila shaders de forma assíncrona. Isso pode reduzir engasgos, mas também pode introduzir falhas gráficas.</string>
<string name="dyna_state">Extended Dynamic State</string>
<string name="disabled">Desativado</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho.</string>
<string name="provoking_vertex">Provoking Vertex</string>
<string name="provoking_vertex_description">Vértice Provocante: Melhora a iluminação e o processamento de vértices em certos jogos. Suportado apenas em GPUs com Vulkan 1.0 ou superior.</string>
<string name="descriptor_indexing">Descriptor Indexing</string>
@@ -498,8 +498,6 @@
<string name="dyna_state">Расширенное динамическое состояние</string>
<string name="dyna_state_description">Управляет количеством функций, доступных в режиме «Расширенное динамическое состояние». Большее число позволяет задействовать больше функций и может повысить производительность, но способно вызывать проблемы с некоторыми драйверами и графикой.</string>
<string name="disabled">Отключено</string>
<string name="vertex_input_dynamic_state">Динамическое состояние ввода вершин</string>
<string name="vertex_input_dynamic_state_description">Включает функцию динамического состояния ввода вершин для повышения качества и производительности</string>
<string name="provoking_vertex">Определяющая вершина</string>
<string name="provoking_vertex_description">Улучшает освещение и обработку вершин в некоторых играх. Поддерживается только ГПУ с Vulkan 1.0+.</string>
<string name="descriptor_indexing">Индексирование дескрипторов</string>
@@ -502,8 +502,6 @@
<string name="dyna_state">Розширений динамічний стан</string>
<string name="dyna_state_description">Керує кількістю функцій, які можна використовувати в «Розширеному динамічному стані». Вище число дозволяє більше функцій і може покращити продуктивність, але може спричинити проблеми з деякими драйверами й виробниками.</string>
<string name="disabled">Вимкнено</string>
<string name="vertex_input_dynamic_state">Динамічний стан введення вершин</string>
<string name="vertex_input_dynamic_state_description">Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності.</string>
<string name="provoking_vertex">Провокативна вершина</string>
<string name="provoking_vertex_description">Покращує освітлення та взаємодію з вершинами у деяких іграх. Лише для ГП з підтримкою Vulkan 1.0+.</string>
<string name="descriptor_indexing">Індексація дескрипторів</string>
@@ -496,8 +496,6 @@
<string name="dyna_state">扩展动态状态</string>
<string name="dyna_state_description">控制在扩展动态状态中可使用的函数数量。更高的数值允许启用更多功能,并可能提升性能,但同时也可能导致额外的图形问题。</string>
<string name="disabled">已禁用</string>
<string name="vertex_input_dynamic_state">顶点输入动态状态</string>
<string name="vertex_input_dynamic_state_description">开启顶点输入动态状态功能来获得更好的质量和性能。</string>
<string name="provoking_vertex">引发顶点</string>
<string name="provoking_vertex_description">改善某些游戏中的光照和顶点处理。仅支持Vulkan 1.0+ GPU。</string>
<string name="descriptor_indexing">描述符索引</string>
@@ -467,8 +467,6 @@
<string name="renderer_asynchronous_shaders_description">非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
<string name="dyna_state">擴展動態狀態</string>
<string name="disabled">已停用</string>
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
<string name="vertex_input_dynamic_state_description">啟用頂點輸入動態狀態以取得更佳的品質及性能</string>
<string name="provoking_vertex">引發頂點</string>
<string name="provoking_vertex_description">改善某些遊戲中的光照和頂點處理。僅支援Vulkan 1.0+ GPU。</string>
<string name="descriptor_indexing">描述符索引</string>
@@ -632,14 +632,12 @@
<item>@string/disabled</item>
<item>ExtendedDynamicState 1</item>
<item>ExtendedDynamicState 2</item>
<item>ExtendedDynamicState 3</item>
</string-array>
<integer-array name="dynaStateValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<string-array name="installKeysResults">
@@ -531,8 +531,6 @@
<string name="dyna_state">Extended Dynamic State</string>
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.</string>
<string name="disabled">Disabled</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
<string name="provoking_vertex">Provoking Vertex</string>
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
<string name="descriptor_indexing">Descriptor Indexing</string>
@@ -1222,12 +1220,12 @@
<!-- Static Themes -->
<string name="static_theme_color">Theme Color</string>
<string name="eden_theme">Eden (Default)</string>
<string name="eden_theme">Eden</string>
<string name="violet">Violet</string>
<string name="blue">Blue</string>
<string name="cyan">Cyan</string>
<string name="red">Red</string>
<string name="green">Green</string>
<string name="green">Green (Default)</string>
<string name="yellow">Yellow</string>
<string name="orange">Orange</string>
<string name="pink">Pink</string>
+7 -10
View File
@@ -453,7 +453,7 @@ struct Values {
Category::RendererAdvanced};
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
#ifdef ANDROID
AstcDecodeMode::Cpu,
AstcDecodeMode::Gpu,
#else
AstcDecodeMode::Gpu,
#endif
@@ -557,6 +557,9 @@ struct Values {
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
Category::RendererHacks};
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
Category::RendererHacks};
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
@@ -583,7 +586,7 @@ struct Values {
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
#if defined (ANDROID) || defined (__APPLE__)
ExtendedDynamicState::Disabled,
ExtendedDynamicState::EDS1,
#else
ExtendedDynamicState::EDS2,
#endif
@@ -598,14 +601,6 @@ struct Values {
Category::RendererExtensions,
Specialization::Scalar};
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
#if defined (ANDROID)
false,
#else
true,
#endif
"vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
@@ -753,6 +748,8 @@ struct Values {
Category::DataStorage};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage};
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
Category::DataStorage};
std::vector<std::string> external_content_dirs;
// Debugging
+1 -1
View File
@@ -154,7 +154,7 @@ ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
ENUM(GameListMode, TreeView, GridView);
ENUM(SpeedMode, Standard, Turbo, Slow);
-1
View File
@@ -342,7 +342,6 @@ add_library(core STATIC
hle/kernel/message_buffer.h
hle/kernel/physical_core.cpp
hle/kernel/physical_core.h
hle/kernel/physical_memory.h
hle/kernel/slab_helpers.h
hle/kernel/svc.cpp
hle/kernel/svc.h
+3 -6
View File
@@ -4,6 +4,7 @@
#include <numeric>
#include <bit>
#include "common/arm64/native_clock.h"
#include "common/alignment.h"
#include "common/literals.h"
#include "core/arm/nce/arm_nce.h"
#include "core/arm/nce/guest_context.h"
@@ -46,8 +47,7 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
Patcher::~Patcher() = default;
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
const size_t image_size = program_image.size();
@@ -175,10 +175,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
return true;
}
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) {
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines) {
const size_t patch_size = GetSectionSize();
const size_t pre_patch_size = GetPreSectionSize();
+2 -5
View File
@@ -14,7 +14,6 @@
#include "common/settings.h"
#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/physical_memory.h"
#include <utility>
using ModuleID = std::array<u8, 32>; // NSO build ID
struct PatchCacheKey {
@@ -56,10 +55,8 @@ public:
}
explicit Patcher();
~Patcher();
bool PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code);
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
bool PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code);
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
size_t GetPreSectionSize() const noexcept;
+4 -11
View File
@@ -38,7 +38,6 @@ struct CipherContext {
static inline const std::string GetCipherName(Mode mode, u32 key_size) {
std::string cipher;
std::size_t effective_bits = key_size * 8;
switch (mode) {
case Mode::CTR:
cipher = "CTR";
@@ -53,7 +52,6 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) {
default:
UNREACHABLE();
}
return fmt::format("AES-{}-{}", effective_bits, cipher);
};
@@ -87,8 +85,7 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) {
// TODO: WHY TEMPLATE???????
template <typename Key, std::size_t KeySize>
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
: ctx(std::make_unique<CipherContext>()) {
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_unique<CipherContext>()) {
ctx->encryption_context = EVP_CIPHER_CTX_new();
ctx->decryption_context = EVP_CIPHER_CTX_new();
@@ -99,9 +96,7 @@ Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
UNIMPLEMENTED();
}
ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher,
"OpenSSL cipher context failed init!");
ASSERT(ctx->encryption_context && ctx->decryption_context && ctx->cipher && "OpenSSL cipher context failed init!");
// now init ciphers
ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL));
ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL));
@@ -165,8 +160,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* dest,
std::size_t sector_id, std::size_t sector_size, Op op) {
ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size.");
for (std::size_t i = 0; i < size; i += sector_size) {
SetIV(CalculateNintendoTweak(sector_id++));
Transcode(src + i, sector_size, dest + i, op);
@@ -177,8 +171,7 @@ template <typename Key, std::size_t KeySize>
void AESCipher<Key, KeySize>::SetIV(std::span<const u8> data) {
const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1);
const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1);
ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts");
ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts");
}
template class AESCipher<Key128>;
-4
View File
@@ -1,4 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace Crypto {} // namespace Crypto
-19
View File
@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "core/file_sys/vfs.h"
#include "key_manager.h"
#include "mbedtls/cipher.h"
namespace Crypto {
typedef std::array<u8, 0x20> SHA256Hash;
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
if (len != 0x40)
return {};
}
} // namespace Crypto
+6 -3
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -160,15 +163,15 @@ u32 KIP::GetMainThreadCpuCore() const {
return header.default_core;
}
const std::vector<u8>& KIP::GetTextSection() const {
std::span<const u8> KIP::GetTextSection() const {
return decompressed_sections[0];
}
const std::vector<u8>& KIP::GetRODataSection() const {
std::span<const u8> KIP::GetRODataSection() const {
return decompressed_sections[1];
}
const std::vector<u8>& KIP::GetDataSection() const {
std::span<const u8> KIP::GetDataSection() const {
return decompressed_sections[2];
}
+7 -3
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <string>
#include <vector>
@@ -70,9 +74,9 @@ public:
u32 GetMainThreadStackSize() const;
u32 GetMainThreadCpuCore() const;
const std::vector<u8>& GetTextSection() const;
const std::vector<u8>& GetRODataSection() const;
const std::vector<u8>& GetDataSection() const;
std::span<const u8> GetTextSection() const;
std::span<const u8> GetRODataSection() const;
std::span<const u8> GetDataSection() const;
u32 GetTextOffset() const;
u32 GetRODataOffset() const;
+14 -13
View File
@@ -117,6 +117,12 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
const std::string disabled_key = fmt::format("Update@{}", version);
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
}
} // Anonymous namespace
PatchManager::PatchManager(u64 title_id_,
@@ -155,8 +161,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@@ -175,8 +180,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
break;
@@ -580,8 +584,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@@ -600,8 +603,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
@@ -704,9 +706,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
@@ -732,9 +733,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
Patch update_patch = {.enabled = !update_disabled,
@@ -771,7 +771,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) {
if (slot == ContentProviderUnionSlot::External) {
if (slot == ContentProviderUnionSlot::External ||
slot == ContentProviderUnionSlot::FrontendManual) {
continue;
}
+226 -247
View File
@@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
}
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
if (!file) {
return nullptr;
}
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
type = Loader::IdentifyFile(file);
if (type == Loader::FileType::Unknown) {
type = Loader::GuessFromFilename(file->GetName());
}
}
if (type == Loader::FileType::NSP) {
auto nsp = std::make_shared<NSP>(file);
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
}
if (type == Loader::FileType::XCI) {
XCI xci(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
return nullptr;
}
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition == nullptr) {
return nullptr;
}
return secure_partition;
}
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
// As a last resort, probe both container parsers directly.
{
auto nsp = std::make_shared<NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
return nsp;
}
}
{
XCI xci(file);
if (xci.GetStatus() == Loader::ResultStatus::Success) {
auto secure_partition = xci.GetSecurePartitionNSP();
if (secure_partition != nullptr) {
return secure_partition;
}
}
}
return nullptr;
}
template <typename Callback>
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
std::optional<u64> base_program_id, Callback&& on_entry) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
if (ncas.empty()) {
return false;
}
std::map<u64, u32> versions;
std::map<u64, std::string> version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
if (!nca) {
continue;
}
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
for (const auto& inner_file : subdirs[0]->GetFiles()) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
break;
}
}
}
}
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
const auto romfs = nca->GetRomFS();
if (!romfs) {
continue;
}
const auto extracted = ExtractRomFS(romfs);
if (!extracted) {
continue;
}
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (!nacp_file) {
continue;
}
const NACP nacp(nacp_file);
auto version_string = nacp.GetVersionString();
if (!version_string.empty()) {
version_strings[title_id] = std::move(version_string);
}
}
}
}
bool added_entries = false;
for (const auto& [title_id, nca_map] : ncas) {
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
continue;
}
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
continue;
}
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
if (!entry_file) {
continue;
}
u32 version = 0;
std::string version_string;
if (title_type == TitleType::Update) {
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
version = version_it->second;
}
if (const auto version_str_it = version_strings.find(title_id);
version_str_it != version_strings.end()) {
version_string = version_str_it->second;
}
}
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
added_entries = true;
}
}
return added_entries;
}
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
u64 title_id, u32 version,
const std::string& version_string,
ContentRecordType content_type, const VirtualFile& file) {
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
[title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
if (it == multi_version_entries.end()) {
ExternalUpdateEntry update_entry;
update_entry.title_id = title_id;
update_entry.version = version;
update_entry.version_string = version_string;
update_entry.files[static_cast<std::size_t>(content_type)] = file;
multi_version_entries.push_back(std::move(update_entry));
return;
}
it->files[static_cast<std::size_t>(content_type)] = file;
if (it->version_string.empty() && !version_string.empty()) {
it->version_string = version_string;
}
}
template <typename EntryMap, typename VersionMap>
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
VersionMap& versions,
std::vector<ExternalUpdateEntry>& multi_version_entries) {
return ForEachContainerEntry(
nsp, true, std::nullopt,
[&entries, &versions,
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& file, u32 version,
const std::string& version_string) {
entries[{title_id, content_type, title_type}] = file;
if (title_type == TitleType::Update) {
versions[title_id] = version;
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
content_type, file);
}
});
}
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
switch (type) {
case NCAContentType::Program:
@@ -1008,6 +1208,26 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
}
}
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
std::optional<u64> base_program_id) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
if (!nsp) {
return false;
}
return ForEachContainerEntry(
nsp, only_content, base_program_id,
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
if (title_type == TitleType::Update) {
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
entry_file);
} else {
AddEntry(title_type, content_type, title_id, entry_file);
}
});
}
void ManualContentProvider::ClearAllEntries() {
entries.clear();
multi_version_entries.clear();
@@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
return nullptr;
}
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 0;
}
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
: load_dirs(std::move(load_directories)) {
ExternalContentProvider::Refresh();
@@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
}
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
auto nsp = NSP(file);
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
if (!nsp) {
return;
}
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
const auto ncas = nsp.GetNCAs();
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
nsp_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
auto xci = XCI(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
if (!nsp) {
return;
}
auto nsp = xci.GetSecurePartitionNSP();
if (nsp == nullptr) {
return;
}
const auto ncas = nsp->GetNCAs();
std::map<u64, u32> xci_versions;
std::map<u64, std::string> xci_version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
xci_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
xci_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = xci_versions.find(title_id);
if (ver_it != xci_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][size_t(content_type)] = nca_file;
}
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = xci_version_strings.find(title_id);
if (str_it != xci_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
existing.files = files_map;
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
@@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
return nullptr;
}
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries)
if (entry.title_id == title_id && entry.files[size_t(type)])
++count;
return count > 1;
}
} // namespace FileSys
+3 -2
View File
@@ -9,6 +9,7 @@
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <ankerl/unordered_dense.h>
@@ -262,6 +263,8 @@ public:
VirtualFile file);
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
u32 version, const std::string& version_string, VirtualFile file);
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
std::optional<u64> base_program_id = std::nullopt);
void ClearAllEntries();
void Refresh() override;
@@ -276,7 +279,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
@@ -303,7 +305,6 @@ public:
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
void ScanDirectory(const VirtualDir& dir);
+2 -3
View File
@@ -7,9 +7,9 @@
#pragma once
#include <cstddef>
#include <vector>
#include "core/hle/kernel/k_typed_address.h"
#include "core/hle/kernel/physical_memory.h"
namespace Kernel {
@@ -97,8 +97,7 @@ struct CodeSet final {
#endif
/// The overall data that backs this code set.
Kernel::PhysicalMemory memory;
std::vector<u8> memory;
/// The segments that comprise this code set.
std::array<Segment, 3> segments;
-23
View File
@@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <vector>
#include "common/alignment.h"
namespace Kernel {
// This encapsulation serves 2 purposes:
// - First, to encapsulate host physical memory under a single type and set an
// standard for managing it.
// - Second to ensure all host backing memory used is aligned to 256 bytes due
// to strict alignment restrictions on GPU memory.
using PhysicalMemoryVector = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
class PhysicalMemory final : public PhysicalMemoryVector {
using PhysicalMemoryVector::PhysicalMemoryVector;
};
} // namespace Kernel
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -93,7 +96,7 @@ union Error::ErrorArguments {
namespace {
template <typename T>
void CopyArgumentData(const std::vector<u8>& data, T& variable) {
void CopyArgumentData(std::span<const u8> data, T& variable) {
ASSERT(data.size() >= sizeof(T));
std::memcpy(&variable, data.data(), sizeof(T));
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -775,7 +778,7 @@ Result SoftwareKeyboard::RequestExit() {
// Inline Software Keyboard Requests
void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestFinalize(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: Finalize");
ChangeState(SwkbdState::NotInitialized);
@@ -783,17 +786,17 @@ void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
ExitKeyboard();
}
void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetUserWordInfo(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented.");
ReplyReleasedUserWordInfo();
}
void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetCustomizeDic(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented.");
}
void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestCalc(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: Calc");
ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon));
@@ -930,17 +933,17 @@ void SoftwareKeyboard::RequestCalcNew() {
}
}
void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetCustomizedDictionaries(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented.");
}
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(std::span<const u8> request_data) {
LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries");
ReplyUnsetCustomizedDictionaries();
}
void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetChangedStringV2Flag(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag");
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
@@ -948,7 +951,7 @@ void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& requ
std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1);
}
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data) {
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(std::span<const u8> request_data) {
LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag");
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -124,16 +127,16 @@ private:
// Inline Software Keyboard Requests
void RequestFinalize(const std::vector<u8>& request_data);
void RequestSetUserWordInfo(const std::vector<u8>& request_data);
void RequestSetCustomizeDic(const std::vector<u8>& request_data);
void RequestCalc(const std::vector<u8>& request_data);
void RequestFinalize(std::span<const u8> request_data);
void RequestSetUserWordInfo(std::span<const u8> request_data);
void RequestSetCustomizeDic(std::span<const u8> request_data);
void RequestCalc(std::span<const u8> request_data);
void RequestCalcOld();
void RequestCalcNew();
void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data);
void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data);
void RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data);
void RequestSetCustomizedDictionaries(std::span<const u8> request_data);
void RequestUnsetCustomizedDictionaries(std::span<const u8> request_data);
void RequestSetChangedStringV2Flag(std::span<const u8> request_data);
void RequestSetMovedCursorV2Flag(std::span<const u8> request_data);
// Inline Software Keyboard Replies
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -34,22 +34,21 @@ namespace Service::AM::Frontend {
namespace {
template <typename T>
void ParseRawValue(T& value, const std::vector<u8>& data) {
void ParseRawValue(T& value, std::span<const u8> data) {
static_assert(std::is_trivially_copyable_v<T>,
"It's undefined behavior to use memcpy with non-trivially copyable objects");
std::memcpy(&value, data.data(), data.size());
}
template <typename T>
T ParseRawValue(const std::vector<u8>& data) {
T ParseRawValue(std::span<const u8> data) {
T value;
ParseRawValue(value, data);
return value;
}
std::string ParseStringValue(const std::vector<u8>& data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
data.size());
std::string ParseStringValue(std::span<const u8> data) {
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()), data.size());
}
std::string GetMainURL(const std::string& url) {
@@ -72,7 +71,7 @@ std::string ResolveURL(const std::string& url) {
return url.substr(0, index) + "lp1" + url.substr(index + 1);
}
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
WebArgInputTLVMap ReadWebArgs(std::span<const u8> web_arg, WebArgHeader& web_arg_header) {
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
if (web_arg.size() == sizeof(WebArgHeader)) {
+14 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -36,8 +36,8 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u
{16, nullptr, "ReleaseAudioInputDeviceNotification"}, //17.0.0+
{17, nullptr, "AcquireAudioOutputDeviceNotification"}, //17.0.0+
{18, nullptr, "ReleaseAudioOutputDeviceNotification"}, //17.0.0+
{19, nullptr, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{20, nullptr, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{19, D<&IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled>, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{20, D<&IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled>, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
{21, nullptr, "IsActiveOutputDeviceEstimatedLowLatency"} //21.0.0+
};
RegisterHandlers(functions);
@@ -170,4 +170,15 @@ Result IAudioDevice::ListAudioOutputDeviceName(
R_SUCCEED();
}
Result IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
R_SUCCEED();
}
Result IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled) {
LOG_WARNING(Service_Audio, "(STUBBED) called");
*out_enabled = false;
R_SUCCEED();
}
} // namespace Service::Audio
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -49,6 +52,8 @@ private:
Result ListAudioOutputDeviceName(
OutArray<AudioDevice::AudioDeviceName, BufferAttr_HipcMapAlias> out_names,
Out<s32> out_count);
Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled);
Result IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled);
KernelHelpers::ServiceContext service_context;
std::unique_ptr<AudioCore::Renderer::AudioDevice> impl;
+5 -5
View File
@@ -164,7 +164,7 @@ private:
rb.Push(ResultSuccess);
}
u64 ReadLeb128(const std::vector<u8>& data, std::size_t& offset) {
u64 ReadLeb128(std::span<const u8> data, std::size_t& offset) {
u64 result{};
u32 shift{};
@@ -180,7 +180,7 @@ private:
return result;
}
std::optional<std::string> ReadString(const std::vector<u8>& data, std::size_t& offset,
std::optional<std::string> ReadString(std::span<const u8> data, std::size_t& offset,
std::size_t length) {
if (length == 0) {
return std::nullopt;
@@ -193,7 +193,7 @@ private:
return output;
}
u32_le ReadAsU32(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
u32_le ReadAsU32(std::span<const u8> data, std::size_t& offset, std::size_t length) {
ASSERT(length == sizeof(u32));
u32_le output{};
std::memcpy(&output, data.data() + offset, sizeof(u32));
@@ -201,7 +201,7 @@ private:
return output;
}
u64_le ReadAsU64(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
u64_le ReadAsU64(std::span<const u8> data, std::size_t& offset, std::size_t length) {
ASSERT(length == sizeof(u64));
u64_le output{};
std::memcpy(&output, data.data() + offset, sizeof(u64));
@@ -209,7 +209,7 @@ private:
return output;
}
void ParseLog(const LogPacketHeaderEntry entry, const std::vector<u8>& log_data) {
void ParseLog(const LogPacketHeaderEntry entry, std::span<const u8> log_data) {
// Possible entries
std::optional<std::string> text_log;
std::optional<u32> line_number;
@@ -67,7 +67,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{44, D<&IApplicationManagerInterface::GetSdCardMountStatusChangedEvent>, "GetSdCardMountStatusChangedEvent"},
{45, nullptr, "GetGameCardAttachmentEvent"},
{46, nullptr, "GetGameCardAttachmentInfo"},
{47, nullptr, "GetTotalSpaceSize"},
{47, D<&IApplicationManagerInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"},
{48, D<&IApplicationManagerInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"},
{49, nullptr, "GetSdCardRemovedEvent"},
{52, D<&IApplicationManagerInterface::GetGameCardUpdateDetectionEvent>, "GetGameCardUpdateDetectionEvent"},
@@ -751,8 +751,12 @@ Result IApplicationManagerInterface::GetSdCardMountStatusChangedEvent(
R_SUCCEED();
}
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
FileSys::StorageId storage_id) {
Result IApplicationManagerInterface::GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id) {
LOG_DEBUG(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).GetTotalSpaceSize(out_total_space_size, storage_id));
}
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id) {
LOG_DEBUG(Service_NS, "called");
R_RETURN(IContentManagementInterface(system).GetFreeSpaceSize(out_free_space_size, storage_id));
}
@@ -50,6 +50,7 @@ public:
u32 flags, u64 application_id, Uid account_id);
Result CheckSdCardMountStatus();
Result GetSdCardMountStatusChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id);
Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
Result GetGameCardUpdateDetectionEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result ResumeAll();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -21,7 +21,6 @@
#include "core/file_sys/system_archive/system_archive.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_memory.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/platform_service_manager.h"
+5 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -28,8 +28,10 @@ public:
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},
+12 -16
View File
@@ -176,11 +176,9 @@ struct ProcessContext {
// Calculate hash.
Sha256Hash hash;
{
const u64 size = nro_header->GetSize();
const u64 size = nro_header->m_size;
std::vector<u8> nro_data(size);
m_process->GetMemory().ReadBlock(base_address, nro_data.data(), size);
u32 hash_len = 0;
EVP_Digest(nro_data.data(), nro_data.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
}
@@ -204,9 +202,7 @@ struct ProcessContext {
R_THROW(RO::ResultNotAuthorized);
}
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size,
u64* out_rw_size, u64 base_address, u64 expected_nro_size,
u64 expected_bss_size) {
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size, u64* out_rw_size, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) {
// Ensure we have a process to work on.
R_UNLESS(m_process != nullptr, RO::ResultInvalidProcess);
@@ -215,17 +211,17 @@ struct ProcessContext {
m_process->GetMemory().ReadBlock(base_address, std::addressof(header), sizeof(header));
// Validate header.
R_UNLESS(header.IsMagicValid(), RO::ResultInvalidNro);
R_UNLESS(header.m_magic == NRO_HEADER_MAGIC, RO::ResultInvalidNro);
// Read sizes from header.
const u64 nro_size = header.GetSize();
const u64 text_ofs = header.GetTextOffset();
const u64 text_size = header.GetTextSize();
const u64 ro_ofs = header.GetRoOffset();
const u64 ro_size = header.GetRoSize();
const u64 rw_ofs = header.GetRwOffset();
const u64 rw_size = header.GetRwSize();
const u64 bss_size = header.GetBssSize();
const u64 nro_size = header.m_size;
const u64 text_ofs = header.m_text_offset;
const u64 text_size = header.m_text_size;
const u64 ro_ofs = header.m_ro_offset;
const u64 ro_size = header.m_ro_size;
const u64 rw_ofs = header.m_rw_offset;
const u64 rw_size = header.m_rw_size;
const u64 bss_size = header.m_bss_size;
// Validate sizes meet expected.
R_UNLESS(nro_size == expected_nro_size, RO::ResultInvalidNro);
@@ -251,7 +247,7 @@ struct ProcessContext {
R_TRY(this->ValidateHasNroHash(base_address, std::addressof(header)));
// Check if NRO has already been loaded.
const ModuleId* module_id = header.GetModuleId();
const ModuleId* module_id = std::addressof(header.m_module_id);
R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), RO::ResultAlreadyLoaded);
// Apply patches to NRO.
+5 -46
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -112,52 +115,8 @@ private:
};
static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader has wrong size");
class NroHeader {
public:
static constexpr u32 Magic = Common::MakeMagic('N', 'R', 'O', '0');
public:
bool IsMagicValid() const {
return m_magic == Magic;
}
u32 GetSize() const {
return m_size;
}
u32 GetTextOffset() const {
return m_text_offset;
}
u32 GetTextSize() const {
return m_text_size;
}
u32 GetRoOffset() const {
return m_ro_offset;
}
u32 GetRoSize() const {
return m_ro_size;
}
u32 GetRwOffset() const {
return m_rw_offset;
}
u32 GetRwSize() const {
return m_rw_size;
}
u32 GetBssSize() const {
return m_bss_size;
}
const ModuleId* GetModuleId() const {
return std::addressof(m_module_id);
}
private:
static constexpr u32 NRO_HEADER_MAGIC = Common::MakeMagic('N', 'R', 'O', '0');
struct NroHeader {
u32 m_entrypoint_insn;
u32 m_mod_offset;
INSERT_PADDING_BYTES_NOINIT(0x8);
+5 -14
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -75,21 +75,16 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
kip->GetKernelCapabilities());
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, const std::vector<u8>& data, u32 offset) {
codeset.memory.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
const auto load_segment = [&codeset](Kernel::CodeSet::Segment& segment, std::span<const u8> data, u32 offset) {
segment.addr = offset;
segment.offset = offset;
segment.size = PageAlignSize(u32(data.size()));
program_image.resize(offset + data.size());
std::memcpy(program_image.data() + offset, data.data(), data.size());
std::memcpy(codeset.memory.data() + offset, data.data(), data.size());
};
load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset());
load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset());
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
codeset.DataSegment().size += kip->GetBSSSize();
// TODO: this is bad form of ASLR, it sucks
@@ -98,13 +93,9 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
: std::rand()) * 0x734287f27) & 0xfff000;
// Setup the process code layout
if (process
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0, aslr_offset, false)
.IsError()) {
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
codeset.memory = std::move(program_image);
const VAddr base_address = GetInteger(process.GetEntryPoint());
process.LoadModule(std::move(codeset), base_address);
+69 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -9,11 +9,15 @@
#include <ostream>
#include <string>
#include <concepts>
#include <algorithm>
#include "common/concepts.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/kip.h"
@@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt;
}
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
u64 program_id = 0,
std::size_t program_index = 0) {
if (!file) {
return nullptr;
}
if (type == FileType::NSP) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
if (type == FileType::XCI) {
FileSys::XCI xci{file, program_id, program_index};
if (xci.GetStatus() != ResultStatus::Success) {
return nullptr;
}
auto secure_nsp = xci.GetSecurePartitionNSP();
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
return nullptr;
}
return secure_nsp;
}
return nullptr;
}
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
if (!nsp) {
return false;
}
const auto& ncas = nsp->GetNCAs();
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
const auto& nca_map = title_entry.second;
return nca_map.find(
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
nca_map.end();
});
}
} // namespace
FileType IdentifyFile(FileSys::VirtualFile file) {
@@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
}
bool IsContainerType(FileType type) {
return type == FileType::NSP || type == FileType::XCI;
}
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
std::size_t program_index) {
if (!file) {
return false;
}
if (type == FileType::Unknown) {
type = IdentifyFile(file);
}
if (!IsContainerType(type)) {
return false;
}
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
}
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
+21 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -46,12 +49,29 @@ enum class FileType {
};
/**
* Identifies the type of a bootable file based on the magic value in its header.
* Identifies the type of a supported file/container based on its structure.
* @param file open file
* @return FileType of file
*/
FileType IdentifyFile(FileSys::VirtualFile file);
/**
* Returns whether the file type represents a container format that can bundle multiple titles
* (currently NSP/XCI).
*/
bool IsContainerType(FileType type);
/**
* Returns whether a container file is bootable as a game (has Application/Program content).
*
* @param file open file
* @param type optional file type; if Unknown it is auto-detected.
* @param program_id optional program id hint for multi-program containers.
* @param program_index optional program index hint for multi-program containers.
*/
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
u64 program_id = 0, std::size_t program_index = 0);
/**
* Guess the type of a bootable file from its name
* @param name String name of bootable file
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -160,7 +160,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
}
// Build program image
Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size));
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
std::memcpy(program_image.data(), data.data(), program_image.size());
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
+40 -59
View File
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cinttypes>
#include <cstring>
#include <vector>
@@ -40,17 +41,6 @@ struct MODHeader {
};
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
const NSOSegmentHeader& header) {
std::vector<u8> uncompressed_data =
Common::Compression::DecompressDataLZ4(compressed_data, header.size);
ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
uncompressed_data.size());
return uncompressed_data;
}
constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
@@ -76,24 +66,16 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
return FileType::NSO;
}
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm,
std::vector<Core::NCE::Patcher>* patches,
s32 patch_index) {
if (nso_file.GetSize() < sizeof(NSOHeader)) {
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm, std::vector<Core::NCE::Patcher>* patches, s32 patch_index) {
if (nso_file.GetSize() < sizeof(NSOHeader))
return std::nullopt;
}
NSOHeader nso_header{};
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header)) {
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header))
return std::nullopt;
}
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return std::nullopt;
if (nso_header.segments.empty())
return std::nullopt;
}
// Allocate some space at the beginning if we are patching in PreText mode.
const size_t module_start = [&]() -> size_t {
@@ -110,42 +92,44 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
return 0;
}();
// Build program image
auto const last_segment_it = &nso_header.segments[nso_header.segments.size() - 1];
// Build program image directly in codeset memory :)
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data = nso_file.ReadBytes(nso_header.segments_compressed_size[i],
nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
codeset.memory.resize(module_start + last_segment_it->location + last_segment_it->size);
{
std::vector<u8> compressed_data(*std::ranges::max_element(nso_header.segments_compressed_size));
std::vector<u8> decompressed_size(std::ranges::max_element(nso_header.segments, [](auto const& a, auto const& b) {
return a.size < b.size;
})->size);
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
ASSERT(r == int(nso_header.segments[i].size));
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
} else {
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
}
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
program_image.resize(module_start + nso_header.segments[i].location +
static_cast<u32>(data.size()));
std::memcpy(program_image.data() + module_start + nso_header.segments[i].location,
data.data(), data.size());
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty()) {
const auto arg_data{Settings::values.program_args.GetValue()};
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
NSOArgumentHeader args_header{
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = program_image.size();
program_image.resize(static_cast<u32>(program_image.size()) +
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
arg_data.size());
NSOArgumentHeader args_header{NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
const auto end_offset = codeset.memory.size();
codeset.memory.resize(u32(codeset.memory.size()) + NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
std::memcpy(codeset.memory.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
std::memcpy(codeset.memory.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), arg_data.size());
}
codeset.DataSegment().size += nso_header.segments[2].bss_size;
u32 image_size{
PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
program_image.resize(image_size);
u32 image_size = PageAlignSize(u32(codeset.memory.size()) + nso_header.segments[2].bss_size);
codeset.memory.resize(image_size);
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
codeset.segments[i].size = PageAlignSize(codeset.segments[i].size);
@@ -154,8 +138,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply patches if necessary
const auto name = nso_file.GetName();
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
std::span<u8> patchable_section(program_image.data() + module_start,
program_image.size() - module_start);
std::span<u8> patchable_section(codeset.memory.data() + module_start, codeset.memory.size() - module_start);
std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
@@ -174,15 +157,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
//Set module ID using build_id from the NSO header
patch->SetModuleID(nso_header.build_id);
// Patch SVCs and MRS calls in the guest code
while (!patch->PatchText(program_image, code)) {
while (!patch->PatchText(codeset.memory, code)) {
patch = &patches->emplace_back();
patch->SetModuleID(nso_header.build_id); // In case the patcher is changed for big modules, the new patcher should also have the build_id
}
} else if (patch) {
// Relocate code patch and copy to the program_image.
// Relocate code patch and copy to the program image.
// Save size before RelocateAndCopy (which may resize)
const size_t size_before_relocate = program_image.size();
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
const size_t size_before_relocate = codeset.memory.size();
if (patch->RelocateAndCopy(load_base, code, codeset.memory, &process.GetPostHandlers())) {
// Update patch section.
auto& patch_segment = codeset.PatchSegment();
auto& post_patch_segment = codeset.PostPatchSegment();
@@ -203,7 +186,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
image_size = static_cast<u32>(program_image.size());
image_size = static_cast<u32>(codeset.memory.size());
}
#endif
@@ -234,9 +217,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
// Load codeset for current process
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), load_base);
return load_base + image_size;
}
+25 -11
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
const FileSys::NSP nsp(nsp_file);
if (nsp.GetStatus() == ResultStatus::Success) {
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
if (nsp.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Extracted Type case
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
return FileType::NSP;
}
// Non-extracted NSPs can legitimately contain only update/DLC content.
// Identify the container format itself; bootability is validated by Load().
if (!nsp.GetNCAs().empty()) {
return FileType::NSP;
}
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
for (const auto& entry : nsp.GetFiles()) {
if (entry == nullptr) {
continue;
}
// Non-Extracted Type case
const auto program_id = nsp.GetProgramTitleID();
if (!nsp.IsExtractedType() &&
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
const auto& name = entry->GetName();
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
return FileType::NSP;
}
}
+10 -4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
const FileSys::XCI xci(xci_file);
if (xci.GetStatus() == ResultStatus::Success &&
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
FileType::NCA) {
if (xci.GetStatus() != ResultStatus::Success) {
return FileType::Error;
}
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
// Bootability is handled by AppLoader_XCI::Load().
if (xci.GetSecurePartitionNSP() != nullptr) {
return FileType::XCI;
}
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -53,6 +56,7 @@ public:
enum class PlayReportType {
Old,
Old2,
Old3,
New,
System,
};
+9 -12
View File
@@ -356,23 +356,18 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Fix bloom effects"),
tr("Removes bloom in Burnout."));
INSERT(Settings,
rescale_hack,
tr("Enable Legacy Rescale Pass"),
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
// Renderer (Extensions)
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
tr("Controls the number of features that can be used in Extended Dynamic State.\n"
"Higher states allow for more features and can increase performance, but may cause "
"additional graphical issues."));
INSERT(Settings,
vertex_input_dynamic_state,
tr("Vertex Input Dynamic State"),
tr("Enables vertex input dynamic state feature for better quality and performance."));
INSERT(Settings,
provoking_vertex,
tr("Provoking Vertex"),
tr("Improves lighting and vertex handling in some games.\n"
"Only Vulkan 1.0+ devices support this extension."));
INSERT(Settings,
descriptor_indexing,
tr("Descriptor Indexing"),
@@ -419,6 +414,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
"their resolution, details and supported controllers and depending on this setting.\n"
"Setting to Handheld can help improve performance for low end systems."));
INSERT(Settings, current_user, QString(), QString());
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
// Controls
@@ -790,7 +788,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
PAIR(ExtendedDynamicState, Disabled, tr("Disabled")),
PAIR(ExtendedDynamicState, EDS1, tr("ExtendedDynamicState 1")),
PAIR(ExtendedDynamicState, EDS2, tr("ExtendedDynamicState 2")),
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
}});
translations->insert({Settings::EnumMetadata<Settings::GameListMode>::Index(),
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -31,7 +31,7 @@ struct RenderAreaLayout {
};
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
constexpr u32 RENDERAREA_LAYOUT_OFFSET = sizeof(RescalingLayout);
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
IR::Program& program, Bindings& bindings, bool optimize);
@@ -491,6 +491,9 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
}
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
if (!ctx.runtime_info.active_color_outputs[index]) {
return;
}
const Id component_id{ctx.Const(component)};
const AttributeType type{ctx.runtime_info.color_output_types[index]};
if (type == AttributeType::Float) {
@@ -1,5 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -91,6 +94,17 @@ void AlphaTest(EmitContext& ctx) {
} // Anonymous namespace
void EmitPrologue(EmitContext& ctx) {
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
// Initialize dual-source blending outputs - prevents MoltenVK crash.
const Id zero{ctx.Const(0.0f)};
const Id one{ctx.Const(1.0f)};
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
for (u32 i = 0; i < 2; ++i) {
if (Sirit::ValidId(ctx.frag_color[i])) {
ctx.OpStore(ctx.frag_color[i], default_color);
}
}
}
if (ctx.stage == Stage::VertexB) {
const Id zero{ctx.Const(0.0f)};
const Id one{ctx.Const(1.0f)};
@@ -17,6 +17,7 @@
#include "common/div_ceil.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
#include "shader_recompiler/frontend/ir/modifiers.h"
namespace Shader::Backend::SPIRV {
namespace {
@@ -1095,7 +1096,8 @@ void EmitContext::DefineRenderArea(const Info& info) {
Decorate(push_constant_struct, spv::Decoration::Block);
Name(push_constant_struct, "RenderAreaInfo");
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset, 0);
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset,
RENDERAREA_LAYOUT_OFFSET);
MemberName(push_constant_struct, render_are_member_index, "render_area");
const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
@@ -1670,13 +1672,24 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
break;
case Stage::Fragment:
for (u32 index = 0; index < 8; ++index) {
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
const bool should_declare = runtime_info.active_color_outputs[index] &&
(info.stores_frag_color[index] ||
profile.need_declared_frag_colors);
if (!need_dual_source && !should_declare) {
continue;
}
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
frag_color[index] = DefineOutput(*this, type, std::nullopt);
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
// Correct mapping for dual-source blending
if (runtime_info.dual_source_blend && index <= 1) {
Decorate(frag_color[index], spv::Decoration::Location, 0u);
Decorate(frag_color[index], spv::Decoration::Index, index);
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
} else {
Decorate(frag_color[index], spv::Decoration::Location, index);
Name(frag_color[index], fmt::format("frag_color{}", index));
}
}
if (info.stores_frag_depth) {
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
Optimization::TexturePass(env, program, host_info);
if (Settings::values.resolution_info.active) {
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
Optimization::RescalingPass(program);
}
Optimization::DeadCodeEliminationPass(program);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -67,11 +67,18 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
if (must_patch_outside) {
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
IR::IREmitter ir{block, it};
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
inst.ReplaceUsesWith(converted);
if (Settings::values.rescale_hack.GetValue()) {
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
inst.ReplaceUsesWith(converted);
} else {
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
inst.ReplaceUsesWith(converted);
}
}
break;
}
@@ -200,11 +200,10 @@ static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
}
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
const ConstBufferAddr& cbuf) {
const ConstBufferAddr& cbuf) {
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
}
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) {
if (const IR::Inst* key = v.InstRecursive()) {
+3
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+6
View File
@@ -110,6 +110,12 @@ struct RuntimeInfo {
/// Output types for each color attachment
std::array<AttributeType, 8> color_output_types{};
/// Fragment color outputs that are active for the current pipeline.
std::array<bool, 8> active_color_outputs{true, true, true, true, true, true, true, true};
/// Dual source blending
bool dual_source_blend{};
};
} // namespace Shader
+57 -14
View File
@@ -356,11 +356,22 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
if (is_indexed) {
BindHostIndexBuffer();
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const auto& regs = maxwell3d->regs;
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
const bool allow_polygon_mode_emulation =
regs.transform_feedback_enabled == 0 &&
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
draw_state.vertex_buffer.count);
const auto polygon_mode = allow_polygon_mode_emulation
? regs.polygon_mode_front
: Maxwell::PolygonMode::Fill;
if (polygon_mode != Maxwell::PolygonMode::Point) {
runtime.BindQuadIndexBuffer(draw_state.topology, polygon_mode,
draw_state.vertex_buffer.first,
draw_state.vertex_buffer.count);
}
}
}
BindHostVertexBuffers();
@@ -760,8 +771,35 @@ void BufferCache<P>::BindHostIndexBuffer() {
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
runtime.BindIndexBuffer(buffer, new_offset, size);
} else {
const auto& regs = maxwell3d->regs;
const bool allow_polygon_mode_emulation =
regs.transform_feedback_enabled == 0 &&
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
auto effective_topology = draw_state.topology;
auto polygon_mode = allow_polygon_mode_emulation ? regs.polygon_mode_front
: Maxwell::PolygonMode::Fill;
if (allow_polygon_mode_emulation) {
switch (draw_state.topology) {
case Maxwell::PrimitiveTopology::Quads:
case Maxwell::PrimitiveTopology::QuadStrip:
case Maxwell::PrimitiveTopology::Polygon:
if (polygon_mode == Maxwell::PolygonMode::Point) {
effective_topology = Maxwell::PrimitiveTopology::Points;
} else if (draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
polygon_mode == Maxwell::PolygonMode::Line) {
effective_topology = Maxwell::PrimitiveTopology::LineStrip;
}
break;
default:
polygon_mode = Maxwell::PolygonMode::Fill;
break;
}
} else {
polygon_mode = Maxwell::PolygonMode::Fill;
}
buffer.MarkUsage(offset, size);
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
runtime.BindIndexBuffer(effective_topology, polygon_mode, draw_state.index_buffer.format,
draw_state.index_buffer.first, draw_state.index_buffer.count,
buffer, offset, size);
}
@@ -1064,13 +1102,23 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
if (maxwell3d->regs.transform_feedback_enabled == 0) {
return;
}
HostBindings<typename P::Buffer> host_bindings;
Buffer& null_buffer = slot_buffers[NULL_BUFFER_ID];
bool reached_end = false;
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
const Binding& binding = channel_state->transform_feedback_buffers[index];
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
break;
const auto& control = maxwell3d->regs.transform_feedback.controls[index];
if (reached_end ||
(control.varying_count == 0 && control.stride == 0)) {
reached_end = true;
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
continue;
}
const Binding& binding = channel_state->transform_feedback_buffers[index];
if (binding.size == 0 || binding.buffer_id == NULL_BUFFER_ID) {
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
continue;
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
@@ -1080,12 +1128,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
host_bindings.buffers.push_back(&buffer);
host_bindings.offsets.push_back(offset);
host_bindings.sizes.push_back(size);
}
if (host_bindings.buffers.size() > 0) {
runtime.BindTransformFeedbackBuffers(host_bindings);
runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
}
}
@@ -14,9 +14,12 @@
#include <mutex>
#include <numeric>
#include <span>
#include <ankerl/unordered_dense.h>
#include <vector>
#include <ankerl/unordered_dense.h>
#include <boost/container/static_vector.hpp>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
#include "common/div_ceil.h"
#include "common/literals.h"
@@ -94,10 +97,10 @@ static constexpr Binding NULL_BINDING{
template <typename Buffer>
struct HostBindings {
boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
boost::container::static_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> offsets;
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> sizes;
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> strides;
u32 min_index{NUM_VERTEX_BUFFERS};
u32 max_index{0};
};
+9 -6
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -19,12 +22,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
ASSERT(memory_manager);
program_id = program_id_;
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
dma_pusher.emplace(system, gpu, *memory_manager, *this);
maxwell_3d.emplace(system, *memory_manager);
fermi_2d.emplace(*memory_manager);
kepler_compute.emplace(system, *memory_manager);
maxwell_dma.emplace(system, *memory_manager);
kepler_memory.emplace(system, *memory_manager);
initialized = true;
}

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