Converted to a simple BMP viewer
This commit is contained in:
parent
875e73d758
commit
5f9d992d6d
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ Cargo.lock
|
|||||||
elf2le
|
elf2le
|
||||||
a.exe
|
a.exe
|
||||||
new.elf
|
new.elf
|
||||||
|
Makefile
|
||||||
|
@ -9,12 +9,8 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
|
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
|
||||||
spin = "0.9.4"
|
spin = "0.9.4"
|
||||||
|
bitvec = { version = "1.0.1", default-features = false, features = ["atomic", "alloc"] }
|
||||||
|
itertools = { version = "0.11", default-features = false, features = ["use_alloc"] }
|
||||||
[profile.dev]
|
|
||||||
panic = "abort"
|
|
||||||
lto = "fat"
|
|
||||||
codegen-units = 1
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
2
dos.json
2
dos.json
@ -7,7 +7,7 @@
|
|||||||
"exe-suffix": ".elf",
|
"exe-suffix": ".elf",
|
||||||
"linker-flavor": "gcc",
|
"linker-flavor": "gcc",
|
||||||
"linker-is-gnu": true,
|
"linker-is-gnu": true,
|
||||||
"llvm-target": "i586-unknown-none-none",
|
"llvm-target": "i386-unknown-none-none",
|
||||||
"max-atomic-width": 64,
|
"max-atomic-width": 64,
|
||||||
"position-independent-executables": false,
|
"position-independent-executables": false,
|
||||||
"relocation-model": "static",
|
"relocation-model": "static",
|
||||||
|
131
src/bmp.rs
Normal file
131
src/bmp.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use bitvec::{view::BitView, field::BitField};
|
||||||
|
|
||||||
|
use crate::println;
|
||||||
|
use crate::vga::Vga18;
|
||||||
|
|
||||||
|
#[repr(packed)]
|
||||||
|
struct PackedBmpHeader {
|
||||||
|
bmp_type: u16,
|
||||||
|
size: u32,
|
||||||
|
reserved: u32,
|
||||||
|
offset: u32,
|
||||||
|
header_size: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
planes: u16,
|
||||||
|
bpp: u16,
|
||||||
|
compression: u32,
|
||||||
|
size_image: u32,
|
||||||
|
xppm: u32,
|
||||||
|
yppm: u32,
|
||||||
|
colors_used: u32,
|
||||||
|
colors_important: u32,
|
||||||
|
}
|
||||||
|
pub struct BmpHeader {
|
||||||
|
pub bmp_type: u16,
|
||||||
|
pub size: u32,
|
||||||
|
pub reserved: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
pub header_size: u32,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub planes: u16,
|
||||||
|
pub bpp: u16,
|
||||||
|
pub compression: u32,
|
||||||
|
pub size_image: u32,
|
||||||
|
pub xppm: u32,
|
||||||
|
pub yppm: u32,
|
||||||
|
pub colors_used: u32,
|
||||||
|
pub colors_important: u32,
|
||||||
|
}
|
||||||
|
impl From::<PackedBmpHeader> for BmpHeader {
|
||||||
|
fn from(value: PackedBmpHeader) -> Self {
|
||||||
|
Self { bmp_type: value.bmp_type, size: value.size, reserved: value.reserved, offset: value.offset, header_size: value.header_size, width: value.width, height: value.height, planes: value.planes, bpp: value.bpp, compression: value.compression, size_image: value.size_image, xppm: value.xppm, yppm: value.yppm, colors_used: value.colors_used, colors_important: value.colors_important }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bmp {
|
||||||
|
pub header: BmpHeader,
|
||||||
|
pub palette_table: alloc::vec::Vec<Vga18>,
|
||||||
|
pub data: Box<[u8]>
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_data(header: &PackedBmpHeader, raw: &[u8]) -> Option<Box<[u8]>> {
|
||||||
|
match header.bpp {
|
||||||
|
8 => {
|
||||||
|
let row_size = (((8 * header.width + 31) >> 5) << 2) as usize;
|
||||||
|
let chunks = raw[..row_size * header.height as usize]
|
||||||
|
.chunks_exact(row_size).rev()
|
||||||
|
.flat_map(|f| f[..header.width as usize].iter().cloned());
|
||||||
|
Some(chunks.collect())
|
||||||
|
},
|
||||||
|
bpp @ (4|2|1) => {
|
||||||
|
let row_size = ((bpp as usize * header.width as usize + 31) >> 5) << 2;
|
||||||
|
let chunks = raw[..row_size * header.height as usize]
|
||||||
|
.chunks_exact(row_size).rev()
|
||||||
|
.map(|row| row.view_bits::<bitvec::order::Msb0>()
|
||||||
|
.chunks(bpp as usize).take(header.width as usize)
|
||||||
|
.map(|b| b.load_le::<u8>()));
|
||||||
|
Some(chunks.flatten().collect())
|
||||||
|
}
|
||||||
|
bpp => {
|
||||||
|
println!("Unsupported BPP {}", bpp);
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_bmp(source: &[u8]) -> Option<Bmp> {
|
||||||
|
// i'm sorry but i love loading file headers like this
|
||||||
|
let header: PackedBmpHeader = unsafe {
|
||||||
|
let mut copy: [u8; size_of::<PackedBmpHeader>()] = [0; size_of::<PackedBmpHeader>()];
|
||||||
|
copy.copy_from_slice(&source[..size_of::<PackedBmpHeader>()]);
|
||||||
|
core::mem::transmute(copy)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 'BM'
|
||||||
|
if header.bmp_type != 0x4D42 {
|
||||||
|
println!("Invalid BMP type");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
if header.size as usize != source.len() {
|
||||||
|
println!("BMP size does not match file length!");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
if header.offset as usize > source.len() {
|
||||||
|
println!("BMP bitmap offset greater than file length!");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
if header.compression != 0 {
|
||||||
|
println!("Compressed BMPs are not supported");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
if {
|
||||||
|
let row_size = ((header.bpp as usize * header.width as usize + 31) >> 5) << 2;
|
||||||
|
let (calc_size, overflow) = row_size.overflowing_mul(header.height as usize);
|
||||||
|
(calc_size > (source.len() - header.offset as usize)) | overflow
|
||||||
|
} {
|
||||||
|
println!("File cannot possibly contain full BMP data!");
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
let color_table = &source[(header.offset - header.colors_used * 4) as usize..];
|
||||||
|
let palette: alloc::vec::Vec<_> = color_table.iter()
|
||||||
|
.tuples().take(header.colors_used as usize)
|
||||||
|
.map(|(b,g,r,_)| Vga18 { red:r>>2,green:g>>2,blue:b>>2 })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let raw_data = &source[header.offset as usize..];
|
||||||
|
let data = load_data(&header, raw_data);
|
||||||
|
|
||||||
|
Some(Bmp {
|
||||||
|
header: BmpHeader::from(header),
|
||||||
|
palette_table: palette,
|
||||||
|
data: data?
|
||||||
|
})
|
||||||
|
}
|
BIN
src/chicken.bmp
Normal file
BIN
src/chicken.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
217
src/dpmi.rs
217
src/dpmi.rs
@ -1,9 +1,11 @@
|
|||||||
use core::{arch::asm, fmt::{Arguments, Write}};
|
#![allow(dead_code)]
|
||||||
|
use core::{arch::asm, fmt::{Arguments, Write}, ffi::CStr, str::FromStr};
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[repr(packed)]
|
#[repr(packed)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct DpmiRegs {
|
pub struct DpmiRegs {
|
||||||
pub edi: u32,
|
pub edi: u32,
|
||||||
pub esi: u32,
|
pub esi: u32,
|
||||||
@ -29,19 +31,21 @@ impl DpmiRegs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
// BIOS function INT 10,0 Set video mode
|
||||||
pub fn set_video_mode(mode: u8) {
|
pub fn set_video_mode(mode: u8) {
|
||||||
let mut regs = DpmiRegs::zero();
|
let mut regs = DpmiRegs::zero();
|
||||||
regs.eax = mode as u32;
|
regs.eax = mode as u32;
|
||||||
real_int(0x10, &mut regs);
|
real_int(0x10, &mut regs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BIOS function INT 16,0 Wait for keystroke and read
|
||||||
pub fn getchar() -> u16 {
|
pub fn getchar() -> u16 {
|
||||||
let mut regs = DpmiRegs::zero();
|
let mut regs = DpmiRegs::zero();
|
||||||
real_int(0x16, &mut regs);
|
real_int(0x16, &mut regs);
|
||||||
regs.eax as u16
|
regs.eax as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BIOS function INT 16,1 Get keyboard status
|
||||||
pub fn kb_status() -> Option<u16> {
|
pub fn kb_status() -> Option<u16> {
|
||||||
let mut regs = DpmiRegs::zero();
|
let mut regs = DpmiRegs::zero();
|
||||||
regs.eax = 0x100;
|
regs.eax = 0x100;
|
||||||
@ -55,17 +59,167 @@ pub fn kb_status() -> Option<u16> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_psp() -> *const u8 {
|
pub fn get_psp(buff: &mut [u8; 256]) {
|
||||||
let psp: u32;
|
// DOS DPMI function 21h,AH 62h - Get PSP Selector
|
||||||
|
// Out: EBX = PSP selector
|
||||||
|
// this really was hurting me so i just did it in assembly
|
||||||
unsafe { asm!(
|
unsafe { asm!(
|
||||||
"int 0x21",
|
"int 0x21",
|
||||||
in("ax") 0x5100_u16,
|
"push esi",
|
||||||
out("ebx") psp
|
"push edi",
|
||||||
|
"mov ds, bx",
|
||||||
|
"xor esi, esi",
|
||||||
|
"mov ecx, 256",
|
||||||
|
"rep movsb",
|
||||||
|
"pop edi",
|
||||||
|
"pop esi",
|
||||||
|
"push es",
|
||||||
|
"pop ds",
|
||||||
|
in("ax") 0x6200_u16,
|
||||||
|
out("ebx") _,
|
||||||
|
in("edi") buff.as_mut_ptr(),
|
||||||
); }
|
); }
|
||||||
(psp << 4) as *const u8
|
}
|
||||||
|
|
||||||
|
pub fn get_args() -> alloc::vec::Vec<alloc::string::String> {
|
||||||
|
let mut buff = [0_u8; 256];
|
||||||
|
get_psp(&mut buff);
|
||||||
|
let cmd_byte_cnt = buff[0x80];
|
||||||
|
let cmd_line = &buff[0x81..0x100.min(0x81+cmd_byte_cnt as usize)];
|
||||||
|
// this is a horrible way of doing this, but i'm lazy
|
||||||
|
core::str::from_utf8(cmd_line).unwrap()
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|s| alloc::string::String::from_str(s).unwrap())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct File {
|
||||||
|
handle: u32,
|
||||||
|
size: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
pub fn open(string: &CStr) -> Option<Self> {
|
||||||
|
// DOS DPMI function 21h, AH 3Dh - Open File
|
||||||
|
// In:
|
||||||
|
// AL = access mode
|
||||||
|
// DS:EDX = pointer to ASCIIZ file name
|
||||||
|
// Out:
|
||||||
|
// if successful:
|
||||||
|
// CF clear
|
||||||
|
// EAX = file handle
|
||||||
|
//
|
||||||
|
// if failed:
|
||||||
|
// CF set
|
||||||
|
// EAX = DOS error code
|
||||||
|
let err: u8;
|
||||||
|
let eax: u32;
|
||||||
|
unsafe { asm!(
|
||||||
|
"int 0x21",
|
||||||
|
"setc bl",
|
||||||
|
inout("eax") 0x00003D00_u32 => eax,
|
||||||
|
in("edx") string.as_ptr(),
|
||||||
|
inout("bl") 0_u8 => err
|
||||||
|
);}
|
||||||
|
|
||||||
|
// Error TODO return error code instead of printing
|
||||||
|
if err == 1 {
|
||||||
|
crate::println!("Could not open file \"{}\" ({}).", string.to_str().unwrap(), eax);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = Self {
|
||||||
|
handle: eax,
|
||||||
|
size: 0
|
||||||
|
};
|
||||||
|
let size = file.find_size()?;
|
||||||
|
file.size = size;
|
||||||
|
|
||||||
|
return Some(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_size(&self) -> u32 { self.size }
|
||||||
|
|
||||||
|
fn seek(&mut self, origin: u8) -> Option<u32> {
|
||||||
|
// DOS DPMI function 21h, AH 42h - Set Current File Position
|
||||||
|
// In:
|
||||||
|
// AH = 42h
|
||||||
|
// AL = origin of move:
|
||||||
|
//
|
||||||
|
// 00h = start
|
||||||
|
// 01h = current
|
||||||
|
// 02h = end
|
||||||
|
//
|
||||||
|
// ECX:EDX = file position
|
||||||
|
// Out:
|
||||||
|
// if successful:
|
||||||
|
// CF clear
|
||||||
|
// EDX:EAX = current file position
|
||||||
|
//
|
||||||
|
// if failed:
|
||||||
|
// CF set
|
||||||
|
// EAX = DOS error code
|
||||||
|
let err: u32;
|
||||||
|
let eax: u32;
|
||||||
|
let edx: u32;
|
||||||
|
unsafe { asm!(
|
||||||
|
"int 0x21",
|
||||||
|
"setc bl",
|
||||||
|
inout("eax") 0x00004200_u32 | origin as u32 => eax,
|
||||||
|
in("ecx") 0,
|
||||||
|
inout("edx") 0 => edx,
|
||||||
|
inout("ebx") self.handle => err
|
||||||
|
);}
|
||||||
|
|
||||||
|
// Error TODO return error code
|
||||||
|
if err == 1 { return None }
|
||||||
|
|
||||||
|
return Some(eax | (edx << 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_size(&mut self) -> Option<u32> {
|
||||||
|
let size = self.seek(2);
|
||||||
|
self.seek(0)?;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, buffer: &mut [u8]) -> Option<u32> {
|
||||||
|
// DOS DPMI function 21h, AH 3Fh - Read File
|
||||||
|
// In:
|
||||||
|
// AH = 3Fh
|
||||||
|
// EBX = file handle
|
||||||
|
// ECX = number of bytes to read (size)
|
||||||
|
// DS:EDX = pointer to buffer to read to (addr)
|
||||||
|
// Out:
|
||||||
|
// if successful:
|
||||||
|
// CF clear
|
||||||
|
// EAX = number of bytes read
|
||||||
|
//
|
||||||
|
// if failed:
|
||||||
|
// CF set
|
||||||
|
// EAX = DOS error code
|
||||||
|
let err: u32;
|
||||||
|
let eax: u32;
|
||||||
|
unsafe { asm!(
|
||||||
|
"int 0x21",
|
||||||
|
"mov ebx, 0",
|
||||||
|
"setc bl",
|
||||||
|
inout("eax") 0x00003F00_u32 => eax,
|
||||||
|
inout("ebx") self.handle => err,
|
||||||
|
in("ecx") buffer.len(),
|
||||||
|
in("edx") buffer.as_mut_ptr(),
|
||||||
|
);}
|
||||||
|
|
||||||
|
// Error TODO return error code
|
||||||
|
if err == 1 { return None }
|
||||||
|
|
||||||
|
return Some(eax);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn real_int(int: u8, regs: &mut DpmiRegs) {
|
pub fn real_int(int: u8, regs: &mut DpmiRegs) {
|
||||||
|
// DPMI function 0300h - Simulate Real Mode Interrupt
|
||||||
|
// TODO get error codes from AX/CF
|
||||||
unsafe { asm!(
|
unsafe { asm!(
|
||||||
"int 0x31",
|
"int 0x31",
|
||||||
in("bx") 0x0000_u16 | int as u16,
|
in("bx") 0x0000_u16 | int as u16,
|
||||||
@ -75,6 +229,52 @@ pub fn real_int(int: u8, regs: &mut DpmiRegs) {
|
|||||||
);}
|
);}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct IntHandler {
|
||||||
|
interrupt: u8,
|
||||||
|
selector: u16,
|
||||||
|
offset: u32
|
||||||
|
}
|
||||||
|
impl IntHandler {
|
||||||
|
pub fn new(interrupt: u8) -> Self {
|
||||||
|
// get old handler
|
||||||
|
// DPMI function 0204h - Get Protected Mode Interrupt Vector
|
||||||
|
let selector: u16;
|
||||||
|
let offset: u32;
|
||||||
|
unsafe { asm!(
|
||||||
|
"int 0x31",
|
||||||
|
in("ax") 0x0204,
|
||||||
|
in("bl") interrupt,
|
||||||
|
out("edx") offset,
|
||||||
|
out("cx") selector
|
||||||
|
); }
|
||||||
|
Self { interrupt, offset, selector }
|
||||||
|
}
|
||||||
|
pub fn set_handler(&mut self, f: extern "x86-interrupt" fn()) {
|
||||||
|
// install new handler
|
||||||
|
// DPMI function 0205h - Set Protected Mode Interrupt Vector
|
||||||
|
let func_ptr = f as *const () as u32;
|
||||||
|
unsafe { asm!(
|
||||||
|
"mov cx, cs",
|
||||||
|
"int 0x31",
|
||||||
|
inout("ax") 0x0205 => _,
|
||||||
|
in("bl") self.interrupt,
|
||||||
|
in("edx") func_ptr,
|
||||||
|
out("cx") _
|
||||||
|
); }
|
||||||
|
}
|
||||||
|
pub fn restore_handler(&mut self) {
|
||||||
|
// restore original handler
|
||||||
|
// DPMI function 0205h - Set Protected Mode Interrupt Vector
|
||||||
|
unsafe { asm!(
|
||||||
|
"int 0x31",
|
||||||
|
inout("ax") 0x0205 => _,
|
||||||
|
in("bl") self.interrupt,
|
||||||
|
in("cx") self.selector,
|
||||||
|
in("edx") self.offset,
|
||||||
|
); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dpmi_print(string: &str) {
|
pub fn dpmi_print(string: &str) {
|
||||||
// DOS DPMI function 21h, AH 9h
|
// DOS DPMI function 21h, AH 9h
|
||||||
// Prints string pointed to
|
// Prints string pointed to
|
||||||
@ -106,14 +306,13 @@ macro_rules! print {
|
|||||||
}
|
}
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! println {
|
macro_rules! println {
|
||||||
() => (_raw_print!("\r\n$"));
|
() => ($crate::_raw_print!("\r\n$"));
|
||||||
($($arg:tt)*) => ($crate::_raw_print!("{}\r\n$", format_args!($($arg)*)));
|
($($arg:tt)*) => ($crate::_raw_print!("{}\r\n$", format_args!($($arg)*)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn _print(args: Arguments) {
|
pub fn _print(args: Arguments) {
|
||||||
let mut s: alloc::string::String = alloc::string::String::new();
|
let mut s: alloc::string::String = alloc::string::String::new();
|
||||||
//let mut s: heapless::String<20> = heapless::String::new();
|
|
||||||
s.write_fmt(args).unwrap();
|
s.write_fmt(args).unwrap();
|
||||||
dpmi_print(s.as_str());
|
dpmi_print(s.as_str());
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ use core::fmt::Write;
|
|||||||
use heapless::String;
|
use heapless::String;
|
||||||
|
|
||||||
pub struct DpmiAlloc { }
|
pub struct DpmiAlloc { }
|
||||||
impl DpmiAlloc { const fn new() -> Self { DpmiAlloc { } } }
|
impl DpmiAlloc {
|
||||||
|
const fn new() -> Self { DpmiAlloc {} }
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
|
unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
|
||||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
@ -24,6 +26,7 @@ unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
|
|||||||
"int 0x21",
|
"int 0x21",
|
||||||
"mov eax, esi",
|
"mov eax, esi",
|
||||||
"mov esi, edx",
|
"mov esi, edx",
|
||||||
|
// We can calculate the handle ourself
|
||||||
inout("eax") 0x0FF91 => _handle,
|
inout("eax") 0x0FF91 => _handle,
|
||||||
inout("ebx") layout.size() as u32 => ptr,
|
inout("ebx") layout.size() as u32 => ptr,
|
||||||
out("edx") _
|
out("edx") _
|
||||||
@ -47,7 +50,7 @@ unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
|
|||||||
"int 0x21",
|
"int 0x21",
|
||||||
"mov esi, edx",
|
"mov esi, edx",
|
||||||
in("eax") 0x0FF92,
|
in("eax") 0x0FF92,
|
||||||
in("ebx") ptr as u32 - 0x10, // handle should be just 0x10 less than the pointer
|
in("ebx") ptr as u32 - 0x10, // in dos32a the handle should be just 0x10 less than the pointer
|
||||||
out("edx") _
|
out("edx") _
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
149
src/main.rs
149
src/main.rs
@ -10,25 +10,154 @@ extern crate alloc;
|
|||||||
mod dpmi;
|
mod dpmi;
|
||||||
mod dpmi_alloc;
|
mod dpmi_alloc;
|
||||||
mod panic;
|
mod panic;
|
||||||
|
mod vga;
|
||||||
|
mod bmp;
|
||||||
|
|
||||||
fn main() {
|
use alloc::{vec, ffi::CString};
|
||||||
println!("Hello, world!");
|
use bmp::Bmp;
|
||||||
debug_trap!();
|
use vga::Mode13hDisplay;
|
||||||
println!("Done.");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
const TEST_BMP: &[u8; 5318] = include_bytes!("chicken.bmp");
|
||||||
#[macro_export]
|
|
||||||
macro_rules! debug_trap {
|
|
||||||
() => { unsafe { asm!("int 0x1"); } };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn start() {
|
pub extern "C" fn start() {
|
||||||
unsafe { asm!(
|
unsafe { asm!(
|
||||||
|
"mov ax, es",
|
||||||
"push ds",
|
"push ds",
|
||||||
"pop es",
|
"pop es",
|
||||||
); }
|
); }
|
||||||
main();
|
main();
|
||||||
dpmi::dpmi_exit();
|
dpmi::dpmi_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = dpmi::get_args();
|
||||||
|
let filename = {
|
||||||
|
if args.is_empty() || args[0].is_empty() {
|
||||||
|
println!("Filename required.");
|
||||||
|
println!("Press Q to exit, or any key to continue with the default image.");
|
||||||
|
match dpmi::getchar() as u8 {
|
||||||
|
b'q' | b'Q' => return,
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(&*args[0])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to load BMP file from filename, or else use the included test image
|
||||||
|
let bmp = {
|
||||||
|
let mut bmp_buff;
|
||||||
|
let src = if let Some(filename) = filename {
|
||||||
|
println!("Loading BMP from {}...", filename);
|
||||||
|
let mut file = match dpmi::File::open(&CString::new(filename).unwrap()) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => {
|
||||||
|
println!("Could not open file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
println!("File size: {} bytes", file.get_size());
|
||||||
|
|
||||||
|
bmp_buff = vec![0; file.get_size() as usize];
|
||||||
|
file.read(&mut bmp_buff);
|
||||||
|
|
||||||
|
&bmp_buff[..]
|
||||||
|
} else {
|
||||||
|
TEST_BMP
|
||||||
|
};
|
||||||
|
if let Some(bmp) = bmp::load_bmp(src) {
|
||||||
|
bmp
|
||||||
|
} else {
|
||||||
|
println!("Could not open BMP. Exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Width x Height x BPP: {}x{}x{}", bmp.header.width, bmp.header.height, bmp.header.bpp);
|
||||||
|
println!("Colors Used, Important: {},{}", bmp.header.colors_used, bmp.header.colors_important);
|
||||||
|
println!("Arrow keys to move, 1-9 to change speed, Q to exit.");
|
||||||
|
println!("Press any key to continue.");
|
||||||
|
dpmi::getchar();
|
||||||
|
|
||||||
|
// mode 13h, 320x200 256 color graphics
|
||||||
|
dpmi::set_video_mode(0x13);
|
||||||
|
// Get screen buffer
|
||||||
|
let mut vga = Mode13hDisplay::default();
|
||||||
|
|
||||||
|
// sets the VGA screen palette to the BMP color palette
|
||||||
|
vga::set_vga_dac_colors(0, &bmp.palette_table);
|
||||||
|
|
||||||
|
// set up new keyboard handler
|
||||||
|
// could do getchar, but this is more fun
|
||||||
|
let mut kb_handler = dpmi::IntHandler::new(9);
|
||||||
|
kb_handler.set_handler(keyboard_int_handler);
|
||||||
|
|
||||||
|
let mut last_scancode = 0xFF;
|
||||||
|
let mut delta = 1;
|
||||||
|
let mut pos = Position { x: 0, y: 0 };
|
||||||
|
loop {
|
||||||
|
let scancode = { *SCANCODE.read() };
|
||||||
|
if scancode != last_scancode {
|
||||||
|
match scancode {
|
||||||
|
0x48 => { pos.y += delta; }, // up
|
||||||
|
0x4B => { pos.x -= delta; }, // left
|
||||||
|
0x4D => { pos.x += delta; }, // right
|
||||||
|
0x50 => { pos.y -= delta; }, // down
|
||||||
|
s @ 0x02..=0x0A => { delta = s as isize - 1; }, // 1-9
|
||||||
|
0x10 => break, // q
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
last_scancode = scancode;
|
||||||
|
draw_loop(&mut vga, &bmp, &pos);
|
||||||
|
}
|
||||||
|
// halt processor so we don't burn the CPU
|
||||||
|
unsafe { asm!("hlt"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore old keyboard handler
|
||||||
|
kb_handler.restore_handler();
|
||||||
|
|
||||||
|
// mode 3h, text mode graphics, DOS default
|
||||||
|
dpmi::set_video_mode(0x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_loop(vga: &mut Mode13hDisplay, bmp: &Bmp, pos: &Position) {
|
||||||
|
vga.clear();
|
||||||
|
vga.copy_to_screen(pos.x, pos.y, bmp.header.width as usize, bmp.header.height as usize, &bmp.data);
|
||||||
|
// TODO wait for blanking interval
|
||||||
|
vga.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Position {
|
||||||
|
x: isize,
|
||||||
|
y: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
static SCANCODE: spin::RwLock<u8> = spin::RwLock::new(0);
|
||||||
|
pub extern "x86-interrupt" fn keyboard_int_handler() {
|
||||||
|
let old_ds: u16;
|
||||||
|
unsafe { asm!(
|
||||||
|
"mov bx, ds",
|
||||||
|
"mov ax, es",
|
||||||
|
"mov ds, ax",
|
||||||
|
out("bx") old_ds
|
||||||
|
); }
|
||||||
|
|
||||||
|
let code: u8;
|
||||||
|
unsafe { asm!(
|
||||||
|
"in al, 0x60",
|
||||||
|
out("al") code
|
||||||
|
); }
|
||||||
|
|
||||||
|
let mut scan = SCANCODE.write();
|
||||||
|
*scan = code;
|
||||||
|
drop(scan);
|
||||||
|
|
||||||
|
unsafe { asm!(
|
||||||
|
"mov ds, bx",
|
||||||
|
"out 0x20, al",
|
||||||
|
in("al") 0x20_u8,
|
||||||
|
in("bx") old_ds
|
||||||
|
); }
|
||||||
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::dpmi::dpmi_exit;
|
use crate::dpmi::dpmi_exit;
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
//crate::println!("Panic! {}", info);
|
crate::println!("Panic! {}", info);
|
||||||
dpmi_exit();
|
dpmi_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
src/vga.rs
Normal file
87
src/vga.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
#[derive(Copy,Clone,Default,PartialEq)]
|
||||||
|
pub struct Vga18 {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mode13h_vga_arr() -> &'static mut [[u8; 320]; 200] {
|
||||||
|
unsafe { &mut *(0xa0000 as *mut [[u8; 320]; 200]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn outb(port: u16, data: u8) {
|
||||||
|
asm! {
|
||||||
|
"out dx, al",
|
||||||
|
in("dx") port,
|
||||||
|
in("al") data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vga_dac_colors(start_index: u8, colors: &[Vga18]) {
|
||||||
|
if colors.is_empty() { return }
|
||||||
|
unsafe { outb(0x3c8, start_index); }
|
||||||
|
for (i, &Vga18 { red, green, blue }) in colors.iter().enumerate() {
|
||||||
|
if i + start_index as usize >= 256 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
outb(0x3c9, red);
|
||||||
|
outb(0x3c9, green);
|
||||||
|
outb(0x3c9, blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mode13hDisplay {
|
||||||
|
buffer: [[u8; 320]; 200]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Mode13hDisplay {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { buffer: [[0; 320]; 200] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode13hDisplay {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn flush(&self) {
|
||||||
|
let vga = mode13h_vga_arr();
|
||||||
|
*vga = self.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.buffer = [[0; 320]; 200];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_to_screen(&mut self, screen_col: isize, screen_line: isize, src_width: usize, src_height: usize, bytes: &[u8]) {
|
||||||
|
let x = screen_col;
|
||||||
|
let y = screen_line;
|
||||||
|
|
||||||
|
for l in 0..src_height {
|
||||||
|
let l_y = l as isize + y;
|
||||||
|
// past screen
|
||||||
|
if l_y >= 200 { break; }
|
||||||
|
// before screen
|
||||||
|
else if l_y < 0 { continue; }
|
||||||
|
// past screen
|
||||||
|
if -x >= src_width as isize { break; }
|
||||||
|
|
||||||
|
let line_len = if x >= 0 {
|
||||||
|
(320 - x).min(src_width as isize)
|
||||||
|
} else {
|
||||||
|
320.min(src_width as isize + x)
|
||||||
|
};
|
||||||
|
|
||||||
|
let src_off = if x >= 0 {
|
||||||
|
l * src_width
|
||||||
|
} else { ((l * src_width) as isize - x) as usize };
|
||||||
|
|
||||||
|
let x_adj = x.max(0);
|
||||||
|
|
||||||
|
self.buffer[l_y as usize][x_adj as usize..x_adj as usize+line_len as usize].copy_from_slice(&bytes[src_off..src_off+line_len as usize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user