1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Read-Only Memory FileSystem service.
//!
//! 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 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.
//!
//! ```toml
//! [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")]

use crate::error::ResultCode;
use std::ffi::CStr;
use std::sync::Mutex;

use crate::services::ServiceReference;

/// Handle to the RomFS service.
pub struct RomFS {
    _service_handler: ServiceReference,
}

static ROMFS_ACTIVE: Mutex<()> = Mutex::new(());

impl RomFS {
    /// Mount the bundled RomFS archive as a virtual drive.
    ///
    /// # Example
    ///
    /// ```
    /// # let _runner = test_runner::GdbRunner::default();
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// #
    /// use ctru::services::romfs::RomFS;
    ///
    /// let romfs = RomFS::new()?;
    ///
    /// // Remember to include the RomFS archive and to use your actual files!
    /// let contents = std::fs::read_to_string("romfs:/test-file.txt");
    /// #
    /// # Ok(())
    /// # }
    /// ```
    #[doc(alias = "romfsMountSelf")]
    pub fn new() -> crate::Result<Self> {
        let _service_handler = ServiceReference::new(
            &ROMFS_ACTIVE,
            || {
                let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
                ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?;
                Ok(())
            },
            || {
                let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
                let _ = unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) };
            },
        )?;

        Ok(Self { _service_handler })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // NOTE: this test only passes when run with a .3dsx, which for now requires separate build
    // and run steps so the 3dsx is built before the runner looks for the executable
    #[test]
    #[should_panic]
    fn romfs_lock() {
        let romfs = RomFS::new().unwrap();

        drop(ROMFS_ACTIVE.try_lock().unwrap());

        drop(romfs);
    }
}