Converted to a simple BMP viewer

This commit is contained in:
Lucia Ceionia 2023-10-29 19:27:06 -05:00
parent 875e73d758
commit 5f9d992d6d
10 changed files with 577 additions and 31 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ Cargo.lock
elf2le
a.exe
new.elf
Makefile

View File

@ -9,12 +9,8 @@ edition = "2021"
[dependencies]
heapless = { version = "0.7.16", features = ["ufmt-impl"] }
spin = "0.9.4"
[profile.dev]
panic = "abort"
lto = "fat"
codegen-units = 1
bitvec = { version = "1.0.1", default-features = false, features = ["atomic", "alloc"] }
itertools = { version = "0.11", default-features = false, features = ["use_alloc"] }
[profile.release]
panic = "abort"

View File

@ -7,7 +7,7 @@
"exe-suffix": ".elf",
"linker-flavor": "gcc",
"linker-is-gnu": true,
"llvm-target": "i586-unknown-none-none",
"llvm-target": "i386-unknown-none-none",
"max-atomic-width": 64,
"position-independent-executables": false,
"relocation-model": "static",

131
src/bmp.rs Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -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;
#[allow(dead_code)]
#[repr(packed)]
#[derive(Default)]
pub struct DpmiRegs {
pub edi: 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) {
let mut regs = DpmiRegs::zero();
regs.eax = mode as u32;
real_int(0x10, &mut regs);
}
// BIOS function INT 16,0 Wait for keystroke and read
pub fn getchar() -> u16 {
let mut regs = DpmiRegs::zero();
real_int(0x16, &mut regs);
regs.eax as u16
}
// BIOS function INT 16,1 Get keyboard status
pub fn kb_status() -> Option<u16> {
let mut regs = DpmiRegs::zero();
regs.eax = 0x100;
@ -55,17 +59,167 @@ pub fn kb_status() -> Option<u16> {
}
}
pub fn get_psp() -> *const u8 {
let psp: u32;
pub fn get_psp(buff: &mut [u8; 256]) {
// 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!(
"int 0x21",
in("ax") 0x5100_u16,
out("ebx") psp
"push esi",
"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) {
// DPMI function 0300h - Simulate Real Mode Interrupt
// TODO get error codes from AX/CF
unsafe { asm!(
"int 0x31",
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) {
// DOS DPMI function 21h, AH 9h
// Prints string pointed to
@ -106,14 +306,13 @@ macro_rules! print {
}
#[macro_export]
macro_rules! println {
() => (_raw_print!("\r\n$"));
() => ($crate::_raw_print!("\r\n$"));
($($arg:tt)*) => ($crate::_raw_print!("{}\r\n$", format_args!($($arg)*)));
}
#[doc(hidden)]
pub fn _print(args: Arguments) {
let mut s: alloc::string::String = alloc::string::String::new();
//let mut s: heapless::String<20> = heapless::String::new();
s.write_fmt(args).unwrap();
dpmi_print(s.as_str());
}

View File

@ -3,7 +3,9 @@ use core::fmt::Write;
use heapless::String;
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 fn alloc(&self, layout: Layout) -> *mut u8 {
@ -24,6 +26,7 @@ unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
"int 0x21",
"mov eax, esi",
"mov esi, edx",
// We can calculate the handle ourself
inout("eax") 0x0FF91 => _handle,
inout("ebx") layout.size() as u32 => ptr,
out("edx") _
@ -47,7 +50,7 @@ unsafe impl core::alloc::GlobalAlloc for DpmiAlloc {
"int 0x21",
"mov esi, edx",
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") _
);
}

View File

@ -10,25 +10,154 @@ extern crate alloc;
mod dpmi;
mod dpmi_alloc;
mod panic;
mod vga;
mod bmp;
fn main() {
println!("Hello, world!");
debug_trap!();
println!("Done.");
}
use alloc::{vec, ffi::CString};
use bmp::Bmp;
use vga::Mode13hDisplay;
#[allow(unused_macros)]
#[macro_export]
macro_rules! debug_trap {
() => { unsafe { asm!("int 0x1"); } };
}
const TEST_BMP: &[u8; 5318] = include_bytes!("chicken.bmp");
#[no_mangle]
pub extern "C" fn start() {
unsafe { asm!(
"mov ax, es",
"push ds",
"pop es",
); }
main();
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
); }
}

View File

@ -1,8 +1,8 @@
use crate::dpmi::dpmi_exit;
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
//crate::println!("Panic! {}", info);
fn panic(info: &core::panic::PanicInfo) -> ! {
crate::println!("Panic! {}", info);
dpmi_exit();
}

87
src/vga.rs Normal file
View 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]);
}
}
}