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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Application Manager service.
//!
//! As the name implies, the AM service manages installed applications. It can:
//! - Read the installed applications on the console and their information (depending on the install location).
//! - Install compatible applications to the console.
//!
//! TODO: [`ctru-rs`](crate) doesn't support installing or uninstalling titles yet.
#![doc(alias = "app")]
#![doc(alias = "manager")]

use crate::error::ResultCode;
use crate::services::fs::MediaType;
use std::marker::PhantomData;

/// General information about a specific title entry.
#[doc(alias = "AM_TitleEntry")]
pub struct Title<'a> {
    id: u64,
    mediatype: MediaType,
    size: u64,
    version: u16,
    _am: PhantomData<&'a Am>,
}

impl<'a> Title<'a> {
    /// Returns this title's ID.
    pub fn id(&self) -> u64 {
        self.id
    }

    /// Returns this title's unique product code.
    #[doc(alias = "AM_GetTitleProductCode")]
    pub fn product_code(&self) -> String {
        let mut buf: [u8; 16] = [0; 16];

        // This operation is safe as long as the title was correctly obtained via [`Am::title_list()`].
        unsafe {
            let _ =
                ctru_sys::AM_GetTitleProductCode(self.mediatype.into(), self.id, buf.as_mut_ptr());
        }

        String::from_utf8_lossy(&buf).to_string()
    }

    /// Returns the size of this title in bytes.
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Returns the installed version of this title.
    pub fn version(&self) -> u16 {
        self.version
    }

    /// Returns this title's media type
    pub fn media_type(&self) -> MediaType {
        self.mediatype
    }
}

/// Handle to the Application Manager service.
pub struct Am(());

impl Am {
    /// Initialize a new service handle.
    ///
    /// # Example
    ///
    /// ```
    /// # let _runner = test_runner::GdbRunner::default();
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// #
    /// use ctru::services::am::Am;
    ///
    /// let app_manager = Am::new()?;
    /// #
    /// # Ok(())
    /// # }
    /// ```
    #[doc(alias = "amInit")]
    pub fn new() -> crate::Result<Am> {
        unsafe {
            ResultCode(ctru_sys::amInit())?;
            Ok(Am(()))
        }
    }

    /// Returns the amount of titles currently installed in a specific install location.
    ///
    /// # Example
    ///
    /// ```
    /// # let _runner = test_runner::GdbRunner::default();
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// #
    /// use ctru::services::am::Am;
    /// use ctru::services::fs::MediaType;
    /// let app_manager = Am::new()?;
    ///
    /// // Number of titles installed on the Nand storage.
    /// let nand_count = app_manager.title_count(MediaType::Nand);
    ///
    /// // Number of apps installed on the SD card storage
    /// let sd_count = app_manager.title_count(MediaType::Sd);
    /// #
    /// # Ok(())
    /// # }
    /// ```
    #[doc(alias = "AM_GetTitleCount")]
    pub fn title_count(&self, mediatype: MediaType) -> crate::Result<u32> {
        unsafe {
            let mut count = 0;
            ResultCode(ctru_sys::AM_GetTitleCount(mediatype.into(), &mut count))?;
            Ok(count)
        }
    }

    /// Returns the list of titles installed in a specific install location.
    ///
    /// # Example
    ///
    /// ```
    /// # let _runner = test_runner::GdbRunner::default();
    /// # use std::error::Error;
    /// # fn main() -> Result<(), Box<dyn Error>> {
    /// #
    /// use ctru::services::am::Am;
    /// use ctru::services::fs::MediaType;
    /// let app_manager = Am::new()?;
    ///
    /// // Number of apps installed on the SD card storage
    /// let sd_titles = app_manager.title_list(MediaType::Sd)?;
    ///
    /// // Unique product code identifier of the 5th installed title.
    /// let product_code = sd_titles[4].product_code();
    /// #
    /// # Ok(())
    /// # }
    /// ```
    #[doc(alias = "AM_GetTitleList")]
    pub fn title_list(&self, mediatype: MediaType) -> crate::Result<Vec<Title>> {
        let count = self.title_count(mediatype)?;
        let mut buf = vec![0; count as usize];
        let mut read_amount = 0;

        unsafe {
            ResultCode(ctru_sys::AM_GetTitleList(
                &mut read_amount,
                mediatype.into(),
                count,
                buf.as_mut_ptr(),
            ))?;
        }

        let mut info: Vec<ctru_sys::AM_TitleEntry> = Vec::with_capacity(count as _);

        unsafe {
            ResultCode(ctru_sys::AM_GetTitleInfo(
                mediatype.into(),
                count,
                buf.as_mut_ptr(),
                info.as_mut_ptr() as _,
            ))?;

            info.set_len(count as _);
        };

        Ok(info
            .into_iter()
            .map(|title| Title {
                id: title.titleID,
                mediatype,
                size: title.size,
                version: title.version,
                _am: PhantomData,
            })
            .collect())
    }
}

impl Drop for Am {
    #[doc(alias = "amExit")]
    fn drop(&mut self) {
        unsafe { ctru_sys::amExit() };
    }
}