1use 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}