1pub mod rng;
2
3pub const DUMMY_RSF: &str = include_str!("../dummy.rsf");
4
5use serde::{Deserialize, Serialize};
6use std::{fs, path::Path};
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum RsfError {
11 #[error("I/O error: {0}")]
12 Io(#[from] std::io::Error),
13 #[error("YAML parse/serialize error: {0}")]
14 Yaml(#[from] serde_yaml::Error),
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Rsf {
20 #[serde(rename = "BasicInfo")]
21 pub basic_info: BasicInfo,
22
23 #[serde(rename = "RomFs")]
24 pub rom_fs: Option<RomFs>,
25
26 #[serde(rename = "TitleInfo")]
27 pub title_info: TitleInfo,
28
29 #[serde(rename = "Option")]
30 pub option: OptionSection,
31
32 #[serde(rename = "AccessControlInfo")]
33 pub access_control_info: AccessControlInfo,
34
35 #[serde(rename = "SystemControlInfo")]
36 pub system_control_info: SystemControlInfo,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct BasicInfo {
41 #[serde(rename = "Title")]
42 pub title: String,
43 #[serde(rename = "ProductCode")]
44 pub product_code: String,
45 #[serde(rename = "Logo")]
46 pub logo: String,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct RomFs {
51 #[serde(rename = "RootPath")]
52 pub root_path: Option<String>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct TitleInfo {
57 #[serde(rename = "Category")]
58 pub category: String,
59 #[serde(rename = "UniqueId")]
60 pub unique_id: u32,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct OptionSection {
65 #[serde(rename = "UseOnSD")]
66 pub use_on_sd: bool,
67 #[serde(rename = "FreeProductCode")]
68 pub free_product_code: bool,
69 #[serde(rename = "MediaFootPadding")]
70 pub media_foot_padding: bool,
71 #[serde(rename = "EnableCrypt")]
72 pub enable_crypt: bool,
73 #[serde(rename = "EnableCompress")]
74 pub enable_compress: bool,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct AccessControlInfo {
79 #[serde(rename = "CoreVersion")]
80 pub core_version: u32,
81
82 #[serde(rename = "DescVersion")]
83 pub desc_version: u32,
84
85 #[serde(rename = "ReleaseKernelMajor")]
86 pub release_kernel_major: String,
87
88 #[serde(rename = "ReleaseKernelMinor")]
89 pub release_kernel_minor: String,
90
91 #[serde(rename = "UseExtSaveData")]
92 pub use_ext_save_data: bool,
93
94 #[serde(rename = "FileSystemAccess")]
95 pub file_system_access: Option<Vec<String>>,
96
97 #[serde(rename = "MemoryType")]
98 pub memory_type: String,
99 #[serde(rename = "SystemMode")]
100 pub system_mode: String,
101 #[serde(rename = "IdealProcessor")]
102 pub ideal_processor: u8,
103 #[serde(rename = "AffinityMask")]
104 pub affinity_mask: u8,
105 #[serde(rename = "Priority")]
106 pub priority: u8,
107 #[serde(rename = "MaxCpu")]
108 pub max_cpu: u8,
109 #[serde(rename = "HandleTableSize")]
110 pub handle_table_size: u32,
111 #[serde(rename = "DisableDebug")]
112 pub disable_debug: bool,
113 #[serde(rename = "EnableForceDebug")]
114 pub enable_force_debug: bool,
115 #[serde(rename = "CanWriteSharedPage")]
116 pub can_write_shared_page: bool,
117 #[serde(rename = "CanUsePrivilegedPriority")]
118 pub can_use_privileged_priority: bool,
119 #[serde(rename = "CanUseNonAlphabetAndNumber")]
120 pub can_use_non_alphabet_and_number: bool,
121 #[serde(rename = "PermitMainFunctionArgument")]
122 pub permit_main_function_argument: bool,
123 #[serde(rename = "CanShareDeviceMemory")]
124 pub can_share_device_memory: bool,
125 #[serde(rename = "RunnableOnSleep")]
126 pub runnable_on_sleep: bool,
127 #[serde(rename = "SpecialMemoryArrange")]
128 pub special_memory_arrange: bool,
129
130 #[serde(rename = "SystemModeExt")]
131 pub system_mode_ext: String,
132 #[serde(rename = "CpuSpeed")]
133 pub cpu_speed: String,
134 #[serde(rename = "EnableL2Cache")]
135 pub enable_l2_cache: bool,
136 #[serde(rename = "CanAccessCore2")]
137 pub can_access_core2: bool,
138
139 #[serde(rename = "IORegisterMapping")]
140 pub io_register_mapping: Option<Vec<String>>,
141 #[serde(rename = "MemoryMapping")]
142 pub memory_mapping: Option<Vec<String>>,
143
144 #[serde(rename = "SystemCallAccess")]
145 pub system_call_access: Option<std::collections::HashMap<String, u32>>,
146
147 #[serde(rename = "ServiceAccessControl")]
148 pub service_access_control: Option<Vec<String>>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct SystemControlInfo {
153 #[serde(rename = "SaveDataSize")]
154 pub save_data_size: String,
155 #[serde(rename = "RemasterVersion")]
156 pub remaster_version: u32,
157 #[serde(rename = "StackSize")]
158 pub stack_size: String,
159
160 #[serde(rename = "Dependency")]
161 pub dependency: Option<std::collections::HashMap<String, String>>,
162}
163
164pub fn load_rsf<P: AsRef<Path>>(path: P) -> Result<Rsf, RsfError> {
166 let text = fs::read_to_string(path)?;
167 let rsf: Rsf = serde_yaml::from_str(&text)?;
168 Ok(rsf)
169}
170
171pub fn save_rsf<P: AsRef<Path>>(path: P, rsf: &Rsf) -> Result<(), RsfError> {
173 let text = serde_yaml::to_string(rsf)?;
174 fs::write(path, text)?;
175 Ok(())
176}
177
178pub fn is_valid_product_code(code: &str) -> bool {
180 code.chars().all(|c| c.is_ascii_alphanumeric())
181}
182
183pub fn sanitize_product_code(code: &str) -> String {
185 code.chars()
186 .filter(|c| c.is_ascii_alphanumeric())
187 .collect()
188}
189
190impl BasicInfo {
191 pub fn set_product_code(&mut self, code: &str) -> Result<(), &'static str> {
192 if is_valid_product_code(code) {
193 self.product_code = code.to_string();
194 Ok(())
195 } else {
196 Err("ProductCode contains invalid characters")
197 }
198 }
199}
200
201use serde_yaml;
202
203pub fn load_embedded_rsf() -> Result<Rsf, serde_yaml::Error> {
205 serde_yaml::from_str(DUMMY_RSF)
206}
207
208pub fn load_rsf_lenient(raw: &str) -> Result<serde_yaml::Value, serde_yaml::Error> {
210 let deserializer = serde_yaml::Deserializer::from_str(raw);
211 let value = serde_yaml::Value::deserialize(deserializer)?;
212 Ok(value)
213}
214
215pub fn load_rsf_safe(raw: &str) -> Result<Rsf, serde_yaml::Error> {
216 let sanitized = sanitize_rsf(raw);
217 serde_yaml::from_str(&sanitized)
218}
219
220
221pub fn sanitize_rsf(raw: &str) -> String {
222 raw.lines()
223 .map(|line| {
224 if let Some((key, value)) = line.split_once(':') {
225 let trimmed = value.trim();
226
227 if trimmed.starts_with('"') || trimmed.starts_with('\'') {
229 return line.to_string();
230 }
231
232 if trimmed.contains('@') {
234 return format!("{key}: \"{trimmed}\"");
235 }
236
237 line.to_string()
238 } else {
239 line.to_string()
240 }
241 })
242 .collect::<Vec<_>>()
243 .join("\n")
244}
245
246