1#![feature(new_uninit)]
2#![feature(new_zeroed_alloc)]
3
4use std::collections::HashMap;
5use std::ffi::CStr;
6use std::fs;
7use std::num::NonZeroU32;
8use std::path::Path;
9use std::str::Utf8Error;
10
11use skyline::info::get_program_id;
12
13use nnsdk::web::offlinewebsession::*;
14use nnsdk::web::*;
15use skyline::nn::os::SystemEventType;
16
17pub use nnsdk::web::{
18 offlinewebsession::OfflineWebSession, OfflineBackgroundKind as Background,
19 OfflineBootDisplayKind as BootDisplay, WebSessionBootMode as Visibility,
20};
21
22mod session;
23pub use session::WebSession;
24
25pub struct PageResult {
26 ret: Box<OfflineHtmlPageReturnValue>,
27}
28
29impl PageResult {
30 pub fn new() -> Self {
31 let mut ret;
32 unsafe {
33 ret = Box::<OfflineHtmlPageReturnValue>::new_zeroed().assume_init();
34
35 OfflineHtmlPageReturnValue(ret.as_mut());
36 }
37
38 PageResult { ret }
39 }
40
41 pub fn get_last_url(&self) -> Result<&str, Utf8Error> {
42 unsafe {
43 let last_url = GetLastUrl(self.ret.as_ref());
44 CStr::from_ptr(last_url as _).to_str()
45 }
46 }
47
48 pub fn get_exit_reason(&self) -> OfflineExitReason {
49 self.ret.get_exit_reason()
50 }
51}
52
53impl AsRef<OfflineHtmlPageReturnValue> for PageResult {
54 fn as_ref(&self) -> &OfflineHtmlPageReturnValue {
55 &self.ret
56 }
57}
58
59impl AsMut<OfflineHtmlPageReturnValue> for PageResult {
60 fn as_mut(&mut self) -> &mut OfflineHtmlPageReturnValue {
61 &mut self.ret
62 }
63}
64
65pub struct Webpage<'a> {
66 files: HashMap<&'a str, &'a [u8]>,
67 dir: Option<&'a Path>,
68 show: Option<&'a str>,
69 htdocs_dir: Option<&'a str>,
70 background: OfflineBackgroundKind,
71 boot_display: OfflineBootDisplayKind,
72 javascript: bool,
73 footer: bool,
74 pointer: bool,
75 boot_icon: bool,
76 web_audio: bool,
77}
78
79impl<'a> Default for Webpage<'a> {
80 fn default() -> Self {
81 Self {
82 files: HashMap::new(),
83 dir: None,
84 show: None,
85 htdocs_dir: None,
86 background: OfflineBackgroundKind::Default,
87 boot_display: OfflineBootDisplayKind::Default,
88 javascript: true,
89 footer: false,
90 pointer: false,
91 boot_icon: false,
92 web_audio: true,
93 }
94 }
95}
96
97#[repr(transparent)]
98#[derive(Debug, Copy, Clone)]
99pub struct OsError(NonZeroU32);
100
101impl<'a> Webpage<'a> {
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 pub fn file<S, D>(&mut self, name: &'a S, data: &'a D) -> &mut Self
108 where
109 S: AsRef<str> + ?Sized + 'a,
110 D: AsRef<[u8]> + ?Sized + 'a,
111 {
112 self.files.insert(name.as_ref(), data.as_ref());
113
114 self
115 }
116
117 pub fn files<Str, Data, Arr>(&mut self, files: &'a Arr) -> &mut Self
118 where
119 Str: AsRef<str> + Sized + 'a,
120 Data: AsRef<[u8]> + Sized + 'a,
121 Arr: AsRef<[(Str, Data)]> + ?Sized + 'a,
122 {
123 for (name, data) in files.as_ref().into_iter() {
124 self.files.insert(name.as_ref(), data.as_ref());
125 }
126
127 self
128 }
129
130 pub fn with_dir<P>(&mut self, dir_path: &'a P) -> &mut Self
131 where
132 P: AsRef<Path> + ?Sized + 'a,
133 {
134 self.dir = Some(dir_path.as_ref());
135
136 self
137 }
138
139 pub fn background(&mut self, bg: OfflineBackgroundKind) -> &mut Self {
140 self.background = bg;
141
142 self
143 }
144
145 pub fn boot_display(&mut self, boot: OfflineBootDisplayKind) -> &mut Self {
146 self.boot_display = boot;
147
148 self
149 }
150
151 pub fn javascript(&mut self, js: bool) -> &mut Self {
152 self.javascript = js;
153
154 self
155 }
156
157 pub fn footer(&mut self, footer: bool) -> &mut Self {
158 self.footer = footer;
159
160 self
161 }
162
163 pub fn pointer(&mut self, pointer: bool) -> &mut Self {
164 self.pointer = pointer;
165
166 self
167 }
168
169 pub fn boot_icon(&mut self, boot_icon: bool) -> &mut Self {
170 self.boot_icon = boot_icon;
171
172 self
173 }
174
175 pub fn web_audio(&mut self, audio: bool) -> &mut Self {
176 self.web_audio = audio;
177
178 self
179 }
180
181 pub fn start_page<S>(&mut self, path: &'a S) -> &mut Self
182 where
183 S: AsRef<str> + ?Sized + 'a,
184 {
185 self.show = Some(path.as_ref());
186
187 self
188 }
189
190 pub fn htdocs_dir<S>(&mut self, path: &'a S) -> &mut Self
191 where
192 S: AsRef<str> + ?Sized + 'a,
193 {
194 self.htdocs_dir = Some(path.as_ref());
195
196 self
197 }
198
199 fn into_page_args(&mut self) -> Result<Box<ShowOfflineHtmlPageArg>, OsError> {
200 let program_id = get_program_id();
201
202 let htdocs_dir = self.htdocs_dir.unwrap_or("temp");
203
204 let folder_path = Path::new("sd:/atmosphere/contents")
205 .join(&format!("{:016X}", program_id))
206 .join(&format!("manual_html/html-document/{}.htdocs/", htdocs_dir));
207
208 if let Some(dir) = self.dir {
209 } else if !folder_path.exists() {
211 let _ = fs::create_dir_all(&folder_path);
212 }
213
214 for (path, data) in self.files.iter() {
215 let file_path = folder_path.join(path);
216 let file_parent = file_path.parent().unwrap();
217 if !file_parent.exists() {
218 fs::create_dir_all(file_parent).unwrap();
219 }
220 fs::write(file_path, data).unwrap();
221 }
222
223 let mut args = new_boxed_html_page_arg(format!(
224 "{}.htdocs/{}",
225 htdocs_dir,
226 self.show.unwrap_or("index.html")
227 ));
228
229 args.set_background_kind(self.background);
230 args.set_boot_display_kind(self.boot_display);
231 args.enable_javascript(self.javascript);
232 args.display_footer(self.footer);
233 args.enable_pointer(self.pointer);
234 args.enable_boot_loading_icon(self.boot_icon);
235 args.enable_web_audio(self.web_audio);
236
237 Ok(args)
238 }
239
240 pub fn open_session(&mut self, boot_mode: Visibility) -> Result<WebSession, OsError> {
241 self.javascript(true);
242
243 let mut args = self.into_page_args()?;
244 args.set_boot_mode(boot_mode);
245
246 let session = OfflineWebSession::new();
247
248 let placeholder = SystemEventType { _unused: [0; 0x29] };
249 let mut message_event: &SystemEventType = &placeholder;
250
251 unsafe {
252 Start(&session, &mut message_event, &args);
253 }
254
255 Ok(WebSession(session))
256 }
257
258 pub fn open(&mut self) -> Result<PageResult, OsError> {
259 let mut args = self.into_page_args().unwrap();
260 let mut page_result = PageResult::new();
261
262 let result = unsafe { ShowOfflineHtmlPage(page_result.as_mut(), args.as_mut()) };
263
264 match result {
265 0 => Ok(page_result),
266 err => Err(OsError(NonZeroU32::new(err).unwrap())),
267 }
268 }
269}
270
271fn new_boxed_html_page_arg<T>(page_path: T) -> Box<ShowOfflineHtmlPageArg>
272where
273 T: AsRef<[u8]>,
274{
275 let mut path_bytes = page_path.as_ref().to_vec();
276
277 if path_bytes.len() > 3072 {
278 path_bytes.truncate(3071);
279 }
280
281 path_bytes.push(b'\0');
282
283 unsafe {
284 let mut instance = Box::<ShowOfflineHtmlPageArg>::new_zeroed().assume_init();
285 ShowOfflineHtmlPageArg(instance.as_mut(), path_bytes.as_ptr());
286 instance
287 }
288}
289
290pub mod dialog;
291pub mod dialog_ok;
292pub extern crate ramhorns;
293pub use ramhorns::*;