Commit Graph
4788 Commits
Author SHA1 Message Date
Ryunam 9300d7b63e menu: When renaming playlist entries, allow CTRL movement between space-delimited text (#19213) 2026-07-16 13:19:46 +02:00
libretroadmin fb993da590 linux_common: route thread cancellation through rthreads
The illuminance-sensor code in input/common/linux_common.c used raw
pthreads: pthread_setcancelstate() around the file-reading critical
section, and pthread_cancel() on close via a pthread_t reconstructed by
casting sthread_get_thread_id() -- a fragile round-trip through the
opaque id.

Add cancellation primitives to rthreads and route the driver through
them:

  void sthread_set_cancel_enable(bool enable);
  bool sthread_cancel(sthread_t *thread);

sthread_cancel() takes the sthread_t directly and uses its backing handle,
dropping the pthread_t cast. Both are implemented with
pthread_setcancelstate()/pthread_cancel() on pthread backends that provide
them, gated by RTHREADS_HAVE_CANCEL which excludes Win32, GEKKO, 3DS and
Android/Bionic (Bionic has no pthread_cancel; rthreads.c is compiled there
even though linux_common.c is not). Where unsupported they are a no-op /
return false, and the existing cooperative sensor->done flag still stops
the thread.

linux_common.c no longer includes <pthread.h> or references pthread
directly. No functional change on Linux.
2026-07-16 00:46:51 +02:00
Ryunam 36bf97f01c menu: Quality-of-life changes when renaming Playlist Entries (#19201)
* menu: When renaming entries, copy existing entry name in text box

* menu: When renaming entries, show caret and allow arrow movement
2026-07-14 17:31:53 +02:00
Craig 839bacb3f5 Android: Build Google Play compatible version / update NDK / JDK (#19202)
* android: depreciate phoenix as phoenix-jelly-bean

* android: use latest NDK (LTS), raise required minimums for play store, otherwise require Android 5.0+
2026-07-13 19:31:46 +02:00
LibretroAdmin 25047ba36a Revert "Android NDK (LTS) update, play store compatible version (#19192)" (#19195)
This reverts commit c21d069c49.
2026-07-13 03:33:18 +02:00
Craig c21d069c49 Android NDK (LTS) update, play store compatible version (#19192)
* android: depreciate phoenix as phoenix-jelly-bean

* android: use latest NDK (LTS), raise required minimums for play store, otherwise require Android 5.0+
2026-07-12 21:41:53 +02:00
libretroadmin e20a598f93 test input: allocate the key matrix with the step array
The test input driver's keyboard matrix - eleven kilobytes across
pads and keycodes - was the last static of the test-driver family
still resident in every default binary. It now allocates alongside
the step array when the driver starts and frees on teardown, with
the keyboard clear running before the free and guarding an absent
matrix.
2026-07-07 16:46:56 +02:00
libretroadmin caddb4f067 input tests: allocate the step arrays when the feature starts
The test joypad driver, test input driver and built-in net-retropad
core each held a static two-hundred-step array - 54, 54 and 411
kilobytes - and the test drivers ship by default, so every binary
carried 520 kilobytes of test scaffolding in bss. On platforms with
demand paging untouched bss is free; on the consoles without it -
exactly where memory matters - bss is cleared at load and fully
resident. The arrays are now allocated when the driver or core
actually initializes and freed on teardown, taking the binary's bss
from 1620 to 1100 kilobytes. Both validation dumps byte-identical.

The same census flags the cloud-sync state at 297 kilobytes and the
Discord queue at 197 as the next candidates of this class.
2026-07-07 16:10:55 +02:00
libretroadmin f72ae347c7 mfi_joypad: guard CoreHaptics rumble against NSException (Haptic_RaiseException)
CoreHaptics signals some failures by throwing an NSException
(Haptic_RaiseException, HapticUtils.h) instead of populating the
NSError out-param. This happens when a pattern player's start/
sendParameters/stop is invoked - or when the lazy strongPlayer/
weakPlayer getters (re)create and start the engine - while the
engine is not running. iOS auto-stops the engine on backgrounding,
audio-session interruption and system overlays, and a per-frame
rumble request from a core can race in before the engine's
stopped/reset handler tears down the now-stale player, so the
next CoreHaptics call throws and the uncaught exception terminates
the app.

apple_gamecontroller_joypad_set_rumble() is the hottest CoreHaptics
entry point (called every frame a core requests rumble), and none
of its player/engine calls were exception-guarded. CoreHaptics
exposes no public running-state query, so the race cannot be closed
by checking state first; wrap the acquire+play sequences in
@try/@catch instead. Covers both the device-vibration path and the
MFi-controller path; on exception we log via RARCH_ERR and skip the
rumble.

Top iOS crash for 1.22.2 (build 497): ~505 devices over the year,
trend rising. iPad is over-represented because most iPads lack a
Taptic Engine (the device path bails at the supportsHaptics guard)
but iPad + paired controller + game rumble exercises the unguarded
controller path heavily.
2026-06-28 21:12:53 +02:00
libretroadmin 9c9b5f2575 (Android) input: resolve real Unicode char for keyboard events
The Android input driver passed the translated RETROK keysym as the
character argument to input_keyboard_event(), instead of the actual
printable character. Two consequences in menu text fields:

  - Modifier and lock keys (Shift, Caps Lock, Num Lock, Scroll Lock)
    have keysym values >= 128. The menu line-editor maps any character
    >= 128 to '?', so merely pressing these keys typed a '?'.
  - Letters were never case-shifted (the keysym ignores meta state), so
    capitals and shifted symbols were impossible.

Resolve the real codepoint via the device's KeyCharacterMap (mirrors
KeyEvent.getUnicodeChar()), honouring layout and shift/caps state and
yielding 0 for non-text keys. The keysym is still passed unchanged as
the key code, so core keyboard input, hotkeys and menu navigation are
unaffected.

Backwards compatible: when JNI is unavailable the character falls back
to the keysym for the ASCII range only (< 0x80), preserving the prior
lowercase behaviour while still suppressing modifier/lock keys (keysyms
>= 0x80) from leaking as '?'.

Fixes the menu capital-letter / '?' issue reported on Android.
2026-06-28 02:10:05 +02:00
libretroadmin 013049996b input/sdl: clean up partially-opened controller on failed pad connect
sdl_pad_connect()'s failure path only closed pad->joypad and nulled it,
but on the SDL2 game-controller branch SDL_GameControllerOpen() can
return a valid controller handle while the open is still treated as
failed (e.g. SDL_GameControllerGetJoystick() returns NULL, so
success == false). In that case pad->controller was neither closed nor
cleared: the SDL handle leaked and pad->controller was left dangling.

This matters when a misbehaving device produces a rapid
connect/disconnect storm (reported with a GameSir T3s in Nintendo
Switch Pro mode, where SDL2 repeatedly fails to open the enumerated
gamepad): every failed connect leaked another controller handle and
left stale state in the pad slot.

Close the game controller on the SDL2 branch (which also closes the
backing joystick, so the joystick close is mutually exclusive with it),
and memset the whole pad slot to zero on failure, mirroring
sdl_pad_disconnect(). This does not fix the underlying SDL-side open
failure, but stops RetroArch from leaking handles and accumulating
stale pad state while it happens.
2026-06-25 16:10:26 +02:00
libretroadmin 059ca263df build: fix CXX_BUILD=1 compilation
Compiling the C sources as C++ (CXX_BUILD=1) failed in 12 places where
the code relied on implicit conversions that C permits but C++ rejects.
All are mechanical and behaviour-preserving:

void* -> typed pointer (explicit cast):
  - input/common/linux_common.c  (illuminance sensor data)
  - tasks/task_screenshot.c       (copy.buffer -> uint32_t*)
  - libretro-db/rmsgpack_dom.c    (alloca result -> dom_value**)

const char* -> char* (strrchr/strchr return values):
  - libretro-common/file/file_path.c  (cast strrchr result; matches the
    char* return contract of the C function)
  - configuration.c                   (extra_path points at a mutable
    stack buffer that is written through -> make it char*)
  - menu/menu_displaylist.c           (hash_type likewise -> char*)

integer -> enum (explicit cast at use site):
  - runloop.c           (unsigned pending_cmd -> enum event_command)
  - runtime_file.c      (settings uints -> last_played style/separator)
  - tasks/task_database.c (int iterate result -> enum scan_verdict)
  - menu/menu_driver.c  (unsigned confirm_msg -> enum msg_hash_enums)

std::atomic copy:
  - cheevos/cheevos.c: the static rcheevos_locals aggregate brace-init
    of the two retro_atomic_int_t members used a bare '0', which under
    CXX_BUILD (std::atomic<int>) selects the deleted copy constructor.
    Use {0} so C++ list-initializes via the converting constructor;
    for the C atomic/int/volatile backends {0} is an equivalent scalar
    initializer.

With these, the tree compiles cleanly under CXX_BUILD=1 (every object
builds and the link stage is reached). This addresses the compilation
failures from #6009; note that CXX_BUILD has no CI coverage, so it will
regress again as new C-idiomatic code lands unless a build job is added.
2026-06-25 09:47:38 +02:00
Joe Osborn 8a8cc448b9 Fix replay seek while recording v2 (#19136)
* bug fixes to checkpoint recording and seekback

When garbage collecting rare datablocks in checkpoints is used to keep
RAM usage down, removing later blocks during seek doesn't work
properly and can cause an infinite loop.  This change correctly
handles GC'd blocks rather than try to remove them again.

* bsvmovie.c: add an intfstream_seek before writing frame data

In 1abde384c8 @amcknight identified a
bug involving rewinding writable streams. This should resolve that
type of issue going forward at the cost of a no-op seek.
2026-06-24 21:45:14 +02:00
Rob LoachandRob Loach 7f6d624094 Add SDL3 Controller Support (#18833)
* sdl3: Add SDL3 Controller support

* sdl3: Add to feature list

* sdl3: Update

* Update SDL3

* Apply suggestion from @RobLoach

* Apply suggestion from @RobLoach

* sdl3: Use a different ID for sdl3_joypad.c

* sdl3: Have a standard mapping flag

* sdl3: Clean up

* sdl3: Only load mappings if the autodetect system is enabled

* sdl3: Add docs pointing to SDL3 directly

* sdl3: Probably don't need to reload the mappings before quitting

* sdl3: Safety checks and protection

* sdl3: Add SDL_SetHints() for Wii

* sdl3: LED

---------

Co-authored-by: Rob Loach <[email protected]>
2026-06-24 03:32:46 +02:00
libretroadmin def562b585 Remove dead unused-variable declarations across core subsystems 2026-06-17 19:39:51 +02:00
Colin Kinloch 9292e260f1 Map xkb fd as private to comply with wayland v7 (#19094)
From the wayland core spec:
> From version 7 onwards, the fd must be mapped with MAP_PRIVATE by the recipient, as MAP_SHARED may fail.
2026-06-07 11:51:18 +02:00
hizzlekizzleandhunterk 490ba1f039 stop 'viewport override' logspam (#19082)
buildfix

Co-authored-by: hunterk <[email protected]>
2026-06-02 22:08:02 +02:00
sonninnos f7cf7305b1 Add confirmation messagebox structure (#19059) 2026-05-24 20:08:36 +02:00
libretroadmin 419ad3ef58 WII/GX: omnibus audit pass (memory safety, leaks, folds, cleanup)
Sweep of the Wii/libogc-side code:

Memory safety
- mem2: _mem2_strndup used a calloc-then-memcpy(n+1) sequence that
  (a) read past s when strlen(s) < n (UB) and (b) left the result
  without a NUL terminator when strlen(s) >= n. Rewritten via
  strnlen() with an explicit terminator and _mem2_malloc instead
  of _mem2_calloc.
- mem2: _mem2_calloc had no num*size overflow check; large pairs
  silently wrapped and produced a tiny allocation that the caller
  proceeded to write a huge amount into. Added the standard
  UINT32_MAX/size guard.
- mem2: _mem2_memalign silently downgraded any requested alignment
  to the heap's 32-byte page size. The lwp_heap can't honour
  stricter requests, so reject them (return NULL) rather than lie.
- gx_joypad: gx_joypad_button returned (pad_state[port] & (1ULL<<key))
  cast to int32_t, truncating bit >= 32 to zero and silently reporting
  "not pressed" for every Wiimote/Classic/Nunchuk binding. Cast to
  bool first.
- gx_joypad: gx_joypad_get_buttons used BITS_COPY16_PTR on a uint64_t
  pad_state, dropping every Wiimote/Classic/Nunchuk bit (28..44) and
  GX_QUIT_KEY (62). Use BITS_COPY64_PTR.
- gx_gfx: menu_tex.data was sized 240*212 uint32_t (203,520 bytes),
  but convert_texture16 writes width*height*2 bytes into it and
  gx_set_video_mode allows up to 560x240 for the 21:9 RGUI aspect
  cases, producing a ~64 KB write past the buffer. Sized for the
  worst-case 560*240 explicitly. The DCFlushRange call alongside
  passed fb_width*fb_pitch (i.e. fb_width^2 * 2), conflating width
  and height; corrected to fb_height*fb_pitch.

Leaks
- platform_gx: frontend_gx_deinit didn't release the device-thread
  mutex/cond/mutex triple created in frontend_gx_init. Re-init
  cycles (CMD_EVENT_QUIT + relaunch) leaked all three. Freed.
- gx_gfx: g_tex.data is memalign'd in init_vtx and was never freed
  by gx_free. Now released alongside the rest of the GX state.
- gx_gfx: both XFB framebuffers in gx->framebuf[] were allocated
  via MEM_K0_TO_K1(memalign(...)) in setup_video_mode and never
  freed. free() takes the cached alias - use MEM_K1_TO_K0() before
  passing.

Correctness
- platform_gx: DEFAULT_DIR_REMAP was filled from DEFAULT_DIR_MENU_CONFIG
  *before* MENU_CONFIG itself was filled, producing a remap path
  rooted at the empty/stale MENU_CONFIG value on first boot. Swapped.
- gx_joypad: check_port0_active() was called inside the per-port
  loop, four times per poll, with a partial pad_count for the first
  three iterations - which could force-enable port 0 spuriously.
  Hoisted outside the loop.
- gx_input: rvl_input_poll's realloc-grow path zeroed only x_last/y_last
  on the (possibly newly-allocated) tail entries, leaving x_abs/y_abs
  uninitialised. The next poll's x_last = x_abs picked up uninitialised
  heap data, producing a one-frame garbage delta on hot-plug. Zero
  exactly the new tail with memset; existing entries keep their
  last known position. Also dropped the shadowed inner 'unsigned i;'
  and the redundant 'gx &&' on the inner if.
- gx_input: lightgun path had a video_driver_get_viewport_info(&vp)
  call immediately followed by six lines that zeroed every field it
  populated. The dispatcher went through the video vtable on every
  lightgun query for nothing - removed.
- gx_audio: gx_audio_data was 'static volatile gx_audio_t *', making
  every member access through the pointer volatile, including the
  4 KB sample data[][] array (which is hot). The three IRQ-shared
  fields are individually volatile-qualified in the struct, which is
  sufficient. Dropped the outer volatile and the gratuitous cast in
  the DMA callback. Also published gx_audio_data only after the
  struct is fully initialised, so the IRQ never sees a partial state.

Folds / cleanup
- mem2: all _mem2_* helpers now static - they are only called from
  the __wrap_* functions in the same TU (Makefile.wii uses
  -Wl,-wrap,malloc,...). Lets the compiler inline them aggressively.
- mem2: _mem2_strdup and _mem2_strndup were using _mem2_calloc and
  then immediately memcpy'ing over the zeroed bytes. _mem2_malloc
  is enough.
- mem2: _CPU_ISR_Disable's "level" output was being silenced with
  a (void)level; cast in __lwp_heap_block_size. Replaced with the
  same `uint32_t level = 0;` idiom the rest of the codebase uses.
- platform_gx: frontend_gx_get_free_mem computed
    SYSMEM1_SIZE - (SYSMEM1_SIZE - SYS_GetArena1Size())
  which is just SYS_GetArena1Size(). Compiler folds it, but the
  source read as if there was something subtle going on. Reduced.
- platform_gx: gx_rom_path is `extern char[]`. The `gx_rom_path &&`
  arm of the if-condition is a tautology - removed.
- platform_gx: the two FRONTEND_FORK_CORE / FRONTEND_FORK_CORE_WITH_ARGS
  switch arms had identical bodies - folded.
- platform_gx: device-name buffer build used two strlcpy()s; one
  snprintf is the same cost and one less line.
- app_booter: two-statement (p_paddr &= 0x3FFFFFFF; p_paddr |= 0x80000000;)
  bit fold; direct-init for phdrs and dolfile; collapsed
  app_booter_main's exeEntryPointAddress to a conditional initialiser.
- gx_gfx: verts[], vertex_ptr[], color_ptr[], menu_gx_resolutions[]
  had external linkage but were referenced only within gx_gfx.c, and
  none were written. Marked static const so they go in .rodata. Call
  sites to GX_SetArray (which takes void*) get explicit casts.
- gx_gfx: gx_blit_line had several declare-then-assign locals derived
  from gx-> and gx_mode; folded into direct const initialisers, with
  the gx NULL-check moved above the derefs.
- gx_audio: gx_audio_copy_swapped (which is not an endianness swap,
  it's a 16/16 word rotate for the AI hardware's L/R lane order)
  renamed to gx_audio_lr_swap and unrolled by 4 for better dispatch
  on Broadway/Gekko.
2026-05-16 22:48:59 +02:00
LibretroAdmin 6ddc7c6089 Drop unused retro_endianness.h includes
Audited all 28 sites that #include <retro_endianness.h>. Three files
reference no symbol from that header (no SWAP*, swap_if_*, MSB_FIRST,
LSB_FIRST, RETRO_IS_*_ENDIAN, is_little_endian, retro_*_to_*, load32be,
store32be, retro_unaligned*, MAY_ALIAS, etc.):

  input/drivers_hid/wiiu_hid.c
    Uses SWAP_IF_BIG(), which is defined in joypad_connection.h and
    reaches this TU transitively via wiiu/hid.h -> wiiu/input.h ->
    connect/joypad_connection.h. joypad_connection.h is the site that
    actually needs retro_endianness.h (for the MSB_FIRST gate on its
    SWAP_IF_BIG definition); wiiu_hid.c does not.

  input/input_driver.c
    No endianness references at all.

  tasks/task_database.c
    No endianness references at all. (task_database_cue.c, included as
    a sibling .c, does use MSB_FIRST and keeps its own include.)
2026-05-16 21:14:23 +02:00
Apaczer a56a8d9776 omit VIDEO_FILTER_TOGGLE hotkey check in menu (#19039)
* input_driver: rm video_filter_toggle in menu

Update runloop.c

* correct indents from prv commits

* msg_hash: correct video_filter spelling

* menu: place video_filter_enable labels in macros
2026-05-14 23:57:35 +02:00
LibretroAdmin a4b0da6777 input: skip 6 sensor cache reads when sensors disabled
input_driver_poll() unconditionally calls input_get_sensor_state_internal()
six times per frame to populate the per-frame sensor caches used by
shader backends. When settings->bools.input_sensors_enable is off (the
default for most users), each call:
  - reads settings->bools.input_sensors_enable
  - reads settings->uints.input_joypad_index[port]
  - branches on the master toggle
  - returns 0.0f

So 6 calls per frame return 0.0f after a handful of pointer derefs
each. Modest cost in absolute terms (low hundreds of cycles per
frame), but pure waste since the helper's return value when sensors
are disabled is fixed at 0.0f.

Short-circuit by checking the master toggle once at the caller. When
sensors are off, memset both caches to zero - which is the same
result the loop would have produced via input_get_sensor_state_internal
return values. When sensors are on, the existing six-call path runs
unchanged.

Behaviour-equivalent. No API change. Sensor-enabled path entirely
untouched.

Subtle: the previous code also zeroed the cache on the frame a user
disabled sensors mid-session, because the helper returned 0 and the
result was stored into the cache. The new code preserves that
behaviour - the memset branch fires on the first frame after disable
and zeroes the cache, so shaders see zero rather than the last
non-zero values.
2026-05-13 21:48:00 +02:00
Apaczer 807aad2b70 add VIDEO_FILTER_TOGGLE hotkey & video_filter_enable settings (#19033)
* add `settings->bools.video_filter_enable`

see gfx/video_driver.c

* add CMD_VIDEO_FILTER_TOGGLE via hotkey
2026-05-13 21:41:42 +02:00
Andrew McKnight 1abde384c8 input/bsv: seek after read in replay_get_serialized_data (#19022)
The in-progress recording stream is rewound and read back to embed
in a .state file; per C99 §7.19.5.3, an input op on an update-mode
stream must precede output with a positioning call. Without this
seek, subsequent intfstream_writes from bsv_movie_next_frame
silently no-op, truncating the .replay at the SAVE_STATE.
2026-05-10 11:09:50 +02:00
Aleksey Samoilov c39cf47281 [Wayland] Add support for xdg-toplevel-tag-v1 protocol (#18317) 2026-05-10 09:39:55 +02:00
Craig 1f23d5bc04 Fix no input driver found segfault, compile error without EGL (#19003)
* fix segfault when no input driver found

* fix implicit declaration of video_driver_get_ident etc. when no EGL

* webOS: allow no EGL/GLSL
2026-05-04 10:35:12 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 2619e44298 (Android) Expose frontend_android_get_name and
_get_version_sdk as cross-TU APIs

These two functions were defined as static in platform_unix.c but called
from android_input.c via local static forward declarations. The setup
worked only because the Android build is the griffin unity build, which
includes both .c files into a single TU (android_input.c via the
defined(ANDROID) branch at griffin.c:700, platform_unix.c via the
defined(__linux__) branch at :1182, and Android NDK toolchains define
both ANDROID and __linux__).

Any non-griffin Android build path - or any future move of either
function - would silently fail to link. Drop the static qualifiers, drop
the bogus static forward declarations in android_input.c, and declare
proper cross-TU prototypes in platform_unix.h alongside the existing
ANDROID-only API surface (android_app_write_cmd, g_android, etc.). No
behaviour change.
2026-05-02 22:41:49 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 2aeaa5fd25 Silence more warnings 2026-05-01 10:01:04 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre cecd19ff0a video_driver: rename {get,set}_size -> {get,set}_output_size
video_driver_get_size and video_driver_set_size publish the size of
the area where the active video driver presents pixels to the user.
The old name says "size" without saying "size of what," and the
ambiguity has cost: the caca driver bug fixed in ad986a6 was hidden
behind exactly this confusion -- caca was passing the libretro
core's frame size to set_size when the API contract was the canvas
(surface) size, and the call site looked plausible until the field
was renamed to expose what it actually held.

Rename the public API to make the contract explicit at the call
site, and add the documentation comment that should have been on
these declarations from the start:

  video_driver_get_size  -> video_driver_get_output_size
  video_driver_set_size  -> video_driver_set_output_size

"output_size" was chosen over the alternatives:

  - window_size is wrong on terminal drivers (caca, sixel) and
    DRM-fullscreen-no-window
  - surface_size collides with SDL_Surface and D3D surface concepts
  - display_size implies the physical monitor, not the output area
  - present_size is jargon and reads oddly as a noun
  - viewport_size collides with the existing vp.{width,height}
    aspect-corrected sub-rect

"output_size" describes purpose (size of the visible output area)
without implying mechanism, doesn't collide with existing terms,
and maps cleanly onto every driver: desktop window output, DRM
scanout output, libcaca canvas output, sixel terminal output.

The doc comment in gfx/video_driver.h spells out the per-driver
mapping (window client area for desktop GL/D3D/Vulkan/Metal,
framebuffer scanout for DRM/KMS, canvas grid for libcaca,
terminal output region for sixel), the consumer list (menu
drivers, CRT switcher, shader subsystem, input drivers, other
video drivers), threading (protected by video_st->display_lock),
and explicitly contrasts with the libretro core's frame size that
lives on each driver's local state (frame_width / frame_height
after the gl1 / fpga / gdi / network / sixel / caca renames; gl2
/ gl3 still keep their canonical video_width / video_height which
mean the backbuffer size).

74 call sites across 33 files updated by word-boundary sed; no
collisions (no other identifiers contain video_driver_get_size or
_set_size as a substring).

Validation: the file with the function definitions
(gfx/video_driver.o) and a representative sample of caller files
(menu/drivers/{xmb,ozone,materialui}.o, gfx/drivers/gl{1,2,3}.o,
gfx/video_crt_switch.o, gfx/video_shader_parse.o) all build clean
in this sandbox.  The remaining 24 files hit unrelated
dependency errors (windows.h not on Linux, libcaca / libvg
headers not installed) before reaching anything rename-related;
none produced a rename-related diagnostic.  Maintainer review on
a full build environment recommended before merge.

No behavior change.  Pure rename + documentation.
2026-05-01 04:49:33 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 4a24762c21 retroarch/menu/input: deduplicate more menu_state_get_ptr() calls
Follow-up to 40ad5bc, applying the same intra-function deduplication
pattern to three more files where a function-top menu_st cache
already exists but inner blocks needlessly re-fetched it (or used
menu_state_get_ptr()-> chained access).

retroarch.c command_event:
   - Drop 3 inner shadows of menu_st (CMD_EVENT_ADD_TO_PLAYLIST,
     CMD_EVENT_RESET_CORE_ASSOCIATION, CMD_EVENT_DISK_EJECT_TOGGLE);
     each is gated by #ifdef HAVE_MENU and the function-top cache
     at the start of command_event is gated by the same condition,
     so the inner shadows always bound the same pointer.
   - Replace 5 direct menu_state_get_ptr()-> chained accesses with
     menu_st-> (3 in CMD_EVENT_CLOSE_CONTENT, 1 each in
     CMD_EVENT_AUDIO_STOP and CMD_EVENT_AUDIO_START).  All inside
     HAVE_MENU paths, so the outer cache is reachable.
   - Also drop a redundant `settings` shadow inside
     CMD_EVENT_ADD_TO_PLAYLIST -- the outer command_event-scope
     `settings = config_get_ptr()` is unconditional and reaches
     into the HAVE_MENU block.

menu/cbs/menu_cbs_ok.c generic_action_ok_displaylist_push:
   - Drop 1 inner shadow in ACTION_OK_DL_DATABASE_MANAGER_LIST.

input/input_driver.c input_keyboard_event:
   - Drop 1 inner shadow in the screensaver-disable block.

Total: 10 fewer calls, no semantic change (menu_state_get_ptr()
returns the same file-scope-static pointer every time, and none
of the touched files reassign menu_st anywhere).

Other multi-call sites surveyed but intentionally skipped:
general_write_handler, menu_action_handle_setting, and
runloop_environment_cb each have several sibling case-block caches
in giant switches.  Promoting any of these to a function-top cache
would penalize every dispatch (most cases don't touch menu_st), so
the per-case pattern is correct there.
2026-05-01 03:29:29 +02:00
Eric Warmenhoven dcf3ae35fd minor NULL pointer checks to fix crashes from organizer 2026-04-28 09:06:01 -04:00
U-DESKTOP-SPFP6AQ\twistedtechre 003a1ff74b input/bsv: bound key_event_count and input_event_count against array size
bsv_movie_handle_read_input_event's read path read attacker-
controlled event counts straight from the .bsv replay file
and used them as loop bounds, indexing into fixed-size
arrays:

  uint8_t   key_event_count;            (.bsv format)
  uint16_t  input_event_count;          (.bsv format)

  bsv_key_data_t   key_events[128];     (struct bsv_movie)
  bsv_input_data_t input_events[512];   (struct bsv_movie)

Pre-this-patch the read loops at lines 822-836 (key) and
848-862 (input) iterated count-times into the fixed-size
arrays with no upper-bound check.  A malformed .bsv could
supply:

  - key_event_count = 255 -> (255-128)*sizeof(bsv_key_data_t)
                         = 1524 bytes of OOB heap-write past
                         key_events[]
  - input_event_count = 65535 ->
                         (65535-512)*sizeof(bsv_input_data_t)
                         = 520184 bytes of OOB heap-write
                         past input_events[]

bsv_movie_t is heap-allocated via calloc in
tasks/task_movie.c::bsv_movie_init_internal so this is a
heap-buffer-overflow with attacker-chosen 8- or 12-byte
payloads at attacker-chosen offsets, producing corruption
of the rest of the bsv_movie_t struct (rewind state,
statestream pointers, save buffers) and far beyond it.

Reachability: the user opens a malformed .bsv file via
Menu -> "Load Replay File" or the --bsvplay command-line
argument.  Same threat class as CDFS / CHD / BPS file-load
bugs -- requires the user to deliberately load attacker-
supplied content.  Initial audit notes flagged this as
netplay-reachable but verification of network/netplay/
netplay_frontend.c shows that netplay's "replay" terminology
refers to internal frame-replay from the netplay ring buffer
and does not invoke the .bsv file-read path; this is a
file-loading bug only.

Fix: in input/bsv/bsvmovie.c, reject the file as malformed
and end playback if either count exceeds its array's
ARRAY_SIZE.  A legitimate writer cannot produce more events
than the array holds because the writer's append path at
lines 489-490 / 475-476 shares the same backing array.

Adds samples/tasks/bsv_replay/bsv_replay_bounds_test as a
regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step.  Four cases covering legitimate counts up to and
including the cap, and out-of-range counts being rejected
without writes.

Test follows the existing samples/tasks pattern (verbatim
copy of the post-fix predicate with maintenance-contract
comment, ASan as the discriminator).  Verified to fire
under ASan when the predicate always returns true:
heap-buffer-overflow WRITE of size 1 at offset 1536 of a
1536-byte allocation in simulate_key_event_read.  With the
predicate the test passes clean.

Note on writer side: the append site at line 489
   movie->input_events[movie->input_event_count++] = data;
has no bound check either, but it's not fed by attacker
data and a single frame is unlikely to produce more than
512 events in practice.  Not addressed by this commit; left
for a separate audit pass.
2026-04-28 03:47:05 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre cba3a9c909 input: bound input_remap_ids[] against analog_value[][8] OOB write
input_driver.c's poll-time remap loop indexed
mapper->analog_value[i][n] using values from
settings->uints.input_remap_ids[i][k] without proper bounds in
two places:

  1. Button -> analog branch (line ~7392):
        handle->analog_value[i][remap_button - RARCH_FIRST_CUSTOM_BIND] = ...
     No bound on remap_button at all.  Reachable values:
     [RARCH_FIRST_CUSTOM_BIND, RARCH_UNMAPPED) i.e. [16, 1024),
     producing remap_axis_bind in [0, 1008).  Indexing past the
     8-element row writes int16_t values up to ~2014 bytes past
     the end of analog_value, into adjacent input_mapper_t
     fields (keys[], key_button[], buttons[]) and beyond into
     adjacent input_driver_state_t fields.

  2. Analog -> analog branch (line ~7427):
        if (remap_axis_bind < sizeof(handle->analog_value[i]))
           handle->analog_value[i][remap_axis_bind] = ...
     The sizeof() returns 16 (the byte size of the int16_t[8]
     row), not the element count.  Indices 8..15 passed the
     check and OOB-wrote into the next user's analog_value row,
     or past the array entirely for the last user.

Both sites read remap_button / remap_axis from
settings->uints.input_remap_ids[i][k], which is loaded from
.rmp files via input_remapping_load_file in configuration.c.
Pre-this-patch the loader fed config_get_int's result directly
into storage with no range check, so a malformed .rmp could
plant any int there.  An attacker who can deliver a .rmp
alongside a ROM (a common distribution channel for
"controller config bundles") gets a 60Hz attacker-controlled
out-of-bounds write into adjacent input state -- including
the keys[] keyboard bitmask for the last user, which controls
which RetroArch hotkeys appear pressed.

Two-pronged fix:

  - Load-time validation in input_remapping_load_file:
    reject _remap values outside [0, RARCH_ANALOG_BIND_LIST_END)
    (clamping to RARCH_UNMAPPED) before storing into
    settings->uints.input_remap_ids[i][j].  Applied to both the
    button-branch (j < RARCH_FIRST_CUSTOM_BIND) and the
    analog-branch (j >= RARCH_FIRST_CUSTOM_BIND) of the
    loader.

  - Use-site bounds in input_driver.c: add an
    ARRAY_SIZE-based bound to the previously-unguarded
    button -> analog branch, and replace the broken sizeof()
    bound with ARRAY_SIZE() in the analog -> analog branch.
    Defence in depth: even if the load-time validator misses
    a path (or a future caller writes to input_remap_ids[]
    bypassing the loader), the use sites are now safe.

Adds samples/tasks/input_remap/input_remap_bounds_test as a
regression test, registered in
.github/workflows/Linux-samples-tasks.yml as an ASan-enabled
step.  Seven cases covering the load-time clamp's accept and
reject paths, in-range writes going to the correct slot,
out-of-range writes being skipped without OOB, and the
specific historical sizeof-vs-ARRAY_SIZE bug shape.

Test follows the existing samples/tasks pattern (verbatim copy
of the post-fix predicates with a maintenance-contract comment;
ASan as the discriminator).  Verified to fire under ASan when
the bound check is removed: heap-buffer-overflow WRITE of size
2 at offset 16 of a 16-byte allocation, in
do_button_to_analog_write.  With the bound the test passes
clean.
2026-04-28 03:26:06 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 1fe435a905 bsv, cdfs: free intfstream_t after intfstream_close at five remaining leak sites
Audit pass over every intfstream_close call site in production
code: out of 53 sites, 48 were already followed by free() (the
established libretro-common convention -- intfstream_close
closes the inner backend, the caller owns the struct).  Five
were not, leaking 48 bytes per call.

Five fixed:

  - input/bsv/bsvmovie.c:1315  check_stream  (open_memory)
  - input/bsv/bsvmovie.c:1643  out_stream    (open_writable_memory)
  - input/bsv/bsvmovie.c:1845  read_mem      (open_memory)
  - libretro-common/formats/cdfs/cdfs.c:433  cue_stream  (alloc-fail path)
  - libretro-common/formats/cdfs/cdfs.c:438  cue_stream  (success path)

The bsv leaks are the more interesting set: those code paths
run on every replay verification (check_stream), every
checkpoint encode (out_stream), and every checkpoint decode
(read_mem) -- so during a long netplay session with an active
replay or in checkpointed playback the leak compounds linearly
with elapsed time.  Each instance is small (48 bytes per
intfstream_t struct) but they accumulate.

The cdfs leak is per-cue-file: every time RetroArch parses a
.cue (loading a multi-track CD image, bulk database scan over
a CD library) it leaks 48 bytes.  Both close paths in
cdfs_open_cue_path were affected.

Note on contract: changing intfstream_close to free the struct
itself was considered and rejected.  intfstream_close ships in
libretro-common which is vendored into many cores; cores that
correctly compensate for the existing convention by calling
free() after close (the same pattern this commit standardises
on inside RetroArch itself) would silently double-free under a
contract change.  Fix-the-call-sites is the only safe path.

The 48 already-correct call sites are left as-is.  This commit
is a pure addition of missing free() calls; no behavioural or
cleanup-order changes.
2026-04-28 03:07:55 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre d44ae5e6ec input: fix OOM NULL-deref bugs across input_driver overlay visibility and wayland registry handlers
Five unchecked alloc + immediate-deref bugs across the shared
input dispatch core and the wayland registry/data-offer
handlers.

=== input/input_driver.c: overlay_visibility calloc ===

input_overlay_set_visibility lazily allocates the visibility
array on first call:

    if (!input_st->overlay_visibility)
    {
       unsigned i;
       input_st->overlay_visibility = (enum overlay_visibility *)calloc(
             MAX_VISIBILITY, sizeof(enum overlay_visibility));

       for (i = 0; i < MAX_VISIBILITY; i++)
          input_st->overlay_visibility[i] = OVERLAY_VISIBILITY_DEFAULT;
    }

    input_st->overlay_visibility[overlay_idx] = vis;

The calloc was unchecked.  Both the init loop and the final
indexed write dereference input_st->overlay_visibility - OOM
was a crash on first visibility change after the overlay was
reloaded.

Fix: NULL-check and early-return.  On failure overlays stay
at their compile-time default visibility, which is strictly
better than crashing.  The next call re-enters this branch
and retries the allocation (input_st->overlay_visibility is
still NULL), so transient OOM is self-healing.

=== input/common/wayland_common.c: three unchecked callocs ===

wl_current_outputs_add: surface_output_t calloc
unchecked before os->output and wl_list_insert(&os->link) that
NULL-deref.  Fixed with early return false - the caller
(registry handler) treats false as 'output not tracked', which
is strictly better than crashing and is self-healing on the
next wl_registry.global re-emission.

wl_registry listener wl_output branch:
display_output_t + output_info_t callocs both unchecked before
od->output = oi / oi->global_id = id / wl_output_add_listener
NULL-derefs.  Fixed with joint NULL-check, free whichever
succeeded (free(NULL) is a no-op), skip adding this output to
wl->all_outputs.

wl_data_device_handle_data_offer: data_offer_ctx
calloc unchecked before offer_data->offer and wl_data_offer_
set_user_data/add_listener NULL-deref.  Fixed with early
return - consequence is a lost drag-and-drop operation on
that specific offer.

=== input/common/wayland_common_webos.c: two unchecked callocs ===

wl_registry handler for webos wl_output: twin of
the wayland_common.c fix - display_output_t + output_info_t
callocs both unchecked, with the same od->output / oi->global_id
deref pattern.  Same fix.

wl_registry handler for webos wl_seat:
seat_info_t calloc unchecked before si->seat / global_id / wl
writes NULL-deref.  Fixed with early return - the compositor
re-emits wl_registry.global if a retry is needed.

=== Swept-clean in the same pass ===

Verified NULL-checked in the same files:

- input_driver.c other 5 sites: input_keyboard_line_append
  realloc (tmp-pattern, guarded), input_remote_new calloc,
  osk realloc (tmp-pattern, guarded), overlay images malloc
  (has defensive num_images=0 on failure to make the
  subsequent for-loop safely iterate zero times), overlay
  ol calloc (prior audit commit).

- wayland_common.c wl_read_pipe realloc (tmp-pattern,
  guarded, with invariant-preserving rewind on failure from
  prior commit).

- wayland_common_webos.c: g_register_ctx malloc, *wwl calloc,
  g_screensaver_ctx malloc, client_name malloc - all guarded.

- input/drivers_joypad/winraw_joypad.c: 4 sites guarded.

Reachability: all five fixes are reachable on OOM.  The
overlay_visibility one is the most user-facing - any
visibility change hits it.  The wayland ones fire on
display/seat/drag-drop events which are compositor-driven
and arbitrarily frequent.

Scope: local to each callsite; no cross-TU changes.  All
existing state-mutation invariants preserved (on failure
we skip the mutation entirely rather than half-apply it).

Thread-safety: all sites run on their respective main
dispatch threads (input main thread, wayland event loop
thread).  No new concurrency introduced.

Reachability timing: the overlay_visibility bug is reachable
on the very first user-invoked overlay visibility change
under OOM and survives as a latent crash for the lifetime
of the process (the allocation is lazy, retried on each
entry until it succeeds).  The wayland bugs are reachable
on any wl_registry.global or wl_data_device.data_offer event
the compositor emits - which for data_offer is every
drag-and-drop enter, and for wl_output/wl_seat is every
display-plug / input-device hotplug event.
2026-04-24 07:14:27 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 9e840aa0bb input/drivers_hid/iohidmanager_hid: hoist !hid NULL-check above hid->slots dereference
iohidmanager_hid_device_add had a dead-code NULL-guard:

    for (i = 0; i < MAX_USERS; i++)
    {
       struct iohidmanager_hid_adapter *a =
          (struct iohidmanager_hid_adapter*)hid->slots[i].data;
       ...
    }

    if (!(adapter = (struct iohidmanager_hid_adapter*)calloc(
             1, sizeof(*adapter))))
       return;
    if (!hid)
       goto error;

The 'if (!hid) goto error' guard only fires after the
for-loop has already unconditionally dereferenced hid via
hid->slots[i].data.  On NULL hid the function segfaults at
the slots lookup; the later guard is unreachable.

=== Reachability ===

iohidmanager_hid_device_matched is the sole caller and fetches
hid via:

    iohidmanager_hid_t *hid =
       (iohidmanager_hid_t*) hid_driver_get_data();
    iohidmanager_hid_device_add(device, hid);

hid_driver_get_data() can return NULL in two known windows:

  1. Early init: IOHIDManager matching callbacks can fire
     before iohidmanager_hid_init's return stores the hid
     pointer in the global driver state.  A device plugged
     in (or already present) at startup triggers the race.

  2. Teardown: iohidmanager_hid_free nulls the global before
     tearing down IOKit subscriptions, so a matching-callback
     dispatched just before the unsubscribe fires with NULL
     hid.

Both windows are small but real - especially on systems where
lots of HID devices enumerate at startup (USB hubs, Bluetooth
adapters), the first window is reliably reachable.

=== Fix ===

Hoist the !hid check to the top of iohidmanager_hid_device_add,
immediately after the local variable declarations and the
device_location_id read.  Remove the now-redundant later guard.

On NULL hid we return early without ever touching hid->slots or
allocating an adapter - a cleaner outcome than the previous
segfault and a strictly simpler state to reason about than the
old two-stage check (which would have leaked an allocated
adapter through goto-error on NULL hid if the dereference
crash had somehow not happened first).

=== Adapter freshness on goto error ===

The error: label at line ~946 walks adapter->hats /
adapter->axes / adapter->buttons linked lists.  These reads are
safe because adapter is always calloc'd fresh (zero-filled)
before any goto error fires from this function: the four
earlier goto-error sites at the time of writing (IOHIDDeviceOpen
failure, element enumeration failures, etc.) all run after the
calloc has succeeded, so adapter is non-NULL.  No change needed
there.

=== Scope ===

Single-file change.  Behaviour under valid (non-NULL) hid is
unchanged.  Behaviour under NULL hid goes from segfault to
silent skip of the device-add - which matches the behaviour
when the same device has already been registered (the earlier
early-return at the top of the function).

Thread-safety: IOHIDManager callbacks run on the run loop
thread, typically the main thread.  No concurrent mutation of
hid itself is introduced by this change - we still rely on
the caller (hid_driver_get_data) to return a consistent
snapshot of the global.
2026-04-24 07:07:59 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 8de244ccaa input/drivers: fix gx_input UAF on mouse realloc failure and ps3 threads malloc NULL-deref
Three bugs across two input drivers reached while sweeping
input/drivers/ for alloc-failure issues.

=== gx_input.c: UAF on mouse realloc failure (Wii/RVL only) ===

rvl_input_poll grows/shrinks the mouse array when the detected
Wiimote count changes:

    gx_input_mouse_t *tmp = (gx_input_mouse_t*)realloc(
          gx->mouse, count * sizeof(gx_input_mouse_t));
    if (!tmp)
       free(gx->mouse);
    else
    {
       gx->mouse     = tmp;
       gx->mouse_max = count;
       ...
    }

    for (i = 0; i < gx->mouse_max; i++)
    {
       gx->mouse[i].x_last = gx->mouse[i].x_abs;
       ...
    }

On realloc failure the old code free'd gx->mouse but left the
dangling pointer and stale gx->mouse_max in place.  The
subsequent loop then iterated through freed memory via
gx->mouse[i].x_last = gx->mouse[i].x_abs - a use-after-free
at every poll until the driver was torn down, with the upper
gate 'if (gx && gx->mouse)' still accepting the freed-but-
non-NULL pointer on the next call.

Fix: on realloc failure clear gx->mouse = NULL and
gx->mouse_max = 0.  The outer gate at the top of
rvl_input_poll then rejects subsequent polls, and the inner
per-poll update loop is now wrapped in 'if (gx->mouse)' so
it skips on OOM rather than iterating through NULL.

On the happy path behaviour is unchanged.

=== gx_input.c: NULL-deref on mouse calloc failure at init ===

gx_input_init unconditionally returned the gx handle even if
the per-Wiimote mouse array calloc at init time had failed:

    gx->mouse_max  = 1;
    gx->mouse      = (gx_input_mouse_t*)calloc(
          gx->mouse_max, sizeof(gx_input_mouse_t));

    return gx;

The RETRO_DEVICE_MOUSE input handler later does

    int x = (gx->mouse[joy_idx].x_abs
           - gx->mouse[joy_idx].x_last) * x_scale;

unconditionally - no 'if (gx->mouse)' guard.  On OOM during
init, the very first mouse event NULL-derefs.

Fix: NULL-check the calloc and fail the whole driver init
(free(gx) + return NULL) if it fails.  A Wii build running
without any mouse support is strictly better than crashing
on first Wiimote motion.

=== ps3_input.c: NULL-deref on SPU thread list malloc ===

ps3_init_spurs allocates a thread-list buffer and immediately
hands it to the SPURS SDK for population:

    ps3->threads = (sys_spu_thread_t *)malloc(
          sizeof(sys_spu_thread_t) * nthread);

    if ((ret = spursGetSpuThreadId(ps3->spurs,
                                    ps3->threads, &nthread)))
       return ret;

malloc was unchecked.  spursGetSpuThreadId writes the thread
IDs into the buffer - behaviour with NULL is
implementation-defined and at best returns an error code but
more realistically crashes inside the SDK.

Fix: NULL-check the malloc and return -1 on OOM.  Matches the
return-code convention of the malloc-failure branch in
ps3_init_gem at line 364 below, which also returns -1.  The
teardown path ps3_end_spurs does free(ps3->threads) which is
NULL-safe via free(NULL).

=== Swept-clean in the same pass ===

Verified via visual inspection that all other calloc/malloc/
realloc call sites in input/drivers/ are already NULL-checked:

- udev_input.c    : 6 sites (3 touch state allocs, device/
                    realloc/driver-init) all guarded.
- winraw_input.c  : 4 sites all guarded.
- rwebinput_input.c: 3 sites all guarded (realloc pattern fixed
                     in an earlier commit).
- qnx_input.c     : 2 sites guarded.
- psp_input.c     : 2 sites guarded.
- dinput.c        : 2 sites guarded.
- Single-alloc drivers x11_input / switch_input / sdl_input /
  ps4_input / linuxraw_input / cocoa_input / android_input :
  all single init callocs guarded.

Reachability: all three fixes are reachable on OOM.  The gx
UAF is the most concerning - it's triggered by routine
Wiimote hotplug on a Wii in low-memory conditions and results
in use-after-free every poll thereafter.  The gx init crash
requires OOM during driver construction.  The ps3 SPURS
crash requires OOM in the PS3 audio init path.

Thread-safety: all three sites are on the main input thread;
no concurrency concerns.

Scope: local to the two files modified; no API changes, no
header changes.  The gx_input driver's external contract is
unchanged (init still returns NULL on failure, the poll
function still silently degrades when mouse state is
unavailable); the ps3_init_spurs function signature and error
convention are unchanged.
2026-04-24 07:00:48 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 55bd49cefc libretro-db: fix OOM bugs in rmsgpack_dom reader state and query parser
Five related fixes across libretro-db's runtime code (the subset
linked into RetroArch itself - c_converter.c and similar offline
tools are out of scope for this patch).

=== rmsgpack_dom.c: rmsgpack_dom_reader_state_new unchecked callocs ===

    struct rmsgpack_dom_reader_state *s = calloc(1, ...);
    s->i        = 0;          /* NULL-deref on OOM */
    s->capacity = 1024;
    s->growable = true;
    s->stack    = calloc(1024, ...);
    return s;

Both callocs unchecked.  The outer calloc's failure NULL-derefs
on the immediate field writes.  The inner (stack) calloc's
failure is caught later when callers iterate - rmsgpack_dom_read
at line ~422 does 's->stack[0] = out' unconditionally.

Fix: NULL-check the outer calloc and return NULL.  NULL-check
the inner calloc and free the outer struct + return NULL on
failure.  Callers must now tolerate NULL return.

=== rmsgpack_dom.c: rmsgpack_dom_reader_state_free NULL-deref ===

    free(state->stack);     /* NULL-deref if state is NULL */
    free(state);

state could not be NULL pre-patch (construction couldn't fail)
but with reader_state_new now able to return NULL, callers that
defer cleanup (like input/bsv/bsvmovie.c's decompress path)
would hit this free with NULL.

Fix: early return on NULL state.

=== rmsgpack_dom.c: rmsgpack_dom_reader_state_push realloc-assign-self ===

    s->capacity *= 2;
    s->stack     = realloc(s->stack, s->capacity * ...);
    if (!s->stack)
       return -1;

Two stacked bugs:
  1. Classic realloc-assign-self: on OOM s->stack goes NULL,
     leaking the old buffer (the only reference).
  2. 'capacity *= 2' happens BEFORE the realloc, so on failure
     the capacity claimed the new size while the allocation is
     still the old size (or NULL).  The -1 return is checked by
     the direct caller, but the out-of-sync state is a heap-
     buffer-overflow trap if anything later ignored the error
     return.

Fix: realloc-to-tmp.  Compute new_capacity into a local, realloc
into new_stack, commit both only on success.  Preserves the -1
OOM signal semantics.

=== query.c: query_parse_method_call zero-arg false-OOM ===

    invocation->argv = (argi > 0) ? malloc(...) : NULL;

    if (!invocation->argv)
    {
       /* emit 'OOM' error */
       goto clean;
    }

argv is legitimately NULL when argi == 0 (a zero-argument
function call like 'foo()') but the next block treated that as
OOM and raised the 'OOM' error string.

This was a pre-existing logic bug tangentially exposed by OOM
auditing.  The observable symptom pre-patch was that any
genuinely-zero-argument function call would fail query parsing
with a bogus 'OOM' error.

Fix: gate the OOM branch on 'argi > 0 && !invocation->argv'.
Skip the memcpy when argv is NULL (nothing to copy when argi==0).

=== input/bsv/bsvmovie.c: bsv_movie_read_deduped_state consumer ===

    struct rmsgpack_dom_reader_state *reader_state =
       rmsgpack_dom_reader_state_new();
    ...
    rmsgpack_dom_read_with(read_mem, &item, reader_state);

With rmsgpack_dom_reader_state_new now able to return NULL, this
caller needs to NULL-check before passing reader_state into
rmsgpack_dom_read_with (which dereferences it freely).

Fix: early bail to the existing 'exit' cleanup label on NULL.
The cleanup already calls rmsgpack_dom_reader_state_free (now
NULL-tolerant), intfstream_close (already NULL-tolerant), and
sets ret=false, so the user-visible outcome on OOM is the same
as the existing 'malformed frame' error paths.

=== Scope notes ===

Other libretro-db files checked during this pass:
  * bintree.c - the single alloc site at line 35 is already
    NULL-checked.
  * libretrodb.c - two sites at lines 771 (buff in create_index)
    and 831 (cursor_new) both use the NULL-check idiom.
  * rmsgpack.c - zero raw alloc sites.
  * Inner callocs in rmsgpack_dom.c:145 (dom_read_map_start) and
    :173 (dom_read_array_start) use the 'if (!(items = ...))'
    idiom and propagate -1 up the rmsgpack callback chain.

Not in scope (standalone offline utilities, not linked into
RetroArch runtime - see libretro-db/Makefile TARGETS):
  * c_converter.c - 7 alloc sites
  * libretrodb_tool.c
  * rmsgpack_test.c

=== Thread-safety ===

All touched paths run synchronously on whichever thread invoked
the db query.  In practice RetroArch accesses libretro-db from
the main thread (menu content scanning, explore-by-metadata)
and from task threads (background scan).  No shared-state
changes; no lock discipline changes.

=== Reachability ===

  * rmsgpack_dom_reader_state_new/free: the allocating variant
    is only called from input/bsv/bsvmovie.c's STATESTREAM
    decode path, used during savestate runahead on cores that
    support bsv movie recording.
  * rmsgpack_dom_reader_state_push: called recursively while
    parsing deep msgpack maps/arrays inside a libretrodb .rdb
    query result.  Capacity doublings trigger on deeply nested
    data (the default capacity is 1024 for the allocating
    variant, MAX_DEPTH stack-alloca for rmsgpack_dom_read - the
    stack variant sets growable=false so never reaches this
    realloc path).
  * query_parse_method_call: every libretrodb query string
    parse - menu-driven 'Explore' category navigation and any
    core's database-backed metadata lookup.
2026-04-24 02:03:35 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre f07549bde4 overlay: fix OOM crash cascade across task_overlay producer and input_driver consumer
Four bugs forming one OOM cascade in the input-overlay load
pipeline.  Fixing any one of them in isolation just pushes the
crash one call-frame deeper; they have to land together.

=== tasks/task_overlay.c: task_overlay_desc_populate_eightway_config ===

    desc->eightway_config = (overlay_eightway_config_t *)
          calloc(1, sizeof(overlay_eightway_config_t));
    eightway              = desc->eightway_config;

    switch (desc->type)
    {
       case OVERLAY_TYPE_DPAD_AREA:
          BIT256_SET(eightway->up, ...);         /* NULL-deref */
          ...

Unchecked calloc; the BIT256_SET writes in the switch body
NULL-deref on OOM.  void-returning function so we can't signal
failure to the caller directly.

Fix: NULL-check and early return.  This leaves
desc->eightway_config = NULL for an area-type desc that needs
it, which then fires the consumer bug below.

=== input/input_driver.c: input_overlay_poll eightway dispatch ===

    case OVERLAY_TYPE_DPAD_AREA:
    case OVERLAY_TYPE_ABXY_AREA:
       input_overlay_get_eightway_state(
             desc, desc->eightway_config,
             &out->buttons, x_dist, y_dist);
       break;

input_overlay_get_eightway_state dereferences eightway->
slope_high unconditionally (line ~2781).  With the producer fix
above leaving eightway_config NULL under OOM, this dispatch path
needs to gate on non-NULL:

    if (desc->eightway_config)
       input_overlay_get_eightway_state(...);

User-visible consequence on OOM: the one area whose config alloc
failed is dead until overlay reload.  Strictly better than a
segfault on the next touch input.

=== tasks/task_overlay.c: task_overlay_handler data alloc ===

    overlay_task_data_t *data = (overlay_task_data_t*)
       calloc(1, sizeof(*data));

    data->overlays                    = loader->overlays;  /* NULL-deref */
    ...

Unchecked calloc in the task's 'finished and not cancelled'
branch; the seven field writes below NULL-deref on OOM.

Fix: NULL-check and early return (skip the task_set_data call).
The consumer (input_overlay_loaded in input/input_driver.c) then
sees NULL task_data, which cascades to the next bug.

=== input/input_driver.c: input_overlay_loaded task_data and ol allocs ===

    overlay_task_data_t *data = (overlay_task_data_t*)task_data;
    ...
    if (err)
       return;

    ol              = (input_overlay_t*)calloc(1, sizeof(*ol));
    ol->overlays    = data->overlays;                     /* NULL-deref */
    ...

Two stacked bugs:
  1. task_data can be NULL (from the producer fix above) but is
     dereferenced as data->overlays on the very next usable line.
     Add an early return.
  2. The ol calloc is unchecked.  The field writes below NULL-deref
     on OOM.  Unlike typical OOM-bail patterns, bailing here has a
     non-trivial cleanup obligation: task_data was transferred to
     us by the retro_task framework and we own it, *and* the
     image_list inside it has heap texture_image attachments on
     each entry's .attr.p that the success path would transfer
     into ol->images via input_overlay_loaded_move_images.  Since
     ol doesn't exist, we need to free each texture explicitly
     before string_list_free (string_list_free only frees the
     list struct and its entries' .data strings, not the .attr.p
     attachments).

Fix both:

    if (!data)
       return;
    ol = (input_overlay_t*)calloc(1, sizeof(*ol));
    if (!ol)
    {
       if (data->image_list)
       {
          size_t i;
          for (i = 0; i < data->image_list->size; i++)
             image_texture_free(
                (struct texture_image*)data->image_list->elems[i].attr.p);
          string_list_free(data->image_list);
       }
       free(data);
       return;
    }

=== Thread-safety ===

task_overlay_handler runs on the retro_task worker thread pool;
input_overlay_loaded runs on the main thread as the task's
completion callback.  The ownership transfer of task_data via
task_set_data is already correct (producer populates, consumer
frees), so no lock discipline changes.

=== Reachability ===

Overlay loading runs every time the user picks an overlay from
the menu, configures an auto-load overlay, or switches between
overlays (e.g. OSK toggle).  On handheld and embedded targets
with the memory pressure typical of running an emulator core +
overlay textures, OOM at any of these four sites is realistic.

=== Scope ===

Left image_transfer / texture loading paths in task_overlay.c
alone (the per-image malloc + image_texture_load calls all
NULL-check and goto their cleanup labels already).  The four
fixes here are the OOM sites that didn't have any guard.
2026-04-24 01:23:26 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 7f745a9c87 input: fix OOM handling in wl_read_pipe and rwebinput_keyboard_cb
Two independent real OOM bugs in input-path buffer-growth code.

=== input/common/wayland_common.c: wl_read_pipe ===

The function reads one PIPE_BUF-sized chunk off a Wayland clipboard
/ drag-and-drop pipe into '*buffer', growing it per-call.  The
caller's loop pattern is:

    while (wl_read_pipe(pipefd[0], &buffer, length, null_terminate) > 0);

i.e. keep reading until bytes_read <= 0 (0 = EOF, <0 = error).

Pre-patch, when the malloc / realloc inside the function failed,
the else-branch of the 'if (output_buffer)' alloc check was
missing entirely.  The flow was:

    pos              = *total_length;
    *total_length   += bytes_read;        /* committed early */
    ...
    output_buffer = realloc(*buffer, _len);
    if (output_buffer) { memcpy(...); *buffer = output_buffer; }
    /* else: fall through, return bytes_read (positive!) */

On OOM:
  - The bytes we just read off the pipe are silently dropped
    (temp[] is a stack buffer, overwritten on the next read()).
  - *total_length has already been incremented, so the caller
    believes the buffer has grown.
  - *buffer is unchanged - on realloc failure it still points at
    the old, smaller allocation.  So the invariant 'size of
    *buffer is *total_length' is broken: the recorded length is
    larger than the actual allocation.
  - Return value is bytes_read > 0, so the caller's while-loop
    continues.  Next iteration:
      pos = *total_length          /* over-sized */
      *total_length += bytes_read
      output_buffer = realloc(*buffer, _len)
    If that one succeeds, the memcpy at offset 'pos' writes past
    the end of what was actually in *buffer - corrupting arbitrary
    heap memory if realloc gave us a buffer exactly sized to the
    caller's new *total_length but missing the gap from the
    failed iteration.

Fix: on allocation failure, rewind *total_length to pos (the
pre-increment value) and return -1.  The caller's while-loop
then terminates; the partial data already stored in *buffer
remains valid and consistent with the (now-un-incremented)
*total_length.  The current-iteration's bytes_read are lost,
which is the same failure mode as any other mid-transfer pipe
read error (not worse).

=== input/drivers/rwebinput_input.c: rwebinput_keyboard_cb ===

The Emscripten rwebinput driver grows its pending-keyboard-event
ring on demand inside the keyboard callback:

    if (rwebinput->keyboard.count >= rwebinput->keyboard.max_size)
    {
       size_t new_max = MAX(1, rwebinput->keyboard.max_size << 1);
       rwebinput->keyboard.events = realloc(rwebinput->keyboard.events,
          new_max * sizeof(rwebinput->keyboard.events[0]));
       rwebinput->keyboard.max_size = new_max;
    }
    rwebinput->keyboard.events[rwebinput->keyboard.count].type = event_type;
    memcpy(&rwebinput->keyboard.events[...].event, key_event, ...);

Two bugs:

(1) 'events = realloc(events, ...)' is the classic realloc-assign-
    self leak: on OOM realloc returns NULL but leaves the original
    buffer valid; the self-assign overwrites the only pointer to
    it, leaking the entire pending-event ring (and any events
    already in it that weren't yet consumed).

(2) max_size is updated unconditionally after the realloc, before
    the return value is inspected.  Then the field-write and
    memcpy below dereference the now-NULL events pointer - a
    NULL-deref crash.  Any state that survived would claim
    'max_size holds 2N events' while actually holding zero.

Fix: realloc into a temp, NULL-check, and only commit both
events-pointer and max_size on success.  On OOM drop this one
event on the floor and return EM_TRUE (the Emscripten callback
convention); losing a single key event in a memory-starved
browser tab is survivable - the pre-patch form was going to
crash the whole page.

=== Thread-safety ===

Neither path's locking changes.  wl_read_pipe runs on whatever
thread services the Wayland event loop; rwebinput_keyboard_cb
runs on the Emscripten main thread.

=== Reachability ===

wl_read_pipe fires on every clipboard paste / drag-and-drop drop
onto a Wayland-native RetroArch window.  rwebinput_keyboard_cb
fires on every keyboard event in the web build; the ring-growth
path triggers only when the input consumer falls behind (rare
under normal play, common during loading screens where the main
thread is blocked).
2026-04-23 23:11:16 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 623d38790e input: align HID adapter slot type with pad_connection_pad_init
GCC on 10.5 PPC warned:

  iohidmanager_hid.c:691: warning: comparison between signed and
  unsigned

pad_connection_pad_init() at input/connect/joypad_connection.c:397
returns int32_t and can return -1 on allocation failure.  Four HID
drivers store that return value in an adapter struct field and use
it for array indexing.  Three got the type wrong:

  iohidmanager_hid.c : uint32_t slot (warning reported here)
  btstack_hid.c      : uint32_t slot (same structural mismatch,
                       silent - no == -1 check at call site)
  libusb_hid.c       : int slot      (compiles clean, but width is
                       implicit rather than explicit)
  wiiusb_hid.c       : int32_t slot  (correct)

Align all four on int32_t, matching the return type of
pad_connection_pad_init and wiiusb_hid.c's precedent.

Array-indexing call sites (hid->slots[adapter->slot], hid->hats
[adapter->slot][...], etc.) are unaffected - signed and unsigned
integer types index arrays identically through integer promotion
to ptrdiff_t.  The only behavioural effect is that slot == -1
checks (iohidmanager_hid.c:691, libusb_hid.c:313) are now signed/
signed comparisons and actually do what they look like they do.

Previously the iohidmanager check "worked" only because the
promotion rules happen to coerce (int)-1 to UINT32_MAX for the
comparison - accidental correctness.
2026-04-21 06:17:38 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre d38dcb543f cocoa_input: fix frame dot-syntax and NSPoint/CGPoint mismatch
GCC 4.0 (Xcode 3.1, PowerPC) reports:

    cocoa_input.m:1062: error: invalid initializer

Two problems compound in this four-line sequence on 32-bit 10.5:

    CGPoint window_pos    = window.frame.origin;
    CGSize window_size    = window.frame.size;

First, NSWindow's frame is declared - (NSRect)frame; on the
10.5-10.9 SDKs, not as @property.  GCC 4.0 only supports Obj-C 2.0
dot-syntax on explicitly declared @property, so window.frame fails
(same class of issue as the keyWindow / alphaValue fixes in
c49a525).

Second, window.frame.origin evaluates to NSPoint, but the variable
is typed CGPoint.  On 32-bit Darwin, NSPoint and CGPoint are
separate, incompatible types — Apple only unified them on LP64.
Same for NSSize vs CGSize on the next line.

Rewrite to fetch the frame once via bracket syntax into an NSRect
local, then feed the float field values directly to CGPointMake.
CGWarpMouseCursorPosition gets the same CGPoint as before; no
runtime change.  Modern clang compiles identically.
2026-04-21 02:02:35 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 2ba81980ae (MFi) Add include header 2026-04-20 14:51:04 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 06f21f4b82 Some buildfixes 2026-04-20 14:47:38 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre dc4872c8a3 (IOHIDManager) Silence warning 2026-04-19 22:49:56 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 691d934785 * Compatibilty fix in rc_client.c
* Some unused variable warning fixes
2026-04-16 14:44:18 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre a1d4129408 dxsdk: add SAL compatibility shim for MSVC 2003 through 2010
The bundled DirectX headers in gfx/include/dxsdk/ are modern Windows
8.x/10 SDK headers and use SAL annotations throughout their function
prototypes. These annotations come in two generations:

  * SAL 1 (double-underscore): __in, __out, __inout, __in_ecount,
    __out_bcount, __deref_out, etc.
  * SAL 2 (leading underscore + trailing underscore): _In_, _Out_,
    _Inout_, _In_reads_(s), _Out_writes_bytes_(s), _COM_Outptr_,
    _Always_(x), _Outptr_opt_result_maybenull_, etc.

Older Microsoft toolchains ship incomplete or nonexistent sal.h:

  * MSVC 2003 (_MSC_VER 1310): no sal.h at all. Both SAL 1 and SAL 2
    macros are undefined.
  * MSVC 2005 (_MSC_VER 1400) and MSVC 2008 RTM (_MSC_VER 1500): sal.h
    has SAL 1 only.
  * MSVC 2008 SP1 and MSVC 2010 (_MSC_VER 1600): sal.h ships a partial
    SAL 2 set (_In_, _Out_, _Inout_, etc.) but is missing the
    buffer-size, COM-outptr, and wrapper families.
  * MSVC 2012+ (_MSC_VER >= 1700): full SAL 2 in sal.h.

Without definitions for these macros, the preprocessor treats them as
unknown identifiers and every function declaration that uses one fails
to parse, producing cascades like:

  error C2143: syntax error : missing ')' before 'type'
  error C2081: '_COM_Outptr_' : name in formal parameter list illegal

Add gfx/include/dxsdk/dxsdk_sal_compat.h which stubs out the missing
SAL 1 and SAL 2 macros to empty (or to a pass-through for wrapper forms
like _Always_(x) / _When_(c,x)) so declarations using them parse as
plain C. The shim is structured in four tiers:

  * Skip the whole thing on MSVC 2012+ via _MSC_VER < 1700 gate.
  * Skip #include <sal.h> on MSVC 2003, where the header does not
    exist, via a _MSC_VER >= 1400 gate.
  * Stub the SAL 1 family, guarded per-macro with #ifndef so MSVC 2005
    through 2010 (which already have them) leave them untouched and
    MSVC 2003 gets working definitions.
  * Stub the SAL 2 family the same way, filling in whatever the
    toolchain is missing without redefinition warnings.

Every macro is wrapped in #ifndef to avoid C4005 redefinition warnings
on toolchains that ship a partial SAL set.

Include the shim from the three bundled headers that tripped MSVC 2008
in griffin builds:

  * gfx/include/dxsdk/d3dcommon.h
  * gfx/include/dxsdk/d3d11shader.h
  * gfx/include/dxsdk/d3dcompiler.h

The shim's outer include guard makes repeat inclusion free, so if
additional DX headers surface the same errors in future build
configurations they can be added with a one-line #include.

This is a build-only fix. The stubs discard static-analysis
information, which older MSVC versions could not consume anyway.
No behavior change on any supported toolchain.
2026-04-16 14:39:27 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre 4413415ff9 (input_driver.c) Performance optimization:
Adds an early-exit check at the top of input_joypad_analog_button(). After resolving the axis binding (checking both user bind and autoconfig fallback), if the result is AXIS_NONE, the function skips directly to the digital button check — bypassing the input_joypad_axis() function call, the deadzone magnitude computation, and the drv->axis() indirect call that would all produce zero anyway.
Why it helps: In the remap loop, input_joypad_analog_button() is called for every pressed button. During typical gameplay, 4–8 buttons are pressed per user (d-pad directions, face buttons, shoulders). Most of these are purely digital — they have joyaxis == AXIS_NONE in both the user bind and autoconfig. The original code called into input_joypad_axis() regardless, which evaluated the joyaxis != AXIS_NONE ternary (returning 0), then went through deadzone checking (returning 0 since normal_mag is 0), then fell through to the digital button check. The early exit skips all of that.
What's eliminated per digital button: One input_joypad_axis() function call (which itself would call drv->axis() if not for the AXIS_NONE guard), the deadzone normal_mag comparison, and the sensitivity scaling check. For a button that does have an analog axis, the behavior is completely unchanged — the early exit is not taken.
2026-04-13 20:16:15 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre fd511e1f77 (input_driver.c) Optimizations pt. 3:
B8: Bitmask cache for old-style cores — CLEAR WINNER (2.0×, 1314 ns @ 16 users)
This is now the single largest remaining optimization. When cores query buttons individually (not using RETRO_DEVICE_ID_JOYPAD_MASK), each of the 16 button queries goes through input_state_wrap → joypad->button() + joypad->axis() — 32 indirect calls per port. A per-port bitmask cache computed once at poll time (or lazily on first query) and served for subsequent individual queries would cut this to 1 joypad->state() call plus 16 bit lookups. Implementation: add a uint32_t joypad_state_cache[MAX_USERS] and bool joypad_cache_valid[MAX_USERS] to input_driver_state_t, invalidate at poll start, populate on first JOYPAD_MASK or individual query per port, serve subsequent queries from cache. The optimization only benefits cores that use the old per-button query pattern — modern cores already use JOYPAD_MASK internally.
2026-04-13 19:57:55 +02:00
U-DESKTOP-SPFP6AQ\twistedtechre e9655fab17 (input_driver.c) Optimizations pt. 2
Analog deadzone axis caching (HIGHEST PRIORITY — 2.5× measured speedup)
The input_joypad_analog_axis function is called 4 times per user per frame
(2 sticks × X/Y). Each call reads all 4 axis values via drv->axis() to
compute a radial deadzone magnitude — but X and Y of the same stick
produce identical magnitude. The fix: restructure the remap loop's
analog section to call a new input_joypad_analog_stick() that processes
both axes of a stick together, reading the 4 raw axis values once,
computing sqrtf once, and applying deadzone/sensitivity to both X and Y
from cached raw values.

Measured savings: 671 ns/frame at 16 users (2.48× speedup on the analog path alone),
644 ns saved on the full poll (1.44× aggregate).

What was done: Added a new input_joypad_analog_stick() function (215 lines) that processes both X and Y axes of a single analog stick together, computing the radial deadzone magnitude only once. The remap loop's inner j loop (which called input_joypad_analog_axis 4 times per user — twice per stick, redundantly computing magnitude each time) was replaced with 2 calls to the new per-stick function.
What was eliminated per stick per user per frame:

4 redundant drv->axis() indirect calls (the Y-axis pass re-reads the same 4 raw values that X already read for magnitude)
1 redundant sqrtf() (X and Y of the same stick have identical radial magnitude)
6 redundant bind lookups and validity checks (the Y pass re-resolves all 6 bind pointers that X already resolved)
1 redundant input_conv_analog_id_to_bind_id macro expansion

Per frame totals eliminated: For each user with 2 sticks: 8 drv->axis() calls, 2 sqrtf() calls, 12 bind lookups, 2 macro expansions. At 16 users: 128 indirect calls and 32 sqrtf calls eliminated.
2026-04-13 19:30:41 +02:00