├── examples ├── ferris │ ├── assets │ │ └── ferris.tif │ ├── Cargo.toml │ └── src │ │ ├── ferris.rs │ │ └── lib.rs └── opl │ ├── Cargo.toml │ └── src │ └── lib.rs ├── .cargo └── config.toml ├── djgpp ├── src │ ├── sys │ │ ├── mod.rs │ │ ├── types.rs │ │ ├── djtypes.rs │ │ ├── farptr.rs │ │ ├── nearptr.rs │ │ └── stat.rs │ ├── malloc.rs │ ├── dos.rs │ ├── stdlib.rs │ ├── pc.rs │ ├── lib.rs │ ├── errno.rs │ ├── go32.rs │ ├── dpmi.rs │ ├── stdio.rs │ └── conio.rs └── Cargo.toml ├── rust-toolchain.toml ├── .gitignore ├── dos_x ├── Cargo.toml └── src │ ├── key.rs │ ├── allocator.rs │ ├── sb.rs │ ├── io.rs │ ├── lib.rs │ ├── adlib.rs │ ├── fs.rs │ └── vga.rs ├── Cargo.toml ├── i386-unknown-none-gnu.json ├── i486-unknown-none-gnu.json ├── i586-unknown-none-gnu.json ├── i686-unknown-none-gnu.json ├── LICENSE-MIT ├── .github └── workflows │ └── ci.yml ├── Cargo.lock ├── README.md └── LICENSE-APACHE /examples/ferris/assets/ferris.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Enet4/dos-rs/HEAD/examples/ferris/assets/ferris.tif -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "i386-unknown-none-gnu" 3 | 4 | [unstable] 5 | build-std = ["core", "alloc"] 6 | -------------------------------------------------------------------------------- /djgpp/src/sys/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod djtypes; 2 | pub mod farptr; 3 | pub mod nearptr; 4 | pub mod stat; 5 | pub mod types; 6 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-09-13" 3 | profile = "minimal" 4 | components = ["rust-src"] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /dump.txt 4 | /symbols.txt 5 | /*.s 6 | /*.asm 7 | /*.exe 8 | /*.EXE 9 | /build/ 10 | -------------------------------------------------------------------------------- /djgpp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "djgpp" 3 | version = "0.1.0" 4 | description = "Low level bindings to DJGPP" 5 | edition = "2024" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /dos_x/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dos_x" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | djgpp = { version = "0.1.0", path = "../djgpp" } 8 | -------------------------------------------------------------------------------- /djgpp/src/malloc.rs: -------------------------------------------------------------------------------- 1 | //! malloc.h 2 | //! 3 | //! For other memory allocation functions, 4 | //! see [`stdlib`](super::stdlib). 5 | 6 | unsafe extern "C" { 7 | pub fn memalign(_align: usize, _amt: usize) -> *mut u8; 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "djgpp", 4 | "dos_x", 5 | "examples/ferris", 6 | "examples/opl", 7 | ] 8 | 9 | resolver = "2" 10 | 11 | [profile.dev] 12 | opt-level = 1 13 | 14 | [profile.release] 15 | codegen-units = 1 16 | strip = "debuginfo" 17 | panic = "abort" 18 | lto = "fat" 19 | -------------------------------------------------------------------------------- /examples/ferris/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ferris" 3 | publish = false 4 | description = "An example of a DOS program written in Rust" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [dependencies] 12 | dos_x = { version = "0.1.0", path = "../../dos_x" } 13 | libm = "0.2.8" 14 | -------------------------------------------------------------------------------- /djgpp/src/sys/types.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | use core::ffi::{c_int, c_ulong}; 4 | 5 | pub type blkcnt_t = c_int; 6 | pub type blksize_t = c_int; 7 | pub type dev_t = c_int; 8 | pub type fsblkcnt_t = c_ulong; 9 | pub type fsfilcnt_t = c_ulong; 10 | pub type ino_t = c_int; 11 | pub type mode_t = c_int; 12 | pub type nlink_t = c_int; 13 | -------------------------------------------------------------------------------- /dos_x/src/key.rs: -------------------------------------------------------------------------------- 1 | //! Simple keyboard input module. 2 | 3 | use djgpp::{dpmi::__dpmi_yield, pc::inportb}; 4 | 5 | pub fn wait_for_keypress(code: u8) { 6 | let mut c: u8 = 0; 7 | while c != code { 8 | unsafe { 9 | c = inportb(0x60); 10 | __dpmi_yield(); 11 | } 12 | } 13 | } 14 | 15 | #[inline] 16 | pub fn get_keypress() -> u8 { 17 | unsafe { inportb(0x60) } 18 | } 19 | -------------------------------------------------------------------------------- /djgpp/src/sys/djtypes.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | use core::ffi::{c_int, c_long, c_longlong, c_uint, c_ulong}; 3 | 4 | pub type clock_t = c_int; 5 | pub type gid_t = c_int; 6 | pub type off_t = c_int; 7 | pub type off64_t = c_longlong; 8 | pub type offset_t = c_longlong; 9 | pub type pid_t = c_int; 10 | pub type size_t = c_ulong; 11 | pub type ssize_t = c_long; 12 | pub type time_t = c_uint; 13 | pub type uid_t = c_int; 14 | -------------------------------------------------------------------------------- /examples/opl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "opl" 3 | publish = false 4 | description = "An example of a DOS program that plays some music" 5 | version = "0.1.0" 6 | edition = "2024" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [dependencies] 12 | dos_x = { version = "0.1.0", path = "../../dos_x" } 13 | 14 | [dependencies.opbinary] 15 | git = "https://github.com/Enet4/opbinary-rs" 16 | branch = "main" 17 | default-features = false 18 | -------------------------------------------------------------------------------- /djgpp/src/dos.rs: -------------------------------------------------------------------------------- 1 | //! dos.h 2 | 3 | unsafe extern "C" { 4 | pub fn _get_dos_version(x: i32) -> u16; 5 | pub fn _get_fat_size(_drive: i32) -> i32; 6 | pub fn _get_fs_type(_drive: i32, _result_str: *const u8) -> i32; 7 | pub fn _is_cdrom_drive(_drive: i32) -> i32; 8 | pub fn _is_fat32(_drive: i32) -> i32; 9 | pub fn _is_ram_drive(_drive: i32) -> i32; 10 | pub fn _media_type(_drive: i32) -> i32; 11 | 12 | pub fn delay(_msec: u32); 13 | } 14 | -------------------------------------------------------------------------------- /dos_x/src/allocator.rs: -------------------------------------------------------------------------------- 1 | //! An allocator which works on DOS. 2 | 3 | use core::alloc::{GlobalAlloc, Layout}; 4 | 5 | #[derive(Debug)] 6 | struct AllocatorImpl; 7 | 8 | unsafe impl Sync for AllocatorImpl {} 9 | 10 | unsafe impl GlobalAlloc for AllocatorImpl { 11 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 12 | unsafe { djgpp::stdlib::calloc(layout.size(), layout.align()) } 13 | } 14 | 15 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 16 | unsafe { djgpp::stdlib::free(ptr) } 17 | } 18 | } 19 | 20 | #[global_allocator] 21 | static ALLOCATOR: AllocatorImpl = AllocatorImpl; 22 | -------------------------------------------------------------------------------- /djgpp/src/stdlib.rs: -------------------------------------------------------------------------------- 1 | //! Extern "C" subset of declarations from `stdlib.h`. 2 | //! 3 | //! We cannot use the `libc` crate 4 | //! because the compilation target i686-unknown-none-gnu is not recognized. 5 | 6 | #[allow(non_camel_case_types)] 7 | pub type c_int = i32; 8 | 9 | #[allow(non_camel_case_types)] 10 | pub type c_short = i16; 11 | 12 | #[allow(non_camel_case_types)] 13 | pub type c_char = i8; 14 | 15 | unsafe extern "C" { 16 | pub fn calloc(nmemb: usize, size: usize) -> *mut u8; 17 | pub fn free(ptr: *mut u8); 18 | 19 | pub fn getenv(_name: *const c_char) -> *const c_char; 20 | 21 | pub fn exit(c: c_int); 22 | } 23 | -------------------------------------------------------------------------------- /djgpp/src/pc.rs: -------------------------------------------------------------------------------- 1 | //! Port control functions (as declared in `pc.h`). 2 | 3 | unsafe extern "C" { 4 | pub fn inportb(_port: u16) -> u8; 5 | pub fn inportw(_port: u16) -> u16; 6 | pub fn inportl(_port: u16) -> u32; 7 | pub fn inportsb(_port: u16, _buf: *mut u8, _len: u32); 8 | pub fn inportsw(_port: u16, _buf: *mut u16, _len: u32); 9 | pub fn inportsl(_port: u16, _buf: *mut u32, _len: u32); 10 | pub fn outportb(_port: u16, _data: u8); 11 | pub fn outportw(_port: u16, _data: u16); 12 | pub fn outportl(_port: u16, _data: u32); 13 | pub fn outportsb(_port: u16, _buf: *const u8, _len: u32); 14 | pub fn outportsw(_port: u16, _buf: *const u16, _len: u32); 15 | pub fn outportsl(_port: u16, _buf: *const u32, _len: u32); 16 | } 17 | -------------------------------------------------------------------------------- /djgpp/src/sys/farptr.rs: -------------------------------------------------------------------------------- 1 | // farptr.h 2 | 3 | unsafe extern "C" { 4 | pub fn _farpokeb(selector: u16, offset: u32, value: u8); 5 | pub fn _farpokew(selector: u16, offset: u32, value: u16); 6 | pub fn _farpokel(selector: u16, offset: u32, value: u32); 7 | pub fn _farpeekb(selector: u16, offset: u32) -> u8; 8 | pub fn _farpeekw(selector: u16, offset: u32) -> u16; 9 | pub fn _farpeekl(selector: u16, offset: u32) -> u32; 10 | pub fn _farsetsel(selector: u16); 11 | pub fn _fargetsel() -> u16; 12 | pub fn _farnspokeb(addr: u32, value: u8); 13 | pub fn _farnspokew(addr: u32, value: u16); 14 | pub fn _farnspokel(addr: u32, value: u32); 15 | pub fn _farnspeekb(addr: u32) -> u8; 16 | pub fn _farnspeekw(addr: u32) -> u16; 17 | pub fn _farnspeekl(addr: u32) -> u32; 18 | } 19 | -------------------------------------------------------------------------------- /i386-unknown-none-gnu.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86", 3 | "cpu": "i386", 4 | "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", 5 | "disable-redzone": true, 6 | "dynamic-linking": false, 7 | "executables": false, 8 | "features": "", 9 | "linker-flavor": "gcc", 10 | "linker-is-gnu": true, 11 | "llvm-target": "i386-unknown-none-gnu", 12 | "max-atomic-width": 32, 13 | "no-default-libraries": true, 14 | "os": "none", 15 | "panic-strategy": "abort", 16 | "position-independent-executables": false, 17 | "relocation-model": "static", 18 | "relro-level": "full", 19 | "target-c-int-width": 32, 20 | "target-endian": "little", 21 | "target-pointer-width": 32, 22 | "vendor": "unknown" 23 | } 24 | -------------------------------------------------------------------------------- /i486-unknown-none-gnu.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86", 3 | "cpu": "i486", 4 | "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", 5 | "disable-redzone": true, 6 | "dynamic-linking": false, 7 | "executables": false, 8 | "features": "", 9 | "linker-flavor": "gcc", 10 | "linker-is-gnu": true, 11 | "llvm-target": "i486-unknown-none-gnu", 12 | "max-atomic-width": 32, 13 | "no-default-libraries": true, 14 | "os": "none", 15 | "panic-strategy": "abort", 16 | "position-independent-executables": false, 17 | "relocation-model": "static", 18 | "relro-level": "full", 19 | "target-c-int-width": 32, 20 | "target-endian": "little", 21 | "target-pointer-width": 32, 22 | "vendor": "unknown" 23 | } 24 | -------------------------------------------------------------------------------- /i586-unknown-none-gnu.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86", 3 | "cpu": "i586", 4 | "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", 5 | "disable-redzone": true, 6 | "dynamic-linking": false, 7 | "executables": false, 8 | "features": "+mmx", 9 | "linker-flavor": "gcc", 10 | "linker-is-gnu": true, 11 | "llvm-target": "i586-unknown-none-gnu", 12 | "max-atomic-width": 32, 13 | "no-default-libraries": true, 14 | "os": "none", 15 | "panic-strategy": "abort", 16 | "position-independent-executables": false, 17 | "relocation-model": "static", 18 | "relro-level": "full", 19 | "target-c-int-width": 32, 20 | "target-endian": "little", 21 | "target-pointer-width": 32, 22 | "vendor": "unknown" 23 | } 24 | -------------------------------------------------------------------------------- /i686-unknown-none-gnu.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86", 3 | "cpu": "i686", 4 | "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", 5 | "disable-redzone": true, 6 | "dynamic-linking": false, 7 | "executables": false, 8 | "features": "+mmx,-sse,-sse2", 9 | "linker-flavor": "gcc", 10 | "linker-is-gnu": true, 11 | "llvm-target": "i686-unknown-none-gnu", 12 | "max-atomic-width": 32, 13 | "no-default-libraries": true, 14 | "os": "none", 15 | "panic-strategy": "abort", 16 | "position-independent-executables": false, 17 | "relocation-model": "static", 18 | "relro-level": "full", 19 | "target-c-int-width": 32, 20 | "target-endian": "little", 21 | "target-pointer-width": 32, 22 | "vendor": "unknown" 23 | } 24 | -------------------------------------------------------------------------------- /djgpp/src/sys/nearptr.rs: -------------------------------------------------------------------------------- 1 | // nearptr.h 2 | 3 | /* Functions to enable "near" pointer access to DOS memory under DPMI 4 | CW Sandmann 7-95 NO WARRANTY: WARNING, since these functions disable 5 | memory protection, they MAY DESTROY EVERYTHING ON YOUR COMPUTER! 6 | */ 7 | 8 | use core::ffi::c_int; 9 | 10 | unsafe extern "C" { 11 | /** Returns 0 if feature not avail */ 12 | pub fn __djgpp_nearptr_enable() -> c_int; 13 | /** Enables protection */ 14 | pub fn __djgpp_nearptr_disable(); 15 | 16 | /* Limit on CS and on DS if prot */ 17 | pub static __djgpp_selector_limit: c_int; 18 | /* Used in calculation below */ 19 | pub static __djgpp_base_address: c_int; 20 | } 21 | 22 | #[macro_export] 23 | macro_rules! djgpp_conventional_base { 24 | () => { 25 | (-__djgpp_base_address) as *const u8 as *const _ 26 | }; 27 | ($addr: expr) => { 28 | $addr + djgpp_conventional_base!() 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Eduardo Pinho 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/ferris/src/ferris.rs: -------------------------------------------------------------------------------- 1 | use dos_x::vga::Palette; 2 | 3 | static FERRIS_TIF_DATA: &[u8] = include_bytes!("../assets/ferris.tif"); 4 | 5 | pub fn ferris_pixel_data() -> &'static [u8] { 6 | &FERRIS_TIF_DATA[0x08..0x0_fa08] 7 | } 8 | 9 | pub fn ferris_color_palette() -> Palette { 10 | // ColorMap tag begins @ 0x0_faca 11 | // ID: 320 (0x0140) 12 | // Type: SHORT (3) 13 | // Count: 768 (0x0300) 14 | // Tag data address: 0x0_fb0a 15 | let palette_offset = 0x0_fb0a; 16 | 17 | let palette_size = 256 * 3 * 2; 18 | let g_offset = 256 * 2; 19 | let b_offset = 256 * 4; 20 | 21 | // color table in tif is channel-contiguous 22 | let palette_r = &FERRIS_TIF_DATA[palette_offset..palette_offset + g_offset]; 23 | let palette_g = &FERRIS_TIF_DATA[palette_offset + g_offset..palette_offset + b_offset]; 24 | let palette_b = &FERRIS_TIF_DATA[palette_offset + b_offset..palette_offset + palette_size]; 25 | 26 | let mut palette = [0xffu8; 768]; 27 | 28 | // convert to standard layout 29 | // and narrow it down to 6 bits per channel 30 | for i in 0..256 { 31 | // R channel 32 | palette[i * 3] = palette_r[i * 2] >> 2; 33 | // G channel 34 | palette[i * 3 + 1] = palette_g[i * 2] >> 2; 35 | // B channel 36 | palette[i * 3 + 2] = palette_b[i * 2] >> 2; 37 | } 38 | 39 | Palette::new(palette) 40 | } 41 | -------------------------------------------------------------------------------- /djgpp/src/sys/stat.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_char, c_int}; 2 | 3 | use super::{ 4 | djtypes::{gid_t, off_t, time_t, uid_t}, 5 | types::{blksize_t, dev_t, ino_t, mode_t, nlink_t}, 6 | }; 7 | 8 | #[repr(C)] 9 | #[derive(Debug)] 10 | pub struct Stat { 11 | pub st_atime: time_t, 12 | pub st_ctime: time_t, 13 | pub st_dev: dev_t, 14 | pub st_gid: gid_t, 15 | pub st_ino: ino_t, 16 | pub st_mode: mode_t, 17 | pub st_mtime: time_t, 18 | pub st_nlink: nlink_t, 19 | pub st_size: off_t, 20 | pub st_blksize: blksize_t, 21 | pub st_uid: uid_t, 22 | pub st_rdev: dev_t, 23 | } 24 | 25 | impl Stat { 26 | pub fn zeroed() -> Self { 27 | Stat { 28 | st_atime: 0, 29 | st_ctime: 0, 30 | st_dev: 0, 31 | st_gid: 0, 32 | st_ino: 0, 33 | st_mode: 0, 34 | st_mtime: 0, 35 | st_nlink: 0, 36 | st_size: 0, 37 | st_blksize: 0, 38 | st_uid: 0, 39 | st_rdev: 0, 40 | } 41 | } 42 | } 43 | 44 | unsafe extern "C" { 45 | pub fn chmod(path: *const c_char, _mode: mode_t) -> c_int; 46 | pub fn fchmod(_fildes: c_int, _mode: mode_t) -> c_int; 47 | pub fn fstat(_fildes: c_int, _buf: *mut Stat) -> c_int; 48 | pub fn mkdir(_path: *const c_char, _mode: mode_t) -> c_int; 49 | pub fn mkfifo(_path: *const c_char, _mode: mode_t) -> c_int; 50 | pub fn stat(_path: *const c_char, _buf: *mut Stat) -> c_int; 51 | pub fn umask(_cmask: mode_t) -> mode_t; 52 | } 53 | -------------------------------------------------------------------------------- /djgpp/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! DJGPP low level API 2 | //! 3 | //! This no-std crate exposes the C API made available 4 | //! when building programs targeting DOS systems via [DJGPP][] 5 | //! 6 | //! ## Using and building 7 | //! 8 | //! Aside from including this in your project, 9 | //! you also need to declare end user applications 10 | //! as a static library: 11 | //! 12 | //! ```toml 13 | //! [lib] 14 | //! crate-type = ["staticlib"] 15 | //! ``` 16 | //! 17 | //! Note that for your programs to work with DJGPP, 18 | //! they need to be linked together using the DJGPP GCC compiler. 19 | //! Install pc-msdosdjgpp-gcc for the intended target architecture 20 | //! (i386, i486, i586, or i686). 21 | //! For the linking to be successful, you need to: 22 | //! 23 | //! 1. Build with the suitable target specification 24 | //! (see the JSON files for one of the supported architectures). 25 | //! For example: 26 | //! `cargo build --target i386-unknown-none-gnu.json` 27 | //! 2. Grab the resulting static library archive file 28 | //! (e.g. `libmyapp.a`), 29 | //! extract all of its compiled objects using `ar`, 30 | //! and convert them all to COFF-GO32 using `elf2djgpp`. 31 | //! Then compile them all together into a new library archive file. 32 | //! 3. Use the DJGPP compiler to create an executable: 33 | //! `i686-msdosdjgpp-gcc libmyapp.a -o myapp.exe` 34 | //! 35 | //! [DJGPP]: https://www.delorie.com/djgpp/ 36 | #![no_std] 37 | pub mod conio; 38 | pub mod dos; 39 | pub mod dpmi; 40 | pub mod errno; 41 | pub mod go32; 42 | pub mod malloc; 43 | pub mod pc; 44 | pub mod stdio; 45 | pub mod stdlib; 46 | 47 | pub mod sys; 48 | -------------------------------------------------------------------------------- /djgpp/src/errno.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_char, c_int}; 2 | 3 | unsafe extern "C" { 4 | pub static errno: c_int; 5 | pub static sys_errlist: *const *const c_char; 6 | pub static sys_nerr: c_int; 7 | pub(crate) static __sys_errlist: *const *const c_char; 8 | pub(crate) static __sys_nerr: c_int; 9 | pub(crate) static _doserrno: c_int; 10 | } 11 | 12 | pub const EDOM: i32 = 1; 13 | pub const ERANGE: i32 = 2; 14 | 15 | pub const E2BIG: i32 = 3; 16 | pub const EACCES: i32 = 4; 17 | pub const EAGAIN: i32 = 5; 18 | pub const EBADF: i32 = 6; 19 | pub const EBUSY: i32 = 7; 20 | pub const ECHILD: i32 = 8; 21 | pub const EDEADLK: i32 = 9; 22 | pub const EEXIST: i32 = 10; 23 | pub const EFAULT: i32 = 11; 24 | pub const EFBIG: i32 = 12; 25 | pub const EINTR: i32 = 13; 26 | pub const EINVAL: i32 = 14; 27 | pub const EIO: i32 = 15; 28 | pub const EISDIR: i32 = 16; 29 | pub const EMFILE: i32 = 17; 30 | pub const EMLINK: i32 = 18; 31 | pub const ENAMETOOLONG: i32 = 19; 32 | pub const ENFILE: i32 = 20; 33 | pub const ENODEV: i32 = 21; 34 | pub const ENOENT: i32 = 22; 35 | pub const ENOEXEC: i32 = 23; 36 | pub const ENOLCK: i32 = 24; 37 | pub const ENOMEM: i32 = 25; 38 | pub const ENOSPC: i32 = 26; 39 | pub const ENOSYS: i32 = 27; 40 | pub const ENOTDIR: i32 = 28; 41 | pub const ENOTEMPTY: i32 = 29; 42 | pub const ENOTTY: i32 = 30; 43 | pub const ENXIO: i32 = 31; 44 | pub const EPERM: i32 = 32; 45 | pub const EPIPE: i32 = 33; 46 | pub const EROFS: i32 = 34; 47 | pub const ESPIPE: i32 = 35; 48 | pub const ESRCH: i32 = 36; 49 | pub const EXDEV: i32 = 37; 50 | 51 | pub const ENMFILE: i32 = 38; 52 | pub const ELOOP: i32 = 39; 53 | pub const EOVERFLOW: i32 = 40; 54 | -------------------------------------------------------------------------------- /dos_x/src/sb.rs: -------------------------------------------------------------------------------- 1 | use djgpp::{ 2 | dos::delay, 3 | pc::{inportb, outportb}, 4 | }; 5 | 6 | const SB_RESET: u16 = 0x6; 7 | const SB_READ_DATA: u16 = 0xA; 8 | const SB_READ_DATA_STATUS: u16 = 0xE; 9 | 10 | unsafe fn reset_dsp(port: u16) -> u8 { 11 | unsafe { 12 | outportb(port + SB_RESET, 1); 13 | delay(1); 14 | outportb(port + SB_RESET, 0); 15 | delay(1); 16 | 17 | let status = inportb(port + SB_READ_DATA_STATUS); 18 | if (status & 0x80) == 0x80 && inportb(port + SB_READ_DATA) == 0xAA { 19 | return 1; 20 | } 21 | } 22 | 23 | 0 24 | } 25 | 26 | pub fn detect_sb() -> Option<(u16, u8, u8)> { 27 | let mut addr = 0; 28 | for temp in [0x220, 0x230, 0x240, 0x250, 0x260, 0x270] { 29 | unsafe { 30 | let x = reset_dsp(temp); 31 | if x != 0 { 32 | addr = temp; 33 | break; 34 | } 35 | } 36 | } 37 | 38 | if addr == 0 { 39 | return None; 40 | } 41 | 42 | let env_blaster = unsafe { djgpp::stdlib::getenv(c"BLASTER".as_ptr()) }; 43 | if env_blaster.is_null() { 44 | return None; 45 | } 46 | 47 | let env_blaster = unsafe { core::ffi::CStr::from_ptr(env_blaster) }.to_bytes(); 48 | let mut dma = 0; 49 | let mut irq = 0; 50 | 51 | for i in 0..env_blaster.len() { 52 | if env_blaster[i] | 32 == b'd' { 53 | dma = env_blaster[i + 1] - b'0'; 54 | } else if env_blaster[i] | 32 == b'i' { 55 | irq = env_blaster[i + 1] - b'0'; 56 | } 57 | } 58 | 59 | if dma == 0 || irq == 0 { 60 | return None; 61 | } 62 | 63 | Some((addr, dma, irq)) 64 | } 65 | -------------------------------------------------------------------------------- /djgpp/src/go32.rs: -------------------------------------------------------------------------------- 1 | //! GO32 functions (as declared in `go32.h`). 2 | 3 | /* These lengths are in bytes, optimized for speed */ 4 | 5 | unsafe extern "C" { 6 | pub fn dosmemget(_offset: u32, _length: usize, _buffer: *mut u8); 7 | pub fn dosmemput(_buffer: *const u8, _length: usize, _offset: u32); 8 | } 9 | 10 | /* The lengths here are in TRANSFERS, not bytes! */ 11 | 12 | unsafe extern "C" { 13 | pub fn _dosmemgetb(_offset: u32, _xfers: usize, _buffer: *mut u8); 14 | pub fn _dosmemgetw(_offset: u32, _xfers: usize, _buffer: *mut u8); 15 | pub fn _dosmemgetl(_offset: u32, _xfers: usize, _buffer: *mut u8); 16 | pub fn _dosmemputb(_buffer: *const u8, _xfers: usize, _offset: u32); 17 | pub fn _dosmemputw(_buffer: *const u8, _xfers: usize, _offset: u32); 18 | pub fn _dosmemputl(_buffer: *const u8, _xfers: usize, _offset: u32); 19 | } 20 | 21 | #[repr(C)] 22 | #[allow(non_camel_case_types)] 23 | pub struct __Go32_Info_Block { 24 | pub size_of_this_structure_in_bytes: u32, 25 | pub linear_address_of_primary_screen: u32, 26 | pub linear_address_of_secondary_screen: u32, 27 | pub linear_address_of_transfer_buffer: u32, 28 | pub size_of_transfer_buffer: u32, /* >= 4k */ 29 | pub pid: u32, 30 | pub master_interrupt_controller_base: u8, 31 | pub slave_interrupt_controller_base: u8, 32 | pub selector_for_linear_memory: u16, 33 | pub linear_address_of_stub_info_structure: u32, 34 | pub linear_address_of_original_psp: u32, 35 | pub run_mode: u16, 36 | pub run_mode_info: u16, 37 | } 38 | 39 | unsafe extern "C" { 40 | pub static _go32_info_block: __Go32_Info_Block; 41 | } 42 | 43 | #[macro_export] 44 | macro_rules! _dos_ds { 45 | () => { 46 | djgpp::go32::_go32_info_block.selector_for_linear_memory 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ main ] 7 | push: 8 | branches: [ main ] 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | ARCH: i586 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Cache elf2djgpp 22 | id: cache-elf2djgpp 23 | uses: actions/cache@v4 24 | with: 25 | path: ~/.cargo/bin/elf2djgpp 26 | key: ${{ runner.os }}-elf2djgpp-bin 27 | - uses: actions-rust-lang/setup-rust-toolchain@v1 28 | with: 29 | cache: true 30 | rustflags: "-W warnings" 31 | - name: Install pc-msdosdjgpp-gcc 32 | env: 33 | MSDOSDJGPP_URL: https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2 34 | run: | 35 | mkdir -p /tmp/downloads 36 | # download .tar.bz2, extract, and copy to /usr/local/ 37 | curl -L $MSDOSDJGPP_URL | tar -xj -C /tmp/downloads/ 38 | sudo cp -r /tmp/downloads/djgpp/* /usr/local/ 39 | # add to PATH 40 | echo "export PATH=/usr/local/${{ env.ARCH }}-pc-msdosdjgpp/bin:$PATH" >> $GITHUB_ENV 41 | # clean up 42 | rm -rf /tmp/downloads 43 | # test 44 | "${{ env.ARCH }}-pc-msdosdjgpp-gcc" --version 45 | - name: Install elf2djgpp 46 | if: steps.cache-elf2djgpp.outputs.cache-hit != 'true' 47 | run: | 48 | mkdir -p /tmp/elf2djgpp 49 | cd /tmp/elf2djgpp 50 | git clone --depth 1 https://github.com/Enet4/elf2djgpp.git 51 | cd elf2djgpp 52 | cargo +stable install --path . 53 | # clean up 54 | rm -rf elf2djgpp 55 | # test 56 | elf2djgpp --version 57 | - name: Build 58 | env: 59 | CC: ${{ env.ARCH }}-pc-msdosdjgpp-gcc 60 | run: ./build.sh 61 | -------------------------------------------------------------------------------- /dos_x/src/io.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_int, CStr}; 2 | 3 | use djgpp::errno::{sys_errlist, EIO}; 4 | 5 | #[derive(Copy, Clone, PartialEq)] 6 | pub struct Error(c_int); 7 | 8 | impl Error { 9 | pub const E_IO: Error = Error(EIO); 10 | pub const E_UNKNOWN: Error = Error(127); 11 | 12 | pub fn new(err: c_int) -> Option { 13 | if err != 0 { 14 | Some(Error(err)) 15 | } else { 16 | None 17 | } 18 | } 19 | 20 | pub fn from_errno() -> Self { 21 | let err = unsafe { djgpp::errno::errno }; 22 | Self::new(err).unwrap_or(Error::E_UNKNOWN) 23 | } 24 | } 25 | 26 | impl core::fmt::Debug for Error { 27 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 28 | f.debug_struct("Error").field("code", &self.0).finish() 29 | } 30 | } 31 | 32 | impl core::fmt::Display for Error { 33 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 34 | let code = self.0; 35 | if (0..=40).contains(&code) { 36 | // fetch message from errlist 37 | unsafe { 38 | let errmsg = *(sys_errlist.offset(code as isize)); 39 | if !errmsg.is_null() { 40 | let msg = CStr::from_ptr(errmsg).to_str().unwrap(); 41 | return write!(f, "{msg} (#{code})"); 42 | } 43 | } 44 | } 45 | write!(f, "Unknown error (#{code})") 46 | } 47 | } 48 | 49 | #[macro_export(local_inner_macros)] 50 | macro_rules! djgpp_try { 51 | ($e: expr) => {{ 52 | let ret = $e; 53 | if ret != 0 { 54 | return core::result::Result::Err($crate::io::Error::from_errno()); 55 | } 56 | }}; 57 | } 58 | 59 | #[macro_export(local_inner_macros)] 60 | macro_rules! println { 61 | ($template: literal) => { 62 | unsafe { 63 | let msg = core::concat!($template, "\n\0").as_ptr() as *const core::ffi::c_char; 64 | $crate::djgpp::stdio::printf(msg); 65 | } 66 | }; 67 | ($template: literal, $($arg: expr),*) => { 68 | unsafe { 69 | let msg = alloc::format!(core::concat!($template, "\n\0"), $(&$arg),*); 70 | $crate::djgpp::stdio::printf(msg.as_ptr() as *const core::ffi::c_char); 71 | } 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /djgpp/src/dpmi.rs: -------------------------------------------------------------------------------- 1 | //! DPMI functions (as declared in `dpmi.h`). 2 | #![allow(non_camel_case_types)] 3 | 4 | pub type __dpmi_error = u16; 5 | 6 | // Rust: 7 | 8 | #[repr(C)] 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct __dpmi_regs_d { 11 | pub edi: u32, 12 | pub esi: u32, 13 | pub ebp: u32, 14 | pub res: u32, 15 | pub ebx: u32, 16 | pub edx: u32, 17 | pub ecx: u32, 18 | pub eax: u32, 19 | } 20 | 21 | #[repr(C)] 22 | #[derive(Debug, Copy, Clone)] 23 | pub struct __dpmi_regs_x { 24 | pub di: u16, 25 | pub di_hi: u16, 26 | pub si: u16, 27 | pub si_hi: u16, 28 | pub bp: u16, 29 | pub bp_hi: u16, 30 | pub res: u16, 31 | pub res_hi: u16, 32 | pub bx: u16, 33 | pub bx_hi: u16, 34 | pub dx: u16, 35 | pub dx_hi: u16, 36 | pub cx: u16, 37 | pub cx_hi: u16, 38 | pub ax: u16, 39 | pub ax_hi: u16, 40 | pub flags: u16, 41 | pub es: u16, 42 | pub ds: u16, 43 | pub fs: u16, 44 | pub gs: u16, 45 | pub ip: u16, 46 | pub cs: u16, 47 | pub sp: u16, 48 | pub ss: u16, 49 | } 50 | 51 | #[repr(C)] 52 | #[derive(Debug, Copy, Clone)] 53 | pub struct __dpmi_regs_h { 54 | pub edi: [u8; 4], 55 | pub esi: [u8; 4], 56 | pub ebp: [u8; 4], 57 | pub res: [u8; 4], 58 | pub bl: u8, 59 | pub bh: u8, 60 | pub ebx_b2: u8, 61 | pub ebx_b3: u8, 62 | pub dl: u8, 63 | pub dh: u8, 64 | pub edx_b2: u8, 65 | pub edx_b3: u8, 66 | pub cl: u8, 67 | pub ch: u8, 68 | pub ecx_b2: u8, 69 | pub ecx_b3: u8, 70 | pub al: u8, 71 | pub ah: u8, 72 | pub eax_b2: u8, 73 | pub eax_b3: u8, 74 | } 75 | 76 | #[repr(C)] 77 | #[derive(Copy, Clone)] 78 | pub union __dpmi_regs { 79 | pub d: __dpmi_regs_d, 80 | pub x: __dpmi_regs_x, 81 | pub h: __dpmi_regs_h, 82 | } 83 | 84 | unsafe extern "C" { 85 | pub fn __dpmi_allocate_dos_memory(size: u32, segment: *mut u16) -> __dpmi_error; 86 | 87 | pub fn __dpmi_free_dos_memory(segment: u16) -> __dpmi_error; 88 | 89 | pub fn __dpmi_simulate_real_mode_procedure( 90 | segment: u16, 91 | offset: u16, 92 | registers: *mut __dpmi_regs, 93 | ) -> __dpmi_error; 94 | 95 | pub fn __dpmi_int(int: u8, registers: *mut __dpmi_regs) -> __dpmi_error; 96 | 97 | pub fn __dpmi_yield() -> __dpmi_error; 98 | } 99 | -------------------------------------------------------------------------------- /dos_x/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A high level API providing access to DOS capabilities via DJGPP. 2 | //! 3 | //! ## Using and building 4 | //! 5 | //! Aside from including this in your project, 6 | //! you also need to declare end user applications 7 | //! as a static library: 8 | //! 9 | //! ```toml 10 | //! [lib] 11 | //! crate-type = ["staticlib"] 12 | //! ``` 13 | //! 14 | //! And bootstrap your program like this: 15 | //! 16 | //! ```rust 17 | //! #![no_std] 18 | //! #![no_main] 19 | //! 20 | //! #[no_mangle] 21 | //! fn main() { 22 | //! // ... 23 | //! } 24 | //! ``` 25 | //! 26 | //! Note that for your programs to work with DJGPP, 27 | //! they need to be linked together using the DJGPP GCC compiler. 28 | //! Install pc-msdosdjgpp-gcc for the intended target architecture 29 | //! (i386, i486, i586, or i686). 30 | //! For the linking to be successful, you need to: 31 | //! 32 | //! 1. Build with the suitable target specification 33 | //! (see the JSON files for one of the supported architectures). 34 | //! For example: 35 | //! `cargo build --target i386-unknown-none-gnu.json` 36 | //! 2. Grab the resulting static library archive file 37 | //! (e.g. `libmyapp.a`), 38 | //! extract all of its compiled objects using `ar`, 39 | //! and convert them all to COFF-GO32 using `elf2djgpp`. 40 | //! Then compile them all together into a new library archive file. 41 | //! 3. Use the DJGPP compiler to create an executable: 42 | //! `i686-msdosdjgpp-gcc libmyapp.a -o myapp.exe` 43 | //! 44 | //! [DJGPP]: https://www.delorie.com/djgpp/ 45 | 46 | #![no_std] 47 | 48 | extern crate alloc; 49 | 50 | pub mod adlib; 51 | pub mod allocator; 52 | pub mod fs; 53 | pub mod io; 54 | pub mod key; 55 | pub mod sb; 56 | pub mod vga; 57 | 58 | // re-export 59 | pub use djgpp; 60 | use djgpp::stdlib::{c_char, c_int}; 61 | 62 | static mut ARGV: &'static [*const c_char] = &[]; 63 | 64 | /** Retrieve the command line arguments */ 65 | pub fn argv() -> &'static [*const c_char] { 66 | unsafe { ARGV } 67 | } 68 | 69 | // This is the entry point for the DOS program via DJGPP 70 | #[unsafe(no_mangle)] 71 | pub extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int { 72 | // initialive arguments 73 | unsafe { 74 | ARGV = core::slice::from_raw_parts(argv as *const _, argc as usize); 75 | } 76 | 77 | unsafe extern "Rust" { 78 | fn dos_main(); 79 | } 80 | 81 | unsafe { 82 | dos_main(); 83 | } 84 | 0 85 | } 86 | -------------------------------------------------------------------------------- /dos_x/src/adlib.rs: -------------------------------------------------------------------------------- 1 | use djgpp::pc::{inportb, outportb}; 2 | 3 | /// The default Adlib address port 4 | const ADLIB_DEFAULT_ADDR: u16 = 0x0388; 5 | /// The default Adlib data port 6 | const ADLIB_DEFAULT_DATA: u16 = 0x0389; 7 | 8 | /// The default Adlib address port for the left speaker 9 | const ADLIB_DEFAULT_L_ADDR: u16 = 0x0220; 10 | /// The default Adlib data port for the left speaker 11 | const ADLIB_DEFAULT_L_DATA: u16 = 0x0221; 12 | 13 | /// The default Adlib address port for the right speaker 14 | const ADLIB_DEFAULT_R_ADDR: u16 = 0x0222; 15 | /// The default Adlib data port for the right speaker 16 | const ADLIB_DEFAULT_R_DATA: u16 = 0x0223; 17 | 18 | pub fn detect_adlib() -> u8 { 19 | unsafe { 20 | // 1) reset both timers 21 | write_command(0x04, 0x60); 22 | // 2) enable the interrupts 23 | write_command(0x04, 0x80); 24 | 25 | // 3) read status register 26 | let status = inportb(ADLIB_DEFAULT_ADDR); 27 | 28 | // 4) write FFh to register 2 (Timer 1) 29 | write_command(0x02, 0xff); 30 | 31 | // 5) start timer 1 by writing 21h to register 4 32 | write_command(0x04, 0x21); 33 | 34 | // 6) delay for at least 80 microseconds 35 | djgpp::dos::delay(1); 36 | 37 | // 7) read the status again 38 | let status2 = inportb(ADLIB_DEFAULT_ADDR); 39 | 40 | // 8) reset both timers and interrupts 41 | write_command(0x04, 0x60); 42 | 43 | // 9) check if est the stored results of steps 3 and 7 by ANDing them 44 | // with E0h. The result of step 3 should be 00h, and the 45 | // result of step 7 should be C0h. If both are correct, an 46 | // AdLib-compatible board is installed in the computer. 47 | if (status & 0xe0) == 0 && (status2 & 0xe0) == 0xc0 { 48 | return 1; 49 | } 50 | } 51 | 52 | 0 53 | } 54 | 55 | /// Send an OPL command 56 | pub unsafe fn write_command(register: u8, data: u8) { 57 | unsafe { 58 | outportb(ADLIB_DEFAULT_ADDR, register); 59 | outportb(ADLIB_DEFAULT_DATA, data); 60 | } 61 | } 62 | 63 | /// Send an OPL command to the left speaker 64 | pub unsafe fn write_command_l(register: u8, data: u8) { 65 | unsafe { 66 | outportb(ADLIB_DEFAULT_L_ADDR, register); 67 | outportb(ADLIB_DEFAULT_L_DATA, data); 68 | } 69 | } 70 | 71 | /// Send an OPL command to the right speaker 72 | pub unsafe fn write_command_r(register: u8, data: u8) { 73 | unsafe { 74 | outportb(ADLIB_DEFAULT_R_ADDR, register); 75 | outportb(ADLIB_DEFAULT_R_DATA, data); 76 | } 77 | } 78 | 79 | pub fn reset_adlib() { 80 | unsafe { 81 | write_command(0x01, 0x20); 82 | write_command(0xb0, 0x00); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "djgpp" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "dos_x" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "djgpp", 14 | ] 15 | 16 | [[package]] 17 | name = "ferris" 18 | version = "0.1.0" 19 | dependencies = [ 20 | "dos_x", 21 | "libm", 22 | ] 23 | 24 | [[package]] 25 | name = "heck" 26 | version = "0.5.0" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 29 | 30 | [[package]] 31 | name = "libm" 32 | version = "0.2.15" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 35 | 36 | [[package]] 37 | name = "opbinary" 38 | version = "0.1.0" 39 | source = "git+https://github.com/Enet4/opbinary-rs?branch=main#3428ab3108bb34f3b59d150cc876d00a83969360" 40 | dependencies = [ 41 | "snafu", 42 | ] 43 | 44 | [[package]] 45 | name = "opl" 46 | version = "0.1.0" 47 | dependencies = [ 48 | "dos_x", 49 | "opbinary", 50 | ] 51 | 52 | [[package]] 53 | name = "proc-macro2" 54 | version = "1.0.101" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 57 | dependencies = [ 58 | "unicode-ident", 59 | ] 60 | 61 | [[package]] 62 | name = "quote" 63 | version = "1.0.40" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 66 | dependencies = [ 67 | "proc-macro2", 68 | ] 69 | 70 | [[package]] 71 | name = "snafu" 72 | version = "0.8.9" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" 75 | dependencies = [ 76 | "snafu-derive", 77 | ] 78 | 79 | [[package]] 80 | name = "snafu-derive" 81 | version = "0.8.9" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" 84 | dependencies = [ 85 | "heck", 86 | "proc-macro2", 87 | "quote", 88 | "syn", 89 | ] 90 | 91 | [[package]] 92 | name = "syn" 93 | version = "2.0.106" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 96 | dependencies = [ 97 | "proc-macro2", 98 | "quote", 99 | "unicode-ident", 100 | ] 101 | 102 | [[package]] 103 | name = "unicode-ident" 104 | version = "1.0.19" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MS-DOS with Rust 3 | 4 | [![ci](https://github.com/Enet4/dos-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Enet4/dos-rs/actions/workflows/ci.yml) 5 | [![pages-build-deployment](https://github.com/Enet4/dos-rs/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/Enet4/dos-rs/actions/workflows/pages/pages-build-deployment) 6 | 7 | This is an attempt at building a Rust project to target 8 | MS-DOS in protected mode via [DJGPP](http://www.delorie.com/djgpp/), 9 | supporting x86 CPUs architectures from i486 to i686. 10 | 11 | To engage in the discussion to reach this goal, please check out [this GitHub issue](https://github.com/Serentty/rusty-dos/issues/3) and [this thread](https://groups.google.com/forum/#!msg/comp.os.msdos.djgpp/0l6wjO-oSM0/wucHtHpCAgAJ). 12 | After many different attempts, 13 | the one that seems the most promising right now is 14 | the conversion of ELF .o objects into DJGPP COFF32 objects. 15 | 16 | This project also contains a few preliminary modules that grant easier 17 | access to DOS-specific capabilities, namely port I/O, calling interrupts, 18 | and VGA graphics. 19 | 20 | ## Overview 21 | 22 | In this repository you will find: 23 | 24 | - [djgpp](djgpp): a low-level library for interfacing with the libc and DJGPP API 25 | - [dos_x](dos_x): an experimental library to assist in the creation of DOS programs 26 | - [examples/ferris](examples/ferris): an example program that shows a picture 27 | - and instructions on how to make this all work. 28 | 29 | ## Status 30 | 31 | While there are not many stability and performance guarantees at the moment, 32 | the proofs of concept written so far appear to work as intended. 33 | There is no `std` support, 34 | but an allocator is available. 35 | 36 | The development experience is also not as fluid as it could be. 37 | The Rust program exports a C main function, 38 | so it exists as a static C library. 39 | The compiled objects need to be converted 40 | before they are linked together using `i686-pc-msdosdjgpp-gcc`. 41 | 42 | Known caveats: 43 | 44 | - Be aware of soundness issues in the compilation of floating point arithmetic 45 | against targets without SSE2. 46 | 47 | The use of `f32` or `f64` may be unreliable, 48 | so test carefully. 49 | 50 | ## Requirements 51 | 52 | - Build and install [an updated version of `elf2dgpp`](https://github.com/Enet4/elf2djgpp) 53 | - A nightly Rust toolchain (defined in [rust-toolchain.toml](rust-toolchain.toml)) 54 | - The [DJGGP GCC toolchain](https://www.delorie.com/djgpp) 55 | (version 14.1.0 is known to work, but it should work with more versions as is). 56 | 57 | ## Building 58 | 59 | ```sh 60 | ./build.sh 61 | # or 62 | ./build.sh release 63 | ``` 64 | 65 | Some variables in the script can be tuned to your liking. 66 | 67 | ## Running 68 | 69 | Copy the resulting `dos_rs.exe` file into your DOS environment, 70 | with [`CWSDPMI.EXE`](http://sandmann.dotster.com/cwsdpmi/) alongside it. 71 | It should then be ready to run on a DOS machine, virtual machine, or emulator. 72 | 73 | ## Related 74 | 75 | [Serentty/rusty-dos](https://github.com/Serentty/rusty-dos): a repository presenting a semi-successful attempt at compiling a Rust program running in 16-bit real mode DOS. 76 | 77 | ## License 78 | 79 | Licensed under either of 80 | 81 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 82 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 83 | 84 | at your option. 85 | 86 | Unless you explicitly state otherwise, any contribution intentionally submitted 87 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 88 | additional terms or conditions. 89 | -------------------------------------------------------------------------------- /djgpp/src/stdio.rs: -------------------------------------------------------------------------------- 1 | //! stdio.h 2 | //! 3 | 4 | #[allow(non_camel_case_types)] 5 | type c_int = i32; 6 | #[allow(non_camel_case_types)] 7 | type c_char = i8; 8 | 9 | #[allow(non_camel_case_types)] 10 | #[repr(C)] 11 | pub struct FILE { 12 | _opaque: [u8; 0], 13 | } 14 | 15 | unsafe extern "C" { 16 | pub fn clearerr(stream: *mut FILE); 17 | pub fn fclose(stream: *mut FILE) -> c_int; 18 | pub fn feof(stream: *mut FILE) -> c_int; 19 | pub fn ferror(stream: *mut FILE) -> c_int; 20 | pub fn fflush(stream: *mut FILE) -> c_int; 21 | pub fn fgetc(stream: *mut FILE) -> c_int; 22 | pub fn fgetpos(stream: *mut FILE, pos: *mut c_int) -> c_int; 23 | pub fn fgets(s: *mut c_char, n: c_int, stream: *mut FILE) -> *mut c_char; 24 | pub fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE; 25 | pub fn fprintf(stream: *mut FILE, format: *const c_char, ...) -> c_int; 26 | pub fn fputc(c: c_int, stream: *mut FILE) -> c_int; 27 | pub fn fputs(s: *const c_char, stream: *mut FILE) -> c_int; 28 | pub fn fread(ptr: *mut c_char, size: c_int, nelem: c_int, stream: *mut FILE) -> c_int; 29 | pub fn freopen(filename: *const c_char, mode: *const c_char, stream: *mut FILE) -> *mut FILE; 30 | pub fn fscanf(stream: *mut FILE, format: *const c_char, ...) -> c_int; 31 | pub fn fseek(stream: *mut FILE, offset: c_int, mode: c_int) -> c_int; 32 | pub fn fsetpos(stream: *mut FILE, pos: *mut c_int) -> c_int; 33 | pub fn ftell(stream: *mut FILE) -> c_int; 34 | pub fn fwrite(ptr: *const c_char, size: c_int, nelem: c_int, stream: *mut FILE) -> c_int; 35 | pub fn getc(stream: *mut FILE) -> c_int; 36 | pub fn getchar() -> c_int; 37 | pub fn gets(s: *mut c_char) -> *mut c_char; 38 | pub fn perror(s: *const c_char); 39 | pub fn printf(format: *const c_char, ...) -> c_int; 40 | pub fn putc(c: c_int, stream: *mut FILE) -> c_int; 41 | pub fn putchar(c: c_int) -> c_int; 42 | pub fn puts(s: *const c_char) -> c_int; 43 | pub fn remove(filename: *const c_char) -> c_int; 44 | pub fn rename(old: *const c_char, new: *const c_char) -> c_int; 45 | pub fn rewind(stream: *mut FILE); 46 | pub fn scanf(format: *const c_char, ...) -> c_int; 47 | pub fn setbuf(stream: *mut FILE, buf: *mut c_char); 48 | pub fn setvbuf(stream: *mut FILE, buf: *mut c_char, mode: c_int, size: usize) -> c_int; 49 | pub fn sprintf(s: *mut c_char, format: *const c_char, ...) -> c_int; 50 | pub fn sscanf(s: *const c_char, format: *const c_char, ...) -> c_int; 51 | pub fn tmpfile() -> *mut FILE; 52 | pub fn tmpnam(s: *mut c_char) -> *mut c_char; 53 | pub fn ungetc(c: c_int, stream: *mut FILE) -> c_int; 54 | pub fn vfprintf(stream: *mut FILE, format: *const c_char, ap: *mut c_int) -> c_int; 55 | pub fn vprintf(format: *const c_char, ap: *mut c_int) -> c_int; 56 | pub fn vsprintf(s: *mut c_char, format: *const c_char, ap: *mut c_int) -> c_int; 57 | 58 | pub fn snprintf(str: *mut c_char, n: usize, fmt: *const c_char, ...) -> c_int; 59 | pub fn vfscanf(stream: *mut FILE, format: *const c_char, ap: *mut c_int) -> c_int; 60 | pub fn vscanf(format: *const c_char, ap: *mut c_int) -> c_int; 61 | pub fn vsnprintf(str: *mut c_char, n: usize, fmt: *const c_char, ap: *mut c_int) -> c_int; 62 | pub fn vsscanf(s: *const c_char, format: *const c_char, ap: *mut c_int) -> c_int; 63 | 64 | pub fn dprintf(_fd: c_int, _format: *const c_char) -> c_int; 65 | pub fn fileno(_stream: *const FILE) -> c_int; 66 | pub fn fdopen(_fildes: c_int, _type: *const c_char) -> *mut FILE; 67 | pub fn mkstemp(_template: *mut c_char) -> c_int; 68 | pub fn pclose(_pf: *mut FILE) -> c_int; 69 | pub fn popen(_command: *const c_char, _mode: *const c_char) -> *mut FILE; 70 | pub fn tempnam(_dir: *const c_char, _prefix: *const c_char) -> *mut c_char; 71 | } 72 | -------------------------------------------------------------------------------- /djgpp/src/conio.rs: -------------------------------------------------------------------------------- 1 | //! conio.h 2 | 3 | use core::ffi::{c_char, c_int, c_uchar, c_void}; 4 | 5 | pub const _NOCURSOR: c_int = 0; 6 | pub const _SOLIDCURSOR: c_int = 1; 7 | pub const _NORMALCURSOR: c_int = 2; 8 | 9 | #[allow(non_camel_case_types)] 10 | #[repr(C)] 11 | pub struct text_info { 12 | pub winleft: c_uchar, 13 | pub wintop: c_uchar, 14 | pub winright: c_uchar, 15 | pub winbottom: c_uchar, 16 | pub attribute: c_uchar, 17 | pub normattr: c_uchar, 18 | pub currmode: c_uchar, 19 | pub screenheight: c_uchar, 20 | pub screenwidth: c_uchar, 21 | pub curx: c_uchar, 22 | pub cury: c_uchar, 23 | } 24 | 25 | pub mod text_modes { 26 | use core::ffi::c_int; 27 | 28 | pub const LASTMODE: c_int = -1; 29 | pub const BW40: c_int = 0; 30 | pub const C40: c_int = 1; 31 | pub const BW80: c_int = 2; 32 | pub const C80: c_int = 3; 33 | pub const MONO: c_int = 7; 34 | pub const C4350: c_int = 64; 35 | } 36 | 37 | #[allow(non_snake_case)] 38 | pub mod COLORS { 39 | use core::ffi::c_int; 40 | 41 | /* dark colors */ 42 | pub const BLACK: c_int = 0; 43 | pub const BLUE: c_int = 1; 44 | pub const GREEN: c_int = 2; 45 | pub const CYAN: c_int = 3; 46 | pub const RED: c_int = 4; 47 | pub const MAGENTA: c_int = 5; 48 | pub const BROWN: c_int = 6; 49 | pub const LIGHTGRAY: c_int = 7; 50 | /* light colors */ 51 | pub const DARKGRAY: c_int = 8; /* "light black" */ 52 | pub const LIGHTBLUE: c_int = 9; 53 | pub const LIGHTGREEN: c_int = 10; 54 | pub const LIGHTCYAN: c_int = 11; 55 | pub const LIGHTRED: c_int = 12; 56 | pub const LIGHTMAGENTA: c_int = 13; 57 | pub const YELLOW: c_int = 14; 58 | pub const WHITE: c_int = 15; 59 | } 60 | 61 | pub const BLINK: c_int = 0x80; 62 | 63 | unsafe extern "C" { 64 | pub static mut directvideo: c_int; 65 | pub static mut _wscroll: c_int; 66 | 67 | pub fn blinkvideo(); 68 | pub fn cgets(string: *mut c_char) -> *mut c_char; 69 | pub fn clreol(); 70 | pub fn clrscr(); 71 | pub fn _conio_kbhit() -> c_int; /* checks for ungetch char */ 72 | pub fn cprintf(format: *const c_char, ...) -> c_int; 73 | pub fn cputs(string: *const c_char) -> c_int; 74 | pub fn cscanf(format: *const c_char, ...) -> c_int; 75 | pub fn delline(); 76 | pub fn getch() -> c_int; 77 | pub fn getche() -> c_int; 78 | pub fn _conio_gettext( 79 | left: c_int, 80 | top: c_int, 81 | right: c_int, 82 | bottom: c_int, 83 | destin: *mut c_void, 84 | ) -> c_int; 85 | pub fn gettextinfo(r: *mut text_info); 86 | pub fn gotoxy(x: c_int, y: c_int); 87 | pub fn gppconio_init(); 88 | pub fn highvideo(); 89 | pub fn insline(); 90 | pub fn intensevideo(); 91 | pub fn lowvideo(); 92 | pub fn movetext( 93 | left: c_int, 94 | top: c_int, 95 | right: c_int, 96 | bottom: c_int, 97 | destleft: c_int, 98 | desttop: c_int, 99 | ) -> c_int; 100 | pub fn normvideo(); 101 | pub fn putch(c: c_int) -> c_int; 102 | pub fn puttext( 103 | left: c_int, 104 | top: c_int, 105 | right: c_int, 106 | bottom: c_int, 107 | source: *mut c_void, 108 | ) -> c_int; 109 | pub fn _setcursortype(cursor_type: c_int); 110 | pub fn _set_screen_lines(nlines: c_int); 111 | pub fn textattr(attr: c_int); 112 | pub fn textbackground(color: c_int); 113 | pub fn textcolor(color: c_int); 114 | pub fn textmode(mode: c_int); 115 | pub fn ungetch(ch: c_int) -> c_int; 116 | pub fn wherex() -> c_int; 117 | pub fn wherey() -> c_int; 118 | pub fn window(left: c_int, top: c_int, right: c_int, bottom: c_int); 119 | } 120 | 121 | pub use _conio_gettext as gettext; 122 | pub use _conio_kbhit as kbhit; 123 | -------------------------------------------------------------------------------- /examples/ferris/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Example Rust program that runs in MS-DOS. 2 | //! 3 | #![no_std] 4 | #![no_main] 5 | extern crate alloc; 6 | use alloc::vec::Vec; 7 | use core::panic::PanicInfo; 8 | use dos_x::{djgpp::stdlib::exit, println}; 9 | use libm::sinf; 10 | 11 | use dos_x::{key::wait_for_keypress, vga::vsync}; 12 | 13 | mod ferris; 14 | 15 | #[unsafe(no_mangle)] 16 | fn dos_main() { 17 | unsafe { 18 | println!("Rust says hello DOS!\nPress Enter to continue"); 19 | 20 | busy_wait(100_000); 21 | wait_for_keypress(0x1c); 22 | 23 | dos_x::vga::set_video_mode_13h(); 24 | 25 | // draw Ferris: 26 | 27 | // 1. grab pixel data 28 | let ferris = Vec::from(ferris::ferris_pixel_data()); 29 | 30 | // 2. grab color palette 31 | let mut palette = ferris::ferris_color_palette(); 32 | 33 | // apply the palette 34 | palette.set(); 35 | // put the pixel data 36 | dos_x::vga::draw_buffer(&ferris); 37 | 38 | vsync(); 39 | 40 | app_loop(&ferris); 41 | 42 | // fade out the screen 43 | for _ in 0..64 { 44 | for v in &mut palette.0 { 45 | *v = v.saturating_sub(1); 46 | } 47 | palette.set(); 48 | vsync(); 49 | } 50 | 51 | // set back to text mode 52 | dos_x::vga::set_video_mode(0x02); 53 | 54 | println!( 55 | "< Bye > 56 | ----- 57 | \\ 58 | \\ 59 | _~^~^~_ 60 | \\) / o o \\ (/ 61 | '_ - _' 62 | / '-----' \\" 63 | ); 64 | } 65 | } 66 | 67 | #[inline(never)] 68 | fn app_loop(ferris: &[u8]) { 69 | let mut shutdown = false; 70 | 71 | let gif_width = 320; 72 | let gif_height = 200; 73 | let ferris_top = 40; 74 | let ferris_bottom = 160; 75 | let mut k = 0; 76 | let mut angle = 0.; 77 | 78 | let mut video = [0; 320 * 200]; 79 | 80 | // do the first draw 81 | video.copy_from_slice(ferris); 82 | 83 | while !shutdown { 84 | let s = sinf(angle * core::f32::consts::PI / 180.); 85 | let osc = (s * 22.) as i32; 86 | k += 2; 87 | if k >= gif_width { 88 | k = 0; 89 | } 90 | angle += 2.; 91 | if angle > 360. { 92 | angle -= 360.; 93 | } 94 | 95 | // draw the ferris in its new position. 96 | // optimization: we pick the lines to scan 97 | // based on the osc value 98 | let top = (ferris_top - osc).max(0) as u32; 99 | let bottom = (ferris_bottom - osc).min(200) as u32; 100 | for y in top..bottom { 101 | let mut dest_ofs = y as usize * 320; 102 | 103 | // draw lines of ferris 104 | for x in 0..320 { 105 | let u = (x + k) % gif_width; 106 | 107 | // optimization: skip rendering if u is off Ferris bounds 108 | if (29..292).contains(&u) { 109 | let v = ((y as i32 + osc) as u32).clamp(0, gif_height - 1); 110 | let src_ofs = u + v * gif_width; 111 | video[dest_ofs] = ferris[src_ofs as usize]; 112 | } 113 | dest_ofs += 1; 114 | } 115 | } 116 | unsafe { 117 | vsync(); 118 | dos_x::vga::draw_buffer(&video); 119 | } 120 | if dos_x::key::get_keypress() == 0x1c { 121 | shutdown = true; 122 | } 123 | } 124 | } 125 | 126 | /// wait a number of cycles 127 | /// (use several thousands of cycles for a visible delay) 128 | fn busy_wait(cycles: usize) { 129 | let mut dummy: u32 = 0; 130 | for i in 0..cycles { 131 | unsafe { 132 | core::ptr::write_volatile(core::hint::black_box(&mut dummy), i as u32); 133 | } 134 | } 135 | } 136 | 137 | #[panic_handler] 138 | fn handle_panic(_info: &PanicInfo) -> ! { 139 | unsafe { 140 | // reset video mode 141 | dos_x::vga::set_video_mode(0x02); 142 | // exit using libc 143 | exit(-1); 144 | core::hint::unreachable_unchecked() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /dos_x/src/fs.rs: -------------------------------------------------------------------------------- 1 | //! File system access under DOS 2 | //! 3 | //! This module is an abstraction similar to 4 | //! the one found in the Rust standard library, 5 | //! but designed for MS-DOS via DJGPP. 6 | 7 | use core::ffi::{c_char, c_int, CStr}; 8 | 9 | use alloc::vec::Vec; 10 | use djgpp::{ 11 | stdio::{clearerr, fclose, feof, fflush, fileno, fopen, fread, FILE}, 12 | sys::stat::{fstat, Stat}, 13 | }; 14 | 15 | use crate::djgpp_try; 16 | use crate::io::Error; 17 | 18 | #[derive(Debug)] 19 | pub struct File { 20 | inner: *mut FILE, 21 | } 22 | 23 | pub type Result = core::result::Result; 24 | 25 | impl File { 26 | /// Attempts to open a file in read-only mode. 27 | pub fn open(path: impl AsRef) -> Result { 28 | Self::create_impl(path, b"rb\0".as_ptr() as *const _) 29 | } 30 | 31 | pub fn create(path: impl AsRef) -> Result { 32 | Self::create_impl(path, b"wb\0".as_ptr() as *const _) 33 | } 34 | 35 | fn create_impl(path: impl AsRef, mode: *const c_char) -> Result { 36 | let filename = path.as_ref(); 37 | unsafe { 38 | let file = fopen(filename.as_ptr(), mode); 39 | if file.is_null() { 40 | return Err(Error::from_errno()); 41 | } 42 | Ok(File { inner: file }) 43 | } 44 | } 45 | 46 | /// Get the file descriptor 47 | fn fileno(&self) -> c_int { 48 | unsafe { fileno(self.inner) } 49 | } 50 | 51 | pub fn metadata(&self) -> Result { 52 | let mut stat = Stat::zeroed(); 53 | unsafe { 54 | djgpp_try!(fstat(self.fileno(), &mut stat)); 55 | Ok(Metadata { stat }) 56 | } 57 | } 58 | 59 | /// Pull some bytes from this source into the specified buffer, 60 | /// returning how many bytes were read. 61 | pub fn read(&mut self, buf: &mut [u8]) -> Result { 62 | let len = buf.len() as c_int; 63 | unsafe { 64 | let ret = fread(buf.as_mut_ptr() as *mut _, 1, len, self.inner); 65 | if ret == len { 66 | return Ok(ret as usize); 67 | } 68 | // check if EOF 69 | let eof = feof(self.inner); 70 | let out = if eof != 0 { 71 | // ret contains the number of bytes read 72 | Ok(ret as usize) 73 | } else { 74 | // error 75 | Err(Error::from_errno()) 76 | }; 77 | clearerr(self.inner); 78 | out 79 | } 80 | } 81 | 82 | /// Read all bytes until EOF in this source, placing them into `buf`. 83 | /// 84 | /// All bytes read from this source will be appended 85 | /// to the specified buffer `buf`. 86 | /// This function will continuously call `read()` 87 | /// to append more data to buf until `read()` 88 | /// returns either `Ok(0)` or an error. 89 | /// 90 | /// If successful, this function will return the total number of bytes read. 91 | pub fn read_to_end(&mut self, buf: &mut Vec) -> Result { 92 | let metadata = self.metadata()?; 93 | 94 | let file_size = metadata.file_size(); 95 | 96 | let offset = buf.len(); 97 | buf.resize(offset + file_size, 0); 98 | 99 | let mut rest = &mut buf[offset..]; 100 | 101 | let mut total = 0; 102 | loop { 103 | let bytes_read = self.read(rest)?; 104 | total += bytes_read; 105 | if bytes_read == 0 { 106 | unsafe { 107 | let rest_len = rest.len(); 108 | buf.set_len(buf.len() - rest_len); 109 | return Ok(total); 110 | } 111 | } 112 | if bytes_read == rest.len() { 113 | return Ok(total); 114 | } 115 | // update buffer rest and try again 116 | rest = &mut rest[bytes_read..]; 117 | } 118 | } 119 | 120 | pub fn flush(&mut self) -> Result<()> { 121 | unsafe { 122 | djgpp_try!(fflush(self.inner)); 123 | Ok(()) 124 | } 125 | } 126 | 127 | /// Close the file descriptor explicitly, 128 | /// returning any error that occurs. 129 | /// 130 | /// Note that the [`Drop`] implementation already closes the file. 131 | /// Use this method when it is desirable to handle the error formally. 132 | pub fn close(self) -> Result<()> { 133 | unsafe { 134 | djgpp_try!(fclose(self.inner)); 135 | core::mem::forget(self); 136 | Ok(()) 137 | } 138 | } 139 | } 140 | 141 | impl Drop for File { 142 | fn drop(&mut self) { 143 | unsafe { 144 | fclose(self.inner); 145 | } 146 | } 147 | } 148 | 149 | /// Read the entire contents of a file into a bytes vector. 150 | /// 151 | /// To reuse an existing vector, 152 | /// prefer using [`File::open`] and [`read_to_end`](File::read_to_end). 153 | pub fn read(path: impl AsRef) -> Result> { 154 | let mut f = File::open(path)?; 155 | let metadata = f.metadata()?; 156 | 157 | let file_size = metadata.file_size(); 158 | let mut buf = alloc::vec![0u8; file_size]; 159 | 160 | let mut rest = &mut buf[..]; 161 | 162 | loop { 163 | let bytes_read = f.read(rest)?; 164 | if bytes_read == 0 { 165 | unsafe { 166 | let rest_len = rest.len(); 167 | buf.set_len(buf.len() - rest_len); 168 | return Ok(buf); 169 | } 170 | } 171 | if bytes_read == rest.len() { 172 | return Ok(buf); 173 | } 174 | // update buffer rest and try again 175 | rest = &mut rest[bytes_read..]; 176 | } 177 | } 178 | 179 | /// File metadata 180 | #[derive(Debug)] 181 | pub struct Metadata { 182 | stat: Stat, 183 | } 184 | 185 | impl Metadata { 186 | pub fn file_size(&self) -> usize { 187 | self.stat.st_size as usize 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /dos_x/src/vga.rs: -------------------------------------------------------------------------------- 1 | //! A simple module for video mode and VGA graphics in DOS. 2 | 3 | use djgpp::dpmi::{__dpmi_int, __dpmi_regs}; 4 | use djgpp::go32::{_dosmemputw, dosmemput}; 5 | use djgpp::pc::{inportb, outportb}; 6 | use djgpp::sys::farptr::{_farpokeb, _farpokel}; 7 | 8 | /// The numerical address to the VGA buffer 9 | pub(crate) const VGA_BUFFER_ADDR: u32 = 0xa0000; 10 | 11 | /// Set the video mode. 12 | /// 13 | /// Example modes: 14 | /// 15 | /// - 0x02 for 80x25 text mode 16 | /// - 0x13 for 320x200 256-color mode 17 | /// 18 | /// 19 | /// ### Safety 20 | /// 21 | /// The caller must ensure that the video mode is valid. 22 | #[inline] 23 | pub unsafe fn set_video_mode(mode: u8) { 24 | unsafe { 25 | let mut regs: __dpmi_regs = core::mem::zeroed(); 26 | 27 | regs.x.ax = mode as u16; 28 | 29 | __dpmi_int(0x10, &mut regs); 30 | } 31 | } 32 | 33 | /// Set the video mode to 13h: 320x200 256-color 34 | #[inline] 35 | pub fn set_video_mode_13h() { 36 | unsafe { 37 | set_video_mode(0x13); 38 | } 39 | } 40 | 41 | /// Put a single pixel value at the given coordinates. 42 | /// 43 | /// ### Safety 44 | /// 45 | /// This function does not check whether the video mode is set correctly. 46 | /// A video buffer of size 64_000 bytes 47 | /// in VGA mode 13h is assumed. 48 | #[inline] 49 | pub unsafe fn put_pixel(x: u32, y: u32, c: u8) { 50 | if y >= 200 || x >= 320 { 51 | return; 52 | } 53 | 54 | let i = x + y * 320; 55 | 56 | unsafe { 57 | _farpokeb(djgpp::_dos_ds!(), VGA_BUFFER_ADDR + i, c); 58 | } 59 | } 60 | 61 | /// Draw a solid horizontal line at the given coordinates. 62 | /// 63 | /// ### Safety 64 | /// 65 | /// This function does not check whether the video mode is set correctly. 66 | /// A video buffer of size 64_000 bytes 67 | /// in VGA mode 13h is assumed. 68 | #[inline] 69 | pub unsafe fn draw_hline(x: i32, y: i32, length: u32, c: u8) { 70 | if y < 0 || y >= 200 { 71 | return; 72 | } 73 | let y = y as u32; 74 | // clamp x and length 75 | let x = x.max(0) as u32; 76 | let length = length.min(320 - x); 77 | 78 | let base = y * 320 + x; 79 | 80 | let cc = c as u16 | (c as u16) << 8; 81 | let cccc = cc as u32 | (cc as u32) << 16; 82 | 83 | // unroll into long far poke calls when possible 84 | let mut i = base; 85 | while i < base + length { 86 | if i & 3 == 0 && i + 3 < base + length { 87 | unsafe { 88 | _farpokel(djgpp::_dos_ds!(), VGA_BUFFER_ADDR + i as u32, cccc); 89 | } 90 | i += 4; 91 | } else { 92 | unsafe { 93 | _farpokeb(djgpp::_dos_ds!(), VGA_BUFFER_ADDR + i as u32, c); 94 | } 95 | i += 1; 96 | } 97 | } 98 | } 99 | 100 | /// Draw a solid vertical line at the given coordinates. 101 | /// 102 | /// ### Safety 103 | /// 104 | /// This function does not check whether the video mode is set correctly. 105 | /// A video buffer of size 64_000 bytes 106 | /// in VGA mode 13h is assumed. 107 | #[inline] 108 | pub unsafe fn draw_vline(x: i32, y: i32, length: u32, c: u8) { 109 | for j in 0..length { 110 | unsafe { 111 | put_pixel(x as u32, y as u32 + j, c); 112 | } 113 | } 114 | } 115 | 116 | /// Draw a solid rectangle at the given coordinates. 117 | /// 118 | /// ### Safety 119 | /// 120 | /// This function does not check whether the video mode is set correctly. 121 | /// A video buffer of size 64_000 bytes 122 | /// in VGA mode 13h is assumed. 123 | pub unsafe fn draw_rect(x: i32, y: i32, width: u32, height: u32, c: u8) { 124 | for j in 0..height as i32 { 125 | unsafe { 126 | draw_hline(x, y + j, width, c); 127 | } 128 | } 129 | } 130 | 131 | /// Given a rectangular portion of pixel data 132 | /// with the dimensions in `data_dim`, 133 | /// copy a rectangular portion of it 134 | /// defined by `origin` (x, y, width, height) 135 | /// onto the display at the `target` (x, y) coordinates. 136 | /// 137 | /// ### Safety 138 | /// 139 | /// This function does not check whether the video mode is set correctly. 140 | /// A video buffer of size 64_000 bytes 141 | /// in VGA mode 13h is assumed. 142 | #[inline] 143 | pub unsafe fn blit_rect( 144 | data: &[u8], 145 | data_dim: (u32, u32), 146 | origin: (u32, u32, u32, u32), 147 | target: (i32, i32), 148 | ) { 149 | let (data_width, data_height) = data_dim; 150 | let (x, y, width, height) = origin; 151 | let (target_x, target_y) = target; 152 | 153 | let data = if data.len() > (data_width * data_height) as usize { 154 | &data[..(data_width * data_height) as usize] 155 | } else { 156 | data 157 | }; 158 | 159 | let mut src = y * data_width + x; 160 | let mut target = target_y * 320 + target_x; 161 | for _ in 0..height { 162 | unsafe { 163 | // blit in contiguous data portions 164 | dosmemput( 165 | data.as_ptr().byte_offset(src as isize), 166 | width as usize, 167 | VGA_BUFFER_ADDR + target as u32, 168 | ); 169 | } 170 | 171 | // next row 172 | src += data_width; 173 | target += 320; 174 | } 175 | } 176 | 177 | /// Draw the entirety of the given data buffer to the video buffer. 178 | /// 179 | /// ### Safety 180 | /// 181 | /// This function does not check whether the video mode is set correctly. 182 | /// A video buffer of size 64_000 bytes is assumed. 183 | #[inline] 184 | pub unsafe fn draw_buffer(data: &[u8]) { 185 | let data = if data.len() > 320 * 200 { 186 | &data[..320 * 200] 187 | } else { 188 | data 189 | }; 190 | unsafe { 191 | _dosmemputw(data.as_ptr(), data.len() / 2, VGA_BUFFER_ADDR); 192 | }; 193 | } 194 | 195 | /// Synchronize the program with the vertical retrace 196 | #[inline] 197 | pub unsafe fn vsync() { 198 | unsafe { 199 | // wait until any previous retrace has ended 200 | loop { 201 | if (inportb(0x3DA) & 8) != 0 { 202 | break; 203 | } 204 | } 205 | 206 | /* wait until a new retrace has just begun */ 207 | loop { 208 | if (inportb(0x3DA) & 8) == 0 { 209 | break; 210 | } 211 | } 212 | } 213 | } 214 | 215 | /// A thin abstraction over the VGA color palette. 216 | /// 217 | /// The array within contains the 256 colors in RGB, 218 | /// in standard layout (RGBRGBRGB...), 219 | /// allocating 8 bits per channel 220 | /// but only using 6 bits per channel. 221 | /// 222 | /// If you prefer not to allocate, 223 | /// see [`set_colors_with`] 224 | /// or [`set_color_single`]. 225 | #[derive(Copy, Clone)] 226 | pub struct Palette(pub [u8; 768]); 227 | 228 | impl Palette { 229 | /// Create a new palette from a given array. 230 | #[inline] 231 | pub fn new(palette: [u8; 768]) -> Self { 232 | Self(palette) 233 | } 234 | 235 | /// Retrieve the palette currently defined in the system. 236 | pub fn get() -> Self { 237 | let mut palette = [0u8; 768]; 238 | // want to read 239 | unsafe { 240 | outportb(0x3c7, 0); 241 | } 242 | for p in &mut palette { 243 | *p = unsafe { inportb(0x3c9) }; 244 | } 245 | Palette(palette) 246 | } 247 | 248 | /// Apply this palette in the system. 249 | pub fn set(&self) { 250 | // want to write 251 | unsafe { 252 | outportb(0x3c8, 0); 253 | } 254 | for p in &self.0 { 255 | unsafe { 256 | outportb(0x3c9, *p); 257 | } 258 | } 259 | } 260 | 261 | /// Apply a single color in this palette to the system's palette. 262 | pub fn set_single(&self, c: u8) { 263 | let i = c as usize * 3; 264 | let r = self.0[i]; 265 | let g = self.0[i + 1]; 266 | let b = self.0[i + 2]; 267 | set_color_single(c, r, g, b); 268 | } 269 | } 270 | 271 | /// Set a single color in the VGA palette. 272 | pub fn set_color_single(c: u8, r: u8, g: u8, b: u8) { 273 | unsafe { 274 | outportb(0x3c8, c); 275 | outportb(0x3c9, r); 276 | outportb(0x3c9, g); 277 | outportb(0x3c9, b); 278 | } 279 | } 280 | 281 | /// Reset the system's VGA palette 282 | /// with the values coming from the given iterator. 283 | /// 284 | /// The values from the iterator should be 285 | /// consecutive 8-bit RGB values 286 | /// (of 6-bit precision). 287 | /// 288 | /// Iteration stops after 768 values. 289 | pub fn set_colors_with(values: impl IntoIterator) { 290 | // want to write 291 | unsafe { 292 | outportb(0x3c8, 0); 293 | } 294 | for p in values.into_iter().take(768) { 295 | unsafe { 296 | outportb(0x3c9, p); 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /examples/opl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Example Rust program that runs in MS-DOS 2 | //! and plays some musical notes. 3 | #![no_std] 4 | #![no_main] 5 | extern crate alloc; 6 | use alloc::format; 7 | use core::ffi::CStr; 8 | use core::hint::unreachable_unchecked; 9 | use core::panic::PanicInfo; 10 | use dos_x::adlib::{self, detect_adlib, reset_adlib}; 11 | use dos_x::djgpp::{dos::delay, stdio::puts, stdlib::exit}; 12 | use dos_x::key::get_keypress; 13 | use dos_x::println; 14 | use dos_x::sb::detect_sb; 15 | use opbinary::vgm::OplCommand; 16 | 17 | #[allow(non_camel_case_types)] 18 | type c_char = i8; 19 | 20 | #[unsafe(no_mangle)] 21 | fn dos_main() { 22 | // try to read command line arguments 23 | let args = dos_x::argv(); 24 | 25 | let vgm_filename = if args.len() > 1 { 26 | unsafe { Some(CStr::from_ptr(args[1])) } 27 | } else { 28 | None 29 | }; 30 | 31 | unsafe { 32 | match detect_adlib() { 33 | 0 => { 34 | puts(b"No Adlib sound card detected :(\0".as_ptr() as *const c_char); 35 | exit(0); 36 | core::hint::unreachable_unchecked() 37 | } 38 | _ => { 39 | puts(b"Adlib sound card detected!\0".as_ptr() as *const c_char); 40 | } 41 | }; 42 | 43 | match detect_sb() { 44 | None => { 45 | puts(b"No Sound Blaster sound card detected :(\0".as_ptr() as *const c_char); 46 | exit(0); 47 | core::hint::unreachable_unchecked() 48 | } 49 | Some((addr, irq, dma)) => { 50 | let msg = format!( 51 | "Sound Blaster sound card detected!\n addr: 0x{:x}\n irq: {}\n dma: {}\0", 52 | addr, irq, dma 53 | ); 54 | puts(msg.as_ptr() as *const _); 55 | } 56 | }; 57 | 58 | busy_wait(1_000); 59 | 60 | match vgm_filename { 61 | Some(filename) => run_player(filename), 62 | None => run_note_loop(), 63 | } 64 | 65 | println!("Bye"); 66 | } 67 | } 68 | 69 | fn run_note_loop() { 70 | // initialize 71 | reset_adlib(); 72 | 73 | // set the instrument 74 | unsafe { 75 | // modulator multiple to 1 76 | adlib::write_command(0x20, 0x01); 77 | // modulator level 78 | adlib::write_command(0x40, 0x17); 79 | // modulator attack / release 80 | adlib::write_command(0x60, 0b1100_0100); 81 | // modulator sustain / release 82 | adlib::write_command(0x80, 0x77); 83 | // set carrier multiple to 1 84 | adlib::write_command(0x23, 0x01); 85 | // carrier level maximum volume (about 47db) 86 | adlib::write_command(0x43, 0x00); 87 | // carrier attack / release 88 | adlib::write_command(0x63, 0xf0); 89 | // carrier sustain / release 90 | adlib::write_command(0x83, 0x77); 91 | } 92 | 93 | println!("Now playing..."); 94 | 95 | let mut shutdown = false; 96 | let mut k = 0; 97 | while !shutdown { 98 | // play a note 99 | // (frequency depends on k) 100 | let freq: u16 = match k { 101 | 0 => 0x2AE, 102 | 1 => 0x181, 103 | 2 => 0x1b0, 104 | 3 => 0x1ca, 105 | 4 => 0x202, 106 | 5 => 0x241, 107 | 6 => 0x287, 108 | 7 => 0x2AE, 109 | _ => 0x2AE, 110 | }; 111 | let [freq_lo, freq_hi] = freq.to_le_bytes(); 112 | let octave: u8 = if k == 0 { 3 } else { 4 }; 113 | unsafe { 114 | // set voice frequency LSB 115 | adlib::write_command(0xa0, freq_lo); 116 | // turn voice on, set octave, and freq MSB 117 | adlib::write_command(0xb0, (1 << 5) | (octave << 2) | freq_hi); 118 | } 119 | unsafe { 120 | delay(150); 121 | // release previous note 122 | adlib::write_command(0xb0, (octave << 2) | freq_hi); 123 | } 124 | 125 | if dos_x::key::get_keypress() == 0x1c { 126 | shutdown = true; 127 | } 128 | 129 | unsafe { 130 | delay(100); 131 | } 132 | 133 | k = (k + 1) & 0x07; 134 | 135 | if dos_x::key::get_keypress() == 0x1c { 136 | shutdown = true; 137 | } 138 | } 139 | 140 | // turn off voice 141 | unsafe { 142 | adlib::write_command(0xb0, 0x00); 143 | } 144 | reset_adlib(); 145 | } 146 | 147 | fn run_player(filename: &CStr) { 148 | // initialize 149 | reset_adlib(); 150 | 151 | println!("Opening file {}...", filename.to_string_lossy()); 152 | 153 | let file_data = dos_x::fs::read(filename).unwrap_or_else(|e| { 154 | println!("Failed to read file: {}", e); 155 | unsafe { 156 | exit(-2); 157 | unreachable_unchecked(); 158 | } 159 | }); 160 | 161 | let vgm = match opbinary::vgm::Vgm::from_bytes(&file_data) { 162 | Ok(vgm) => { 163 | println!( 164 | "VGM v{:x}, {} samples", 165 | vgm.header.base_header.version, vgm.header.total_samples 166 | ); 167 | 168 | if vgm.header.ym3812_clock == 0 && vgm.header.ymf262_clock == 0 { 169 | println!("Not an OPL music file"); 170 | unsafe { 171 | exit(-3); 172 | unreachable_unchecked(); 173 | } 174 | } 175 | if vgm.header.ymf262_clock != 0 { 176 | println!("YMF262 clock: {} Hz (OPL3)", vgm.header.ymf262_clock); 177 | } 178 | if vgm.header.ym3812_clock != 0 { 179 | println!("YM3812 clock: {} Hz (OPL2)", vgm.header.ym3812_clock); 180 | } 181 | 182 | vgm.into_opl_vgm() 183 | } 184 | Err(e) => { 185 | println!("Could not read VGM file: {}", e); 186 | unsafe { 187 | exit(-3); 188 | unreachable_unchecked(); 189 | } 190 | } 191 | }; 192 | 193 | println!("Now playing... (Hold Esc to stop)"); 194 | 195 | fn samples_to_ms(samples: u32) -> u32 { 196 | samples * 10 / 441 197 | } 198 | 199 | for cmd in vgm.opl_commands { 200 | match cmd { 201 | OplCommand::Opl3 { 202 | port: 0, 203 | address, 204 | data, 205 | } => unsafe { 206 | adlib::write_command_l(address, data); 207 | }, 208 | OplCommand::Opl3 { 209 | port: 1, 210 | address, 211 | data, 212 | } => unsafe { 213 | adlib::write_command_r(address, data); 214 | }, 215 | OplCommand::Opl2 { address, data } 216 | | OplCommand::Opl3 { 217 | port: _, 218 | address, 219 | data, 220 | } => unsafe { 221 | adlib::write_command(address, data); 222 | }, 223 | OplCommand::Wait { samples } => unsafe { 224 | delay(samples_to_ms(samples as u32)); 225 | if get_keypress() == 1 { 226 | break; 227 | } 228 | }, 229 | OplCommand::SmallWait { n } => unsafe { 230 | delay(samples_to_ms(n as u32 + 1)); 231 | if get_keypress() == 1 { 232 | break; 233 | } 234 | }, 235 | OplCommand::Wait735 => unsafe { 236 | delay(samples_to_ms(735)); 237 | if get_keypress() == 1 { 238 | break; 239 | } 240 | }, 241 | OplCommand::Wait882 => unsafe { 242 | delay(samples_to_ms(882)); 243 | }, 244 | } 245 | } 246 | 247 | // turn off voice for every channel 248 | unsafe { 249 | for i in 0..9 { 250 | adlib::write_command(0xb0 | i, 0x00); 251 | delay(1); 252 | adlib::write_command_l(0xb0 | i, 0x00); 253 | delay(1); 254 | adlib::write_command_r(0xb0 | i, 0x00); 255 | delay(1); 256 | } 257 | } 258 | reset_adlib(); 259 | } 260 | 261 | /// wait a number of cycles 262 | /// (use several thousands of cycles for a visible delay) 263 | fn busy_wait(cycles: usize) { 264 | let mut dummy: u32 = 0; 265 | for i in 0..cycles { 266 | unsafe { 267 | core::ptr::write_volatile(core::hint::black_box(&mut dummy), i as u32); 268 | } 269 | } 270 | } 271 | 272 | #[panic_handler] 273 | fn handle_panic(_info: &PanicInfo) -> ! { 274 | unsafe { 275 | // reset video mode 276 | dos_x::vga::set_video_mode(0x02); 277 | // exit using libc 278 | exit(-1); 279 | core::hint::unreachable_unchecked() 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Eduardo Pinho 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------