1use crate::cga::{Cga, CgaKind, CgaMonitor};
4use crate::crt::{Beam, Crt, FrameView, Scanout, Timing};
5use crate::hercules::Hercules;
6use crate::input::{Keyboard, Mouse};
7use crate::palette::Phosphor;
8use crate::speaker::PcSpeaker;
9use crate::svga::{Svga, VbeMode};
10use crate::vga::{Vga, VgaKind};
11use game_toolkit_dosbus::{Channel, Tick};
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum Adapter {
16 Vga,
18 Ega,
20 Cga(CgaMonitor),
22 Tandy,
24 Hercules(Phosphor),
26 Svga { memory_kib: u32 },
28}
29
30#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct BusTiming {
37 pub mem8_ns: f64,
39 pub mem16_ns: f64,
41 pub mem32_ns: f64,
43 pub io_ns: f64,
45}
46
47impl BusTiming {
48 pub const INSTANT: Self = Self {
51 mem8_ns: 0.0,
52 mem16_ns: 0.0,
53 mem32_ns: 0.0,
54 io_ns: MIN_IO_NS,
55 };
56 pub const ISA8: Self = Self {
58 mem8_ns: 1000.0,
59 mem16_ns: 2000.0,
60 mem32_ns: 4000.0,
61 io_ns: 1000.0,
62 };
63 pub const ISA16: Self = Self {
66 mem8_ns: 500.0,
67 mem16_ns: 500.0,
68 mem32_ns: 1000.0,
69 io_ns: 800.0,
70 };
71 pub const VLB: Self = Self {
73 mem8_ns: 100.0,
74 mem16_ns: 100.0,
75 mem32_ns: 120.0,
76 io_ns: 300.0,
77 };
78 pub const PCI: Self = Self {
80 mem8_ns: 50.0,
81 mem16_ns: 50.0,
82 mem32_ns: 60.0,
83 io_ns: 200.0,
84 };
85}
86
87pub const MIN_IO_NS: f64 = 100.0;
90
91pub mod cpu {
93 pub const XT_8088: f64 = 4_772_727.0;
95 pub const AT_286: f64 = 8_000_000.0;
97 pub const I386DX_33: f64 = 33_000_000.0;
98 pub const I486DX2_66: f64 = 66_000_000.0;
99 pub const PENTIUM_100: f64 = 100_000_000.0;
100}
101
102#[derive(Clone, Copy, Debug)]
104pub struct MachineConfig {
105 pub adapter: Adapter,
106 pub bus: BusTiming,
107 pub cpu_hz: f64,
109 pub cga_snow: bool,
111}
112
113impl Default for MachineConfig {
114 fn default() -> Self {
115 Self {
116 adapter: Adapter::Vga,
117 bus: BusTiming::VLB,
118 cpu_hz: cpu::I486DX2_66,
119 cga_snow: true,
120 }
121 }
122}
123
124impl MachineConfig {
125 pub fn new(adapter: Adapter) -> Self {
126 let mut c = Self {
127 adapter,
128 ..Default::default()
129 };
130 if matches!(
131 adapter,
132 Adapter::Cga(_) | Adapter::Tandy | Adapter::Hercules(_)
133 ) {
134 c.bus = BusTiming::ISA8;
135 c.cpu_hz = cpu::XT_8088;
136 }
137 if adapter == Adapter::Ega {
138 c.bus = BusTiming::ISA16;
139 c.cpu_hz = cpu::AT_286;
140 }
141 c
142 }
143
144 pub fn with_bus(mut self, bus: BusTiming) -> Self {
145 self.bus = bus;
146 self
147 }
148
149 pub fn with_cpu(mut self, hz: f64) -> Self {
150 self.cpu_hz = hz;
151 self
152 }
153}
154
155pub enum Video {
157 Vga(Box<Vga>),
158 Cga(Box<Cga>),
159 Hercules(Box<Hercules>),
160 Svga(Box<Svga>),
161}
162
163macro_rules! with_card {
164 ($video:expr, $c:ident => $e:expr) => {
165 match $video {
166 Video::Vga($c) => $e,
167 Video::Cga($c) => $e,
168 Video::Hercules($c) => $e,
169 Video::Svga($c) => $e,
170 }
171 };
172}
173
174impl Video {
175 fn timing(&mut self) -> Timing {
176 with_card!(self, c => c.timing())
177 }
178
179 fn take_dirty(&mut self) -> bool {
180 match self {
181 Video::Vga(c) => std::mem::take(&mut c.timing_dirty),
182 Video::Cga(c) => std::mem::take(&mut c.timing_dirty),
183 Video::Hercules(c) => std::mem::take(&mut c.timing_dirty),
184 Video::Svga(c) => c.take_dirty(),
185 }
186 }
187
188 pub fn resolution(&self) -> (u32, u32) {
190 with_card!(self, c => c.logical_resolution())
191 }
192
193 pub fn text_geometry(&self) -> Option<(u32, u32, u32)> {
195 match self {
196 Video::Vga(c) => c.text_geometry(),
197 Video::Cga(c) => c.text_geometry(),
198 Video::Hercules(c) => c.text_geometry(),
199 Video::Svga(c) => {
200 if c.vbe.is_some() {
201 None
202 } else {
203 c.vga.text_geometry()
204 }
205 }
206 }
207 }
208
209 pub fn as_vga(&self) -> Option<&Vga> {
210 match self {
211 Video::Vga(v) => Some(v),
212 Video::Svga(s) => Some(&s.vga),
213 _ => None,
214 }
215 }
216
217 pub fn as_cga(&self) -> Option<&Cga> {
218 match self {
219 Video::Cga(c) => Some(c),
220 _ => None,
221 }
222 }
223
224 pub fn as_hercules(&self) -> Option<&Hercules> {
225 match self {
226 Video::Hercules(h) => Some(h),
227 _ => None,
228 }
229 }
230
231 pub fn as_svga(&self) -> Option<&Svga> {
232 match self {
233 Video::Svga(s) => Some(s),
234 _ => None,
235 }
236 }
237
238 fn bios_mode(&self) -> u8 {
239 match self {
240 Video::Vga(v) => v.bios_mode,
241 Video::Cga(c) => c.bios_mode,
242 Video::Hercules(h) => h.bios_mode,
243 Video::Svga(s) => s.vga.bios_mode,
244 }
245 }
246}
247
248pub const PIT_HZ: f64 = Tick::HZ_NUMERATOR as f64 / (Tick::HZ_DENOMINATOR * 12) as f64;
250
251pub struct Dos {
258 now: f64,
259 crt: Crt,
260 video: Video,
261 bus: BusTiming,
262 cpu_hz: f64,
263 keyboard: Keyboard,
264 mouse: Mouse,
265 timer_pending: u64,
267 timer_taken: Tick,
269 exit: bool,
270 adapter: Adapter,
271 pub(crate) tty: (u32, u32),
273 pub(crate) speaker: PcSpeaker,
274}
275
276impl Dos {
277 pub fn new(config: MachineConfig) -> Self {
278 let mut video = match config.adapter {
279 Adapter::Vga => Video::Vga(Box::new(Vga::new(VgaKind::Vga))),
280 Adapter::Ega => Video::Vga(Box::new(Vga::new(VgaKind::Ega))),
281 Adapter::Cga(m) => {
282 let mut c = Cga::new(CgaKind::Cga);
283 c.set_monitor(m);
284 c.snow = config.cga_snow;
285 Video::Cga(Box::new(c))
286 }
287 Adapter::Tandy => Video::Cga(Box::new(Cga::new(CgaKind::Tandy))),
288 Adapter::Hercules(p) => Video::Hercules(Box::new(Hercules::new(p))),
289 Adapter::Svga { memory_kib } => {
290 Video::Svga(Box::new(Svga::new(memory_kib as usize * 1024)))
291 }
292 };
293 let timing = video.timing();
294 video.take_dirty();
295 let mut dos = Self {
296 now: 0.0,
297 crt: Crt::new(timing, 0.0),
298 video,
299 bus: config.bus,
300 cpu_hz: config.cpu_hz,
301 keyboard: Keyboard::default(),
302 mouse: Mouse::default(),
303 timer_pending: 0,
304 timer_taken: Tick::ZERO,
305 exit: false,
306 adapter: config.adapter,
307 tty: (0, 0),
308 speaker: PcSpeaker::new(),
309 };
310 let mode = dos.video.bios_mode();
311 dos.mouse
312 .reset_for_mode(mode, matches!(config.adapter, Adapter::Hercules(_)));
313 dos
314 }
315
316 pub fn adapter(&self) -> Adapter {
317 self.adapter
318 }
319
320 pub fn video(&self) -> &Video {
322 &self.video
323 }
324
325 pub fn now_ns(&self) -> f64 {
330 self.now
331 }
332
333 pub(crate) fn now_tick(&self) -> Tick {
335 Tick::from_secs_f64(self.now * 1e-9)
336 }
337
338 pub fn now(&self) -> f64 {
340 self.now * 1e-9
341 }
342
343 pub fn cpu(&mut self, cycles: u64) {
345 self.now += cycles as f64 * 1e9 / self.cpu_hz;
346 }
347
348 pub fn cpu_hz(&self) -> f64 {
349 self.cpu_hz
350 }
351
352 pub fn set_cpu_hz(&mut self, hz: f64) {
353 self.cpu_hz = hz.max(1.0);
354 }
355
356 pub fn bus(&self) -> BusTiming {
357 self.bus
358 }
359
360 pub fn set_bus(&mut self, bus: BusTiming) {
362 self.bus = bus;
363 }
364
365 pub fn delay_us(&mut self, us: f64) {
367 self.now += us * 1000.0;
368 }
369
370 pub fn idle_until(&mut self, t_ns: f64) {
372 if t_ns > self.now {
373 self.now = t_ns;
374 }
375 self.sync();
376 }
377
378 pub fn sync(&mut self) {
383 let target = self.crt.dot_at(self.now);
384 with_card!(&mut self.video, c => self.crt.advance(&mut **c, target));
385 }
386
387 fn check_timing(&mut self) {
388 if self.video.take_dirty() {
389 let t = self.video.timing();
390 with_card!(&mut self.video, c => self.crt.retime(&mut **c, t, self.now));
391 }
392 }
393
394 pub fn beam(&self) -> Beam {
396 self.crt.beam_at(self.now)
397 }
398
399 pub fn timing(&self) -> Timing {
401 *self.crt.timing()
402 }
403
404 pub fn refresh_hz(&self) -> f64 {
406 self.crt.timing().refresh_hz()
407 }
408
409 pub fn resolution(&self) -> (u32, u32) {
411 self.video.resolution()
412 }
413
414 fn wait_dot(&mut self, line: u32, dot: u32) {
415 let from = self.crt.dot_at(self.now) + 1;
416 let g = self.crt.next_dot(from, line, dot);
417 self.now = self
418 .now
419 .max(self.crt.time_of(g) + 0.5 / (self.crt.timing().dot_clock_hz * 1e-9));
420 self.sync();
421 }
422
423 pub fn wait_vretrace(&mut self) {
426 let t = *self.crt.timing();
427 self.wait_dot(t.v_retrace_start, 0);
428 }
429
430 pub fn wait_vretrace_end(&mut self) {
432 if self.beam().v_retrace {
433 let t = *self.crt.timing();
434 self.wait_dot(t.v_retrace_end, 0);
435 }
436 }
437
438 pub fn wait_vblank(&mut self) {
441 let t = *self.crt.timing();
442 self.wait_dot(t.v_blank_start, 0);
443 }
444
445 pub fn wait_frame_start(&mut self) {
447 self.wait_dot(0, 0);
448 }
449
450 pub fn wait_line(&mut self, line: u32) {
452 self.wait_dot(line, 0);
453 }
454
455 pub fn wait_hblank(&mut self) {
458 let b = self.beam();
459 let t = *self.crt.timing();
460 self.wait_dot(b.line, t.h_active);
461 }
462
463 pub fn wait_beam(&mut self, line: u32, dot: u32) {
465 self.wait_dot(line, dot);
466 }
467
468 pub fn frames(&self) -> u64 {
470 self.crt.frames_completed()
471 }
472
473 pub fn frame(&self) -> FrameView<'_> {
478 self.crt.frame()
479 }
480
481 pub fn live_frame(&self) -> FrameView<'_> {
484 self.crt.live()
485 }
486
487 pub fn outb(&mut self, port: u16, v: u8) {
492 self.now += self.bus.io_ns.max(MIN_IO_NS);
493 self.sync();
494 match port {
495 0x40 | 0x43 => {
497 self.count_timer_ticks();
500 self.speaker.outb(self.now_tick(), port, v);
501 }
502 0x41 | 0x42 | 0x61 => self.speaker.outb(self.now_tick(), port, v),
503 _ => with_card!(&mut self.video, c => c.port_out(port, v)),
504 }
505 self.check_timing();
506 }
507
508 pub fn outw(&mut self, port: u16, v: u16) {
511 self.outb(port, v as u8);
512 self.outb(port + 1, (v >> 8) as u8);
513 }
514
515 pub fn inb(&mut self, port: u16) -> u8 {
517 self.now += self.bus.io_ns.max(MIN_IO_NS);
518 let beam = self.crt.beam_at(self.now);
519 match port {
520 0x60 => self.keyboard.port60(),
521 0x40..=0x43 | 0x61 => self.speaker.inb(self.now_tick(), port),
522 _ => {
523 self.sync();
524 with_card!(&mut self.video, c => c.port_in(port, &beam))
525 }
526 }
527 }
528
529 fn mem_write_raw(&mut self, addr: u32, v: u8) {
533 let beam = self.crt.beam_at(self.now);
534 match &mut self.video {
535 Video::Vga(c) => {
536 c.mem_write(addr, v);
537 }
538 Video::Cga(c) => {
539 c.mem_write(addr, v, &beam);
540 }
541 Video::Hercules(c) => {
542 c.mem_write(addr, v);
543 }
544 Video::Svga(c) => {
545 c.mem_write(addr, v);
546 }
547 }
548 }
549
550 fn mem_read_raw(&mut self, addr: u32) -> u8 {
551 let beam = self.crt.beam_at(self.now);
552 match &mut self.video {
553 Video::Vga(c) => c.mem_read(addr),
554 Video::Cga(c) => c.mem_read(addr, &beam),
555 Video::Hercules(c) => c.mem_read(addr),
556 Video::Svga(c) => c.mem_read(addr),
557 }
558 .unwrap_or(0xFF)
559 }
560
561 pub fn poke(&mut self, addr: u32, v: u8) {
563 self.now += self.bus.mem8_ns;
564 self.sync();
565 self.mem_write_raw(addr, v);
566 }
567
568 pub fn peek(&mut self, addr: u32) -> u8 {
570 self.now += self.bus.mem8_ns;
571 self.sync();
572 self.mem_read_raw(addr)
573 }
574
575 pub fn pokew(&mut self, addr: u32, v: u16) {
577 self.now += self.bus.mem16_ns;
578 self.sync();
579 self.mem_write_raw(addr, v as u8);
580 self.mem_write_raw(addr + 1, (v >> 8) as u8);
581 }
582
583 pub fn peekw(&mut self, addr: u32) -> u16 {
584 self.now += self.bus.mem16_ns;
585 self.sync();
586 let lo = self.mem_read_raw(addr) as u16;
587 lo | (self.mem_read_raw(addr + 1) as u16) << 8
588 }
589
590 pub fn poke32(&mut self, addr: u32, v: u32) {
592 self.now += self.bus.mem32_ns;
593 self.sync();
594 for (i, b) in v.to_le_bytes().into_iter().enumerate() {
595 self.mem_write_raw(addr + i as u32, b);
596 }
597 }
598
599 pub fn poke_slice(&mut self, addr: u32, data: &[u8]) {
601 self.write_block(addr, data.len(), |i| data[i]);
602 }
603
604 pub fn fill(&mut self, addr: u32, len: usize, v: u8) {
606 self.write_block(addr, len, |_| v);
607 }
608
609 fn write_block(&mut self, addr: u32, len: usize, byte: impl Fn(usize) -> u8) {
610 const STEP: usize = 16;
612 let per_byte = self.bus.mem32_ns / 4.0;
613 if per_byte == 0.0 {
614 self.sync();
615 for i in 0..len {
616 self.mem_write_raw(addr + i as u32, byte(i));
617 }
618 return;
619 }
620 let mut i = 0;
621 while i < len {
622 let n = STEP.min(len - i);
623 self.now += per_byte * n as f64;
624 self.sync();
625 for k in i..i + n {
626 self.mem_write_raw(addr + k as u32, byte(k));
627 }
628 i += n;
629 }
630 }
631
632 pub fn peek_slice(&mut self, addr: u32, out: &mut [u8]) {
634 self.now += self.bus.mem32_ns / 4.0 * out.len() as f64;
635 self.sync();
636 for (i, o) in out.iter_mut().enumerate() {
637 *o = self.mem_read_raw(addr + i as u32);
638 }
639 }
640
641 pub fn copy_vram(&mut self, src: u32, dst: u32, len: usize) {
645 for i in 0..len as u32 {
646 self.now += self.bus.mem8_ns * 2.0;
647 self.sync();
648 let v = self.mem_read_raw(src + i);
649 self.mem_write_raw(dst + i, v);
650 }
651 }
652
653 pub fn set_mode(&mut self, mode: u8) -> bool {
667 self.now += 50_000.0; self.sync();
669 let ok = match &mut self.video {
670 Video::Vga(v) => v.set_mode(mode),
671 Video::Cga(c) => c.set_mode(mode),
672 Video::Hercules(h) => h.set_mode(mode),
673 Video::Svga(s) => s.set_vga_mode(mode),
674 };
675 if ok {
676 self.after_mode_set();
677 }
678 ok
679 }
680
681 fn after_mode_set(&mut self) {
682 self.video.take_dirty();
683 let t = self.video.timing();
684 with_card!(&mut self.video, c => {
685 self.crt.reset(&mut **c, t, self.now);
686 c.vretrace_start();
687 });
688 self.tty = (0, 0);
689 let mode = self.video.bios_mode();
690 self.mouse
691 .reset_for_mode(mode, matches!(self.adapter, Adapter::Hercules(_)));
692 if mode == 0xFF {
693 let (w, h) = self.video.resolution();
694 self.mouse.range = [0, w as i32 - 1, 0, h as i32 - 1];
695 }
696 }
697
698 pub fn mode(&self) -> u8 {
700 self.video.bios_mode()
701 }
702
703 pub fn vbe_set_mode(&mut self, number: u16) -> Option<VbeMode> {
706 self.now += 100_000.0;
707 self.sync();
708 let Video::Svga(s) = &mut self.video else {
709 return None;
710 };
711 let m = s.set_vbe_mode(number)?;
712 self.after_mode_set();
713 Some(m)
714 }
715
716 pub fn vbe_set_bank(&mut self, bank: u32) {
719 self.now += 2_000.0;
720 self.sync();
721 if let Video::Svga(s) = &mut self.video
722 && let Some(v) = &mut s.vbe
723 {
724 v.bank = bank;
725 }
726 }
727
728 pub fn vbe_bank(&self) -> u32 {
730 match &self.video {
731 Video::Svga(s) => s.vbe.as_ref().map_or(0, |v| v.bank),
732 _ => 0,
733 }
734 }
735
736 pub fn vbe_pitch(&self) -> u32 {
738 match &self.video {
739 Video::Svga(s) => s.vbe.as_ref().map_or(0, |v| v.pitch),
740 _ => 0,
741 }
742 }
743
744 pub fn vbe_set_scanline(&mut self, pixels: u32) {
747 self.now += 2_000.0;
748 self.sync();
749 if let Video::Svga(s) = &mut self.video
750 && let Some(v) = &mut s.vbe
751 {
752 v.pitch = pixels * v.mode.bytes_per_pixel();
753 }
754 }
755
756 pub fn vbe_set_display_start(&mut self, x: u32, y: u32, wait: bool) {
760 self.now += 2_000.0;
761 self.sync();
762 if let Video::Svga(s) = &mut self.video
763 && let Some(v) = &mut s.vbe
764 {
765 v.start = y * v.pitch + x * v.mode.bytes_per_pixel();
766 }
767 if wait {
768 self.wait_vretrace();
769 }
770 }
771
772 pub fn vbe_set_dac_width(&mut self, bits: u8) -> u8 {
774 self.sync();
775 if let Video::Svga(s) = &mut self.video {
776 s.vga.dac_8bit = bits >= 8;
777 s.vga.refresh_dac_all();
778 return if s.vga.dac_8bit { 8 } else { 6 };
779 }
780 6
781 }
782
783 pub fn vbe_mode(&self) -> Option<VbeMode> {
785 match &self.video {
786 Video::Svga(s) => s.vbe.as_ref().map(|v| v.mode),
787 _ => None,
788 }
789 }
790
791 pub fn hercules_graphics(&mut self, page: u8) -> bool {
795 if !matches!(self.video, Video::Hercules(_)) {
796 return false;
797 }
798 self.outb(0x3BF, 0x03);
799 self.outb(0x3B8, 0x02);
800 for (i, v) in crate::hercules::HGC_GRAPHICS_CRTC.iter().enumerate() {
801 self.outb(0x3B4, i as u8);
802 self.outb(0x3B5, *v);
803 }
804 self.outb(0x3B8, 0x0A | if page & 1 != 0 { 0x80 } else { 0 });
805 if let Video::Hercules(h) = &mut self.video {
806 h.bios_mode = 0x7F;
807 }
808 self.mouse.range = [0, 719, 0, 347];
809 true
810 }
811
812 pub fn hercules_text(&mut self) -> bool {
814 if !matches!(self.video, Video::Hercules(_)) {
815 return false;
816 }
817 self.outb(0x3B8, 0x00);
818 for (i, v) in crate::hercules::HGC_TEXT_CRTC.iter().enumerate() {
819 self.outb(0x3B4, i as u8);
820 self.outb(0x3B5, *v);
821 }
822 self.outb(0x3B8, 0x28);
823 if let Video::Hercules(h) = &mut self.video {
824 h.bios_mode = 7;
825 }
826 true
827 }
828
829 pub fn cga_set_monitor(&mut self, m: CgaMonitor) {
831 self.sync();
832 if let Video::Cga(c) = &mut self.video {
833 c.set_monitor(m);
834 }
835 }
836
837 pub fn cga_set_composite_hue(&mut self, degrees: f32) {
839 self.sync();
840 if let Video::Cga(c) = &mut self.video {
841 c.composite_hue = degrees;
842 }
843 }
844
845 pub fn hercules_set_phosphor(&mut self, p: Phosphor) {
847 self.sync();
848 if let Video::Hercules(h) = &mut self.video {
849 h.phosphor = p;
850 }
851 }
852
853 pub fn load_font(&mut self, font: &crate::font::Font, map: usize) {
856 self.now += 20_000.0;
857 self.sync();
858 match &mut self.video {
859 Video::Vga(v) => v.load_font(font, map),
860 Video::Svga(s) => s.vga.load_font(font, map),
861 _ => {}
862 }
863 }
864
865 pub fn set_timer_hz(&mut self, hz: f64) {
871 let d = (PIT_HZ / hz.max(18.2)).round().clamp(1.0, 65536.0) as u32 as u16;
873 self.outb(0x43, 0x36); self.outb(0x40, d as u8);
875 self.outb(0x40, (d >> 8) as u8);
876 }
877
878 pub fn timer_hz(&self) -> f64 {
880 PIT_HZ / self.speaker.pit().channel(Channel::Ch0).divisor() as f64
881 }
882
883 pub fn timer_ticks(&self) -> u64 {
885 self.speaker.pit().irq0_edges(Tick::ZERO, self.now_tick())
887 }
888
889 pub fn take_timer_ticks(&mut self) -> u64 {
892 self.count_timer_ticks();
893 std::mem::take(&mut self.timer_pending)
894 }
895
896 fn count_timer_ticks(&mut self) {
897 let now = self.now_tick();
898 self.timer_pending += self.speaker.pit().irq0_edges(self.timer_taken, now);
899 self.timer_taken = now;
900 }
901
902 pub fn bios_ticks(&self) -> u64 {
904 (self.now * 1e-9 * PIT_HZ / 65536.0) as u64
905 }
906
907 pub fn keyboard(&self) -> &Keyboard {
911 &self.keyboard
912 }
913
914 pub fn keyboard_mut(&mut self) -> &mut Keyboard {
915 &mut self.keyboard
916 }
917
918 pub fn key(&self, scan: u8) -> bool {
920 self.keyboard.down(scan)
921 }
922
923 pub fn kbhit(&self) -> bool {
925 self.keyboard.hit()
926 }
927
928 pub fn getch(&mut self) -> Option<crate::input::KeyPress> {
930 self.keyboard.read()
931 }
932
933 pub fn mouse(&self) -> &Mouse {
934 &self.mouse
935 }
936
937 pub fn mouse_mut(&mut self) -> &mut Mouse {
938 &mut self.mouse
939 }
940
941 pub fn mouse_pixel(&self) -> (i32, i32) {
944 let (w, h) = self.video.resolution();
945 let [x0, x1, y0, y1] = self.mouse.range;
946 let sx = (self.mouse.x - x0) as i64 * w as i64 / (x1 - x0 + 1).max(1) as i64;
947 let sy = (self.mouse.y - y0) as i64 * h as i64 / (y1 - y0 + 1).max(1) as i64;
948 (sx as i32, sy as i32)
949 }
950
951 pub fn exit(&mut self) {
956 self.exit = true;
957 }
958
959 pub fn exit_requested(&self) -> bool {
960 self.exit
961 }
962}
963
964#[cfg(test)]
965mod tests {
966 use super::*;
967
968 fn vga() -> Dos {
969 let mut d = Dos::new(MachineConfig::default().with_bus(BusTiming::INSTANT));
970 assert!(d.set_mode(0x13));
971 d
972 }
973
974 #[test]
975 fn status_register_polling_terminates() {
976 let mut d = vga();
978 while d.inb(0x3DA) & 8 != 0 {}
979 while d.inb(0x3DA) & 8 == 0 {}
980 assert!(d.beam().v_retrace);
981 }
982
983 #[test]
984 fn polling_with_bus_cost_finds_retrace() {
985 let mut d = Dos::new(MachineConfig::default());
986 d.set_mode(0x13);
987 while d.inb(0x3DA) & 8 != 0 {}
988 while d.inb(0x3DA) & 8 == 0 {}
989 assert!(d.beam().v_retrace);
990 assert_eq!(d.beam().line, d.timing().v_retrace_start);
991 }
992
993 #[test]
994 fn wait_vretrace_lands_on_retrace_start() {
995 let mut d = vga();
996 d.wait_vretrace();
997 let b = d.beam();
998 assert!(b.v_retrace);
999 assert_eq!(b.line, d.timing().v_retrace_start);
1000 let f = b.frame;
1001 d.wait_vretrace();
1002 assert_eq!(d.beam().frame, f + 1);
1003 }
1004
1005 #[test]
1006 fn mode13h_pixel_reaches_the_screen() {
1007 let mut d = vga();
1008 d.poke(0xA0000 + 100 * 320 + 160, 4); d.wait_vretrace();
1010 d.wait_vretrace();
1011 let f = d.frame();
1012 let [ax, ay, ..] = f.active;
1013 let x = ax + 160 * 2;
1014 let y = ay + 100 * 2;
1015 let i = ((y * f.width + x) * 4) as usize;
1016 assert_eq!(&f.rgba[i..i + 3], &[0xAA, 0, 0]);
1017 let i2 = (((y + 1) * f.width + x + 1) * 4) as usize;
1018 assert_eq!(
1019 &f.rgba[i2..i2 + 3],
1020 &[0xAA, 0, 0],
1021 "double-scanned and doubled"
1022 );
1023 }
1024
1025 #[test]
1026 fn palette_change_mid_frame_splits_the_screen() {
1027 let mut d = vga();
1028 d.fill(0xA0000, 64000, 1);
1029 d.wait_frame_start();
1030 d.outb(0x3C8, 1);
1031 d.outb(0x3C9, 63);
1032 d.outb(0x3C9, 0);
1033 d.outb(0x3C9, 0);
1034 d.wait_line(200);
1035 d.outb(0x3C8, 1);
1036 d.outb(0x3C9, 0);
1037 d.outb(0x3C9, 63);
1038 d.outb(0x3C9, 0);
1039 d.wait_vretrace();
1040 let f = d.frame();
1041 let [ax, ay, ..] = f.active;
1042 let px = |y: u32| {
1043 let i = (((ay + y) * f.width + ax + 10) * 4) as usize;
1044 [f.rgba[i], f.rgba[i + 1], f.rgba[i + 2]]
1045 };
1046 assert_eq!(px(10), [255, 0, 0]);
1047 assert_eq!(px(390), [0, 255, 0]);
1048 }
1049
1050 #[test]
1051 fn slow_bus_copy_tears() {
1052 let mut d = Dos::new(MachineConfig::default().with_bus(BusTiming::ISA16));
1055 d.set_mode(0x13);
1056 d.wait_vretrace();
1057 d.wait_frame_start();
1058 let data = vec![2u8; 64000];
1059 d.poke_slice(0xA0000, &data);
1060 assert!(
1061 d.now_ns() > 14e6,
1062 "64000 bytes on ISA16 take longer than a frame"
1063 );
1064 }
1065
1066 #[test]
1067 fn timer_ticks() {
1068 let mut d = vga();
1069 d.set_timer_hz(100.0);
1070 d.delay_us(105_000.0);
1071 assert_eq!(d.take_timer_ticks(), 10);
1072 assert_eq!(d.take_timer_ticks(), 0);
1073 assert!((d.timer_hz() - 100.0).abs() < 0.1);
1074 }
1075
1076 #[test]
1077 fn timer_programmed_through_the_ports() {
1078 let mut d = vga();
1079 assert!((d.timer_hz() - 18.2).abs() < 0.01);
1081 d.outb(0x43, 0x36);
1083 d.outb(0x40, (1193u16 & 0xFF) as u8);
1084 d.outb(0x40, (1193u16 >> 8) as u8);
1085 d.delay_us(10_500.0);
1086 assert_eq!(d.take_timer_ticks(), 10);
1087 d.outb(0x43, 0x00);
1089 let lo = d.inb(0x40);
1090 let hi = d.inb(0x40);
1091 let count = u16::from_le_bytes([lo, hi]);
1092 assert!((1..=1193).contains(&count), "{count}");
1093 d.delay_us(5_200.0); d.set_timer_hz(100.0);
1097 assert_eq!(d.timer_ticks(), 0);
1098 d.delay_us(10_500.0);
1099 assert_eq!(d.take_timer_ticks(), 5 + 1);
1100 assert_eq!(d.take_timer_ticks(), 0);
1101 }
1102
1103 #[test]
1104 fn mode_support_per_adapter() {
1105 let mut d = Dos::new(MachineConfig::new(Adapter::Cga(CgaMonitor::Rgb)));
1106 assert!(d.set_mode(4));
1107 assert!(!d.set_mode(0x13));
1108 let mut d = Dos::new(MachineConfig::new(Adapter::Svga { memory_kib: 2048 }));
1109 assert_eq!(d.vbe_set_mode(0x101).map(|m| m.width), Some(640));
1110 assert_eq!(d.resolution(), (640, 480));
1111 assert!((d.refresh_hz() - 59.94).abs() < 0.05);
1112 }
1113}