From 42a7f3870db69fc12c7a14b163516bb055764c73 Mon Sep 17 00:00:00 2001
From: Craig Carnell <1188869+cscd98@users.noreply.github.com>
Date: Sat, 15 Aug 2026 10:52:00 +0100
Subject: [PATCH 1/3] libretro: update libretro.h from RA
---
src/libretro/libretro.h | 637 ++++++++++++++++++++++++++++++++++++----
1 file changed, 584 insertions(+), 53 deletions(-)
diff --git a/src/libretro/libretro.h b/src/libretro/libretro.h
index f1764ef42..911c411f9 100644
--- a/src/libretro/libretro.h
+++ b/src/libretro/libretro.h
@@ -4,12 +4,12 @@
* @file libretro.h
* @version 1
* @author libretro
- * @copyright Copyright (C) 2010-2023 The RetroArch team
+ * @copyright Copyright (C) 2010-2024 The RetroArch team
*
* @paragraph LICENSE
* The following license statement only applies to this libretro API header (libretro.h).
*
- * Copyright (C) 2010-2023 The RetroArch team
+ * Copyright (C) 2010-2024 The RetroArch team
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
@@ -219,7 +219,7 @@ extern "C" {
#define RETRO_DEVICE_KEYBOARD 3
/**
- * An abstraction around a light gun, simular to the PlayStation's Guncon.
+ * An abstraction around a light gun, similar to the PlayStation's Guncon.
*
* When provided as the \c device argument to \c retro_input_state_t,
* the \c id argument denotes one of several possible inputs.
@@ -272,7 +272,10 @@ extern "C" {
* [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen,
* and 0x7fff corresponds to the far right/bottom of the screen.
* The "screen" is here defined as area that is passed to the frontend and
- * later displayed on the monitor.
+ * later displayed on the monitor. If the pointer is outside this screen,
+ * such as in the black surrounding areas when actual display is larger,
+ * edge position is reported. An explicit edge detection is also provided,
+ * that will return 1 if the pointer is near the screen edge or actually outside it.
*
* The frontend is free to scale/resize this screen as it sees fit, however,
* (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the
@@ -406,7 +409,8 @@ extern "C" {
/* Id values for LIGHTGUN. */
#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X 13 /*Absolute Position*/
-#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 /*Absolute*/
+#define RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y 14 /*Absolute Position*/
+/** Indicates if lightgun points off the screen or near the edge */
#define RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN 15 /*Status Check*/
#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2
#define RETRO_DEVICE_ID_LIGHTGUN_RELOAD 16 /*Forced off-screen shot*/
@@ -421,17 +425,18 @@ extern "C" {
#define RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT 12
/* deprecated */
#define RETRO_DEVICE_ID_LIGHTGUN_X 0 /*Relative Position*/
-#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 /*Relative*/
-#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 /*Use Aux:A*/
-#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 /*Use Aux:B*/
-#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 /*Use Start*/
+#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 /*Relative Position*/
+#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 /*Use Aux:A instead*/
+#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 /*Use Aux:B instead*/
+#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 /*Use Start instead*/
/* Id values for POINTER. */
-#define RETRO_DEVICE_ID_POINTER_X 0
-#define RETRO_DEVICE_ID_POINTER_Y 1
-#define RETRO_DEVICE_ID_POINTER_PRESSED 2
-#define RETRO_DEVICE_ID_POINTER_COUNT 3
-
+#define RETRO_DEVICE_ID_POINTER_X 0
+#define RETRO_DEVICE_ID_POINTER_Y 1
+#define RETRO_DEVICE_ID_POINTER_PRESSED 2
+#define RETRO_DEVICE_ID_POINTER_COUNT 3
+/** Indicates if pointer is off the screen or near the edge */
+#define RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN 15
/** @} */
/* Returned from retro_get_region(). */
@@ -479,6 +484,8 @@ enum retro_language
RETRO_LANGUAGE_BELARUSIAN = 32,
RETRO_LANGUAGE_GALICIAN = 33,
RETRO_LANGUAGE_NORWEGIAN = 34,
+ RETRO_LANGUAGE_IRISH = 35,
+ RETRO_LANGUAGE_THAI = 36,
RETRO_LANGUAGE_LAST,
/** Defined to ensure that sizeof(retro_language) == sizeof(int). Do not use. */
@@ -513,6 +520,9 @@ enum retro_language
/* Video ram lets a frontend peek into a game systems video RAM (VRAM). */
#define RETRO_MEMORY_VIDEO_RAM 3
+/* ROM lets a frontend peek into a game systems ROM. */
+#define RETRO_MEMORY_ROM 4
+
/** @} */
/* Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. */
@@ -1094,7 +1104,7 @@ enum retro_mod
* to write audio. The audio callbacks must be called from within the
* notification callback.
* The amount of audio data to write is up to the core.
- * Generally, the audio callback will be called continously in a loop.
+ * Generally, the audio callback will be called continuously in a loop.
*
* A frontend may disable this callback in certain situations.
* The core must be able to render audio with the "normal" interface.
@@ -1332,7 +1342,7 @@ enum retro_mod
*
Changing the emulated system's internal resolution,
* within the limits defined by the existing values of \c max_width and \c max_height.
* Use \c RETRO_ENVIRONMENT_SET_GEOMETRY instead,
- * and adjust \c retro_get_system_av_info to account fo
+ * and adjust \c retro_get_system_av_info to account for
* supported scale factors and screen layouts
* when computing \c max_width and \c max_height.
* Only use this environment call if \c max_width or \c max_height needs to increase.
@@ -1673,22 +1683,6 @@ enum retro_mod
*/
#define RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE (43 | RETRO_ENVIRONMENT_EXPERIMENTAL)
-/**
- * Notifies the frontend of any quirks associated with serialization.
- *
- * Should be set in either \c retro_init or \c retro_load_game, but not both.
- * @param[in, out] data uint64_t *.
- * Pointer to the core's serialization quirks.
- * The frontend will set the flags of the quirks it supports
- * and clear the flags of those it doesn't.
- * Behavior is undefined if \c NULL.
- * @return \c true if this environment call is supported.
- * @see retro_serialize
- * @see retro_unserialize
- * @see RETRO_SERIALIZATION_QUIRK
- */
-#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44
-
/**
* The frontend will try to use a "shared" context when setting up a hardware context.
* Mostly applicable to OpenGL.
@@ -1720,6 +1714,24 @@ enum retro_mod
*/
#define RETRO_ENVIRONMENT_GET_VFS_INTERFACE (45 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+/**
+ * Returns a list of frontend-authorized filesystem locations.
+ *
+ * Paths returned by this call must be directly usable with the VFS interface,
+ * for example saf://... on Android.
+ *
+ * @param[out] data struct retro_vfs_authorized_locations *.
+ * The frontend owns the returned pointers. The core must copy strings
+ * if it needs to retain them.
+ * If \c data is \c NULL, the frontend should only return whether this
+ * environment callback is available.
+ *
+ * @return \c true if this environment call is available,
+ * \c false otherwise.
+ * @see RETRO_ENVIRONMENT_GET_VFS_INTERFACE
+ */
+#define RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS (93 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
/**
* Returns an interface that the core can use
* to set the state of any accessible device LEDs.
@@ -2569,6 +2581,307 @@ enum retro_mod
*/
#define RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY 80
+/**
+ * Returns the audio sample rate the frontend is targeting, in Hz.
+ * The intended use case is for the core to use the result to select an ideal sample rate.
+ *
+ * @param[out] data unsigned *.
+ * Pointer to the \c unsigned integer in which the frontend will store its target sample rate.
+ * Behavior is undefined if \c data is NULL.
+ * @return \c true if this environment call is available,
+ * regardless of the value returned in \c data.
+*/
+#define RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE (81 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Returns the local player's netplay client index when using frontend-managed
+ * multiplayer/rollback netplay.
+ *
+ * @param[out] data unsigned *.
+ * Pointer to an unsigned integer where the frontend stores the local client index.
+ * 0 indicates host. Values > 0 indicate connected clients.
+ * @return \\c true if the environment call is available and value was written,
+ * \\c false otherwise.
+*/
+#define RETRO_ENVIRONMENT_GET_NETPLAY_CLIENT_INDEX (82 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Allocates a region of executable memory, optionally dual-mapped.
+ *
+ * The frontend allocates memory suitable for JIT code generation and returns
+ * it to the core. The returned mode tells the core how to use the memory:
+ *
+ * - \c RETRO_EXEC_MEM_MODE_UNRESTRICTED: The platform has no restrictions
+ * on executable memory. The core should self-allocate. Do not call
+ * this environment with a non-zero size in this mode.
+ * - \c RETRO_EXEC_MEM_MODE_RWX: Single mapping, read-write-execute.
+ * \c rx and \c rw point to the same region.
+ * - \c RETRO_EXEC_MEM_MODE_WX_TOGGLE: Single mapping, write XOR execute.
+ * \c rx and \c rw point to the same region; the core must toggle
+ * protections itself (e.g. mprotect) before writing or executing.
+ * - \c RETRO_EXEC_MEM_MODE_DUAL_MAP: Separate read-execute and read-write
+ * mappings of the same physical pages. The core writes through \c rw
+ * and executes from \c rx.
+ *
+ * Returned memory is page-aligned. The frontend tracks all allocations
+ * and frees any outstanding ones when the core is unloaded.
+ *
+ * The core sets \c version and \c size before calling. The frontend fills
+ * in \c mode, \c rx, and \c rw on success.
+ *
+ * If \c size is 0, this is a probe: the frontend returns \c true and sets
+ * \c mode to indicate what kind of memory it would provide, but does not
+ * allocate. \c rx and \c rw will be NULL. Cores can use this to decide
+ * whether to take a JIT code path before committing to an allocation.
+ *
+ * @param[in,out] data struct retro_exec_mem_alloc *.
+ * @return \c true if the allocation succeeded, \c false otherwise.
+ * If the frontend does not support this call, returns \c false
+ * and the core should fall back to managing its own executable memory.
+ * @see retro_exec_mem_alloc
+ * @see RETRO_ENVIRONMENT_EXEC_MEM_FREE
+ */
+#define RETRO_ENVIRONMENT_EXEC_MEM_ALLOC 83
+
+/**
+ * Frees a region of executable memory previously allocated with
+ * \c RETRO_ENVIRONMENT_EXEC_MEM_ALLOC.
+ *
+ * This is optional; the frontend will free all outstanding allocations
+ * when the core is unloaded. Cores that never need to release memory
+ * mid-session need not call this.
+ *
+ * @param[in] data struct retro_exec_mem_free *.
+ * @return \c true if the memory was freed, \c false otherwise.
+ * @see retro_exec_mem_free
+ * @see RETRO_ENVIRONMENT_EXEC_MEM_ALLOC
+ */
+#define RETRO_ENVIRONMENT_EXEC_MEM_FREE 84
+
+/**
+ * Queries whether the frontend can accept audio samples in 32-bit
+ * native-endian IEEE-754 float format, and, if so, obtains a float
+ * sample-batch callback the core may use in place of the standard
+ * int16 \c retro_audio_sample_batch_t callback.
+ *
+ * Rationale: frontend resamplers and DSP chains operate on float, and
+ * most modern audio drivers expose a native float output path. A core
+ * whose audio is float-native (e.g. one with a float software mixer or
+ * a float decoder) currently has to squash its output down to int16 at
+ * the libretro boundary, only for the frontend to immediately widen it
+ * back to float. Negotiating float output here removes that redundant
+ * int16<->float round-trip on both sides of the boundary.
+ *
+ * On success the frontend sets \c batch in the supplied
+ * \c retro_audio_sample_float_callback. The core may call that function
+ * from within \c retro_run() (or from the audio callback registered via
+ * \c RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK), passing interleaved stereo
+ * frames of float samples normalized to the range [-1.0, 1.0]. The
+ * return value has the same meaning as \c retro_audio_sample_batch_t.
+ *
+ * Contract:
+ * - The core must commit to a single output format for the lifetime of
+ * a loaded game; it must not mix int16 and float batch calls. Perform
+ * negotiation once, during \c retro_load_game() (after any
+ * \c RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK call).
+ * - If this returns \c false the core must keep using the int16
+ * \c retro_audio_sample_batch_t / \c retro_audio_sample_t callbacks.
+ * - The \c batch function pointer is owned by the frontend and remains
+ * valid until \c retro_unload_game().
+ * - Frontends that do not recognize this call return \c false, so older
+ * frontends transparently keep the int16 path.
+ *
+ * @param[out] data struct retro_audio_sample_float_callback *.
+ * @return \c true if float audio output is supported, \c false otherwise.
+ * @see retro_audio_sample_batch_float_t
+ * @see retro_audio_sample_float_callback
+ */
+#define RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT (85 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Queries how much system memory the frontend has available.
+ *
+ * A core may use this to size large internal allocations (a memory pool,
+ * heap or asset cache) to the running machine instead of to a fixed
+ * compile-time default. The reported values are advisory snapshots: \c free
+ * in particular may include reclaimable cache and can change immediately
+ * after the call, so a core should take a fraction of it and clamp the
+ * result -- it must never assume it can allocate the whole amount.
+ *
+ * Frontends that do not implement this return \c false, in which case the
+ * core is expected to fall back to its own defaults.
+ *
+ * @param[out] data struct retro_memory_status *.
+ * @return \c true if the frontend filled in the structure, \c false otherwise.
+ * @see retro_memory_status
+ */
+#define RETRO_ENVIRONMENT_GET_MEMORY_STATUS (86 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Notifies the frontend of any quirks associated with serialization.
+ *
+ * Should be set in either \c retro_init or \c retro_load_game, but not both.
+ * @param[in, out] data uint64_t *.
+ * Pointer to the core's serialization quirks.
+ * The frontend will set the flags of the quirks it supports
+ * and clear the flags of those it doesn't.
+ * Behavior is undefined if \c NULL.
+ * @return \c true if this environment call is supported.
+ * @see retro_serialize
+ * @see retro_unserialize
+ * @see RETRO_SERIALIZATION_QUIRK
+ */
+#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 87
+
+/**
+ * Queries whether the active video driver can present a 10-bit-per-channel
+ * (30-bit) source surface end to end, i.e. whether a frame submitted as
+ * #RETRO_PIXEL_FORMAT_XRGB2101010 reaches the display without the frontend
+ * narrowing it to 8 bits per channel.
+ *
+ * Unlike SET_PIXEL_FORMAT, which accepts XRGB2101010 unconditionally and
+ * transparently down-converts when the driver cannot present 10-bit, this
+ * lets a core discover the real capability so it can avoid pointless work:
+ * a core that has both a 10-bit and an 8-bit output path should prefer the
+ * 8-bit path when this returns \c false, since emitting 10-bit only to have
+ * the frontend narrow it wastes effort and, for content that starts at 8
+ * bits, is a no-op round trip; going straight to 8 bits also rounds rather
+ * than truncates.
+ *
+ * The result may change across a driver reinit (e.g. the user switches
+ * video driver or toggles HDR), so a core that cares should query it when
+ * (re)choosing its pixel format rather than caching it indefinitely.
+ *
+ * @param[out] data bool *.
+ * Set to \c true if a 10-bit source surface is presented natively, \c false
+ * if XRGB2101010 frames are down-converted to 8-bit.
+ * @return \c true if the environment call is recognised (the value at
+ * \c data is then valid), \c false if it is unsupported (an older frontend);
+ * a core must treat "unsupported" as "no guarantee of native 10-bit".
+ */
+#define RETRO_ENVIRONMENT_GET_SCREEN_10BPC_CAPABLE (88 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Queries the luminance, in nits, that the frontend treats as SDR paper
+ * white when presenting HDR content.
+ *
+ * Only meaningful together with #RETRO_PIXEL_FORMAT_HDR10_2101010: a core
+ * encoding absolute luminance itself has to know where the user expects
+ * ordinary, non-glowing image content to sit, otherwise the whole frame is
+ * either dim or searing. The value corresponds to the frontend's HDR paper
+ * white setting; typical values are 100-400.
+ *
+ * The user can change it at any time, so a core should re-query it when it
+ * rebuilds its colour tables rather than caching it forever.
+ *
+ * @param[out] data float *.
+ * Set to the paper white luminance in nits.
+ * @return \c true if the call is recognised and the value is valid, \c false
+ * on a frontend that does not implement it; callers should then assume a
+ * sensible default (200 nits).
+ */
+#define RETRO_ENVIRONMENT_GET_HDR_PAPER_WHITE_NITS (89 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Queries the colour-gamut treatment the frontend applies to SDR content
+ * when presenting HDR.
+ *
+ * Only meaningful together with #RETRO_PIXEL_FORMAT_HDR10_2101010. A core
+ * emitting that format performs its own Rec.709 -> Rec.2020 rotation, and
+ * has to make the same choice the frontend makes for SDR content, or a
+ * scene will change saturation when the user switches the core between an
+ * SDR format and HDR10. The values match the frontend's "Colour Boost"
+ * setting:
+ *
+ * 0 Accurate -- proper Rec.709 -> Rec.2020 conversion, no boost
+ * 1 Expanded -- Rec.709 -> a slightly wider space
+ * 2 Wide -- Rec.709 -> DCI-P3
+ * 3 Super -- no rotation at all; values stay Rec.709 and the boost
+ * comes from the display interpreting them as Rec.2020
+ *
+ * A core should re-query this when it rebuilds its colour tables, since the
+ * user can change it at any time.
+ *
+ * @param[out] data unsigned *.
+ * Set to one of the values above.
+ * @return \c true if the call is recognised, \c false on a frontend that
+ * does not implement it; callers should then assume 0 (Accurate).
+ */
+#define RETRO_ENVIRONMENT_GET_HDR_EXPAND_GAMUT (90 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Queries which HDR output mode the frontend is presenting with.
+ *
+ * Only meaningful together with #RETRO_PIXEL_FORMAT_HDR10_2101010. Both
+ * output modes accept the same PQ Rec.2020 frame, but they do not treat its
+ * primaries identically: an HDR10 swapchain presents the samples as-is,
+ * while an scRGB swapchain converts them and applies a Rec.2020 -> Rec.709
+ * rotation on the way. A core that encodes its own gamut -- which it must,
+ * to honour #RETRO_ENVIRONMENT_GET_HDR_EXPAND_GAMUT -- therefore has to know
+ * which of the two it is feeding, or its colour choice is undone by that
+ * rotation on one of them.
+ *
+ * Values:
+ * 0 HDR output is off
+ * 1 HDR10 (PQ, Rec.2020 swapchain); samples are presented unchanged
+ * 2 scRGB (linear FP16, Rec.709); the frontend applies Rec.2020 ->
+ * Rec.709 to the decoded samples
+ *
+ * The user can switch this at any time, so a core should re-query it when it
+ * rebuilds its colour tables rather than caching it indefinitely.
+ *
+ * @param[out] data unsigned *.
+ * Set to one of the values above.
+ * @return \c true if the call is recognised, \c false on a frontend that
+ * does not implement it; callers should then assume 1 (HDR10), which is the
+ * mode that needs no compensation.
+ */
+#define RETRO_ENVIRONMENT_GET_HDR_OUTPUT_MODE (91 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Queries the peak luminance the frontend is presenting to, in cd/m2 (nits).
+ *
+ * Only meaningful together with #RETRO_PIXEL_FORMAT_HDR10_2101010. A core
+ * emitting HDR10 encodes absolute luminance itself, so it decides where its
+ * brightest content lands. #RETRO_ENVIRONMENT_GET_HDR_PAPER_WHITE_NITS says
+ * where ordinary content sits; this says how much room there is above it. The
+ * gap between the two is the entire headroom a core has for highlights, and
+ * without it a core has to guess - a guess that is too dark on a bright display
+ * and clips on a dim one.
+ *
+ * This is what the *display* can do, not what the content wants, so it is a
+ * ceiling to roll off toward rather than a level to target. A core should not
+ * assume anything it emits below this value is reproduced exactly; displays
+ * tone map internally, and many report a peak they can only hold over a small
+ * window.
+ *
+ * May be lower than paper white if the user has configured it so. A core
+ * should treat that as zero headroom and clamp to paper white rather than
+ * producing a negative range.
+ *
+ * The user can change this at any time, so a core should re-query it rather
+ * than caching it indefinitely.
+ *
+ * @param[out] data float *.
+ * Set to the peak luminance in nits.
+ * @return \c true if the call is recognised, \c false on a frontend that does
+ * not implement it; callers should then fall back to a sensible default.
+ * 1000 nits is a reasonable assumption, being both the HDR10 reference peak
+ * and roughly what mid-range HDR displays achieve.
+ */
+#define RETRO_ENVIRONMENT_GET_HDR_MAX_NITS (92 | RETRO_ENVIRONMENT_EXPERIMENTAL)
+
+/**
+ * Result of \c RETRO_ENVIRONMENT_GET_MEMORY_STATUS.
+ *
+ * Sizes are in bytes; a field the frontend cannot determine is left at 0.
+ */
+struct retro_memory_status
+{
+ uint64_t free; /**< Physical memory currently available to allocate. */
+ uint64_t total; /**< Total physical memory installed. */
+};
+
/**@}*/
/**
@@ -2681,6 +2994,20 @@ struct retro_vfs_dir_handle;
*/
#define RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS (1 << 0)
+/**
+ * Indicates that the file will be read once, from start to finish,
+ * and then closed.
+ *
+ * No mapping or caching is wanted: the caller already keeps the bytes
+ * it asked for, so anything the frontend holds on to beyond the call
+ * is dead weight. A frontend that buffers its reads may wish to skip
+ * doing so for such a stream, since a whole-file read gains nothing
+ * from being split across a buffer and copied twice.
+ *
+ * Only meaningful together with \c RETRO_VFS_FILE_ACCESS_READ.
+ */
+#define RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK (1 << 1)
+
/** @} */
/** @defgroup RETRO_VFS_SEEK_POSITION File Seek Positions
@@ -2745,6 +3072,10 @@ typedef const char *(RETRO_CALLCONV *retro_vfs_get_path_t)(struct retro_vfs_file
* @param path The path to open.
* @param mode A bitwise combination of \c RETRO_VFS_FILE_ACCESS flags.
* At a minimum, one of \c RETRO_VFS_FILE_ACCESS_READ or \c RETRO_VFS_FILE_ACCESS_WRITE must be specified.
+ * If \c RETRO_VFS_FILE_ACCESS_WRITE is specified and \c RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING is not specified,
+ * and no file or directory exists at \c path, this function will attempt to create an empty file at \c path.
+ * If either \c RETRO_VFS_FILE_ACCESS_WRITE is not specified or \c RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING is specified,
+ * and no file or directory exists at \c path, this function will return \c NULL without attempting to create a file at \c path.
* @param hints A bitwise combination of \c RETRO_VFS_FILE_ACCESS_HINT flags.
* @return A handle to the opened file,
* or \c NULL upon failure.
@@ -2816,8 +3147,7 @@ typedef int64_t (RETRO_CALLCONV *retro_vfs_tell_t)(struct retro_vfs_file_handle
* @param stream The file to set the position of.
* @param offset The new position, in bytes.
* @param seek_position The position to seek from.
- * @return The new position,
- * or -1 if there was an error.
+ * @return 0 on success, -1 on failure.
* @since VFS API v1
* @see File Seek Positions
* @see filestream_seek
@@ -2902,6 +3232,19 @@ typedef int (RETRO_CALLCONV *retro_vfs_rename_t)(const char *old_path, const cha
*/
typedef int (RETRO_CALLCONV *retro_vfs_stat_t)(const char *path, int32_t *size);
+/**
+ * Gets information about the given file (64-bit size).
+ *
+ * @param path The path to the file to query.
+ * @param[out] size The reported size of the file in bytes.
+ * May be \c NULL, in which case this value is ignored.
+ * @return A bitmask of \c RETRO_VFS_STAT flags,
+ * or 0 if \c path doesn't refer to a valid file.
+ * @see RETRO_VFS_STAT
+ * @since VFS API v4
+ */
+typedef int (RETRO_CALLCONV *retro_vfs_stat_64_t)(const char *path, int64_t *size);
+
/**
* Creates a directory at the given path.
*
@@ -3057,6 +3400,10 @@ struct retro_vfs_interface
/** @copydoc retro_vfs_closedir_t */
retro_vfs_closedir_t closedir;
+
+ /* VFS API v4 */
+ /** @copydoc retro_vfs_stat_64_t */
+ retro_vfs_stat_64_t stat_64;
};
/**
@@ -3098,6 +3445,33 @@ struct retro_vfs_interface_info
struct retro_vfs_interface *iface;
};
+/**
+ * Represents a single frontend-authorized filesystem location.
+ *
+ * The \c path field must be directly usable through the frontend VFS
+ * interface, for example saf://... on Android.
+ *
+ * The frontend owns all returned pointers. Cores must copy strings if they
+ * need to retain them after the environment callback returns.
+ */
+struct retro_vfs_authorized_location
+{
+ const char *path;
+ const char *label;
+ unsigned flags;
+};
+
+/**
+ * Represents the list of frontend-authorized filesystem locations.
+ *
+ * This is returned by RETRO_ENVIRONMENT_GET_VFS_AUTHORIZED_LOCATIONS.
+ */
+struct retro_vfs_authorized_locations
+{
+ const struct retro_vfs_authorized_location *locations;
+ size_t count;
+};
+
/** @} */
/** @defgroup GET_HW_RENDER_INTERFACE Hardware Rendering Interface
@@ -4197,6 +4571,33 @@ struct retro_log_callback
/** Indicates CPU support for the ASIMD instruction set. */
#define RETRO_SIMD_ASIMD (1 << 21)
+/** Indicates CPU support for the AVX512 instruction set. */
+#define RETRO_SIMD_AVX512 (1 << 22)
+
+/** Indicates CPU support for the LZCNT instruction (x86 ABM / ARM CLZ). */
+#define RETRO_SIMD_LZCNT (1 << 23)
+
+/**
+ * Indicates CPU support for the PCLMULQDQ carry-less multiply instruction.
+ *
+ * Distinct from \c RETRO_SIMD_AES: AES-NI is CPUID.1:ECX[25] and
+ * PCLMULQDQ is CPUID.1:ECX[1]. They shipped together on most parts but
+ * hypervisors mask them independently and some early Westmere SKUs had
+ * AES fused off, so one must not be used as a proxy for the other.
+ */
+#define RETRO_SIMD_PCLMUL (1 << 24)
+
+/**
+ * Indicates CPU support for the ARMv8 CRC32 instructions
+ * (\c crc32b / \c crc32h / \c crc32w / \c crc32x).
+ *
+ * These compute CRC-32/ISO-HDLC, the gzip and PNG polynomial, not
+ * CRC-32C. Optional in ARMv8.0 and mandatory from ARMv8.1, so a
+ * 64-bit ARM CPU does not imply their presence: Apple's A7 through
+ * A10 lack them, for instance.
+ */
+#define RETRO_SIMD_CRC32 (1 << 25)
+
/** @} */
/**
@@ -4458,15 +4859,18 @@ enum retro_sensor_action
/* Id values for SENSOR types. */
/**
- * Returns the device's acceleration along its local X axis minus the effect of gravity, in m/s^2.
+ * Returns the device's acceleration along its local X axis, in g (standard gravity, 9.80665 m/s^2).
+ * Includes the effect of gravity;
+ * a device at rest on a table will have values close to 0, 0, 1.
*
- * Positive values mean that the device is accelerating to the right.
+ * Positive values mean that the device is accelerating to the right,
* assuming the user is looking at it head-on.
*/
#define RETRO_SENSOR_ACCELEROMETER_X 0
/**
- * Returns the device's acceleration along its local Y axis minus the effect of gravity, in m/s^2.
+ * Returns the device's acceleration along its local Y axis, in g (standard gravity, 9.80665 m/s^2).
+ * Includes the effect of gravity.
*
* Positive values mean that the device is accelerating upwards,
* assuming the user is looking at it head-on.
@@ -4474,7 +4878,8 @@ enum retro_sensor_action
#define RETRO_SENSOR_ACCELEROMETER_Y 1
/**
- * Returns the the device's acceleration along its local Z axis minus the effect of gravity, in m/s^2.
+ * Returns the device's acceleration along its local Z axis, in g (standard gravity, 9.80665 m/s^2).
+ * Includes the effect of gravity.
*
* Positive values indicate forward acceleration towards the user,
* assuming the user is looking at the device head-on.
@@ -5174,14 +5579,14 @@ struct retro_hw_render_callback
* character is the text character of the pressed key. (UTF-32).
* key_modifiers is a set of RETROKMOD values or'ed together.
*
- * The pressed/keycode state can be indepedent of the character.
+ * The pressed/keycode state can be independent of the character.
* It is also possible that multiple characters are generated from a
* single keypress.
* Keycode events should be treated separately from character events.
* However, when possible, the frontend should try to synchronize these.
* If only a character is posted, keycode should be RETROK_UNKNOWN.
*
- * Similarily if only a keycode event is generated with no corresponding
+ * Similarly if only a keycode event is generated with no corresponding
* character, character should be 0.
*/
typedef void (RETRO_CALLCONV *retro_keyboard_event_t)(bool down, unsigned keycode,
@@ -5360,14 +5765,14 @@ typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const c
* on the host's file system.
*
* @param index The index of the disk image to get the path of.
- * @param path A buffer to store the path in.
- * @param len The size of \c path, in bytes.
+ * @param s A buffer to store the path in.
+ * @param len The size of \c s, in bytes.
* @return \c true if the disk image's location was successfully
- * queried and copied into \c path,
+ * queried and copied into \c s,
* \c false if the index is invalid
* or the core couldn't locate the disk image.
*/
-typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len);
+typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *s, size_t len);
/**
* Returns a friendly label for the given disk image.
@@ -5383,12 +5788,12 @@ typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path
* so that the frontend can provide better guidance to the player.
*
* @param index The index of the disk image to return a label for.
- * @param label A buffer to store the resulting label in.
- * @param len The length of \c label, in bytes.
+ * @param s A buffer to store the resulting label in.
+ * @param len The length of \c s, in bytes.
* @return \c true if the disk image at \c index is valid
- * and a label was copied into \c label.
+ * and a label was copied into \c s.
*/
-typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len);
+typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *s, size_t len);
/**
* An interface that the frontend can use to exchange disks
@@ -5624,7 +6029,56 @@ enum retro_pixel_format
*/
RETRO_PIXEL_FORMAT_RGB565 = 2,
- /** Defined to ensure that sizeof(retro_pixel_format) == sizeof(int). Do not use. */
+ /**
+ * XRGB2101010, native endian.
+ * 32-bit packed: 2 ignored high bits followed by 10-bit R, G, B
+ * (i.e. bits [29:20]=R, [19:10]=G, [9:0]=B; the top 2 bits are ignored).
+ * Intended for cores that decode 10-bit-per-channel content (e.g. HDR10
+ * sources) and want to pass it through without narrowing to 8 bits.
+ *
+ * A frontend is not required to render this natively: if the active video
+ * driver does not support a 10-bit source surface, the frontend transparently
+ * down-converts to XRGB8888, so a core may rely on SET_PIXEL_FORMAT accepting
+ * this value but should not assume the display path is 10-bit end to end.
+ */
+ RETRO_PIXEL_FORMAT_XRGB2101010 = 3,
+
+ /**
+ * HDR10: PQ-encoded Rec.2020, 10 bits per channel, native endian.
+ *
+ * Bit layout is identical to #RETRO_PIXEL_FORMAT_XRGB2101010 -- 2 ignored
+ * high bits then 10-bit R, G, B (bits [29:20]=R, [19:10]=G, [9:0]=B) --
+ * but the *encoding* differs, and that is the whole point of a separate
+ * value: the samples are SMPTE ST.2084 (PQ) over Rec.2020 primaries,
+ * covering 0..10000 nits absolute, exactly as HDR10 video does.
+ *
+ * XRGB2101010 is 10-bit SDR: the frontend treats 1.0 as paper white and
+ * cannot represent anything brighter, so a core has no way to make a
+ * highlight exceed the SDR white level. With this format the core
+ * chooses absolute luminance per pixel, so specular highlights, muzzle
+ * flashes, explosions and emissive surfaces can sit well above paper
+ * white while the rest of the image stays where it was.
+ *
+ * A frontend that accepts this format MUST pass the samples through to an
+ * HDR10 (PQ / Rec.2020) swapchain without re-encoding them: no inverse
+ * tonemap, no Rec.709->Rec.2020 rotation, no paper-white scaling, since
+ * the core has already applied all of it. A frontend that cannot present
+ * HDR10 natively must reject the format from SET_PIXEL_FORMAT rather than
+ * silently down-converting -- PQ samples interpreted as SDR look badly
+ * wrong, so the usual transparent narrowing is not safe here. Cores
+ * should therefore keep an SDR path and fall back when this is refused.
+ *
+ * Cores should query #RETRO_ENVIRONMENT_GET_HDR_PAPER_WHITE_NITS to learn
+ * the luminance the user considers "SDR white" and map their normal
+ * output to it; content authored above that value is what produces the
+ * HDR effect.
+ */
+ RETRO_PIXEL_FORMAT_HDR10_2101010 = 4,
+
+ /**
+ * @private Defined to ensure that sizeof(retro_pixel_format) == sizeof(int).
+ * Do not use.
+ */
RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX
};
@@ -5732,7 +6186,7 @@ struct retro_message
enum retro_message_target
{
/**
- * Indicates that the frontent should display the given message
+ * Indicates that the frontend should display the given message
* using all other targets defined by \c retro_message_target at once.
*/
RETRO_MESSAGE_TARGET_ALL = 0,
@@ -5923,7 +6377,7 @@ struct retro_message_ext
/**
* The progress of an asynchronous task.
*
- * A value betwen 0 and 100 (inclusive) indicates the task's percentage,
+ * A value between 0 and 100 (inclusive) indicates the task's percentage,
* and a value of -1 indicates a task of unknown completion.
*
* @note Since message type is a hint, a frontend may ignore progress values.
@@ -7377,6 +7831,46 @@ struct retro_device_power
/** @} */
+/** @defgroup Executable Memory Modes
+ * Describes how the frontend provisions executable memory.
+ * @{
+ */
+
+#define RETRO_EXEC_MEM_MODE_UNAVAILABLE 0 /**< No executable memory available */
+#define RETRO_EXEC_MEM_MODE_UNRESTRICTED 1 /**< No restrictions; core should self-allocate */
+#define RETRO_EXEC_MEM_MODE_RWX 2 /**< Single mapping, read-write-execute */
+#define RETRO_EXEC_MEM_MODE_WX_TOGGLE 3 /**< Single mapping, write XOR execute (core toggles) */
+#define RETRO_EXEC_MEM_MODE_DUAL_MAP 4 /**< Separate R-X and R-W mappings of same pages */
+
+/**
+ * Parameters for \c RETRO_ENVIRONMENT_EXEC_MEM_ALLOC.
+ *
+ * The core fills in \c version and \c size before calling.
+ * The frontend fills in \c mode, \c rx, and \c rw on success.
+ * @see RETRO_ENVIRONMENT_EXEC_MEM_ALLOC
+ */
+struct retro_exec_mem_alloc
+{
+ unsigned version; /**< Set by core (currently 1). */
+ size_t size; /**< Set by core: requested bytes. */
+ unsigned mode; /**< Set by frontend: one of \c RETRO_EXEC_MEM_MODE_*. */
+ void *rx; /**< Set by frontend: execute from this pointer. */
+ void *rw; /**< Set by frontend: write through this pointer.
+ Equal to \c rx when mode is RWX or WX_TOGGLE. */
+};
+
+/**
+ * Parameters for \c RETRO_ENVIRONMENT_EXEC_MEM_FREE.
+ * @see RETRO_ENVIRONMENT_EXEC_MEM_FREE
+ */
+struct retro_exec_mem_free
+{
+ void *rx; /**< The \c rx pointer returned by a previous alloc call.
+ The matching \c rw pointer is also accepted. */
+};
+
+/** @} */
+
/**
* @defgroup Callbacks
* @{
@@ -7447,6 +7941,40 @@ typedef void (RETRO_CALLCONV *retro_audio_sample_t)(int16_t left, int16_t right)
typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_t)(const int16_t *data,
size_t frames);
+/**
+ * Renders multiple audio frames in one go, in float format.
+ *
+ * This is the float counterpart of \c retro_audio_sample_batch_t. It is
+ * only valid after the frontend has answered \c true to
+ * \c RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT, and must not be
+ * mixed with the int16 callbacks within the same loaded game.
+ *
+ * @param data A pointer to interleaved stereo float sample frames,
+ * normalized to the range [-1.0, 1.0]. One frame is a left/right
+ * pair, e.g. float buf[4] = { l, r, l, r }; is 2 frames.
+ * @param frames The number of frames represented in \c data.
+ *
+ * @return The number of frames that were processed.
+ *
+ * @see RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT
+ * @see retro_audio_sample_batch_t
+ */
+typedef size_t (RETRO_CALLCONV *retro_audio_sample_batch_float_t)(
+ const float *data, size_t frames);
+
+/**
+ * Float audio sample-batch callback handed to the core in response to
+ * \c RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT.
+ *
+ * @see RETRO_ENVIRONMENT_GET_AUDIO_SAMPLE_BATCH_FLOAT
+ */
+struct retro_audio_sample_float_callback
+{
+ /* Set by the frontend. The core calls this instead of the int16
+ * batch callback once float output has been negotiated. */
+ retro_audio_sample_batch_float_t batch;
+};
+
/**
* Polls input.
*
@@ -7459,6 +7987,9 @@ typedef void (RETRO_CALLCONV *retro_input_poll_t)(void);
*
* @param port Which player 'port' to query.
* @param device Which device to query for. Will be masked with \c RETRO_DEVICE_MASK.
+ * @warning Poll with a base device ID only; passing an ID created via
+ * \c RETRO_DEVICE_SUBCLASS is reserved for future definition, and the masking
+ * noted above is a frontend convenience that must not be relied upon.
* @param index The input index to retrieve.
* The exact semantics depend on the device type given in \c device.
* @param id The ID of which value to query, like \c RETRO_DEVICE_ID_JOYPAD_B.
@@ -7700,7 +8231,7 @@ RETRO_API size_t retro_serialize_size(void);
* @see retro_serialize_size()
* @see retro_unserialize()
*/
-RETRO_API bool retro_serialize(void *data, size_t size);
+RETRO_API bool retro_serialize(void *data, size_t len);
/**
* Unserialize the given state data, and load it into the internal state.
@@ -7709,7 +8240,7 @@ RETRO_API bool retro_serialize(void *data, size_t size);
*
* @see retro_serialize()
*/
-RETRO_API bool retro_unserialize(const void *data, size_t size);
+RETRO_API bool retro_unserialize(const void *data, size_t len);
/**
* Reset all the active cheats to their default disabled state.
From 1204ef117c9ac2e57493e0a2691ad1f7ebae3675 Mon Sep 17 00:00:00 2001
From: Craig Carnell <1188869+cscd98@users.noreply.github.com>
Date: Sat, 15 Aug 2026 10:40:35 +0100
Subject: [PATCH 2/3] libretro: add VFS support
---
src/beeb/src/DirectDiscImage.cpp | 29 +++
src/libretro/Makefile.common | 1 +
src/libretro/core.cpp | 3 +
src/libretro/vfs.cpp | 367 +++++++++++++++++++++++++++++++
src/libretro/vfs.h | 48 ++++
src/shared/c/file_io.cpp | 52 ++++-
src/shared/c/path_posix.cpp | 29 +++
7 files changed, 527 insertions(+), 2 deletions(-)
create mode 100644 src/libretro/vfs.cpp
create mode 100644 src/libretro/vfs.h
diff --git a/src/beeb/src/DirectDiscImage.cpp b/src/beeb/src/DirectDiscImage.cpp
index 2104b9425..615245c5b 100644
--- a/src/beeb/src/DirectDiscImage.cpp
+++ b/src/beeb/src/DirectDiscImage.cpp
@@ -6,6 +6,10 @@
#include
#include
+#ifdef B2_LIBRETRO_CORE
+#include "../../libretro/vfs.h"
+#endif
+
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
@@ -137,7 +141,11 @@ bool DirectDiscImage::Read(uint8_t *value,
return false;
}
+#ifdef B2_LIBRETRO_CORE
+ int c = retro_vfs_fgetc(reinterpret_cast(m_fp));
+#else
int c = fgetc(m_fp);
+#endif
if (c == EOF) {
// This case is OK - the disc image is logically its full size, even
// when truncated.
@@ -165,9 +173,15 @@ bool DirectDiscImage::Write(uint8_t side,
}
bool good = false;
+#ifdef B2_LIBRETRO_CORE
+ if (retro_vfs_fputc(value, reinterpret_cast(m_fp)) != EOF) {
+ good = true;
+ }
+#else
if (fputc(value, m_fp) != EOF) {
good = true;
}
+#endif
return good;
}
@@ -248,7 +262,11 @@ bool DirectDiscImage::fopenAndSeek(bool write,
mode = "rb";
}
+#ifdef B2_LIBRETRO_CORE
+ m_fp = reinterpret_cast(retro_vfs_fopen(m_path, mode));
+#else
m_fp = fopenUTF8(m_path.c_str(), mode);
+#endif
if (!m_fp) {
return false;
}
@@ -256,10 +274,17 @@ bool DirectDiscImage::fopenAndSeek(bool write,
m_fp_write = write;
}
+#ifdef B2_LIBRETRO_CORE
+ if (retro_vfs_fseek(reinterpret_cast(m_fp), (int64_t)index, SEEK_SET) != 0) {
+ this->Close();
+ return false;
+ }
+#else
if (fseek(m_fp, (long)index, SEEK_SET) != 0) {
this->Close();
return false;
}
+#endif
return true;
}
@@ -269,7 +294,11 @@ bool DirectDiscImage::fopenAndSeek(bool write,
void DirectDiscImage::Close() const {
if (m_fp) {
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_fclose(reinterpret_cast(m_fp));
+#else
fclose(m_fp);
+#endif
m_fp = nullptr;
}
}
diff --git a/src/libretro/Makefile.common b/src/libretro/Makefile.common
index 0b57bfdf5..0ed2319af 100644
--- a/src/libretro/Makefile.common
+++ b/src/libretro/Makefile.common
@@ -7,6 +7,7 @@ INCFLAGS := \
SOURCES_CPP := \
$(CORE_DIR)/libretro/adapters.cpp \
$(CORE_DIR)/libretro/core.cpp \
+ $(CORE_DIR)/libretro/vfs.cpp \
$(CORE_DIR)/beeb/src/6502.cpp \
$(CORE_DIR)/beeb/src/6522.cpp \
$(CORE_DIR)/beeb/src/1770.cpp \
diff --git a/src/libretro/core.cpp b/src/libretro/core.cpp
index ac4ad5c72..556204aad 100644
--- a/src/libretro/core.cpp
+++ b/src/libretro/core.cpp
@@ -103,6 +103,7 @@ other QoL
#include "libretro.h"
#include "adapters.h"
#include "b2_libretro_keymap.h"
+#include "vfs.h"
LOG_DEFINE(OUTPUT,"OUTPUT",&log_printer_nowhere,false);
@@ -1201,6 +1202,8 @@ bool retro_load_game(const struct retro_game_info *info)
check_variables();
+ retro_vfs_init(environ_cb);
+
if(info != nullptr)
{
log_cb(RETRO_LOG_INFO, "Loading game: %s \n",info->path);
diff --git a/src/libretro/vfs.cpp b/src/libretro/vfs.cpp
new file mode 100644
index 000000000..f611acc71
--- /dev/null
+++ b/src/libretro/vfs.cpp
@@ -0,0 +1,367 @@
+#include "vfs.h"
+#include
+#include
+#include
+
+#if defined(_WIN32)
+#include
+#define B2_MKDIR(path) _mkdir(path)
+#else
+#include
+#include
+#define B2_MKDIR(path) mkdir(path, 0777)
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static struct retro_vfs_interface *g_vfs_interface = nullptr;
+static uint32_t g_vfs_interface_version = 0;
+
+void retro_vfs_init(retro_environment_t environ_cb) {
+
+ // already initialized
+ if (g_vfs_interface != nullptr)
+ return;
+
+ if (!environ_cb) {
+ return;
+ }
+
+ struct retro_vfs_interface_info vfs_info;
+ vfs_info.required_interface_version = LIBRETRO_VFS_MAX_SUPPORTED_VERSION;
+ vfs_info.iface = nullptr;
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_info) && vfs_info.iface) {
+ g_vfs_interface = vfs_info.iface;
+ g_vfs_interface_version = vfs_info.required_interface_version;
+ }
+}
+
+bool is_retro_vfs_available(void) {
+ return g_vfs_interface != nullptr;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+struct retro_vfs_file {
+ bool via_vfs = false;
+ struct retro_vfs_file_handle *vfs_handle = nullptr;
+ FILE *stdio_handle = nullptr;
+};
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static unsigned get_vfs_open_flags(const char *mode) {
+ unsigned flags = 0;
+
+ bool has_plus = strchr(mode, '+') != nullptr;
+ bool has_w = strchr(mode, 'w') != nullptr;
+ bool has_a = strchr(mode, 'a') != nullptr;
+
+ if (has_w || has_a) {
+ flags |= RETRO_VFS_FILE_ACCESS_WRITE;
+ if (has_plus) {
+ flags |= RETRO_VFS_FILE_ACCESS_READ;
+ }
+ if (has_a) {
+ flags |= RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
+ }
+ } else if (has_plus) {
+ // "r+b" - read/write, file must already exist.
+ flags |= RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING;
+ } else {
+ flags |= RETRO_VFS_FILE_ACCESS_READ;
+ }
+
+ return flags;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+retro_vfs_file *retro_vfs_fopen(const std::string &path, const char *mode) {
+ retro_vfs_file *f = new retro_vfs_file();
+
+ if (g_vfs_interface && g_vfs_interface->open) {
+ unsigned flags = get_vfs_open_flags(mode);
+
+ f->vfs_handle = g_vfs_interface->open(path.c_str(), flags, RETRO_VFS_FILE_ACCESS_HINT_NONE);
+ if (!f->vfs_handle) {
+ delete f;
+ return nullptr;
+ }
+
+ f->via_vfs = true;
+ return f;
+ }
+
+ // fallback no VFS
+ f->stdio_handle = fopen(path.c_str(), mode);
+ if (!f->stdio_handle) {
+ delete f;
+ return nullptr;
+ }
+
+ f->via_vfs = false;
+ return f;
+}
+
+void retro_vfs_fclose(retro_vfs_file *f) {
+ if (!f) {
+ return;
+ }
+
+ if (f->via_vfs) {
+ if (g_vfs_interface && g_vfs_interface->close && f->vfs_handle) {
+ g_vfs_interface->close(f->vfs_handle);
+ }
+ } else if (f->stdio_handle) {
+ fclose(f->stdio_handle);
+ }
+
+ delete f;
+}
+
+size_t retro_vfs_fread(void *ptr, size_t size, size_t nmemb, retro_vfs_file *f) {
+ if (!f || size == 0 || nmemb == 0) {
+ return 0;
+ }
+
+ if (f->via_vfs) {
+ if (!g_vfs_interface || !g_vfs_interface->read) {
+ return 0;
+ }
+
+ int64_t n = g_vfs_interface->read(f->vfs_handle, ptr, (uint64_t)size * nmemb);
+ if (n < 0) {
+ return 0;
+ }
+
+ return (size_t)n / size;
+ }
+
+ return fread(ptr, size, nmemb, f->stdio_handle);
+}
+
+size_t retro_vfs_fwrite(const void *ptr, size_t size, size_t nmemb, retro_vfs_file *f) {
+ if (!f || size == 0 || nmemb == 0) {
+ return 0;
+ }
+
+ if (f->via_vfs) {
+ if (!g_vfs_interface || !g_vfs_interface->write) {
+ return 0;
+ }
+
+ int64_t n = g_vfs_interface->write(f->vfs_handle, ptr, (uint64_t)size * nmemb);
+ if (n < 0) {
+ return 0;
+ }
+
+ return (size_t)n / size;
+ }
+
+ return fwrite(ptr, size, nmemb, f->stdio_handle);
+}
+
+int retro_vfs_fseek(retro_vfs_file *f, int64_t offset, int whence) {
+ if (!f) {
+ return -1;
+ }
+
+ if (f->via_vfs) {
+ if (!g_vfs_interface || !g_vfs_interface->seek) {
+ return -1;
+ }
+
+ int seek_position;
+ switch (whence) {
+ default:
+ case SEEK_SET:
+ seek_position = RETRO_VFS_SEEK_POSITION_START;
+ break;
+
+ case SEEK_CUR:
+ seek_position = RETRO_VFS_SEEK_POSITION_CURRENT;
+ break;
+
+ case SEEK_END:
+ seek_position = RETRO_VFS_SEEK_POSITION_END;
+ break;
+ }
+
+ return g_vfs_interface->seek(f->vfs_handle, offset, seek_position) < 0 ? -1 : 0;
+ }
+
+ return fseek(f->stdio_handle, (long)offset, whence);
+}
+
+int64_t retro_vfs_ftell(retro_vfs_file *f) {
+ if (!f) {
+ return -1;
+ }
+
+ if (f->via_vfs) {
+ if (!g_vfs_interface || !g_vfs_interface->tell) {
+ return -1;
+ }
+
+ return g_vfs_interface->tell(f->vfs_handle);
+ }
+
+ return ftell(f->stdio_handle);
+}
+
+int retro_vfs_fgetc(retro_vfs_file *f) {
+ if (!f) {
+ return EOF;
+ }
+
+ if (f->via_vfs) {
+ unsigned char c;
+ if (retro_vfs_fread(&c, 1, 1, f) != 1) {
+ return EOF;
+ }
+ return c;
+ }
+
+ return fgetc(f->stdio_handle);
+}
+
+int retro_vfs_fputc(int c, retro_vfs_file *f) {
+ if (!f) {
+ return EOF;
+ }
+
+ if (f->via_vfs) {
+ unsigned char b = (unsigned char)c;
+ if (retro_vfs_fwrite(&b, 1, 1, f) != 1) {
+ return EOF;
+ }
+ return b;
+ }
+
+ return fputc(c, f->stdio_handle);
+}
+
+bool retro_vfs_ferror(retro_vfs_file *f) {
+ if (!f) {
+ return true;
+ }
+
+ if (f->via_vfs) {
+ return f->vfs_handle == nullptr;
+ }
+
+ return ferror(f->stdio_handle) != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+bool retro_vfs_stat(const std::string &path, uint64_t *file_size, bool *can_write) {
+ if (g_vfs_interface && g_vfs_interface->stat && g_vfs_interface_version >= 3) {
+ int32_t size = 0;
+ int result = g_vfs_interface->stat(path.c_str(), &size);
+
+ if (!(result & RETRO_VFS_STAT_IS_VALID)) {
+ return false;
+ }
+
+ if (result & RETRO_VFS_STAT_IS_DIRECTORY) {
+ return false;
+ }
+
+ if (file_size) {
+ *file_size = size < 0 ? 0 : (uint64_t)size;
+ }
+
+ if (can_write) {
+ *can_write = true;
+ }
+
+ return true;
+ }
+
+ // fallback - no VFS
+ FILE *fp = fopen(path.c_str(), "rb");
+ if (!fp) {
+ return false;
+ }
+
+ if (fseek(fp, 0, SEEK_END) != 0) {
+ fclose(fp);
+ return false;
+ }
+
+ long size = ftell(fp);
+ fclose(fp);
+
+ if (size < 0) {
+ return false;
+ }
+
+ if (file_size) {
+ *file_size = (uint64_t)size;
+ }
+
+ if (can_write) {
+ FILE *wfp = fopen(path.c_str(), "r+b");
+ *can_write = wfp != nullptr;
+ if (wfp) {
+ fclose(wfp);
+ }
+ }
+
+ return true;
+}
+
+bool retro_vfs_mkdir(const std::string &path) {
+ if (g_vfs_interface && g_vfs_interface->mkdir && g_vfs_interface_version >= 3) {
+ int result = g_vfs_interface->mkdir(path.c_str());
+ // 0 = success, -2 = already exists - both fine.
+ return result == 0 || result == -2;
+ }
+
+ if (B2_MKDIR(path.c_str()) == 0) {
+ return true;
+ }
+
+ return errno == EEXIST;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+bool retro_vfs_glob(const std::string &folder,
+ std::function fun) {
+ if (g_vfs_interface && g_vfs_interface->opendir && g_vfs_interface_version >= 3) {
+ struct retro_vfs_dir_handle *dir = g_vfs_interface->opendir(folder.c_str(), true);
+ if (!dir) {
+ return false;
+ }
+
+ while (g_vfs_interface->readdir(dir)) {
+ const char *name = g_vfs_interface->dirent_get_name(dir);
+ if (!name) {
+ continue;
+ }
+
+ std::string path = folder;
+ if (!path.empty() && path.back() != '/' && path.back() != '\\') {
+ path += "/";
+ }
+ path += name;
+
+ fun(path, g_vfs_interface->dirent_is_dir(dir));
+ }
+
+ g_vfs_interface->closedir(dir);
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/libretro/vfs.h b/src/libretro/vfs.h
new file mode 100644
index 000000000..c85cd602c
--- /dev/null
+++ b/src/libretro/vfs.h
@@ -0,0 +1,48 @@
+#ifndef VFS_H
+#define VFS_H
+
+#include
+#include
+#include
+#include "libretro.h"
+
+#define LIBRETRO_VFS_MAX_SUPPORTED_VERSION 3
+
+void retro_vfs_init(retro_environment_t environ_cb);
+bool is_retro_vfs_available(void);
+
+//////////////////////////////////////////////////////////////////////////
+// File handle - wraps either a retro_vfs_file_handle* or a stdio FILE*.
+//////////////////////////////////////////////////////////////////////////
+
+struct retro_vfs_file;
+
+retro_vfs_file *retro_vfs_fopen(const std::string &path, const char *mode);
+void retro_vfs_fclose(retro_vfs_file *f);
+
+size_t retro_vfs_fread(void *ptr, size_t size, size_t nmemb, retro_vfs_file *f);
+size_t retro_vfs_fwrite(const void *ptr, size_t size, size_t nmemb, retro_vfs_file *f);
+
+int retro_vfs_fseek(retro_vfs_file *f, int64_t offset, int whence);
+int64_t retro_vfs_ftell(retro_vfs_file *f);
+
+int retro_vfs_fgetc(retro_vfs_file *f);
+int retro_vfs_fputc(int c, retro_vfs_file *f);
+
+bool retro_vfs_ferror(retro_vfs_file *f);
+
+//////////////////////////////////////////////////////////////////////////
+// Misc - stat/mkdir equivalents.
+//////////////////////////////////////////////////////////////////////////
+
+bool retro_vfs_stat(const std::string &path, uint64_t *file_size, bool *can_write);
+bool retro_vfs_mkdir(const std::string &path);
+
+//////////////////////////////////////////////////////////////////////////
+// Directory listing
+//////////////////////////////////////////////////////////////////////////
+
+bool retro_vfs_glob(const std::string &folder,
+ std::function fun);
+
+#endif
diff --git a/src/shared/c/file_io.cpp b/src/shared/c/file_io.cpp
index 77545a714..9efecb50f 100644
--- a/src/shared/c/file_io.cpp
+++ b/src/shared/c/file_io.cpp
@@ -9,6 +9,10 @@
#include
#include
+#ifdef B2_LIBRETRO_CORE
+#include "../../libretro/vfs.h"
+#endif
+
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
@@ -53,12 +57,15 @@ static bool LoadFile2(ContType *data,
uint32_t flags,
const char *mode) {
static_assert(sizeof(typename ContType::value_type) == 1, "LoadFile2 can only load into a vector of bytes");
- FILE *f = NULL;
bool good = false;
long len;
size_t num_bytes, num_read;
- f = fopenUTF8(path.c_str(), mode);
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_file *f = retro_vfs_fopen(path, mode);
+#else
+ FILE *f = fopenUTF8(path.c_str(), mode);
+#endif
if (!f) {
if (errno == ENOENT && (flags & LoadFlag_MightNotExist)) {
// ignore this error.
@@ -69,12 +76,21 @@ static bool LoadFile2(ContType *data,
goto done;
}
+#ifdef B2_LIBRETRO_CORE
+ if (retro_vfs_fseek(f, 0, SEEK_END) != 0) {
+ AddError(logs, path, "load", "fseek (1) failed", errno);
+ goto done;
+ }
+
+ len = (long)retro_vfs_ftell(f);
+#else
if (fseek(f, 0, SEEK_END) == -1) {
AddError(logs, path, "load", "fseek (1) failed", errno);
goto done;
}
len = ftell(f);
+#endif
if (len < 0) {
AddError(logs, path, "load", "ftell failed", errno);
goto done;
@@ -87,19 +103,34 @@ static bool LoadFile2(ContType *data,
}
#endif
+#ifdef B2_LIBRETRO_CORE
+ if (retro_vfs_fseek(f, 0, SEEK_SET) != 0) {
+ AddError(logs, path, "load", "fseek (2) failed", errno);
+ goto done;
+ }
+#else
if (fseek(f, 0, SEEK_SET) == -1) {
AddError(logs, path, "load", "fseek (2) failed", errno);
goto done;
}
+#endif
num_bytes = (size_t)len;
data->resize(num_bytes);
+#ifdef B2_LIBRETRO_CORE
+ num_read = retro_vfs_fread(data->data(), 1, num_bytes, f);
+ if (retro_vfs_ferror(f)) {
+ AddError(logs, path, "load", "read failed", errno);
+ goto done;
+ }
+#else
num_read = fread(data->data(), 1, num_bytes, f);
if (ferror(f)) {
AddError(logs, path, "load", "read failed", errno);
goto done;
}
+#endif
// Number of bytes read may be smaller if mode is rt.
data->resize(num_read);
@@ -111,7 +142,11 @@ done:;
}
if (f) {
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_fclose(f);
+#else
fclose(f);
+#endif
f = NULL;
}
@@ -122,18 +157,31 @@ done:;
//////////////////////////////////////////////////////////////////////////
static bool SaveFile2(const void *data, size_t data_size, const std::string &path, const LogSet *logs, const char *fopen_mode) {
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_file *f = retro_vfs_fopen(path, fopen_mode);
+#else
FILE *f = fopenUTF8(path.c_str(), fopen_mode);
+#endif
if (!f) {
AddError(logs, path, "save", "fopen failed", errno);
return false;
}
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_fwrite(data, 1, data_size, f);
+ bool bad = retro_vfs_ferror(f);
+#else
fwrite(data, 1, data_size, f);
bool bad = !!ferror(f);
+#endif
int e = errno;
+#ifdef B2_LIBRETRO_CORE
+ retro_vfs_fclose(f);
+#else
fclose(f);
+#endif
f = nullptr;
if (bad) {
diff --git a/src/shared/c/path_posix.cpp b/src/shared/c/path_posix.cpp
index 513f64b95..9d4a295d1 100644
--- a/src/shared/c/path_posix.cpp
+++ b/src/shared/c/path_posix.cpp
@@ -8,6 +8,10 @@
#include
#include
+#ifdef B2_LIBRETRO_CORE
+#include "../../libretro/vfs.h"
+#endif
+
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
@@ -15,6 +19,13 @@ bool PathGlob(const std::string &folder,
std::function
fun) {
+#ifdef B2_LIBRETRO_CORE
+ if (retro_vfs_glob(folder, fun)) {
+ return true;
+ }
+ // no VFS from the frontend - fall through
+#endif
+
DIR *d = opendir(folder.c_str());
if (!d) {
return -1;
@@ -39,6 +50,12 @@ bool PathGlob(const std::string &folder,
//////////////////////////////////////////////////////////////////////////
bool PathIsFileOnDisk(const std::string &path, uint64_t *file_size, bool *can_write) {
+#ifdef B2_LIBRETRO_CORE
+ if (is_retro_vfs_available()) {
+ return retro_vfs_stat(path, file_size, can_write);
+ }
+#endif
+
struct stat st;
if (stat(path.c_str(), &st) == -1) {
return false;
@@ -80,6 +97,18 @@ bool PathIsFolderOnDisk(const std::string &path) {
//////////////////////////////////////////////////////////////////////////
bool PathCreateFolder(const std::string &path) {
+#ifdef B2_LIBRETRO_CORE
+ if (is_retro_vfs_available()) {
+ bool ok = false;
+ for (size_t i = 0; i < path.size(); ++i) {
+ if (path[i] == '/') {
+ ok = retro_vfs_mkdir(path.substr(0, i + 1));
+ }
+ }
+ return ok;
+ }
+#endif
+
int last_rc = -1;
int last_errno = 0;
From 7441824a9b38816c8083b02a5a3d5d5333f479e2 Mon Sep 17 00:00:00 2001
From: Craig Carnell <1188869+cscd98@users.noreply.github.com>
Date: Sat, 15 Aug 2026 11:23:38 +0100
Subject: [PATCH 3/3] libretro: enable android x86_64 and armv7a
---
.gitlab-ci.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 725253292..08b9cbdfd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -156,10 +156,10 @@ libretro-build-osx-arm64:
################################### CELLULAR #################################
# Android ARMv7a
-#android-armeabi-v7a:
-# extends:
-# - .libretro-android-jni-armeabi-v7a
-# - .core-defs
+android-armeabi-v7a:
+ extends:
+ - .libretro-android-jni-armeabi-v7a
+ - .core-defs
# Android ARMv8a
android-arm64-v8a:
@@ -168,10 +168,10 @@ android-arm64-v8a:
- .core-defs
# Android 64-bit x86
-#android-x86_64:
-# extends:
-# - .libretro-android-jni-x86_64
-# - .core-defs
+android-x86_64:
+ extends:
+ - .libretro-android-jni-x86_64
+ - .core-defs
# Android 32-bit x86
#android-x86: