1#![doc=document_features::document_features!()]
3
4#![deny(warnings)]
5
6#![no_std]
7
8#[cfg(feature="load")]
9use core::fmt::{self, Debug, Display, Formatter};
10#[cfg(feature="load")]
11use core::mem::{MaybeUninit, forget, transmute};
12use core::num::NonZeroU32;
13#[cfg(feature="load")]
14use core::ptr::{self};
15#[cfg(feature="load")]
16use core::slice::{self};
17#[cfg(feature="load")]
18use core::sync::atomic::{AtomicBool, Ordering};
19#[cfg(feature="load")]
20use either::{Either, Left, Right};
21#[cfg(feature="load")]
22use exit_no_std::exit;
23#[cfg(feature="load")]
24use iter_identify_first_last::IteratorIdentifyFirstLastExt;
25#[cfg(feature="load")]
26use panicking::panicking;
27#[cfg(feature="load")]
28use pc_ints::*;
29
30#[doc(hidden)]
31pub use core::write as std_write;
32#[doc(hidden)]
33pub use core::writeln as std_writeln;
34
35#[doc(hidden)]
36#[inline]
37pub const fn hash(w: u16, p: u16) -> u8 {
38 let w = w.wrapping_add(p);
39 ((w ^ (w >> 8)) & 0x007F) as u8
40}
41
42const CODE_PAGE_SIZE: u16 = 512;
43
44#[derive(Debug, Clone)]
45#[repr(C, align(8))]
46pub struct CodePage(pub [u8; CODE_PAGE_SIZE as _]);
47
48impl CodePage {
49 const fn to_upper_half_char(&self, c: u8) -> Option<char> {
50 let offset = 2 * c as usize;
51 let hb = self.0[offset];
52 let lb = self.0[offset + 1];
53 if let Some(c) = NonZeroU32::new(((hb as u32) << 8) | (lb as u32)) {
54 Some(unsafe { char::from_u32_unchecked(c.get()) })
55 } else {
56 None
57 }
58 }
59
60 pub const fn to_char(&self, c: u8) -> Option<char> {
61 let half = c & 0x7F;
62 if c == half {
63 Some(c as char)
64 } else {
65 self.to_upper_half_char(half)
66 }
67 }
68
69 pub const fn from_char(&self, c: char) -> Option<u8> {
70 if (c as u32) >> 7 == 0 {
71 Some(c as u32 as u8)
72 } else if (c as u32) >> 16 != 0 {
73 None
74 } else {
75 let w = (c as u32) as u16;
76 let hash_param = (self.0[510] as u16) | ((self.0[511] as u16) << 8);
77 let offset = 256 + 2 * hash(w, hash_param) as usize;
78 let try_1 = self.0[offset];
79 if try_1 >> 7 != 0 { return None; }
80 if let Some(x) = self.to_upper_half_char(try_1) {
81 if x == c { return Some(0x80 | try_1); }
82 }
83 let try_2 = self.0[offset + 1];
84 if try_2 >> 7 != 0 { return None; }
85 if let Some(x) = self.to_upper_half_char(try_2) {
86 if x == c { return Some(0x80 | try_2); }
87 }
88 None
89 }
90 }
91
92 #[cfg(feature="load")]
93 pub fn load_or_exit_with_msg(exit_code: u8) -> &'static CodePage {
94 match Self::load() {
95 Ok(cp) => cp,
96 Err(e) => {
97 write!(DosLastChanceWriter, "Error: {e}.").unwrap();
98 exit(exit_code);
99 },
100 }
101 }
102
103 #[cfg(feature="load")]
104 pub fn load() -> Result<&'static CodePage, CodePageLoadError> {
105 let mut loaded = LoadedCodePageGuard::acquire();
106 let loaded_code_page = loaded.code_page();
107 if let Some(code_page) = loaded_code_page {
108 return Ok(code_page);
109 }
110 let dos_ver = int_21h_ah_30h_dos_ver();
111 if dos_ver.al_major < 3 || dos_ver.al_major == 3 && dos_ver.ah_minor < 30 {
112 return Err(CodePageLoadError::Dos33Required);
113 }
114 let code_page_memory = int_31h_ax_0100h_rm_alloc(CODE_PAGE_SIZE.checked_add(15).unwrap() / 16)
115 .map_err(|e| CodePageLoadError::CanNotAlloc { err_code: e.ax_err })?;
116 let code_page_selector = RmAlloc { selector: code_page_memory.dx_selector };
117 let code_page_memory = unsafe { slice::from_raw_parts_mut(
118 ((code_page_memory.ax_segment as u32) << 4) as *mut u8,
119 CODE_PAGE_SIZE.into()
120 ) };
121 let code_page_n = int_21h_ax_6601h_code_page()
122 .map_err(|e| CodePageLoadError::CanNotGetSelectedCodePage { err_code: e.ax_err })?
123 .bx_active;
124 if !(100 ..= 999).contains(&code_page_n) {
125 return Err(CodePageLoadError::UnsupportedCodePage { code_page: code_page_n });
126 }
127 let mut code_page: [MaybeUninit<u8>; 13] = unsafe { MaybeUninit::uninit().assume_init() };
128 code_page[.. 9].copy_from_slice(unsafe { transmute::<&[u8], &[MaybeUninit<u8>]>(&b"CODEPAGE\\"[..]) });
129 code_page[9].write(b'0' + (code_page_n / 100) as u8);
130 code_page[10].write(b'0' + ((code_page_n % 100) / 10) as u8);
131 code_page[11].write(b'0' + (code_page_n % 10) as u8);
132 code_page[12].write(0);
133 let code_page: [u8; 13] = unsafe { transmute(code_page) };
134 let code_page = int_21h_ah_3Dh_open(code_page.as_ptr(), 0x00)
135 .map_err(|e| CodePageLoadError::CanNotOpenCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
136 .ax_handle;
137 let code_page = File(code_page);
138 let mut code_page_buf: &mut [MaybeUninit<u8>] = unsafe { transmute(&mut code_page_memory[..]) };
139 loop {
140 if code_page_buf.is_empty() {
141 let mut byte: MaybeUninit<u8> = MaybeUninit::uninit();
142 let read = int_21h_ah_3Fh_read(code_page.0, slice::from_mut(&mut byte))
143 .map_err(|e| CodePageLoadError::CanNotReadCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
144 .ax_read;
145 if read != 0 {
146 return Err(CodePageLoadError::InvalidCodePageFile { code_page: code_page_n });
147 }
148 break;
149 }
150 let read = int_21h_ah_3Fh_read(code_page.0, code_page_buf)
151 .map_err(|e| CodePageLoadError::CanNotReadCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
152 .ax_read;
153 if read == 0 { break; }
154 code_page_buf = &mut code_page_buf[read as usize ..];
155 }
156 if !code_page_buf.is_empty() {
157 return Err(CodePageLoadError::InvalidCodePageFile { code_page: code_page_n });
158 }
159 let code_page = unsafe { &*(code_page_memory.as_ptr() as *const CodePage) };
160 forget(code_page_selector);
161 loaded_code_page.replace(code_page);
162 Ok(code_page)
163 }
164
165 #[cfg(feature="load")]
166 pub fn inkey(&self) -> Result<Option<Either<u8, char>>, InkeyErr> {
167 let c = int_21h_ah_06h_dl_FFh_inkey().map_err(|_| InkeyErr)?;
168 let c = match c {
169 Some(x) => x.al_char,
170 None => return Ok(None),
171 };
172 if c == 0 {
173 let c = int_21h_ah_06h_dl_FFh_inkey().map_err(|_| InkeyErr)?;
174 let c = c.ok_or(InkeyErr)?.al_char;
175 Ok(Some(Left(c)))
176 } else {
177 Ok(self.to_char(c).map(Right))
178 }
179 }
180}
181
182#[cfg(feature="load")]
183pub fn inkey() -> Result<Option<Either<u8, char>>, InkeyErr> {
184 let cp = CodePage::load().map_err(|_| InkeyErr)?;
185 cp.inkey()
186}
187
188#[cfg(feature="load")]
189#[derive(Debug)]
190pub struct InkeyErr;
191
192#[cfg(feature="load")]
193struct DosLastChanceWriter;
194
195#[cfg(feature="load")]
196impl DosLastChanceWriter {
197 pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
198 <Self as fmt::Write>::write_fmt(self, args)
199 }
200}
201
202#[cfg(feature="load")]
203impl fmt::Write for DosLastChanceWriter {
204 fn write_char(&mut self, c: char) -> fmt::Result {
205 let c = c as u32;
206 let c = if c > 0x7F || c == '\r' as u32 {
207 b'?'
208 } else {
209 c as u8
210 };
211 if c == b'\n' {
212 int_21h_ah_02h_out_ch(b'\r');
213 }
214 int_21h_ah_02h_out_ch(c);
215 Ok(())
216 }
217
218 fn write_str(&mut self, s: &str) -> fmt::Result {
219 for c in s.chars() {
220 self.write_char(c)?;
221 }
222 Ok(())
223 }
224}
225
226#[cfg(feature="load")]
227struct File(u16);
228
229#[cfg(feature="load")]
230impl Drop for File {
231 fn drop(&mut self) {
232 let r = int_21h_ah_3Eh_close(self.0);
233 if r.is_err() && !panicking() {
234 #[allow(clippy::panicking_unwrap)]
235 r.unwrap();
236 }
237 }
238}
239
240#[cfg(feature="load")]
241struct LoadedCodePageGuard;
242
243#[cfg(feature="load")]
244static LOADED_CODE_PAGE_GUARD: AtomicBool = AtomicBool::new(false);
245
246#[cfg(feature="load")]
247static mut LOADED_CODE_PAGE: Option<&'static CodePage> = None;
248
249#[cfg(feature="load")]
250impl LoadedCodePageGuard {
251 fn acquire() -> Self {
252 loop {
253 if LOADED_CODE_PAGE_GUARD.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed).is_ok() {
254 break;
255 }
256 }
257 LoadedCodePageGuard
258 }
259
260 fn code_page(&mut self) -> &mut Option<&'static CodePage> {
261 unsafe { &mut *ptr::addr_of_mut!(LOADED_CODE_PAGE) }
262 }
263}
264
265#[cfg(feature="load")]
266impl Drop for LoadedCodePageGuard {
267 fn drop(&mut self) {
268 LOADED_CODE_PAGE_GUARD.store(false, Ordering::SeqCst);
269 }
270}
271
272#[cfg(feature="load")]
273pub enum CodePageLoadError {
274 Dos33Required,
275 CanNotAlloc { err_code: u16 },
276 CanNotGetSelectedCodePage { err_code: u16 },
277 UnsupportedCodePage { code_page: u16 },
278 CanNotOpenCodePageFile { code_page: u16, err_code: u16 },
279 CanNotReadCodePageFile { code_page: u16, err_code: u16 },
280 InvalidCodePageFile { code_page: u16 },
281}
282
283#[cfg(feature="load")]
284impl CodePageLoadError {
285 pub fn code_page(&self) -> Option<u16> {
286 match self {
287 CodePageLoadError::Dos33Required => None,
288 CodePageLoadError::CanNotAlloc { .. } => None,
289 CodePageLoadError::CanNotGetSelectedCodePage { .. } => None,
290 &CodePageLoadError::UnsupportedCodePage { code_page } => Some(code_page),
291 &CodePageLoadError::CanNotOpenCodePageFile { code_page, .. } => Some(code_page),
292 &CodePageLoadError::CanNotReadCodePageFile { code_page, .. } => Some(code_page),
293 &CodePageLoadError::InvalidCodePageFile { code_page } => Some(code_page),
294 }
295 }
296}
297
298#[cfg(feature="load")]
299impl Display for CodePageLoadError {
300 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
301 match self {
302 CodePageLoadError::Dos33Required => write!(f, "DOS >= 3.3 reequired"),
303 CodePageLoadError::CanNotAlloc { err_code } =>
304 write!(f, "cannot allocate real-mode memory for code page ({err_code:04X}h)"),
305 CodePageLoadError::CanNotGetSelectedCodePage { err_code } =>
306 write!(f, "cannon get selected code page ({err_code:04X}h)"),
307 CodePageLoadError::UnsupportedCodePage { code_page } => write!(f, "unsupported code page {code_page}"),
308 CodePageLoadError::CanNotOpenCodePageFile { code_page, err_code } =>
309 write!(f, "cannot open code page file 'CODEPAGE\\{code_page}' ({err_code:04X}h)"),
310 CodePageLoadError::CanNotReadCodePageFile { code_page, err_code } =>
311 write!(f, "cannot read code page file 'CODEPAGE\\{code_page}' ({err_code:04X}h)"),
312 CodePageLoadError::InvalidCodePageFile { code_page } => write!(f, "invalid code page file 'CODEPAGE\\{code_page}'"),
313 }
314 }
315}
316
317#[cfg(feature="load")]
318impl Debug for CodePageLoadError {
319 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
320 <Self as Display>::fmt(self, f)
321 }
322}
323
324#[cfg(feature="load")]
325struct RmAlloc {
326 selector: u16,
327}
328
329#[cfg(feature="load")]
330impl Drop for RmAlloc {
331 fn drop(&mut self) {
332 let _ = int_31h_ax_0101h_rm_free(self.selector);
333 }
334}
335
336#[cfg(feature="load")]
337pub struct DosStdout { pub panic: bool }
338
339#[cfg(feature="load")]
340impl DosStdout {
341 pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
342 <Self as fmt::Write>::write_fmt(self, args)
343 }
344}
345
346#[cfg(feature="load")]
347impl fmt::Write for DosStdout {
348 fn write_char(&mut self, c: char) -> fmt::Result {
349 let cp = CodePage::load();
350 let cp = if self.panic { cp.unwrap() } else { cp.map_err(|_| fmt::Error)? };
351 let c = cp.from_char(c).unwrap_or(b'?');
352 match c {
353 b'\r' => Ok(()),
354 b'\n' => match int_21h_ah_40h_write(1, b"\r\n") {
355 Err(_) => Err(fmt::Error),
356 Ok(AxWritten { ax_written }) if ax_written < 2 => Err(fmt::Error),
357 _ => Ok(()),
358 },
359 c => match int_21h_ah_40h_write(1, &[c]) {
360 Err(_) | Ok(AxWritten { ax_written: 0 }) => Err(fmt::Error),
361 _ => Ok(()),
362 }
363 }
364 }
365
366 fn write_str(&mut self, s: &str) -> fmt::Result {
367 let cp = CodePage::load();
368 let cp = if self.panic { cp.unwrap() } else { cp.map_err(|_| fmt::Error)? };
369 let mut buf = [0; 128];
370 for (skip_newline, s) in s.split('\n').identify_last() {
371 let mut i = 0;
372 for (is_last, c) in s.chars().identify_last() {
373 buf[i] = cp.from_char(c).unwrap_or(b'?');
374 i += 1;
375 if is_last || i == buf.len() {
376 match int_21h_ah_40h_write(1, &buf[.. i]) {
377 Err(_) => return Err(fmt::Error),
378 Ok(AxWritten { ax_written }) if usize::from(ax_written) < i => return Err(fmt::Error),
379 _ => { },
380 }
381 i = 0;
382 }
383 }
384 if !skip_newline {
385 match int_21h_ah_40h_write(1, b"\r\n") {
386 Err(_) => return Err(fmt::Error),
387 Ok(AxWritten { ax_written }) if ax_written < 2 => return Err(fmt::Error),
388 _ => { },
389 }
390 }
391 }
392 Ok(())
393 }
394}
395
396#[cfg(feature="load")]
397#[macro_export]
398macro_rules! print {
399 (
400 $($arg:tt)*
401 ) => {
402 $crate::std_write!($crate::DosStdout { panic: true }, $($arg)*).unwrap()
403 };
404}
405
406#[cfg(feature="load")]
407#[macro_export]
408macro_rules! println {
409 (
410 ) => {
411 $crate::std_writeln!($crate::DosStdout { panic: true }).unwrap()
412 };
413 (
414 $($arg:tt)*
415 ) => {
416 $crate::std_writeln!($crate::DosStdout { panic: true }, $($arg)*).unwrap()
417 };
418}