Safe and idiomatic Rust wrapper around [`libctru`](https://github.com/devkitPro/libctru).
## Getting Started
Thoroughly read the [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki/Getting-Started) to meet the requirements
and to understand what it takes to develop homebrew software on the Nintendo 3DS family of consoles.
After that, you can simply add the crate as a dependency to your project and build your final binary by using [`cargo-3ds`](https://github.com/rust3ds/cargo-3ds)
or by manually compiling for the `armv6k-nintendo-3ds` target.
## Examples
Many examples to demonstrate the `ctru-rs` functionality are available in the [`examples`](./examples/) folder. Simply run them via
```bash
cargo 3ds run --example <example-name>
```
## License
This project is distributed under the Zlib license.
//! This is similar to the `hello-world` example, with the main difference of using 2 virtual `Console`s that can be alternated to print on both screens.
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// Guest Mii's won't be available regardless of their whitelist/blacklist state if the [`MiiSelector`] is run without setting [`Options::ENABLE_GUESTS`].
/// Look into [`MiiSelector::set_options()`] to see how to work with options.
///
/// # Example
///
/// ```no_run
/// # fn main() {
/// #
/// use ctru::applets::mii_selector::{Index, MiiSelector};
/// let mut mii_selector = MiiSelector::new();
///
/// // Blacklist the guest Mii at index 1 so that it cannot be selected.
/// Depending on the configuration, the Mii Selector window will appear either on the bottom screen (default behaviour) or the top screen (see [`Options::USE_TOP_SCREEN`]).
///
/// TODO: UNSAFE OPERATION, LAUNCHING APPLETS REQUIRES GRAPHICS, WITHOUT AN ACTIVE GFX THIS WILL CAUSE A SEGMENTATION FAULT.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::applets::mii_selector::{MiiSelector, Options};
///
/// let mut mii_selector = MiiSelector::new();
/// mii_selector.set_title("Select a Mii!");
///
/// let opts = Options::ENABLE_CANCEL & Options::ENABLE_GUESTS;
//! Applets are small integrated programs that the OS makes available to the developer to streamline commonly needed functionality.
//! Thanks to these integrations the developer can avoid wasting time re-implementing common features and instead use a more reliable base for their application.
//!
//! Unlike [services](crate::services), applets aren't accessed via a system subprocess (which would require obtaining a special handle at runtime).
//! Instead, the application builds a configuration storing the various parameters which is then used to "launch" the applet.
//!
//! Applets block execution of the thread that launches them as long as the user doesn't close the applet.
//! This applet opens a virtual keyboard on the console's bottom screen which lets the user write UTF-16 valid text.
// TODO: Implement remaining functionality (password mode, filter callbacks, etc.). Also improve "max text length" API. Improve `number of buttons` API when creating a new SoftwareKeyboard.
// TODO: Split the Parental PIN lock operations into a different type.
//! The [`Console`] works as a virtual shell that renders on screen all output of `stdout`. As such, it is useful as a basic interface to show info to the user,
//! such as in simple "Hello World" applications or more complex software that does not need much user interaction.
//!
//! Have a look at [`Soc::redirect_to_3dslink()`](crate::services::soc::Soc::redirect_to_3dslink) for a better alternative when debugging applications.
usestd::cell::RefMut;
usestd::default::Default;
@ -7,19 +14,63 @@ use crate::services::gfx::Screen;
@@ -7,19 +14,63 @@ use crate::services::gfx::Screen;
/// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen.
/// This means that any text written to `stdout` and `stderr` (e.g. using `println!`, `eprintln!` or `dbg!`) will become visible in the area taken by the console.
///
/// # Notes
///
/// The [`Console`] will take full possession of the screen handed to it as long as it stays alive. It also supports some ANSI codes, such as text color and cursor positioning.
/// The [`Console`]'s window size will be:
/// - 40x30 on the [`BottomScreen`](crate::services::gfx::BottomScreen).
/// - 50x30 on the normal [`TopScreen`](crate::services::gfx::TopScreen).
/// - 100x30 on the [`TopScreen`](crate::services::gfx::TopScreen) when wide mode is enabled.
///
/// # Alternatives
///
/// If you'd like to see live standard output while running the application but cannot or do not want to show the text on the 3DS itself,
/// you can try using [`Soc::redirect_to_3dslink`](crate::services::soc::Soc::redirect_to_3dslink) while activating the `--server` flag for `3dslink` (also supported by `cargo-3ds`).
/// More info in the [`cargo-3ds` docs](https://github.com/rust3ds/cargo-3ds#running-executables).
#[doc(alias = "PrintConsole")]
pubstructConsole<'screen>{
context: Box<PrintConsole>,
_screen: RefMut<'screen,dynScreen>,
}
impl<'screen>Console<'screen>{
/// Initialize a console on the chosen screen, overwriting whatever was on the screen
/// previously (including other consoles). The new console is automatically selected for
/// printing.
/// Initialize a console on the chosen screen.
///
/// # Notes
///
/// [Console] automatically takes care of flushing and swapping buffers for its screen when printing.
/// This operation overwrites whatever was on the screen before the initialization (including other [`Console`]s)
/// and changes the [`FramebufferFormat`](crate::services::gspgpu::FramebufferFormat) of the selected screen to better suit the [`Console`].
///
/// The new console is automatically selected for printing.
///
/// [`Console`] automatically takes care of flushing and swapping buffers for its screen when printing.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::gfx::Gfx;
/// use ctru::console::Console;
///
/// // Initialize graphics.
/// let gfx = Gfx::new()?;
///
/// // Create a `Console` that takes control of the upper LCD screen.
/// let top_console = Console::new(gfx.top_screen.borrow_mut());
//! Among these features, [`ctru-rs`](crate) also automatically includes functionality to properly integrate the Rust `std` with the console's operating system,
//! which the developer would otherwise need to implement manually.
//!
//! # Usage
//!
//! Thoroughly read the official [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki) which guides you through the setup needed to install the required toolchain and helpful tools.
//! After following the guide and understanding the many quirks of the Nintendo 3DS homebrew development environment, you can create a new project by including this crate as a dependency
//! in your `Cargo.toml` manifest and build your binaries either manually (for the `armv6k-nintendo-3ds` target) or via [`cargo-3ds`](https://github.com/rust3ds/cargo-3ds).
/// Some values are not ordered *like* the Mii Editor UI. The mapped values can be seen [here](https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values).
///
/// This struct is returned by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector)
/// This struct can be retrieved by [`MiiSelector::launch()`](crate::applets::mii_selector::MiiSelector::launch).
#[derive(Clone, Debug)]
pubstructMiiData{
puboptions: MiiDataOptions,
pubstructMii{
/// Mii options.
puboptions: Options,
/// Position taken by the Mii on the Mii Selector screen.
pubselector_position: SelectorPosition,
/// Console the Mii was created on.
pubconsole_identity: ConsoleIdentity,
/// Unique system ID, not dependant on the MAC address
pubsystem_id: [u8;8],
/// Console's MAC address.
pubmac_address: [u8;6],
/// General information about the Mii.
pubdetails: Details,
/// Mii name.
pubname: String,
/// Mii height.
pubheight: u8,
/// Mii width.
pubwidth: u8,
/// Face details.
pubface_details: FaceDetails,
/// Hair details.
pubhair_details: HairDetails,
/// Eyes details.
pubeye_details: EyeDetails,
/// Eyebrow details.
pubeyebrow_details: EyebrowDetails,
/// Nose details.
pubnose_details: NoseDetails,
/// Mouth details.
pubmouth_details: MouthDetails,
/// Mustache details.
pubmustache_details: MustacheDetails,
/// Beard details.
pubbeard_details: BeardDetails,
pubglass_details: GlassDetails,
/// Glasses details.
pubglass_details: GlassesDetails,
/// Mole details.
pubmole_details: MoleDetails,
/// Name of the Mii's original author.
pubauthor_name: String,
}
implFrom<ctru_sys::MiiData>forMiiData{
implFrom<ctru_sys::MiiData>forMii{
fnfrom(mii_data: ctru_sys::MiiData)-> Self{
letraw_mii_data=mii_data._bindgen_opaque_blob;
// Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii
@ -272,7 +360,7 @@ impl From<ctru_sys::MiiData> for MiiData {
@@ -272,7 +360,7 @@ impl From<ctru_sys::MiiData> for MiiData {
/// Returns `true` if the application is running in the foreground as normal.
///
/// # Notes
///
/// This function is called as such since it automatically handles all checks for Home Menu switching, Sleep mode and other events that could take away control from the application.
/// For this reason, its main use is as the condition of a while loop that controls the main logic for your program.
///
/// # Example
///
/// ```no_run
/// use std::error::Error;
/// use ctru::services::apt::Apt;
///
/// // In a simple `main` function, the structure should be the following.
/// fn main() -> Result<(), Box<dyn Error>> {
///
/// let apt = Apt::new()?;
///
/// while apt.main_loop() {
/// // Main program logic should be written here.
/// }
///
/// // Optional clean-ups after running the application should be written after the main loop.
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "aptMainLoop")]
pubfnmain_loop(&self)-> bool{
unsafe{ctru_sys::aptMainLoop()}
}
/// Set (in percentage) the amount of time to lend to the application thread spawned on the syscore (core #1).
///
/// # Notes
///
/// It is necessary to set a time limit before spawning threads on the syscore.
/// The percentage value must be withing 5% and 89%, though it is suggested to use lower values (around 30-45%) to avoid slowing down the OS processes.
@ -263,17 +328,37 @@ impl Camera for BothOutwardCam {
@@ -263,17 +328,37 @@ impl Camera for BothOutwardCam {
}
}
/// Represents a camera and its functionality
/// Generic functionality common to all cameras.
// TODO: Change "set true/set parameters" scheme (classic of C code) into a single "set parameter" scheme using enums. This is valid for stuff such as [`TrimmingParams`]
pubtraitCamera{
/// Returns the raw value of the selected camera
/// Returns the raw value of the selected camera.
fncamera_as_raw(&self)-> ctru_sys::u32_;
/// Returns the raw port of the selected camera
/// Returns the raw port of the selected camera.
fnport_as_raw(&self)-> ctru_sys::u32_{
ctru_sys::PORT_CAM1
}
/// Returns true if the camera is busy (receiving data)
/// Returns `true` if the camera is busy (receiving data).
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::cam::{Cam, Camera};
/// let cam = Cam::new()?;
///
/// let inward = &cam.inner_cam;
///
/// // Inward cam is not busy since it is not being used.
/// assert!(!inward.is_busy()?);
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "CAMU_IsBusy")]
fnis_busy(&self)-> crate::Result<bool>{
unsafe{
letmutis_busy=false;
@ -283,7 +368,26 @@ pub trait Camera {
@@ -283,7 +368,26 @@ pub trait Camera {
}
/// Returns the maximum amount of transfer bytes based on the view size, trimming, and other
/// modifications set to the camera
/// modifications set to the camera.
///
/// # Example
///
/// ```no_run
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::cam::{Cam, Camera};
/// let cam = Cam::new()?;
///
/// let inward = &cam.inner_cam;
///
/// // Inward cam is not busy since it is not being used.
/// let transfer_count = inward.transfer_byte_count();
@ -351,9 +462,14 @@ pub trait Camera {
@@ -351,9 +462,14 @@ pub trait Camera {
}
}
/// Sets the trimming parameters revolving around the center of the image.
/// Set the trimming bounds relatively to the center of the image.
///
/// # Notes
///
/// The new width will be `trim_width / 2` to the left and right of the center.
/// The new height will be `trim_height / 2` above and below the center.
// TODO: This function doesn't use `TrimmingParams`. It'd be better to merge it with `set_trimming_params()` and change the `TrimmingParams` representation.
#[doc(alias = "CAMU_SetTrimmingParamsCenter")]
fnset_trimming_params_center(
&mutself,
trim_width: i16,
@ -373,7 +489,8 @@ pub trait Camera {
@@ -373,7 +489,8 @@ pub trait Camera {
//! The HID service provides access to user input such as button presses, touch screen presses,
//! and circle pad information. It also provides information from the sound volume slider,
//! the accelerometer, and the gyroscope.
//! The HID service provides read access to user input such as [button presses](Hid::keys_down), [touch screen presses](Hid::touch_position),
//! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope.
// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality.
#![doc(alias = "input")]
#![doc(alias = "controller")]
#![doc(alias = "gamepad")]
usecrate::error::ResultCode;
bitflags::bitflags!{
/// A set of flags corresponding to the button and directional pad
/// inputs on the 3DS
usebitflags::bitflags;
bitflags!{
/// A set of flags corresponding to the button and directional pad inputs present on the 3DS.
//! System services used to handle system-specific functionalities.
//! OS services used to handle system-specific functionality.
//!
//! Most of the 3DS console's functionalities (when writing homebrew) are locked behind services,
//! Most of the 3DS console's functionalities (when writing user-land homebrew) are accessible via services,
//! which need to be initialized before accessing any particular feature.
//!
//! Some include: button input, audio playback, graphics rendering, built-in cameras, etc.
//! To ensure safety while using the underlying services, [`ctru-rs`](crate) leverages Rust's lifetime model.
//! After initializing the handle for a specific service (e.g. [`Apt`](apt::Apt)) the service will be accessible as long as there is at least one handle "alive".
//! As such, handles should be dropped *after* the use of a specific service. This is particularly important for services which are necessary for functionality
//! "outside" their associated methods, such as [`RomFS`](romfs::RomFS), which creates an accessible virtual filesystem, or [`Soc`](soc::Soc),
//! which enables all network communications via sockets.
//!
//! In [`ctru-rs`](crate) some services only allow a single handle to be created at a time, to ensure a safe and controlled environment.
//! The NDSP service is used to handle communications to the DSP processor present on the console's motherboard.
//! Thanks to the DSP processor the program can play sound effects and music on the console's built-in speakers or to any audio device
//! connected via the audio jack.
#![doc(alias = "audio")]
pubmodwave;
usewave::{Wave,WaveStatus};
usewave::{Status,Wave};
usecrate::error::ResultCode;
usecrate::services::ServiceReference;
@ -14,49 +19,80 @@ use std::sync::Mutex;
@@ -14,49 +19,80 @@ use std::sync::Mutex;
constNUMBER_OF_CHANNELS: u8=24;
/// Audio output mode.
#[doc(alias = "ndspOutputMode")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pubenumOutputMode{
/// Single-Channel.
Mono=ctru_sys::NDSP_OUTPUT_MONO,
/// Dual-Channel.
Stereo=ctru_sys::NDSP_OUTPUT_STEREO,
/// Surround.
Surround=ctru_sys::NDSP_OUTPUT_SURROUND,
}
/// PCM formats supported by the audio engine.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pubenumAudioFormat{
/// PCM 8bit single-channel.
PCM8Mono=ctru_sys::NDSP_FORMAT_MONO_PCM8,
/// PCM 16bit single-channel.
PCM16Mono=ctru_sys::NDSP_FORMAT_MONO_PCM16,
/// PCM 8bit interleaved dual-channel.
PCM8Stereo=ctru_sys::NDSP_FORMAT_STEREO_PCM8,
/// PCM 16bit interleaved dual-channel.
PCM16Stereo=ctru_sys::NDSP_FORMAT_STEREO_PCM16,
}
/// Representation of volume mix for a channel.
/// Representation of the volume mix for a channel.
#[derive(Copy, Clone, Debug, PartialEq)]
pubstructAudioMix{
raw: [f32;12],
}
/// Interpolation used between audio frames.
#[doc(alias = "ndspInterpType")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pubenumInterpolationType{
/// Polyphase interpolation.
Polyphase=ctru_sys::NDSP_INTERP_POLYPHASE,
/// Linear interpolation.
Linear=ctru_sys::NDSP_INTERP_LINEAR,
/// No interpolation.
None=ctru_sys::NDSP_INTERP_NONE,
}
/// Errors returned by [`ndsp`](self) functions.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pubenumNdspError{
/// Channel ID
/// Channel with the specified ID does not exist.
InvalidChannel(u8),
/// Channel ID
/// Channel with the specified ID is already being used.
ChannelAlreadyInUse(u8),
/// Channel ID
/// The wave is already busy playing in the channel with the specified ID.
WaveBusy(u8),
/// Sample amount requested, Max sample amount
/// The sample amount requested was larger than the maximum.
SampleCountOutOfBounds(usize,usize),
}
/// NDSP Channel representation.
///
/// There are 24 individual channels in total and each can play a different audio [`Wave`] simultaneuosly.
///
/// # Default
///
/// NDSP initialises all channels with default values on initialization, but the developer is supposed to change these values to correctly work with the service.
///
/// In particular:
/// - Default audio format is set to [`AudioFormat::PCM16Mono`].
/// - Default sample rate is set to 1 Hz.
/// - Default interpolation type is set to [`InterpolationType::Polyphase`].
/// - Default mix is set to [`AudioMix::default()`]
///
/// The handle to a channel can be retrieved with [`Ndsp::channel()`]
pubstructChannel<'ndsp>{
id: u8,
_rf: RefMut<'ndsp,()>,// we don't need to hold any data
//! This service lets the application access a virtual mounted device created using a folder included within the application bundle.
//! After mounting the RomFS file system, the included files and folders will be accessible exactly like any other file, just by using the drive prefix `romfs:/<file-path>`.
//!
//! # Usage
//!
//! This module only gets compiled if the configured RomFS directory is found and the `romfs`
//! feature is enabled.
//!
//! Configure the path in Cargo.toml (the default path is "romfs"). Paths are relative to the
//! Configure the path in your project's `Cargo.toml` manifest (the default path is "romfs"). Paths are relative to the
//! `CARGO_MANIFEST_DIR` environment variable, which is the directory containing the manifest of
//! your package.
//!
@ -11,6 +16,16 @@
@@ -11,6 +16,16 @@
//! [package.metadata.cargo-3ds]
//! romfs_dir = "romfs"
//! ```
//!
//! Alternatively, you can include the RomFS archive manually when building with `3dsxtool`.
//!
//! # Notes
//!
//! `std::path` has problems when parsing file paths that include the `romfs:` prefix.
//! As such, it's suggested to use the paths directly or to do simple append operations to avoid unexpected behaviour.
//! Related [issue](https://github.com/rust-lang/rust/issues/52331).
#![doc(alias = "embed")]
#![doc(alias = "filesystem")]
usecrate::error::ResultCode;
usestd::ffi::CStr;
@ -18,6 +33,7 @@ use std::sync::Mutex;
@@ -18,6 +33,7 @@ use std::sync::Mutex;
//! By using this service the program enables the use of network sockets and utilities such as those found in `std::net`, which are completely inaccessible by default.
//! As such, remember to hold a handle to this service handle while using any network functionality, or else the `std::net` methods will return generic OS errors.
#![doc(alias = "socket")]
#![doc(alias = "network")]
uselibc::memalign;
usestd::net::Ipv4Addr;
@ -8,10 +13,7 @@ use crate::error::ResultCode;
@@ -8,10 +13,7 @@ use crate::error::ResultCode;
usecrate::services::ServiceReference;
usecrate::Error;
/// Network socket service
///
/// Initializing this service will enable the use of network sockets and utilities
/// such as those found in `std::net`. The service will close once this struct gets dropped.