Skip to main content

libretro_core/
subsystem.rs

1//! Subsystem descriptor builders for multi-content loading.
2//!
3//! These types retain nested descriptor strings and arrays so frontends can read
4//! subsystem metadata after registration without relying on temporary storage.
5
6use std::ffi::{CString, c_char};
7
8use crate::raw;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
11pub struct SubsystemId(u32);
12
13impl SubsystemId {
14    pub const fn new(id: u32) -> Self {
15        Self(id)
16    }
17
18    pub const fn as_raw(self) -> u32 {
19        self.0
20    }
21}
22
23impl From<u32> for SubsystemId {
24    fn from(id: u32) -> Self {
25        Self::new(id)
26    }
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
30pub struct SubsystemMemoryType(u32);
31
32impl SubsystemMemoryType {
33    pub const fn new(memory_type: u32) -> Self {
34        Self(memory_type)
35    }
36
37    pub const fn as_raw(self) -> u32 {
38        self.0
39    }
40}
41
42impl From<u32> for SubsystemMemoryType {
43    fn from(memory_type: u32) -> Self {
44        Self::new(memory_type)
45    }
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct SubsystemMemoryInfo {
50    pub extension: String,
51    pub memory_type: SubsystemMemoryType,
52}
53
54impl SubsystemMemoryInfo {
55    pub fn new(extension: impl Into<String>, memory_type: impl Into<SubsystemMemoryType>) -> Self {
56        Self {
57            extension: extension.into(),
58            memory_type: memory_type.into(),
59        }
60    }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct SubsystemRomInfo {
65    pub description: String,
66    pub valid_extensions: String,
67    pub need_fullpath: bool,
68    pub block_extract: bool,
69    pub required: bool,
70    pub memory: Vec<SubsystemMemoryInfo>,
71}
72
73impl SubsystemRomInfo {
74    pub fn new(description: impl Into<String>, valid_extensions: impl Into<String>) -> Self {
75        Self {
76            description: description.into(),
77            valid_extensions: valid_extensions.into(),
78            need_fullpath: false,
79            block_extract: false,
80            required: true,
81            memory: Vec::new(),
82        }
83    }
84
85    pub fn with_need_fullpath(mut self, need_fullpath: bool) -> Self {
86        self.need_fullpath = need_fullpath;
87        self
88    }
89
90    pub fn with_block_extract(mut self, block_extract: bool) -> Self {
91        self.block_extract = block_extract;
92        self
93    }
94
95    pub fn with_required(mut self, required: bool) -> Self {
96        self.required = required;
97        self
98    }
99
100    pub fn with_memory(mut self, memory: impl IntoIterator<Item = SubsystemMemoryInfo>) -> Self {
101        self.memory = memory.into_iter().collect();
102        self
103    }
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct SubsystemInfo {
108    pub description: String,
109    pub identifier: String,
110    pub id: SubsystemId,
111    pub roms: Vec<SubsystemRomInfo>,
112}
113
114impl SubsystemInfo {
115    pub fn new(
116        description: impl Into<String>,
117        identifier: impl Into<String>,
118        id: impl Into<SubsystemId>,
119    ) -> Self {
120        Self {
121            description: description.into(),
122            identifier: identifier.into(),
123            id: id.into(),
124            roms: Vec::new(),
125        }
126    }
127
128    pub fn with_roms(mut self, roms: impl IntoIterator<Item = SubsystemRomInfo>) -> Self {
129        self.roms = roms.into_iter().collect();
130        self
131    }
132}
133
134pub(crate) struct SubsystemInfoStorage {
135    pub(crate) _strings: Vec<CString>,
136    pub(crate) _memory: Vec<Vec<raw::retro_subsystem_memory_info>>,
137    pub(crate) _roms: Vec<Vec<raw::retro_subsystem_rom_info>>,
138    pub(crate) raw: Vec<raw::retro_subsystem_info>,
139}
140
141impl SubsystemInfoStorage {
142    pub(crate) fn new(subsystems: &[SubsystemInfo]) -> Self {
143        let mut strings = Vec::new();
144        let mut memory = Vec::new();
145        let mut roms = Vec::new();
146        let mut raw = Vec::with_capacity(subsystems.len() + 1);
147
148        for subsystem in subsystems {
149            let desc = push_string(&mut strings, &subsystem.description);
150            let ident = push_string(&mut strings, &subsystem.identifier);
151            let mut subsystem_roms = Vec::with_capacity(subsystem.roms.len());
152
153            for rom in &subsystem.roms {
154                let rom_desc = push_string(&mut strings, &rom.description);
155                let valid_extensions = push_string(&mut strings, &rom.valid_extensions);
156                let rom_memory = rom
157                    .memory
158                    .iter()
159                    .map(|info| raw::retro_subsystem_memory_info {
160                        extension: push_string(&mut strings, &info.extension),
161                        memory_type: info.memory_type.as_raw(),
162                    })
163                    .collect::<Vec<_>>();
164                let memory_ptr = ptr_or_null(&rom_memory);
165                let num_memory = rom_memory.len() as u32;
166                memory.push(rom_memory);
167                subsystem_roms.push(raw::retro_subsystem_rom_info {
168                    desc: rom_desc,
169                    valid_extensions,
170                    need_fullpath: rom.need_fullpath,
171                    block_extract: rom.block_extract,
172                    required: rom.required,
173                    memory: memory_ptr,
174                    num_memory,
175                });
176            }
177
178            let roms_ptr = ptr_or_null(&subsystem_roms);
179            let num_roms = subsystem_roms.len() as u32;
180            roms.push(subsystem_roms);
181            raw.push(raw::retro_subsystem_info {
182                desc,
183                ident,
184                roms: roms_ptr,
185                num_roms,
186                id: subsystem.id.as_raw(),
187            });
188        }
189
190        raw.push(raw::retro_subsystem_info::default());
191
192        Self {
193            _strings: strings,
194            _memory: memory,
195            _roms: roms,
196            raw,
197        }
198    }
199}
200
201fn push_string(strings: &mut Vec<CString>, value: &str) -> *const c_char {
202    strings.push(crate::sanitize_cstring(value));
203    strings
204        .last()
205        .expect("pushed subsystem string must be present")
206        .as_ptr()
207}
208
209fn ptr_or_null<T>(values: &[T]) -> *const T {
210    if values.is_empty() {
211        std::ptr::null()
212    } else {
213        values.as_ptr()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::{
220        SubsystemId, SubsystemInfo, SubsystemInfoStorage, SubsystemMemoryInfo, SubsystemMemoryType,
221        SubsystemRomInfo,
222    };
223    use std::ffi::CStr;
224
225    #[test]
226    fn subsystem_storage_retains_strings_and_nested_arrays() {
227        let storage = SubsystemInfoStorage::new(&[SubsystemInfo::new("Super Game Boy", "sgb", 7)
228            .with_roms([
229                SubsystemRomInfo::new("Game Boy ROM", "gb|gbc").with_memory([
230                    SubsystemMemoryInfo::new("sav", SubsystemMemoryType::new(0x101)),
231                    SubsystemMemoryInfo::new("rtc", 0x102),
232                ]),
233                SubsystemRomInfo::new("BIOS", "bin")
234                    .with_need_fullpath(true)
235                    .with_block_extract(true)
236                    .with_required(false),
237            ])]);
238
239        assert_eq!(storage.raw.len(), 2);
240        assert_eq!(storage.raw[0].id, SubsystemId::new(7).as_raw());
241        assert_eq!(storage.raw[0].num_roms, 2);
242        assert_eq!(storage.raw[1], crate::raw::retro_subsystem_info::default());
243        let roms = unsafe {
244            std::slice::from_raw_parts(storage.raw[0].roms, storage.raw[0].num_roms as usize)
245        };
246        assert_eq!(
247            unsafe { CStr::from_ptr(storage.raw[0].desc) },
248            c"Super Game Boy"
249        );
250        assert_eq!(unsafe { CStr::from_ptr(storage.raw[0].ident) }, c"sgb");
251        assert_eq!(unsafe { CStr::from_ptr(roms[0].desc) }, c"Game Boy ROM");
252        assert_eq!(roms[0].num_memory, 2);
253        let memory = unsafe { std::slice::from_raw_parts(roms[0].memory, 2) };
254        assert_eq!(unsafe { CStr::from_ptr(memory[0].extension) }, c"sav");
255        assert_eq!(memory[0].memory_type, 0x101);
256        assert_eq!(memory[1].memory_type, 0x102);
257        assert!(roms[1].need_fullpath);
258        assert!(roms[1].block_extract);
259        assert!(!roms[1].required);
260        assert!(roms[1].memory.is_null());
261        assert_eq!(roms[1].num_memory, 0);
262    }
263}