hello_rs_libretro/lib.rs
1// hello-rs-libretro - A minimal hello world libretro core in Rust
2// Copyright (C) 2025 David Brinovec
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! A minimal [libretro](https://www.libretro.com/) core written in Rust.
18//!
19//! This crate serves as a hello world example and developer reference for
20//! writing libretro cores in Rust without wrapper crates. See the
21//! [README](https://github.com/Davidian1024/hello-rs-libretro) for full
22//! documentation including hard-won lessons and deployment instructions.
23
24use std::sync::Mutex;
25
26use crate::core::HelloCore;
27
28pub mod core;
29pub mod libretro;
30pub mod types;
31
32/// The single global instance of the core.
33///
34/// Libretro cores are single-instance by design — RetroArch never loads more
35/// than one instance of a core within the same process. This makes it safe to
36/// use a global static for core state, which is necessary since the libretro
37/// API uses fixed C ABI function signatures that leave no room for passing
38/// state through arguments.
39///
40/// The [`Mutex`] wrapper ensures safe access from Rust's perspective even
41/// though RetroArch calls core functions from a single thread. The
42/// [`Option`] allows the core to be cleanly initialized in [`retro_init`]
43/// and dropped in [`retro_deinit`].
44static CORE: Mutex<Option<HelloCore>> = Mutex::new(None);
45
46/// Callback provided by RetroArch for communicating core capabilities and
47/// requesting frontend services.
48///
49/// This is the primary general-purpose communication channel from the core
50/// back to RetroArch. It accepts a command number and a pointer to command-
51/// specific data, making it a generic interface for functionality too
52/// specialized to deserve its own dedicated function in the libretro API.
53///
54/// Set by RetroArch via [`retro_set_environment`], which is the first
55/// function called when a core is loaded — before [`retro_init`]. Stored
56/// as a static because it is needed in multiple places throughout the
57/// core's lifecycle.
58///
59/// # Arguments
60/// * `u32` — command number identifying what is being requested or set.
61/// Constants for these commands are defined in [`crate::types`] with the
62/// `RETRO_ENVIRONMENT_` prefix (e.g. [`RETRO_ENVIRONMENT_SET_PIXEL_FORMAT`],
63/// [`RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME`]).
64/// * `*mut c_void` — pointer to command-specific data. The actual type
65/// pointed to varies by command — it may be a bool, a u32, a struct, or
66/// null depending on the command.
67///
68/// # Returns
69/// `true` if RetroArch understood and accepted the command, `false` if the
70/// command is unsupported or was rejected.
71pub static mut ENVIRONMENT_CALLBACK: Option<
72 unsafe extern "C" fn(u32, *mut std::ffi::c_void) -> bool,
73> = None;
74
75/// Callback provided by RetroArch for submitting a completed video frame.
76///
77/// This is the only way a core can display anything — cores cannot open
78/// windows or write to the display directly. RetroArch owns the display;
79/// the core hands it a pixel buffer each frame and RetroArch handles
80/// scaling, shaders, recording, and everything else.
81///
82/// Set by RetroArch via [`retro_set_video_refresh`] before [`retro_init`]
83/// is called. Called once per frame inside [`retro_run`].
84///
85/// # Arguments
86/// * `*const c_void` — pointer to the pixel buffer. The format must match
87/// what was declared via [`RETRO_ENVIRONMENT_SET_PIXEL_FORMAT`]. For
88/// XRGB8888 each pixel is 4 bytes in 0x00RRGGBB order.
89/// * First `u32` — width of the frame in pixels.
90/// * Second `u32` — height of the frame in pixels.
91/// * `usize` — pitch: the number of **bytes** per row, not pixels. For a
92/// tightly packed XRGB8888 buffer this is `width * 4`. RetroArch requires
93/// this explicitly to support buffers where rows contain padding bytes,
94/// which can occur when buffers are aligned to cache line boundaries.
95pub static mut VIDEO_REFRESH_CALLBACK: Option<
96 unsafe extern "C" fn(*const std::ffi::c_void, u32, u32, usize),
97> = None;
98
99/// Callback provided by RetroArch for submitting a single stereo audio sample.
100///
101/// One of two ways a core can submit audio to RetroArch, the other being
102/// the more efficient [`AUDIO_SAMPLE_BATCH_CALLBACK`]. This variant submits
103/// one stereo sample at a time and is generally only useful for cores that
104/// generate audio one sample at a time naturally. For most cores including
105/// this one, [`AUDIO_SAMPLE_BATCH_CALLBACK`] is preferred.
106///
107/// Set by RetroArch via [`retro_set_audio_sample`] before [`retro_init`]
108/// is called. Not used by this core but must be stored per the libretro
109/// spec.
110///
111/// # Arguments
112/// * First `i16` — left channel sample, signed 16-bit PCM.
113/// * Second `i16` — right channel sample, signed 16-bit PCM.
114pub static mut AUDIO_SAMPLE_CALLBACK: Option<unsafe extern "C" fn(i16, i16)> = None;
115
116/// Callback provided by RetroArch for submitting a batch of stereo audio samples.
117///
118/// The preferred way to submit audio to RetroArch, more efficient than
119/// [`AUDIO_SAMPLE_CALLBACK`] since it amortizes the overhead of crossing
120/// the FFI boundary across many samples at once. This core uses this
121/// callback exclusively.
122///
123/// Set by RetroArch via [`retro_set_audio_sample_batch`] before [`retro_init`]
124/// is called. Called once per frame inside [`retro_run`] after the video
125/// frame has been submitted.
126///
127/// # Arguments
128/// * `*const i16` — pointer to an interleaved stereo sample buffer in the
129/// form [L, R, L, R, ...] where each sample is signed 16-bit PCM.
130/// * `usize` — number of stereo frames in the buffer. Note this is frames,
131/// not samples — a buffer containing 1600 i16 values represents 800
132/// stereo frames.
133///
134/// # Returns
135/// The number of stereo frames actually consumed by RetroArch. In practice
136/// this is always equal to the number submitted.
137pub static mut AUDIO_SAMPLE_BATCH_CALLBACK: Option<
138 unsafe extern "C" fn(*const i16, usize) -> usize,
139> = None;
140
141/// Callback provided by RetroArch for polling input state.
142///
143/// Must be called exactly once per frame at the start of [`retro_run`]
144/// before any input state is read via [`INPUT_STATE_CALLBACK`]. Calling it
145/// tells RetroArch to snapshot the current state of all input devices.
146/// Reading input without calling this first results in undefined behavior
147/// per the libretro spec.
148///
149/// Set by RetroArch via [`retro_set_input_poll`] before [`retro_init`]
150/// is called. Takes no arguments and returns nothing — it is purely a
151/// synchronization signal.
152pub static mut INPUT_POLL_CALLBACK: Option<unsafe extern "C" fn()> = None;
153
154/// Callback provided by RetroArch for querying the state of a specific input.
155///
156/// Must only be called after [`INPUT_POLL_CALLBACK`] has been called to
157/// snapshot controller state for the current frame. Set by RetroArch via
158/// [`retro_set_input_state`] before [`retro_init`] is called.
159///
160/// # Arguments
161/// * First `u32` — port number identifying the controller slot.
162/// 0 = player 1, 1 = player 2, etc.
163/// * Second `u32` — device type identifying what kind of controller is
164/// plugged in. 1 = RetroPad (the standard libretro gamepad, modeled
165/// after a Super Nintendo controller with added L2/R2/L3/R3 buttons).
166/// * Third `u32` — index. For analog sticks: 0 = left stick, 1 = right
167/// stick. For digital buttons: always 0.
168/// * Fourth `u32` — button or axis id. For a RetroPad: 0 = B, 1 = Y,
169/// 2 = Select, 3 = Start, 4 = D-pad Up, 5 = D-pad Down, 6 = D-pad Left,
170/// 7 = D-pad Right, 8 = A, 9 = X, 10 = L, 11 = R.
171///
172/// # Returns
173/// For digital buttons: non-zero if pressed, zero if not pressed.
174/// For analog axes: a signed 16-bit value in the range -32768 to 32767
175/// representing the axis position.
176pub static mut INPUT_STATE_CALLBACK: Option<unsafe extern "C" fn(u32, u32, u32, u32) -> i16> = None;