dos_cp/
lib.rs

1//! ## Feature flags
2#![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}