├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── debugger ├── Cargo.toml ├── README.md └── src │ ├── bin │ └── debugger-main.rs │ ├── interface.glade │ ├── interface.rs │ └── lib.rs ├── disasm ├── Cargo.toml ├── README.md └── src │ └── bin │ └── disasm-main.rs ├── docs ├── demo_com_16bit.html ├── demo_com_32bit.html ├── games_com.html ├── index.html └── render │ ├── demo_com_16bit │ ├── 13_1.png │ ├── 13_165plasm.png │ ├── 13_244b.png │ ├── 13_alpc.png │ ├── 13_basicboy.png │ ├── 13_beziesux.png │ ├── 13_blah.png │ ├── 13_blaze5.png │ ├── 13_bob.png │ ├── 13_chaos.png │ ├── 13_conf.png │ ├── 13_dreamer.png │ ├── 13_ectotrax.png │ ├── 13_fire.png │ ├── 13_fire17.png │ ├── 13_fire2.png │ ├── 13_flame2.png │ ├── 13_fridge.png │ ├── 13_hungecek.png │ ├── 13_jive.png │ ├── 13_jomppa.png │ ├── 13_julia.png │ ├── 13_lava.png │ ├── 13_leaf.png │ ├── 13_legend.png │ ├── 13_lkccmini.png │ ├── 13_luminous.png │ ├── 13_lumps.png │ ├── 13_madness.png │ ├── 13_miracle.png │ ├── 13_mistake.png │ ├── 13_morales.png │ ├── 13_nicefire.png │ ├── 13_optimize.png │ ├── 13_pack.png │ ├── 13_phong.png │ ├── 13_pikku.png │ ├── 13_pixelize.png │ ├── 13_plasma.png │ ├── 13_plasmalr.png │ ├── 13_plasmexp.png │ ├── 13_platinum.png │ ├── 13_proto256.png │ ├── 13_riddle.png │ ├── 13_saverave.png │ ├── 13_skylight.png │ ├── 13_snow.png │ ├── 13_specifi.png │ ├── 13_spline.png │ ├── 13_sqwerz3.png │ ├── 13_static.png │ ├── 13_tiled.png │ ├── 13_unknown.png │ ├── 13_wamma.png │ ├── 13_water.png │ ├── 13_waves.png │ ├── 13_wd95.png │ ├── 13_wetwet.png │ ├── 13_x.png │ └── 13_zork.png │ ├── demo_com_32bit │ ├── 11_200h.png │ ├── 13_anding.png │ ├── 13_blobsf.png │ ├── 13_bt7.png │ ├── 13_distant.png │ ├── 13_ems.png │ ├── 13_enchante.png │ ├── 13_entry2.png │ ├── 13_fire!.png │ ├── 13_fire3d.png │ ├── 13_fireline.png │ ├── 13_flame.png │ ├── 13_fountain_of_sparks.png │ ├── 13_fractal.png │ ├── 13_frcmirez.png │ ├── 13_glasenapy.png │ ├── 13_gob4k.png │ ├── 13_juls.png │ ├── 13_mbl.png │ ├── 13_noc200.png │ ├── 13_ripped.png │ ├── 13_rwater.png │ ├── 13_sierpins.png │ ├── 13_stars.png │ ├── 13_suka.png │ ├── 13_textaroo.png │ ├── 13_voronoy.png │ ├── 13_wtrfall.png │ └── 13_xwater.png │ └── games_com │ ├── 04_cfire.png │ ├── 04_digdug.png │ ├── 04_f15.png │ ├── 04_galaxian.png │ ├── 04_hhm.png │ ├── 04_keng.png │ ├── 04_mspacman.png │ ├── 04_panic.png │ ├── 04_pcmanv2.png │ ├── 04_pipes.png │ ├── 04_ptrooper.png │ ├── 04_rollo.png │ ├── 04_shamus.png │ ├── 04_sky1.png │ ├── 04_starcham.png │ ├── 04_zaxxon.png │ ├── 11_pong21.png │ └── 13_invaders.png ├── dustbox ├── Cargo.toml ├── benches │ └── cpu.rs ├── build.rs └── src │ ├── bios.rs │ ├── cmos.rs │ ├── codepage │ ├── cp437.rs │ └── mod.rs │ ├── cpu │ ├── decoder.rs │ ├── decoder_test.rs │ ├── encoder.rs │ ├── encoder_test.rs │ ├── flag.rs │ ├── flag_test.rs │ ├── instruction.rs │ ├── mod.rs │ ├── op.rs │ ├── parameter.rs │ ├── register.rs │ ├── register_test.rs │ └── segment.rs │ ├── debug │ ├── breakpoints.rs │ ├── breakpoints_test.rs │ ├── debugger.rs │ ├── debugger_test.rs │ ├── memory_breakpoints.rs │ ├── memory_breakpoints_test.rs │ ├── mod.rs │ ├── tracer.rs │ └── tracer_test.rs │ ├── dos │ ├── dos.rs │ └── mod.rs │ ├── format │ ├── exe.rs │ └── mod.rs │ ├── gpu │ ├── crtc.rs │ ├── dac.rs │ ├── font.rs │ ├── graphic_card.rs │ ├── mod.rs │ ├── modes.rs │ ├── modes_test.rs │ ├── palette.rs │ ├── render.rs │ ├── render_test.rs │ └── video_parameters.rs │ ├── hex.rs │ ├── keyboard.rs │ ├── keyboard_test.rs │ ├── lib.rs │ ├── machine.rs │ ├── machine_test.rs │ ├── memory │ ├── flat_memory.rs │ ├── memory_address.rs │ ├── mmu.rs │ ├── mmu_test.rs │ └── mod.rs │ ├── mouse.rs │ ├── ndisasm.rs │ ├── pic.rs │ ├── pic_test.rs │ ├── pit.rs │ ├── pit_test.rs │ ├── storage.rs │ ├── string.rs │ ├── string_test.rs │ └── tools.rs ├── exeinfo ├── Cargo.toml ├── README.md └── src │ └── bin │ └── exeinfo-main.rs ├── frontend ├── Cargo.toml ├── README.md └── src │ └── bin │ └── frontend-main.rs ├── fuzzer ├── Cargo.toml ├── README.md └── src │ ├── bin │ └── fuzzer-main.rs │ ├── fuzzer.rs │ └── lib.rs ├── harness ├── Cargo.toml ├── README.md ├── sets │ ├── demo-com-16bit.yml │ ├── demo-com-32bit.yml │ └── games-com-commercial-16bit.yml ├── src │ └── bin │ │ └── harness-main.rs └── templates │ └── test_category.tpl.html └── utils ├── dos-memory ├── Makefile ├── memory.asm └── memory.com ├── dos-mouse ├── Makefile ├── README.md ├── mouse.asm └── mouse.com ├── dos-readfile ├── FILE.DAT ├── Makefile ├── README.md ├── readfile.asm └── readfile.com ├── dumpcs ├── Makefile ├── README.md ├── dumpcs.asm └── dumpcs.com ├── palette ├── Makefile ├── README.md ├── palette.asm └── palette.com ├── prober ├── .gitignore ├── Makefile ├── README.md ├── print.inc.asm ├── prober.tpl.asm └── regs.inc.asm ├── realtest ├── .gitignore ├── Makefile ├── cc.c ├── ex1.asm └── test.c └── scroll ├── Makefile ├── scroll.asm └── scroll.com /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.rs.bk 3 | *.glade~ 4 | 5 | # temporary solution for not comitting misc text files 6 | *.txt 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | sudo: required 4 | dist: bionic 5 | 6 | matrix: 7 | fast_finish: true 8 | include: 9 | - env: TARGET=x86_64-unknown-linux-gnu 10 | rust: stable 11 | #- env: TARGET=x86_64-unknown-linux-gnu 12 | # rust: nightly 13 | 14 | install: 15 | - sudo apt-get install -y libgtk-3-dev libsdl2-dev libsdl2-gfx-dev libegl1-mesa-dev libgles2-mesa-dev nasm 16 | - ndisasm -V 17 | - cd .. && git clone --depth 1 https://github.com/martinlindhe/dos-software-decoding && cd - 18 | 19 | script: 20 | - cargo build --all 21 | - cargo test --all 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "debugger", 4 | "dustbox", 5 | "disasm", 6 | "exeinfo", 7 | "frontend", 8 | "fuzzer", 9 | "harness", 10 | ] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Martin Lindhe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | cargo test --all -- --color always --nocapture 3 | 4 | test-harness: 5 | cargo run --release --package harness harness/sets/demo-com-16bit.yml 6 | cargo run --release --package harness harness/sets/demo-com-32bit.yml 7 | cargo run --release --package harness harness/sets/games-com-commercial-16bit.yml 8 | 9 | expensive-encode: 10 | cargo test encode -- --color always --nocapture --ignored 11 | 12 | bench: 13 | cargo bench --all 14 | 15 | mips: 16 | cargo test --release mips -- --nocapture 17 | 18 | run: 19 | cargo run --package debugger 20 | 21 | run-release: 22 | cargo run --release --package debugger 23 | 24 | disasm: 25 | cargo run --release --package disasm 26 | 27 | install-disasm: 28 | cargo install --path disasm --force 29 | 30 | fuzz: 31 | cargo run --package fuzzer -- supersafe --mutations 50 --host 172.16.72.129 32 | # cargo run --package fuzzer -- dosbox-x --mutations 20 33 | # cargo run --package fuzzer -- vmrun --mutations 50 --vmx "/Users/m/Documents/Virtual Machines.localized/Windows XP Professional.vmwarevm/Windows XP Professional.vmx" --username vmware --password vmware 34 | 35 | lint: 36 | cargo clippy --all 37 | 38 | prober: 39 | cd utils/prober && make 40 | 41 | glade: 42 | glade debugger/src/interface.glade 43 | 44 | coverage: 45 | # XXX requires linux -jan 2020. osx support https://github.com/xd009642/tarpaulin/issues/152 46 | cargo tarpaulin --out Html 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | [![Build Status](https://travis-ci.org/martinlindhe/dustbox-rs.svg?branch=master)](https://travis-ci.org/martinlindhe/dustbox-rs) 4 | 5 | Early WIP PC x86 emulator with the goal of easily running MS-DOS games on Windows, macOS and Linux. 6 | 7 | In the current state, dustbox runs a some demos and is still in it's early stages. 8 | If you are looking for a more complete dos emulator, I suggest you check out [dosbox-x](https://github.com/joncampbell123/dosbox-x). 9 | 10 | ## Rough status june 2019 11 | 12 | | Component | Status | Notes | 13 | | ---------- | ------ | -------------------------------------------------------- | 14 | | 16 bit CPU | 95% | interrupts are incomplete | 15 | | 32 bit CPU | 20% | some instructions supported | 16 | | FPU | 5% | | 17 | | PIT | 1% | | 18 | | PIC | 1% | | 19 | | MS-DOS | 5% | simulating MS-DOS behavior (interrupts, command.com env) | 20 | | EMS/XMS | - | extended memory managers | 21 | | Keyboard | 1% | | 22 | | Mouse | 25% | | 23 | | CGA | 5% | | 24 | | EGA | 5% | | 25 | | VGA | 5% | | 26 | | Sound | - | not started | 27 | | CD-ROM | - | not started | 28 | | Harddrive | - | not started | 29 | 30 | ## Contributing 31 | 32 | Any help and contributions are much welcome! 33 | 34 | ## Dependencies 35 | 36 | On Ubuntu, use `sudo apt install nasm libgtk-3-dev libcairo2-dev libpango1.0-dev libatk1.0-dev gdk-pixbuf2.0-dev libsdl2-dev libsdl2-gfx-dev` 37 | 38 | On Windows, use `vcpkg install sdl2 gtk` + `scoop install nasm` 39 | 40 | On macOS, use `brew install nasm sdl2 sdl2_gfx` 41 | 42 | ## Running 43 | 44 | To launch the debugger: 45 | 46 | ```sh 47 | cargo run --package dustbox_debugger 48 | ``` 49 | 50 | then interact with the debugger using the input box ('help' to get started). 51 | 52 | To launch the front-end: 53 | 54 | ```sh 55 | cargo run --package dustbox_frontend path-to-dos-executable 56 | ``` 57 | 58 | ## Tests 59 | 60 | To run all normal tests 61 | 62 | ```sh 63 | cargo test --all 64 | ``` 65 | 66 | There is additional tests that are expensive, they also generate the tests/render/demo images. 67 | 68 | In order to run the expensive tests you need to check out the dos-software-decoding repo in the parent directory and pass the `--ignored` flag to cargo: 69 | 70 | ```sh 71 | cd .. && git clone --depth 1 https://github.com/martinlindhe/dos-software-decoding && cd - 72 | cargo test --release -- --ignored 73 | ``` 74 | 75 | ## License 76 | 77 | Under [MIT](LICENSE) 78 | -------------------------------------------------------------------------------- /debugger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "debugger" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/lib.rs" 9 | 10 | [dependencies] 11 | cairo-rs = "0.8" 12 | clap = "2.33" 13 | dustbox = { path = "../dustbox" } 14 | gdk = "0.12" 15 | gdk-pixbuf = "0.8" 16 | gtk = { version = "0.8", features = [ "v3_16" ] } 17 | -------------------------------------------------------------------------------- /debugger/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | The dustbox-gtk debugger interface 4 | 5 | ## TODO 6 | 7 | add notes to opened file: 8 | 9 | - id file with a checksum 10 | - notes by checksum in sqlite file, named like the binary file but extra extension (.decompile-info maybe ?) 11 | - note per offset 12 | 13 | toggle show decompilation: 14 | 15 | - update if any data in sections marked as code changes 16 | - always redraw dw/db values 17 | -------------------------------------------------------------------------------- /debugger/src/bin/debugger-main.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::cell::RefCell; 3 | 4 | use clap::{Arg, App}; 5 | 6 | use debugger::interface::Interface; 7 | use dustbox::debug::Debugger; 8 | 9 | fn main() { 10 | let matches = App::new("dustbox-disasm") 11 | .version("0.1") 12 | .arg(Arg::with_name("INPUT") 13 | .help("Sets the input file to use") 14 | .index(1)) 15 | .get_matches(); 16 | 17 | let mut debugger = Debugger::default(); 18 | 19 | if matches.is_present("INPUT") { 20 | let filename = matches.value_of("INPUT").unwrap(); 21 | debugger.load_executable(&filename); 22 | } 23 | 24 | let app = Rc::new(RefCell::new(debugger)); 25 | 26 | let mut gui = Interface::default(app); 27 | gui.main(); 28 | } 29 | -------------------------------------------------------------------------------- /debugger/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod interface; -------------------------------------------------------------------------------- /disasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "disasm" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "dustbox-disasm" 9 | path = "src/bin/disasm-main.rs" 10 | 11 | [dependencies] 12 | chrono = "0.4" 13 | clap = "2.33" 14 | dustbox = { path = "../dustbox" } 15 | -------------------------------------------------------------------------------- /disasm/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Disassembler with tracing abilities 4 | -------------------------------------------------------------------------------- /disasm/src/bin/disasm-main.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | 3 | use dustbox::machine::Machine; 4 | use dustbox::cpu::{Decoder}; 5 | use dustbox::debug::ProgramTracer; 6 | use dustbox::tools; 7 | 8 | use clap::{Arg, App}; 9 | 10 | fn main() { 11 | let matches = App::new("dustbox-disasm") 12 | .version("0.1") 13 | .arg(Arg::with_name("INPUT") 14 | .help("Sets the input file to use") 15 | .required(true) 16 | .index(1)) 17 | .arg(Arg::with_name("flat") 18 | .long("flat") 19 | .help("Show a flat disassembly listing (no tracing)")) 20 | .arg(Arg::with_name("timestamp") 21 | .long("timestamp") 22 | .help("Include a timestamp in the output")) 23 | .get_matches(); 24 | 25 | let filename = matches.value_of("INPUT").unwrap(); 26 | println!("; Source {}", filename); 27 | if matches.is_present("timestamp") { 28 | // disabled by default for reproducibility 29 | println!("; Generated {}", Local::now().to_rfc2822()); 30 | } 31 | println!(); 32 | 33 | if matches.is_present("flat") { 34 | flat_disassembly(filename); 35 | } else { 36 | trace_disassembly(filename); 37 | } 38 | } 39 | 40 | fn flat_disassembly(filename: &str) { 41 | let mut machine = Machine::deterministic(); 42 | match tools::read_binary(filename) { 43 | Ok(data) => machine.load_executable(&data, 0x085F), 44 | Err(err) => panic!("failed to read {}: {}", filename, err), 45 | } 46 | 47 | let mut decoder = Decoder::default(); 48 | let mut ma = machine.cpu.get_memory_address(); 49 | 50 | let mut rom_end = machine.rom_base; 51 | rom_end.add_offset(machine.rom_length as u16); 52 | 53 | println!("; starting flat disassembly at {}", ma); 54 | 55 | loop { 56 | let op = decoder.get_instruction_info(&mut machine.mmu, ma.segment(), ma.offset()); 57 | println!("{}", op); 58 | ma.inc_n(op.bytes.len() as u16); 59 | if ma.value() >= rom_end.value() { 60 | break; 61 | } 62 | } 63 | } 64 | 65 | fn trace_disassembly(filename: &str) { 66 | let mut machine = Machine::deterministic(); 67 | match tools::read_binary(filename) { 68 | Ok(data) => machine.load_executable(&data, 0x085F), 69 | Err(err) => panic!("failed to read {}: {}", filename, err), 70 | } 71 | let mut tracer = ProgramTracer::default(); 72 | tracer.trace_execution(&mut machine); 73 | println!("{}", tracer.present_trace(&mut machine)); 74 | } 75 | -------------------------------------------------------------------------------- /docs/demo_com_16bit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dustbox - compatibility 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /docs/demo_com_32bit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dustbox - compatibility 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/games_com.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dustbox - compatibility 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dustbox - DOS emulator 6 | 7 | 8 | 9 |

demos:

10 | dos-software-decoding/demo-com-16bit compatibility
11 | dos-software-decoding/demo-com-32bit compatibility
12 | 13 |

16-bit games:

14 | dos-software-decoding/games-com compatibility
15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_1.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_165plasm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_165plasm.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_244b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_244b.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_alpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_alpc.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_basicboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_basicboy.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_beziesux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_beziesux.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_blah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_blah.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_blaze5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_blaze5.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_bob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_bob.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_chaos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_chaos.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_conf.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_dreamer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_dreamer.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_ectotrax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_ectotrax.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_fire.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_fire17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_fire17.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_fire2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_fire2.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_flame2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_flame2.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_fridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_fridge.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_hungecek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_hungecek.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_jive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_jive.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_jomppa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_jomppa.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_julia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_julia.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_lava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_lava.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_leaf.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_legend.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_lkccmini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_lkccmini.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_luminous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_luminous.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_lumps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_lumps.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_madness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_madness.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_miracle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_miracle.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_mistake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_mistake.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_morales.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_morales.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_nicefire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_nicefire.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_optimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_optimize.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_pack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_pack.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_phong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_phong.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_pikku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_pikku.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_pixelize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_pixelize.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_plasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_plasma.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_plasmalr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_plasmalr.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_plasmexp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_plasmexp.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_platinum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_platinum.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_proto256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_proto256.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_riddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_riddle.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_saverave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_saverave.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_skylight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_skylight.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_snow.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_specifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_specifi.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_spline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_spline.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_sqwerz3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_sqwerz3.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_static.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_tiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_tiled.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_unknown.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_wamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_wamma.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_water.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_waves.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_wd95.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_wd95.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_wetwet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_wetwet.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_x.png -------------------------------------------------------------------------------- /docs/render/demo_com_16bit/13_zork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_16bit/13_zork.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/11_200h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/11_200h.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_anding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_anding.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_blobsf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_blobsf.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_bt7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_bt7.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_distant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_distant.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_ems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_ems.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_enchante.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_enchante.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_entry2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_entry2.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_fire!.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_fire!.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_fire3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_fire3d.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_fireline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_fireline.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_flame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_flame.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_fountain_of_sparks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_fountain_of_sparks.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_fractal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_fractal.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_frcmirez.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_frcmirez.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_glasenapy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_glasenapy.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_gob4k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_gob4k.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_juls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_juls.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_mbl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_mbl.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_noc200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_noc200.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_ripped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_ripped.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_rwater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_rwater.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_sierpins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_sierpins.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_stars.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_suka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_suka.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_textaroo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_textaroo.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_voronoy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_voronoy.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_wtrfall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_wtrfall.png -------------------------------------------------------------------------------- /docs/render/demo_com_32bit/13_xwater.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/demo_com_32bit/13_xwater.png -------------------------------------------------------------------------------- /docs/render/games_com/04_cfire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_cfire.png -------------------------------------------------------------------------------- /docs/render/games_com/04_digdug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_digdug.png -------------------------------------------------------------------------------- /docs/render/games_com/04_f15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_f15.png -------------------------------------------------------------------------------- /docs/render/games_com/04_galaxian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_galaxian.png -------------------------------------------------------------------------------- /docs/render/games_com/04_hhm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_hhm.png -------------------------------------------------------------------------------- /docs/render/games_com/04_keng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_keng.png -------------------------------------------------------------------------------- /docs/render/games_com/04_mspacman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_mspacman.png -------------------------------------------------------------------------------- /docs/render/games_com/04_panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_panic.png -------------------------------------------------------------------------------- /docs/render/games_com/04_pcmanv2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_pcmanv2.png -------------------------------------------------------------------------------- /docs/render/games_com/04_pipes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_pipes.png -------------------------------------------------------------------------------- /docs/render/games_com/04_ptrooper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_ptrooper.png -------------------------------------------------------------------------------- /docs/render/games_com/04_rollo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_rollo.png -------------------------------------------------------------------------------- /docs/render/games_com/04_shamus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_shamus.png -------------------------------------------------------------------------------- /docs/render/games_com/04_sky1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_sky1.png -------------------------------------------------------------------------------- /docs/render/games_com/04_starcham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_starcham.png -------------------------------------------------------------------------------- /docs/render/games_com/04_zaxxon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/04_zaxxon.png -------------------------------------------------------------------------------- /docs/render/games_com/11_pong21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/11_pong21.png -------------------------------------------------------------------------------- /docs/render/games_com/13_invaders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/docs/render/games_com/13_invaders.png -------------------------------------------------------------------------------- /dustbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dustbox" 3 | version = "0.0.1" 4 | authors = [ 5 | "Martin Lindhe " 6 | ] 7 | edition = "2018" 8 | description = "PC x86 emulator with the goal of easily running MS-DOS games on Windows, macOS and Linux." 9 | license = "MIT" 10 | repository = "https://github.com/martinlindhe/dustbox-rs" 11 | exclude = [ 12 | "utils/*", 13 | ] 14 | 15 | [badges] 16 | travis-ci = { repository = "martinlindhe/dustbox-rs" } 17 | 18 | [lib] 19 | path = "src/lib.rs" 20 | 21 | [dependencies] 22 | bincode = "1.2" 23 | chrono = "0.4" 24 | image = { version = "0.22", default-features = false, features = [ "png" ] } 25 | rand = "0.7" 26 | rand_xorshift = "0.2" 27 | sdl2 = { version = "0.33", default-features = false, features = [ "gfx" ] } 28 | serde = "1.0" 29 | serde_derive = "1.0" 30 | tempfile = "3.1" 31 | toml = "0.5" 32 | 33 | [dev-dependencies] 34 | criterion = "0.3" 35 | pretty_assertions = "0.6" 36 | 37 | [target.'cfg(windows)'.build-dependencies] 38 | vcpkg = "0.2" 39 | 40 | [[bench]] 41 | name = "cpu" 42 | harness = false 43 | -------------------------------------------------------------------------------- /dustbox/benches/cpu.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | extern crate dustbox; 5 | 6 | use criterion::Criterion; 7 | 8 | use dustbox::machine::Machine; 9 | 10 | fn exec_simple_loop(c: &mut Criterion) { 11 | let mut machine = Machine::deterministic(); 12 | let code: Vec = vec![ 13 | 0xB9, 0xFF, 0xFF, // mov cx,0xffff 14 | 0x49, // dec cx 15 | 0xEB, 0xFA, // jmp short 0x100 16 | ]; 17 | 18 | machine.load_executable(&code, 0x085F); 19 | 20 | c.bench_function("execute small jmp short loop", move |b| b.iter(|| machine.execute_instruction())); 21 | } 22 | 23 | fn disasm_small_prog(c: &mut Criterion) { 24 | let mut machine = Machine::deterministic(); 25 | let code: Vec = vec![ 26 | 0x80, 0x3E, 0x31, 0x10, 0x00, // cmp byte [0x1031],0x0 27 | 0xB9, 0xFF, 0xFF, // mov cx,0xffff 28 | 0x49, // dec cx 29 | 0xEB, 0xFA, // jmp short 0x100 30 | 0x83, 0xC7, 0x3A, // add di,byte +0x3a 31 | 0xBB, 0x8F, 0x79, // mov bx,0x798f 32 | 0xEB, 0xFA, // jmp short 0x100 33 | 0xB9, 0xFF, 0xFF, // mov cx,0xffff 34 | ]; 35 | machine.load_executable(&code, 0x085F); 36 | 37 | c.bench_function("disasm small prog", move |b| b.iter(|| machine.cpu.decoder.disassemble_block_to_str(&mut machine.mmu, 0x85F, 0x100, 8))); 38 | } 39 | 40 | criterion_group!(benches, exec_simple_loop, disasm_small_prog); 41 | criterion_main!(benches); 42 | -------------------------------------------------------------------------------- /dustbox/build.rs: -------------------------------------------------------------------------------- 1 | // windows build to pick up gtk-3 libs installed with vcpkg correctly 2 | // jan 2019 still an issue, see https://github.com/gtk-rs/gtk/issues/702#issuecomment-438049273 3 | 4 | #[cfg(windows)] 5 | extern crate vcpkg; 6 | 7 | #[cfg(windows)] 8 | fn ensure_lib_file_win(lib_path: &str, src_name: &str, dst_name: &str) { 9 | let src_path = std::path::Path::new(lib_path).join(src_name); 10 | let dst_path = std::path::Path::new(lib_path).join(dst_name); 11 | 12 | if !dst_path.exists() { 13 | std::fs::copy(src_path, dst_path).unwrap(); 14 | } 15 | } 16 | 17 | #[cfg(windows)] 18 | fn win_main() { 19 | let target_triple = std::env::var("TARGET").unwrap(); 20 | println!("XXX VCPKG_PATH IS {}", std::env::var("VCPKG_ROOT").unwrap()); 21 | if target_triple == "x86_64-pc-windows-msvc" { 22 | std::env::set_var("GTK_LIB_DIR", 23 | std::path::Path::new(&std::env::var("VCPKG_ROOT").unwrap()).join("installed\\x64-windows\\lib")); 24 | } else if target_triple == "i686-pc-windows-msvc" { 25 | std::env::set_var("GTK_LIB_DIR", 26 | std::path::Path::new(&std::env::var("VCPKG_ROOT").unwrap()).join("installed\\x86-windows\\lib")); 27 | } else { 28 | panic!(""); 29 | } 30 | 31 | let lib_path = std::env::var("GTK_LIB_DIR").unwrap(); 32 | ensure_lib_file_win(&lib_path, "gtk-3.0.lib", "gtk-3.lib"); 33 | ensure_lib_file_win(&lib_path, "gdk-3.0.lib", "gdk-3.lib"); 34 | 35 | vcpkg::find_package("gtk").unwrap(); 36 | vcpkg::find_package("glib").unwrap(); 37 | vcpkg::find_package("harfbuzz").unwrap(); 38 | } 39 | 40 | fn main() { 41 | #[cfg(windows)] 42 | win_main(); 43 | } -------------------------------------------------------------------------------- /dustbox/src/bios.rs: -------------------------------------------------------------------------------- 1 | // https://wiki.osdev.org/BIOS 2 | // dosbox-x: src/hardware/bios.cpp 3 | 4 | use crate::memory::{MMU, MemoryAddress}; 5 | 6 | #[derive(Clone)] 7 | pub struct BIOS { 8 | } 9 | 10 | impl BIOS { 11 | pub const DATA_SEG: u16 = 0x0040; // bios data segment, 256 byte at 000400 to 0004FF 12 | 13 | pub const DATA_INITIAL_MODE: u16 = 0x0010; 14 | pub const DATA_CURRENT_MODE: u16 = 0x0049; 15 | pub const DATA_NB_COLS: u16 = 0x004A; 16 | pub const DATA_PAGE_SIZE: u16 = 0x004C; 17 | pub const DATA_CURRENT_START: u16 = 0x004E; 18 | pub const DATA_CURSOR_POS: u16 = 0x0050; 19 | pub const DATA_CURSOR_TYPE: u16 = 0x0060; 20 | pub const DATA_CURRENT_PAGE: u16 = 0x0062; 21 | pub const DATA_CRTC_ADDRESS: u16 = 0x0063; 22 | pub const DATA_CURRENT_MSR: u16 = 0x0065; 23 | pub const DATA_CURRENT_PAL: u16 = 0x0066; 24 | pub const DATA_NB_ROWS: u16 = 0x0084; 25 | pub const DATA_CHAR_HEIGHT: u16 = 0x0085; 26 | pub const DATA_VIDEO_CTL: u16 = 0x0087; 27 | pub const DATA_SWITCHES: u16 = 0x0088; 28 | pub const DATA_MODESET_CTL: u16 = 0x0089; 29 | pub const DATA_DCC_INDEX: u16 = 0x008A; 30 | pub const DATA_CRTCPU_PAGE: u16 = 0x008A; 31 | pub const DATA_VS_POINTER: u16 = 0x00A8; 32 | 33 | const ROM_SEG: u16 = 0xF000; // bios rom segment, 64k at F_0000 to F_FFFF 34 | const ROM_EQUIPMENT_WORD: u16 = 0x0410; 35 | 36 | pub fn default() -> Self { 37 | BIOS { 38 | } 39 | } 40 | 41 | pub fn init(&mut self, mut mmu: &mut MMU) { 42 | self.init_ivt(&mut mmu); 43 | self.write_configuration_data_table(&mut mmu); 44 | } 45 | 46 | fn init_ivt(&mut self, mmu: &mut MMU) { 47 | const IRET: u8 = 0xCF; 48 | for irq in 0..0xFF { 49 | self.write_ivt_entry(mmu, irq, BIOS::ROM_SEG, u16::from(irq)); 50 | mmu.write_u8(BIOS::ROM_SEG, u16::from(irq), IRET); 51 | } 52 | } 53 | 54 | fn write_ivt_entry(&self, mmu: &mut MMU, number: u8, seg: u16, offset: u16) { 55 | let _seg = 0; 56 | let _offset = u16::from(number) * 4; 57 | mmu.write_u16(_seg, _offset, offset); 58 | mmu.write_u16(_seg, _offset + 2, seg); 59 | } 60 | 61 | /// initializes the Configuration Data Table 62 | fn write_configuration_data_table(&self, mmu: &mut MMU) { 63 | let mut addr = MemoryAddress::RealSegmentOffset(BIOS::ROM_SEG, 0xE6F5); 64 | mmu.write_u16_inc(&mut addr, 8); // table size 65 | mmu.write_u8_inc(&mut addr, 0xFC); // model: AT 66 | mmu.write_u8_inc(&mut addr, 0); // submodel 67 | mmu.write_u8_inc(&mut addr, 0); // BIOS revision 68 | mmu.write_u8_inc(&mut addr, 0b0000_0000); // feature byte 1 69 | mmu.write_u8_inc(&mut addr, 0b0000_0000); // feature byte 2 70 | mmu.write_u8_inc(&mut addr, 0b0000_0000); // feature byte 3 71 | mmu.write_u8_inc(&mut addr, 0b0000_0000); // feature byte 4 72 | mmu.write_u8_inc(&mut addr, 0b0000_0000); // feature byte 5 73 | mmu.write_u16(BIOS::ROM_SEG, BIOS::ROM_EQUIPMENT_WORD, 0x0021); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dustbox/src/cmos.rs: -------------------------------------------------------------------------------- 1 | // https://wiki.osdev.org/CMOS 2 | // dosbox-x: src/hardware/cmos.cpp 3 | 4 | #[derive(Clone)] 5 | pub struct CMOS { 6 | } 7 | 8 | impl CMOS { 9 | pub fn default() -> Self { 10 | // XXX see CMOS_Init in dosbox-x 11 | CMOS { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /dustbox/src/codepage/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cp437; 2 | -------------------------------------------------------------------------------- /dustbox/src/cpu/flag_test.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::flag::Flags; 2 | 3 | #[test] 4 | fn can_pack_unpack_flags() { 5 | let mut flags = Flags::new(); 6 | flags.set_u16(0xFFFF); 7 | assert_eq!(0x0DD5, flags.u16()); 8 | } 9 | -------------------------------------------------------------------------------- /dustbox/src/cpu/instruction.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::cpu::Segment; 4 | use crate::cpu::Op; 5 | use crate::cpu::{Parameter, ParameterSet}; 6 | use crate::cpu::{OperandSize, AddressSize}; 7 | use crate::hex::hex_bytes; 8 | use crate::string::right_pad; 9 | 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub struct Instruction { 12 | pub command: Op, 13 | pub params: ParameterSet, 14 | pub length: u8, 15 | // op prefixes 16 | pub segment_prefix: Segment, // segment prefix opcode 17 | pub repeat: RepeatMode, // REPcc prefix 18 | pub lock: bool, // LOCK prefix 19 | pub op_size: OperandSize, // 0x66 prefix 20 | pub address_size: AddressSize, // 0x67 prefix 21 | } 22 | 23 | impl fmt::Display for Instruction { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | let instr = self.describe_instruction(); 26 | if self.segment_prefix == Segment::Default || self.hide_segment_prefix() { 27 | write!(f, "{}", instr) 28 | } else { 29 | write!(f, "{} {}", self.segment_prefix.as_str(), instr) 30 | } 31 | } 32 | } 33 | 34 | impl Instruction { 35 | pub fn new(op: Op) -> Self { 36 | Instruction::new3(op, Parameter::None, Parameter::None, Parameter::None) 37 | } 38 | 39 | pub fn new1(op: Op, dst: Parameter) -> Self { 40 | Instruction::new3(op, dst, Parameter::None, Parameter::None) 41 | } 42 | 43 | pub fn new2(op: Op, dst: Parameter, src: Parameter) -> Self { 44 | Instruction::new3(op, dst, src, Parameter::None) 45 | } 46 | 47 | pub fn new3(op: Op, dst: Parameter, src: Parameter, src2: Parameter) -> Self { 48 | let op_size = Instruction::op_size_from_op(&op); 49 | Instruction { 50 | command: op, 51 | segment_prefix: Segment::Default, 52 | params: ParameterSet {dst, src, src2}, 53 | lock: false, 54 | repeat: RepeatMode::None, 55 | op_size, 56 | address_size: AddressSize::_16bit, 57 | length: 0, 58 | } 59 | } 60 | 61 | // used to decorate tracer 62 | pub fn is_ret(&self) -> bool { 63 | self.command == Op::Retn || self.command == Op::Retf || self.command == Op::RetImm16 64 | } 65 | 66 | // used to decorate tracer 67 | pub fn is_loop(&self) -> bool { 68 | self.command == Op::Loop || self.command == Op::Loope || self.command == Op::Loopne 69 | } 70 | 71 | // used to decorate tracer 72 | pub fn is_unconditional_jmp(&self) -> bool { 73 | self.command == Op::JmpShort || self.command == Op::JmpNear || self.command == Op::JmpFar 74 | } 75 | 76 | fn op_size_from_op(op: &Op) -> OperandSize { 77 | match *op { 78 | Op::Mov32 | Op::Inc32 | Op::Dec32 => OperandSize::_32bit, 79 | _ => OperandSize::_16bit, 80 | } 81 | } 82 | 83 | fn hide_segment_prefix(&self) -> bool { 84 | self.command == Op::Add8 || self.command == Op::Add16 || self.command == Op::Add32 || 85 | self.command == Op::Adc8 || self.command == Op::Adc16 || self.command == Op::Adc32 || 86 | self.command == Op::Sub8 || self.command == Op::Sub16 || self.command == Op::Sub32 || 87 | self.command == Op::Sbb8 || self.command == Op::Sbb16 || self.command == Op::Sbb32 || 88 | self.command == Op::Inc8 || self.command == Op::Inc16 || self.command == Op::Inc32 || 89 | self.command == Op::Dec8 || self.command == Op::Dec16 || self.command == Op::Dec32 || 90 | self.command == Op::Mul8 || self.command == Op::Mul16 || self.command == Op::Mul32 || 91 | self.command == Op::Div8 || self.command == Op::Div16 || self.command == Op::Div32 || 92 | self.command == Op::Imul8 || self.command == Op::Imul16 || self.command == Op::Imul32 || 93 | self.command == Op::Idiv8 || self.command == Op::Idiv16 || self.command == Op::Idiv32 || 94 | self.command == Op::And8 || self.command == Op::And16 || self.command == Op::And32 || 95 | self.command == Op::Or8 || self.command == Op::Or16 || self.command == Op::Or32 || 96 | self.command == Op::Xor8 || self.command == Op::Xor16 || self.command == Op::Xor32 || 97 | self.command == Op::Cmp8 || self.command == Op::Cmp16 || self.command == Op::Cmp32 || 98 | self.command == Op::Test8 || self.command == Op::Test16 || self.command == Op::Test32 || 99 | self.command == Op::Xchg8 || self.command == Op::Xchg16 || self.command == Op::Xchg32 || 100 | self.command == Op::Mov8 || self.command == Op::Mov16 || self.command == Op::Mov32 || 101 | self.command == Op::Movsx16 || self.command == Op::Movsx32 || self.command == Op::Movzx16 102 | } 103 | 104 | fn describe_instruction(&self) -> String { 105 | let op_space = 9; 106 | let mut prefix = self.repeat.as_str().to_owned(); 107 | if prefix != "" { 108 | prefix = right_pad(&prefix, op_space); 109 | } 110 | 111 | match self.params.dst { 112 | Parameter::None => format!("{}{}", prefix, self.command), 113 | _ => { 114 | let cmd = right_pad(&format!("{}{}", prefix, self.command), op_space); 115 | 116 | match self.params.src2 { 117 | Parameter::None => match self.params.src { 118 | Parameter::None => format!("{}{}", cmd, self.params.dst), 119 | _ => format!("{}{}, {}", cmd, self.params.dst, self.params.src), 120 | }, 121 | _ => format!( 122 | "{}{}, {}, {}", 123 | cmd, 124 | self.params.dst, 125 | self.params.src, 126 | self.params.src2 127 | ), 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | #[derive(Debug, PartialEq)] 135 | pub struct InstructionInfo { 136 | pub segment: usize, 137 | pub offset: usize, 138 | pub bytes: Vec, 139 | pub instruction: Instruction, 140 | } 141 | 142 | impl fmt::Display for InstructionInfo { 143 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 144 | write!( 145 | f, 146 | "[{:04X}:{:04X}] {} {}", 147 | self.segment, 148 | self.offset, 149 | right_pad(&hex_bytes(&self.bytes), 16), 150 | format!("{}", self.instruction), 151 | ) 152 | } 153 | } 154 | 155 | #[derive(Copy, Clone, Debug, PartialEq)] 156 | pub enum RepeatMode { 157 | None, 158 | Rep, 159 | Repe, // alias repz 160 | Repne, // alias repnz 161 | } 162 | 163 | impl RepeatMode { 164 | fn as_str(&self) -> &str { 165 | match *self { 166 | RepeatMode::None => "", 167 | RepeatMode::Rep => "Rep", 168 | RepeatMode::Repe => "Repe", 169 | RepeatMode::Repne => "Repne", 170 | } 171 | } 172 | } 173 | 174 | #[derive(Debug)] 175 | pub struct ModRegRm { 176 | pub md: u8, /// "mod" is correct name, but is reserved keyword 177 | pub reg: u8, 178 | pub rm: u8, 179 | } 180 | 181 | impl ModRegRm { 182 | pub fn u8(&self) -> u8 { 183 | (self.md << 6) | // high 2 bits 184 | (self.reg << 3) | // mid 3 bits 185 | self.rm // low 3 bits 186 | } 187 | 188 | pub fn rm_reg(rm: u8, reg: u8) -> u8 { 189 | // md 3 = register adressing 190 | // XXX ModRegRm.rm really should use enum AMode, not like AMode is now. naming there is wrong 191 | ModRegRm{md: 3, rm, reg}.u8() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /dustbox/src/cpu/op.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub enum Op { 5 | /// ASCII Adjust After Addition 6 | Aaa, 7 | 8 | /// ASCII Adjust AX Before Division 9 | Aad, 10 | 11 | /// ASCII Adjust AX After Multiply 12 | Aam, 13 | 14 | /// ASCII Adjust AL After Subtraction 15 | Aas, 16 | 17 | Adc8, Adc16, Adc32, 18 | Add8, Add16, Add32, 19 | And8, And16, And32, 20 | 21 | /// Adjust RPL Field of Segment Selector 22 | Arpl, 23 | 24 | /// Check Array Index Against Bounds 25 | Bound, 26 | 27 | /// Bit Scan Forward 28 | Bsf, 29 | 30 | /// Bit Test 31 | Bt, 32 | 33 | Bts, 34 | CallNear, CallFar, 35 | 36 | /// Convert Byte to Word 37 | Cbw, 38 | 39 | /// Clear Carry Flag 40 | Clc, 41 | 42 | /// Clear Direction Flag 43 | Cld, 44 | 45 | /// Clear Interrupt Flag 46 | Cli, 47 | 48 | /// Complement Carry Flag 49 | Cmc, 50 | 51 | Cmp8, Cmp16, Cmp32, 52 | Cmpsb, Cmpsw, 53 | 54 | /// Convert Word to Doubleword 55 | Cwd16, Cwde32, 56 | 57 | /// Decimal Adjust AL after Addition 58 | Daa, 59 | 60 | /// Decimal Adjust AL after Subtraction 61 | Das, 62 | 63 | Dec8, Dec16, Dec32, 64 | Div8, Div16, Div32, 65 | 66 | Enter, 67 | Hlt, 68 | 69 | Idiv8, Idiv16, Idiv32, 70 | Imul8, Imul16, Imul32, 71 | 72 | /// Input from Port 73 | In8, In16, 74 | 75 | Inc8, Inc16, Inc32, 76 | 77 | /// Input from Port to String 78 | Insb, Insw, 79 | 80 | Int, 81 | Into, 82 | Iret, 83 | 84 | /// Jump if above (CF=0 and ZF=0). (alias: jnbe) 85 | Ja, 86 | 87 | /// Jump if carry (CF=1). (alias: jb, jnae) 88 | Jc, 89 | 90 | /// Jump if CX register is 0. 91 | Jcxz, 92 | 93 | /// Jump if greater (ZF=0 and SF=OF). (alias: jnle) 94 | Jg, 95 | 96 | /// Jump if less (SF ≠ OF). (alias: jnge) 97 | Jl, 98 | 99 | JmpShort, JmpNear, JmpFar, 100 | 101 | /// Jump if not above (CF=1 or ZF=1). (alias: jbe) 102 | Jna, 103 | 104 | /// Jump if not carry (CF=0). (alias: jae, jnb) 105 | Jnc, 106 | 107 | /// Jump if not greater (ZF=1 or SF ≠ OF). (alias: jle) 108 | Jng, 109 | 110 | /// Jump if not less (SF=OF). (alias: jge) 111 | Jnl, 112 | 113 | /// Jump if not overflow (OF=0). 114 | Jno, 115 | 116 | /// Jump if not sign (SF=0). 117 | Jns, 118 | 119 | /// Jump if not zero (ZF=0). (alias: jne) 120 | Jnz, 121 | 122 | /// Jump if overflow (OF=1). 123 | Jo, 124 | 125 | /// Jump short if parity even (PF=1) 126 | Jpe, 127 | 128 | /// Jump short if parity odd (PF=0). 129 | Jpo, 130 | 131 | /// Jump if sign (SF=1). 132 | Js, 133 | 134 | /// Jump if zero (ZF ← 1). (alias: je) 135 | Jz, 136 | 137 | /// Load Status Flags into AH Register 138 | Lahf, 139 | 140 | /// Load Access Rights Byte 141 | Lar16, 142 | 143 | /// Load DS:r16 with far pointer from memory. 144 | Lds, 145 | 146 | /// Load Effective Address 147 | /// Computes the effective address of the source operand and stores it in the destination operand. 148 | Lea16, 149 | 150 | Leave, 151 | 152 | /// Load ES:r16 with far pointer from memory. 153 | Les, 154 | 155 | /// Load byte at address DS:(E)SI into AL. 156 | Lodsb, 157 | 158 | /// Load word at address DS:(E)SI into AX. 159 | Lodsw, 160 | 161 | /// Load dword at address DS:(E)SI into EAX. 162 | Lodsd, 163 | 164 | /// Decrement count (cx); jump short if count ≠ 0. 165 | Loop, 166 | 167 | /// Decrement count (cx); jump short if count ≠ 0 and ZF = 1. 168 | Loope, 169 | 170 | /// Decrement count (cx); jump short if count ≠ 0 and ZF = 0. 171 | Loopne, 172 | 173 | Mov8, Mov16, Mov32, 174 | Movsb, Movsw, Movsd, 175 | 176 | /// Move with Sign-Extension 177 | Movsx16, Movsx32, 178 | 179 | /// Move with Zero-Extend 180 | Movzx16, Movzx32, 181 | 182 | Mul8, Mul16, Mul32, 183 | Neg8, Neg16, Neg32, 184 | Nop, 185 | Not8, Not16, Not32, 186 | Or8, Or16, Or32, 187 | Out8, Out16, 188 | Outsb, Outsw, 189 | Pop16, Pop32, 190 | 191 | /// Pop DI, SI, BP, BX, DX, CX, and AX. 192 | Popa16, 193 | 194 | /// Pop EDI, ESI, EBP, EBX, EDX, ECX, and EAX. 195 | Popad32, 196 | 197 | /// Pop top of stack into lower 16 bits of EFLAGS. 198 | Popf, 199 | 200 | Push16, Push32, 201 | 202 | /// Push AX, CX, DX, BX, original SP, BP, SI, and DI. 203 | Pusha16, 204 | 205 | /// Push EAX, ECX, EDX, EBX, original ESP, EBP, ESI, and EDI. 206 | Pushad32, 207 | 208 | /// push 16 bit FLAGS register onto stack 209 | Pushf, 210 | 211 | Rcl8, Rcl16, Rcl32, 212 | 213 | /// Rotate 9 bits (CF, r/m8) right 214 | Rcr8, 215 | 216 | /// Rotate 17 bits (CF, r/m16) right 217 | Rcr16, 218 | 219 | /// Rotate 33 bits (CF, r/m32) right 220 | Rcr32, 221 | 222 | Retn, Retf, RetImm16, 223 | 224 | Rol8, Rol16, Rol32, 225 | Ror8, Ror16, Ror32, 226 | 227 | /// Store AH into Flags 228 | Sahf, 229 | 230 | /// "salc", or "setalc" is a undocumented Intel instruction 231 | /// http://ref.x86asm.net/coder32.html#gen_note_u_SALC_D6 232 | /// http://www.rcollins.org/secrets/opcodes/SALC.html 233 | /// used by dos-software-decoding/demo-256/luminous/luminous.com 234 | Salc, 235 | 236 | Sar8, Sar16, Sar32, 237 | 238 | /// Integer Subtraction with Borrow 239 | Sbb8, Sbb16, Sbb32, 240 | 241 | Scasb, Scasw, 242 | 243 | /// setc: Set byte if carry (CF=1). 244 | /// alias setb: Set byte if below (CF=1). 245 | Setc, 246 | 247 | /// setg: Set byte if greater (ZF=0 and SF=OF). 248 | /// alias setnle: Set byte if not less or equal (ZF=0 and SF=OF). 249 | Setg, 250 | 251 | /// setnz: Set byte if not zero (ZF=0). 252 | /// alias setne: Set byte if not equal (ZF=0). 253 | Setnz, 254 | 255 | /// Multiply `dst` by 2, `src` times (alias sal) 256 | Shl8, 257 | /// Multiply `dst` by 2, `src` times (alias sal) 258 | Shl16, 259 | /// Multiply `dst` by 2, `src` times (alias sal) 260 | Shl32, 261 | 262 | /// Double Precision Shift Left 263 | Shld, 264 | 265 | Shr8, Shr16, Shr32, 266 | 267 | /// Double Precision Shift Right 268 | Shrd, 269 | 270 | Sldt, 271 | 272 | // Set Carry Flag 273 | Stc, 274 | 275 | /// Set Direction Flag 276 | Std, 277 | 278 | /// Set Interrupt Flag 279 | Sti, 280 | 281 | Stosb, Stosw, Stosd, 282 | Sub8, Sub16, Sub32, 283 | Test8, Test16, Test32, 284 | 285 | /// Exchange Register/Memory with Register 286 | Xchg8, Xchg16, Xchg32, 287 | 288 | Xlatb, 289 | 290 | Xor8, Xor16, Xor32, 291 | 292 | /// (FPU) Absolute Value 293 | Fabs, 294 | 295 | /// (FPU) Add 296 | Fadd, Faddp, 297 | 298 | /// (FPU) Change Sign 299 | Fchs, 300 | 301 | /// (FPU) Compare Floating Point Values 302 | Fcom, Fcomp, 303 | 304 | /// (FPU) Cosine 305 | Fcos, 306 | 307 | /// (FPU) Divide 308 | Fdiv, Fdivp, Fidiv, 309 | 310 | /// (FPU) Reverse Divide 311 | Fdivr, 312 | 313 | /// (FPU) Free Floating-Point Register 314 | Ffree, 315 | 316 | /// (FPU) Compare Integer 317 | Ficom, Ficomp, 318 | 319 | /// (FPU) Load Integer 320 | Fild, 321 | 322 | /// (FPU) Initialize Floating-Point Unit 323 | Finit, 324 | 325 | /// (FPU) Store Integer 326 | Fist, Fistp, 327 | 328 | /// (FPU) Store Integer with Truncation 329 | Fisttp, 330 | 331 | /// (FPU) Load Floating Point Value 332 | Fld, 333 | 334 | /// (FPU) Load Constant +1.0 335 | Fld1, 336 | 337 | /// (FPU) Load Constant log₂10 338 | Fldl2t, 339 | 340 | /// (FPU) Load Constant log₂e 341 | Fldl2e, 342 | 343 | /// (FPU) Load Constant +0.0 344 | Fldz, 345 | 346 | /// (FPU) Load Constant π 347 | Fldpi, 348 | 349 | /// (FPU) Load x87 FPU Control Word 350 | Fldcw, 351 | 352 | /// (FPU) Multiply 353 | Fmul, Fimul, 354 | 355 | /// (FPU) Partial Arctangent 356 | Fpatan, 357 | 358 | /// (FPU) Round to Integer 359 | Frndint, 360 | 361 | /// (FPU) Sine 362 | Fsin, 363 | 364 | /// (FPU) Sine and Cosine 365 | Fsincos, 366 | 367 | /// (FPU) 368 | Fsqrt, 369 | 370 | /// (FPU) Store Floating Point Value 371 | Fst, Fstp, 372 | 373 | /// (FPU) Store x87 FPU Status Word 374 | Fstsw, 375 | 376 | /// (FPU) Store x87 FPU Control Word 377 | Fnstcw, 378 | 379 | /// (FPU) Subtract 380 | Fsub, Fsubp, 381 | 382 | /// (FPU) Reverse Subtract 383 | Fsubr, Fsubrp, 384 | 385 | /// (FPU) Test 386 | Ftst, 387 | 388 | /// (FPU) Wait 389 | Fwait, 390 | 391 | /// (FPU) Exchange Register Contents 392 | Fxch, 393 | 394 | /// Initial state 395 | Uninitialized, 396 | 397 | /// Invalid encoding. XXX also used for unhandled encodings atm 398 | Invalid(Vec, Invalid), 399 | } 400 | 401 | impl fmt::Display for Op { 402 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 403 | match self { 404 | Op::Invalid(bytes, _) => { 405 | let mut x = Vec::new(); 406 | for b in bytes { 407 | x.push(format!("{:02X}", b)); 408 | } 409 | write!(f, "INVALID {}", x.join(", ")) 410 | } 411 | _ => write!(f, "{:?}", self), 412 | } 413 | } 414 | } 415 | 416 | impl Op { 417 | pub fn is_valid(&self) -> bool { 418 | match *self { 419 | Op::Uninitialized | Op::Invalid(_, _) => false, 420 | _ => true, 421 | } 422 | } 423 | } 424 | 425 | /// the class of instruction decode error that occured 426 | #[derive(Clone, Debug, PartialEq)] 427 | pub enum Invalid { 428 | /// a reg value was unhandled / invalid 429 | Reg(u8), 430 | 431 | /// unimplemented / invalid CPU instr 432 | Op, 433 | 434 | /// unimplemented / invalid FPU instr 435 | FPUOp, 436 | } 437 | -------------------------------------------------------------------------------- /dustbox/src/cpu/parameter.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::num::Wrapping; 3 | 4 | use crate::cpu::segment::Segment; 5 | use crate::cpu::register::{R, AMode}; 6 | 7 | /// A set of Parameters for an Instruction 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub struct ParameterSet { 10 | pub dst: Parameter, 11 | pub src: Parameter, 12 | pub src2: Parameter, 13 | } 14 | 15 | impl ParameterSet { 16 | // returns the number of parameters 17 | pub fn count(&self) -> usize { 18 | match self.dst { 19 | Parameter::None => 0, 20 | _ => match self.src { 21 | Parameter::None => 1, 22 | _ => match self.src2 { 23 | Parameter::None => 2, 24 | _ => 3, 25 | }, 26 | }, 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug, PartialEq)] 32 | pub enum Parameter { 33 | /// 8-bit general purpose register 34 | Reg8(R), 35 | /// 16-bit general purpose register 36 | Reg16(R), 37 | /// 16-bit segment register 38 | SReg16(R), 39 | /// 32-bit general purpose register 40 | Reg32(R), 41 | /// 80-bit fpu register 42 | FPR80(R), 43 | 44 | Imm8(u8), // byte 0x80 45 | ImmS8(i8), // byte +0x3f 46 | Imm16(u16), // word 0x8000 47 | Imm32(u32), // dword 0x8000_0000 48 | Ptr16Imm(u16, u16), // jmp far u16:u16 49 | 50 | Ptr8(Segment, u16), // byte [u16], like "byte [0x4040]" 51 | Ptr8Amode(Segment, AMode), // byte [amode], like "byte [bx]" 52 | Ptr8AmodeS8(Segment, AMode, i8), // byte [amode+s8], like "byte [bp-0x20]" 53 | Ptr8AmodeS16(Segment, AMode, i16), // byte [amode+s16], like "byte [bp-0x2020]" 54 | 55 | Ptr16(Segment, u16), // word [u16], like "word [0x4040]" 56 | Ptr16Amode(Segment, AMode), // word [amode], like "word [bx]" 57 | Ptr16AmodeS8(Segment, AMode, i8), // word [amode+s8], like "word [bp-0x20]" 58 | Ptr16AmodeS16(Segment, AMode, i16), // word [amode+s16], like "word [bp-0x2020]" 59 | 60 | Ptr32(Segment, u16), // dword [u16], like "dword [0x4040]" 61 | Ptr32Amode(Segment, AMode), // dword [amode], like "dword [bx]" 62 | Ptr32AmodeS8(Segment, AMode, i8), // dword [amode+s8], like "dword [bp-0x20]" 63 | Ptr32AmodeS16(Segment, AMode, i16), // dword [amode+s16], like "dword [bp-0x2020]" 64 | None, 65 | } 66 | 67 | impl fmt::Display for Parameter { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | match *self { 70 | Parameter::Reg8(ref r) | 71 | Parameter::Reg16(ref r) | 72 | Parameter::Reg32(ref r) | 73 | Parameter::SReg16(ref r) | 74 | Parameter::FPR80(ref r) => write!(f, "{}", r), 75 | 76 | Parameter::Imm8(imm) => write!(f, "0x{:02X}", imm), 77 | Parameter::Imm16(imm) => write!(f, "0x{:04X}", imm), 78 | Parameter::Imm32(imm) => write!(f, "0x{:08X}", imm), 79 | Parameter::ImmS8(imm) => write!( 80 | f, 81 | "byte {}0x{:02X}", 82 | if imm < 0 { "-" } else { "+" }, 83 | if imm < 0 { 84 | (Wrapping(0) - Wrapping(imm)).0 85 | } else { 86 | imm 87 | } 88 | ), 89 | Parameter::Ptr16Imm(seg, v) => write!(f, "{:04X}:{:04X}", seg, v), 90 | Parameter::Ptr8(seg, v) => write!(f, "byte [{}:0x{:04X}]", seg, v), 91 | Parameter::Ptr8Amode(seg, ref amode) => write!(f, "byte [{}:{}]", seg, amode), 92 | Parameter::Ptr8AmodeS8(seg, ref amode, imm) => write!( 93 | f, 94 | "byte [{}:{}{}0x{:02X}]", 95 | seg, 96 | amode, 97 | if imm < 0 { "-" } else { "+" }, 98 | if imm < 0 { 99 | (Wrapping(0) - Wrapping(imm)).0 100 | } else { 101 | imm 102 | } 103 | ), 104 | Parameter::Ptr8AmodeS16(seg, ref amode, imm) => write!( 105 | f, 106 | "byte [{}:{}{}0x{:04X}]", 107 | seg, 108 | amode, 109 | if imm < 0 { "-" } else { "+" }, 110 | if imm < 0 { 111 | (Wrapping(0) - Wrapping(imm)).0 112 | } else { 113 | imm 114 | } 115 | ), 116 | Parameter::Ptr16(seg, v) => write!(f, "word [{}:0x{:04X}]", seg, v), 117 | Parameter::Ptr16Amode(seg, ref amode) => write!(f, "word [{}:{}]", seg, amode), 118 | Parameter::Ptr16AmodeS8(seg, ref amode, imm) => write!( 119 | f, 120 | "word [{}:{}{}0x{:02X}]", 121 | seg, 122 | amode, 123 | if imm < 0 { "-" } else { "+" }, 124 | if imm < 0 { 125 | (Wrapping(0) - Wrapping(imm)).0 126 | } else { 127 | imm 128 | } 129 | ), 130 | Parameter::Ptr16AmodeS16(seg, ref amode, imm) => write!( 131 | f, 132 | "word [{}:{}{}0x{:04X}]", 133 | seg, 134 | amode, 135 | if imm < 0 { "-" } else { "+" }, 136 | if imm < 0 { 137 | (Wrapping(0) - Wrapping(imm)).0 138 | } else { 139 | imm 140 | } 141 | ), 142 | Parameter::Ptr32(seg, v) => write!(f, "dword [{}:0x{:04X}]", seg, v), 143 | Parameter::Ptr32Amode(seg, ref amode) => write!(f, "dword [{}:{}]", seg, amode), 144 | Parameter::Ptr32AmodeS8(seg, ref amode, imm) => write!( 145 | f, 146 | "dword [{}:{}{}0x{:02X}]", 147 | seg, 148 | amode, 149 | if imm < 0 { "-" } else { "+" }, 150 | if imm < 0 { 151 | (Wrapping(0) - Wrapping(imm)).0 152 | } else { 153 | imm 154 | } 155 | ), 156 | Parameter::Ptr32AmodeS16(seg, ref amode, imm) => write!( 157 | f, 158 | "dword [{}:{}{}0x{:04X}]", 159 | seg, 160 | amode, 161 | if imm < 0 { "-" } else { "+" }, 162 | if imm < 0 { 163 | (Wrapping(0) - Wrapping(imm)).0 164 | } else { 165 | imm 166 | } 167 | ), 168 | Parameter::None => write!(f, ""), 169 | } 170 | } 171 | } 172 | 173 | impl Parameter { 174 | pub fn is_imm(&self) -> bool { 175 | match *self { 176 | Parameter::Imm8(_) | 177 | Parameter::Imm16(_) | 178 | Parameter::Imm32(_) | 179 | Parameter::ImmS8(_) => true, 180 | _ => false, 181 | } 182 | } 183 | 184 | pub fn is_ptr(&self) -> bool { 185 | match *self { 186 | Parameter::Ptr8(_, _) | 187 | Parameter::Ptr16(_, _) | 188 | Parameter::Ptr16Imm(_, _) | 189 | Parameter::Ptr8Amode(_, _) | 190 | Parameter::Ptr8AmodeS8(_, _, _) | 191 | Parameter::Ptr8AmodeS16(_, _, _) | 192 | Parameter::Ptr16Amode(_, _) | 193 | Parameter::Ptr16AmodeS8(_, _, _) | 194 | Parameter::Ptr16AmodeS16(_, _, _) => true, 195 | _ => false, 196 | } 197 | } 198 | 199 | pub fn is_reg(&self) -> bool { 200 | match *self { 201 | Parameter::Reg8(_) | 202 | Parameter::Reg16(_) | 203 | Parameter::Reg32(_) | 204 | Parameter::SReg16(_) => true, 205 | _ => false, 206 | } 207 | } 208 | 209 | pub fn is_none(&self) -> bool { 210 | *self == Parameter::None 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /dustbox/src/cpu/register_test.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::register::{R, RegisterState}; 2 | 3 | #[test] 4 | fn can_access_gpr() { 5 | let mut r = RegisterState::default(); 6 | r.set_r32(R::ECX, 0xFFFF_FFFF); 7 | assert_eq!(0xFFFF_FFFF, r.get_r32(R::ECX)); 8 | 9 | r.set_r16(R::CX, 0x1616); 10 | assert_eq!(0x1616, r.get_r16(R::CX)); 11 | assert_eq!(0xFFFF_1616, r.get_r32(R::ECX)); 12 | 13 | r.set_r8(R::CL, 0x08); 14 | assert_eq!(0x08, r.get_r8(R::CL)); 15 | assert_eq!(0xFFFF_1608, r.get_r32(R::ECX)); 16 | } 17 | -------------------------------------------------------------------------------- /dustbox/src/cpu/segment.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::cpu::register::R; 4 | 5 | #[derive(Debug, Copy, Clone, PartialEq)] 6 | pub enum Segment { 7 | Default, 8 | CS, 9 | DS, 10 | ES, 11 | FS, 12 | GS, 13 | SS, 14 | } 15 | 16 | impl fmt::Display for Segment { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | write!(f, "{}", self.as_str()) 19 | } 20 | } 21 | 22 | impl Segment { 23 | pub fn as_str(&self) -> &str { 24 | match *self { 25 | Segment::Default | Segment::DS => "ds", 26 | Segment::CS => "cs", 27 | Segment::ES => "es", 28 | Segment::FS => "fs", 29 | Segment::GS => "gs", 30 | Segment::SS => "ss", 31 | } 32 | } 33 | 34 | pub fn as_register(self) -> R { 35 | match self { 36 | Segment::Default | Segment::DS => R::DS, 37 | Segment::CS => R::CS, 38 | Segment::ES => R::ES, 39 | Segment::FS => R::FS, 40 | Segment::GS => R::GS, 41 | Segment::SS => R::SS, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dustbox/src/debug/breakpoints.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | #[path = "./breakpoints_test.rs"] 3 | mod breakpoints_test; 4 | 5 | #[derive(Default)] 6 | pub struct Breakpoints { 7 | breakpoints: Vec, 8 | } 9 | 10 | /// a list of addresses for the debugger to break on when CS:IP reach one of them 11 | impl Breakpoints { 12 | pub fn add(&mut self, bp: u32) -> Option { 13 | if self.breakpoints.iter().find(|&&x|x == bp).is_none() { 14 | self.breakpoints.push(bp); 15 | Some(bp) 16 | } else { 17 | None 18 | } 19 | } 20 | 21 | pub fn remove(&mut self, bp: u32) -> Option { 22 | // TODO later: simplify when https://github.com/rust-lang/rust/issues/40062 is stable 23 | match self.breakpoints.iter().position(|x| *x == bp) { 24 | Some(pos) => { 25 | self.breakpoints.remove(pos); 26 | Some(bp) 27 | }, 28 | None => None, 29 | } 30 | } 31 | 32 | /// returns a Vec with breakpoints sorted ascending 33 | pub fn get(&self) -> Vec { 34 | let mut sorted = self.breakpoints.clone(); 35 | sorted.sort(); 36 | sorted 37 | } 38 | 39 | pub fn clear(&mut self) { 40 | self.breakpoints.clear(); 41 | } 42 | 43 | /// returns true if address is at breakpoint 44 | pub fn hit(&self, address: u32) -> bool { 45 | self.breakpoints.iter().any(|&x| x == address) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dustbox/src/debug/breakpoints_test.rs: -------------------------------------------------------------------------------- 1 | use crate::debug::breakpoints::Breakpoints; 2 | 3 | #[test] 4 | fn sorted_breakpoints() { 5 | let mut bps = Breakpoints::default(); 6 | bps.add(3); 7 | bps.add(1); 8 | bps.add(2); 9 | 10 | assert_eq!(vec![1,2,3], bps.get()); 11 | } 12 | -------------------------------------------------------------------------------- /dustbox/src/debug/debugger_test.rs: -------------------------------------------------------------------------------- 1 | use crate::debug::Debugger; 2 | use crate::cpu::R; 3 | 4 | #[test] 5 | fn test_parse_hex_string() { 6 | let mut dbg = Debugger::default(); 7 | dbg.machine.cpu.set_r16(R::CS, 0x085F); 8 | assert_eq!(0x1234, dbg.parse_register_hex_string("1234").unwrap()); 9 | assert_eq!(0xFFFF, dbg.parse_register_hex_string("FFFF").unwrap()); 10 | assert_eq!(0xFFFF, dbg.parse_register_hex_string("0xFFFF").unwrap()); 11 | assert_eq!(0xFFFF, dbg.parse_register_hex_string("0XFFFF").unwrap()); 12 | assert_eq!(0x085F, dbg.parse_register_hex_string("CS").unwrap()); 13 | } 14 | 15 | #[test] 16 | fn test_parse_segment_offset_pair() { 17 | let mut dbg = Debugger::default(); 18 | dbg.machine.cpu.set_r16(R::CS, 0x085F); 19 | assert_eq!(0x8731, dbg.parse_segment_offset_pair("085F:0141").unwrap()); 20 | assert_eq!(0x8731, dbg.parse_segment_offset_pair("0x085F:0x0141").unwrap()); 21 | assert_eq!(0x8731, dbg.parse_segment_offset_pair("CS:0141").unwrap()); 22 | assert_eq!(0x873F, dbg.parse_segment_offset_pair("873F").unwrap()); 23 | } 24 | 25 | 26 | #[test] 27 | fn test_dis_toml_file() { 28 | // XXX make use of this 29 | 30 | #[derive(Debug, Deserialize)] 31 | struct DisNote { 32 | offset: usize, 33 | text: String, 34 | extra: Option, 35 | } 36 | 37 | #[derive(Debug, Deserialize)] 38 | struct DisToml { 39 | notes: Option>, 40 | } 41 | 42 | let toml_str = r#" 43 | notes = [ 44 | { offset = 0x0185, text = "push cs + 0x20", extra = "085F:0185"}, 45 | { offset = 0x0189, text = "???", extra = "085F:0189 (mov word [ds:0x0201], ax)"}, 46 | { offset = 0x018E, text = "push 0", extra = "085F:018E"}, 47 | { offset = 0x018F, text = "set cs:ip to 087F:0000", extra = "085F:018F"}, 48 | ] 49 | "#; 50 | 51 | let decoded: DisToml = toml::from_str(toml_str).unwrap(); 52 | println!("{:#?}", decoded); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /dustbox/src/debug/memory_breakpoints.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[cfg(test)] 4 | #[path = "./memory_breakpoints_test.rs"] 5 | mod memory_breakpoints_test; 6 | 7 | #[derive(Default)] 8 | pub struct MemoryBreakpoints { 9 | breakpoints: Vec, 10 | 11 | /// tracks previous memory values to find changes 12 | map: HashMap, 13 | } 14 | 15 | /// a list of addresses for the debugger to break on when memory content changes 16 | impl MemoryBreakpoints { 17 | pub fn default() -> Self { 18 | MemoryBreakpoints { 19 | breakpoints: vec![0; 0], 20 | map: HashMap::new(), 21 | } 22 | } 23 | 24 | pub fn add(&mut self, bp: u32) -> Option { 25 | if self.breakpoints.iter().find(|&&x|x == bp).is_none() { 26 | self.breakpoints.push(bp); 27 | Some(bp) 28 | } else { 29 | None 30 | } 31 | } 32 | 33 | pub fn remove(&mut self, bp: u32) -> Option { 34 | // TODO later: simplify when https://github.com/rust-lang/rust/issues/40062 is stable 35 | match self.breakpoints.iter().position(|x| *x == bp) { 36 | Some(pos) => { 37 | self.breakpoints.remove(pos); 38 | Some(bp) 39 | }, 40 | None => None, 41 | } 42 | } 43 | 44 | /// returns a Vec with breakpoints sorted ascending 45 | pub fn get(&self) -> Vec { 46 | let mut sorted = self.breakpoints.clone(); 47 | sorted.sort(); 48 | sorted 49 | } 50 | 51 | pub fn clear(&mut self) { 52 | self.breakpoints.clear(); 53 | } 54 | 55 | /// returns true if memory value has changed since last check 56 | pub fn has_changed(&mut self, address: u32, val: u8) -> bool { 57 | let t = self.map.entry(address).or_insert(val); 58 | let old = *t; 59 | *t = val; 60 | old != val 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /dustbox/src/debug/memory_breakpoints_test.rs: -------------------------------------------------------------------------------- 1 | use crate::debug::memory_breakpoints::MemoryBreakpoints; 2 | 3 | #[test] 4 | fn sorted_memory_breakpoints() { 5 | let mut bps = MemoryBreakpoints::default(); 6 | bps.add(3); 7 | bps.add(1); 8 | bps.add(2); 9 | 10 | assert_eq!(vec![1,2,3], bps.get()); 11 | } 12 | 13 | #[test] 14 | fn memory_breakpoints_has_changed() { 15 | let mut bps = MemoryBreakpoints::default(); 16 | 17 | assert_eq!(false, bps.has_changed(0x1234, 1)); 18 | assert_eq!(false, bps.has_changed(0x1234, 1)); 19 | assert_eq!(true, bps.has_changed(0x1234, 2)); 20 | assert_eq!(false, bps.has_changed(0x1234, 2)); 21 | } 22 | -------------------------------------------------------------------------------- /dustbox/src/debug/mod.rs: -------------------------------------------------------------------------------- 1 | // these modules are re-exported as a single module 2 | 3 | pub use self::breakpoints::*; 4 | mod breakpoints; 5 | 6 | pub use self::memory_breakpoints::*; 7 | mod memory_breakpoints; 8 | 9 | pub use self::tracer::*; 10 | mod tracer; 11 | 12 | pub use self::debugger::*; 13 | mod debugger; 14 | -------------------------------------------------------------------------------- /dustbox/src/dos/mod.rs: -------------------------------------------------------------------------------- 1 | // these modules are re-exported as a single module 2 | 3 | pub use self::dos::*; 4 | mod dos; 5 | -------------------------------------------------------------------------------- /dustbox/src/format/exe.rs: -------------------------------------------------------------------------------- 1 | /// http://www.delorie.com/djgpp/doc/exe/ 2 | /// http://www.delorie.com/djgpp/doc/rbinter/id/51/29.html 3 | 4 | use std::fmt; 5 | 6 | use bincode::deserialize; 7 | 8 | pub struct ExeFile { 9 | pub header: ExeHeader, 10 | pub relocs: Vec, 11 | pub program_data: Vec, 12 | 13 | /// total .EXE file size 14 | exe_size: usize, 15 | } 16 | 17 | pub enum ParseError { 18 | WrongMagic, 19 | } 20 | 21 | const DEBUG_PARSER: bool = false; 22 | 23 | /// Header pages is 512 bytes 24 | /// also sometimes called blocks 25 | const PAGE_SIZE: u16 = 512; 26 | 27 | impl ExeFile { 28 | pub fn from_data(data: &[u8]) -> Result { 29 | let header = match ExeHeader::from_data(data) { 30 | Ok(hdr) => hdr, 31 | Err(e) => panic!(e), 32 | }; 33 | 34 | if header.exe_data_end_offset() > data.len() { 35 | println!("WARNING: program end = {:04X} but data len = {:04X}", header.exe_data_end_offset(), data.len()); 36 | } 37 | let program_start = header.exe_data_start_offset(); 38 | let program_data = data[program_start..data.len()].to_vec(); 39 | let relocs = header.parse_relocations(data); 40 | println!(" program start in exe: {:04X}", program_start); 41 | 42 | Ok(ExeFile { 43 | header, 44 | relocs, 45 | program_data, 46 | exe_size: data.len(), 47 | }) 48 | } 49 | 50 | pub fn print_details(&self) { 51 | println!("exe file size: {} bytes", self.exe_size); 52 | self.header.print_details(); 53 | 54 | if self.header.relocations > 0 { 55 | println!("relocations:"); 56 | for (i, reloc) in self.relocs.iter().enumerate() { 57 | println!(" {}: {}", i, reloc); 58 | } 59 | } 60 | } 61 | } 62 | 63 | #[derive(Deserialize, Debug)] 64 | pub struct ExeHeader { 65 | /// magic number "MZ" 66 | pub signature: [u8; 2], 67 | 68 | /// number of bytes in last 512-byte page of executable 69 | pub bytes_in_last_page: u16, 70 | 71 | /// Total number of 512-byte pages in executable (includes any partial last page) 72 | /// If `bytes_in_last_page` is non-zero, only that much of the last block is used. 73 | pub pages: u16, 74 | 75 | /// Number of relocation entries. 76 | pub relocations: u16, 77 | 78 | /// Header size in 16-byte paragraphs. 79 | pub header_paragraphs: u16, 80 | 81 | /// Minimum paragraphs of memory required to allocate in addition to executable's size. 82 | pub min_extra_paragraphs: u16, 83 | 84 | /// Maximum paragraphs to allocate in addition to executable's size. 85 | pub max_extra_paragraphs: u16, 86 | 87 | /// Initial (relative) SS. 88 | pub ss: i16, 89 | 90 | /// Initial SP. 91 | pub sp: u16, 92 | 93 | /// Checksum (usually unset). 94 | pub checksum: u16, 95 | 96 | /// Initial IP. 97 | pub ip: u16, 98 | 99 | /// Initial (relative) CS. 100 | pub cs: i16, 101 | 102 | /// Offset within header of relocation table. 103 | /// 40h or greater for new-format (NE,LE,LX,W3,PE,etc.) executable. 104 | pub reloc_table_offset: u16, 105 | 106 | /// Overlay number (normally 0000h = main program). 107 | pub overlay_number: u16, 108 | } 109 | 110 | impl ExeHeader { 111 | pub fn from_data(data: &[u8]) -> Result { 112 | let h: ExeHeader = deserialize(data).unwrap(); 113 | if h.signature[0] != 0x4D || h.signature[1] != 0x5A { 114 | return Err(ParseError::WrongMagic); 115 | } 116 | 117 | Ok(h) 118 | } 119 | 120 | /// Returns the header size in bytes. 121 | fn header_size(&self) -> usize { 122 | // a header paragraph is 16 bytes wide 123 | (self.header_paragraphs as usize) * 16 124 | } 125 | 126 | /// Returns the starting offset of the program code inside the EXE file. 127 | fn exe_data_start_offset(&self) -> usize { 128 | // XXX note this is not the start of CODE! 129 | self.header_size() 130 | } 131 | 132 | /// Returns the end offset of the program code inside the EXE file. 133 | fn exe_data_end_offset(&self) -> usize { 134 | let mut code_end = self.pages as usize * 512; 135 | if self.bytes_in_last_page > 0 { 136 | code_end -= 512 - self.bytes_in_last_page as usize; 137 | } 138 | code_end 139 | } 140 | 141 | /// parses the exe header relocation table 142 | fn parse_relocations(&self, data: &[u8]) -> Vec { 143 | let mut relocs = Vec::new(); 144 | 145 | if self.relocations > 0 { 146 | if DEBUG_PARSER { 147 | println!("relocations ({}):", self.relocations); 148 | } 149 | let mut offset = self.reloc_table_offset as usize; 150 | for i in 0..self.relocations { 151 | let reloc: ExeRelocation = deserialize(&data[offset..offset+4]).unwrap(); 152 | if DEBUG_PARSER { 153 | println!(" {}: {:?}", i, reloc); 154 | } 155 | relocs.push(reloc); 156 | offset += 4; 157 | } 158 | } 159 | relocs 160 | } 161 | 162 | fn print_details(&self) { 163 | println!("ExeHeader::print_details {:#?}", self); 164 | let pages_in_bytes = self.pages as usize * PAGE_SIZE as usize; 165 | println!("pages: {}, and {} bytes in last page, pages in bytes = {}", self.pages, self.bytes_in_last_page, pages_in_bytes); 166 | println!("header size: {} paragraphs / {} bytes (0x{:04X})", self.header_paragraphs, self.header_size(), self.header_size()); 167 | println!("extra paragraphs: min {}, max {}", self.min_extra_paragraphs, self.max_extra_paragraphs); 168 | println!("ss:sp = {:04X}:{:04X}", self.ss, self.sp); 169 | println!("cs:ip = {:04X}:{:04X}", self.cs, self.ip); 170 | println!("checksum: {:04X}", self.checksum); 171 | if self.overlay_number != 0 { 172 | println!("overlay number: {}", self.overlay_number); 173 | } 174 | 175 | if self.reloc_table_offset >= 0x40 { 176 | println!("ERROR: unhandled new-format (NE,LE,LX,W3,PE,etc.) executable"); 177 | } 178 | 179 | if self.relocations > 0 { 180 | let reloc_start = self.reloc_table_offset as usize; 181 | let reloc_end = (reloc_start) + (self.relocations as usize * 4); 182 | let reloc_size = reloc_end - reloc_start; 183 | println!("- relocations ({}) from {:04X} to {:04X} ({} bytes)", self.relocations, reloc_start, reloc_end, reloc_size); 184 | } 185 | 186 | let code_start = self.exe_data_start_offset(); 187 | let code_end = self.exe_data_end_offset(); 188 | let code_size = code_end - code_start; 189 | println!("- exe data from {:04X} to {:04X} ({} bytes)", code_start, code_end, code_size); 190 | } 191 | } 192 | 193 | #[derive(Deserialize, Debug)] 194 | pub struct ExeRelocation { 195 | pub offset: u16, 196 | pub segment: u16, 197 | } 198 | 199 | impl fmt::Display for ExeRelocation { 200 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 201 | write!(f, "{:04X}:{:04X}", self.segment, self.offset) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /dustbox/src/format/mod.rs: -------------------------------------------------------------------------------- 1 | // these modules are re-exported as a single module 2 | 3 | pub use self::exe::*; 4 | mod exe; 5 | -------------------------------------------------------------------------------- /dustbox/src/gpu/crtc.rs: -------------------------------------------------------------------------------- 1 | const DEBUG_CRTC: bool = false; 2 | 3 | #[derive(Clone, Default)] 4 | pub struct CRTC { 5 | horizontal_total: u8, 6 | horizontal_display_end: u8, 7 | start_horizontal_blanking: u8, 8 | end_horizontal_blanking: u8, 9 | start_horizontal_retrace: u8, 10 | end_horizontal_retrace: u8, 11 | vertical_total: u8, 12 | overflow: u8, 13 | preset_row_scan: u8, 14 | maximum_scan_line: u8, 15 | cursor_start: u8, 16 | cursor_end: u8, 17 | start_address_high: u8, 18 | start_address_low: u8, 19 | cursor_location_high: u8, 20 | cursor_location_low: u8, 21 | vertical_retrace_start: u8, 22 | pub vertical_retrace_end: u8, 23 | vertical_display_end: u8, 24 | offset: u8, 25 | underline_location: u8, 26 | start_vertical_blanking: u8, 27 | end_vertical_blanking: u8, 28 | mode_control: u8, 29 | line_compare: u8, 30 | 31 | pub index: u8, 32 | read_only: bool, 33 | } 34 | 35 | impl CRTC { 36 | // 03D4 rW CRT (6845) register index (CGA/MCGA/color EGA/color VGA) 37 | // selects which register (0-11h) is to be accessed through 03D5 38 | // bit 7-6 =0: (VGA) reserved 39 | // bit 5 =0: (VGA) reserved for testage 40 | // bit 4-0 : selects which register is to be accessed through 03D5 41 | pub fn set_index(&mut self, data: u8) { 42 | if DEBUG_CRTC { 43 | // println!("CRTC set_index {}", data & 0x1F); 44 | } 45 | self.index = data & 0x1F; 46 | } 47 | 48 | // 03D5 -W CRT (6845) data register (CGA/MCGA/color EGA/color VGA) (see #P0708) 49 | // selected by PORT 03D4h. registers 0C-0F may be read (see also PORT 03B5h) 50 | // MCGA, native EGA and VGA use very different defaults from those 51 | // mentioned for the other adapters; for additional notes and 52 | // registers 00h-0Fh and EGA/VGA registers 10h-18h and ET4000 53 | // registers 32h-37h see PORT 03B5h (see #P0654) 54 | // registers 10h-11h on CGA, EGA, VGA and 12h-14h on EGA, VGA are conflictive with MCGA (see #P0710) 55 | pub fn write_current(&mut self, data: u8) { 56 | if DEBUG_CRTC { 57 | println!("CRTC write_current {:02X} = {:02X}", self.index, data); 58 | } 59 | match self.index { 60 | 0x00 => self.horizontal_total = data, 61 | 0x01 => self.horizontal_display_end = data, 62 | 0x02 => self.start_horizontal_blanking = data, 63 | 0x03 => self.end_horizontal_blanking = data, 64 | 0x04 => self.start_horizontal_retrace = data, 65 | 0x05 => self.end_horizontal_retrace = data, 66 | 0x06 => self.vertical_total = data, 67 | 0x07 => self.overflow = data, 68 | 0x08 => self.preset_row_scan = data, 69 | 0x09 => self.maximum_scan_line = data, 70 | 0x0A => self.cursor_start = data, 71 | 0x0B => self.cursor_end = data, 72 | 0x0C => self.start_address_high = data, 73 | 0x0D => self.start_address_low = data, 74 | 0x0E => self.cursor_location_high = data, 75 | 0x0F => self.cursor_location_low = data, 76 | 0x10 => self.vertical_retrace_start = data, 77 | 0x11 => self.vertical_retrace_end = data, 78 | 0x12 => self.vertical_display_end = data, 79 | 0x13 => self.offset = data, 80 | 0x14 => self.underline_location = data, 81 | 0x15 => self.start_vertical_blanking = data, 82 | 0x16 => self.end_vertical_blanking = data, 83 | 0x17 => self.mode_control = data, 84 | 0x18 => self.line_compare = data, 85 | _ => panic!(), 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dustbox/src/gpu/dac.rs: -------------------------------------------------------------------------------- 1 | use crate::gpu::palette::{ColorSpace, text_palette}; 2 | use crate::gpu::palette::ColorSpace::RGB; 3 | 4 | const DEBUG_DAC: bool = false; 5 | 6 | #[derive(Clone)] 7 | pub struct DAC { 8 | /// DAC bits, usually 6 or 8 9 | bits: u8, 10 | 11 | pub pel_mask: u8, 12 | 13 | /// color component for next out 03c9, 0 = red, 1 = green, 2 = blue 14 | pub pel_index: u8, 15 | 16 | pub state: State, 17 | 18 | /// set by io write to 03c7 19 | pub read_index: u8, 20 | 21 | /// set by io write to 03c8 22 | pub write_index: u8, 23 | 24 | first_changed: usize, 25 | 26 | pub combine: [u8; 16], 27 | 28 | pub pal: Vec, 29 | 30 | pub hidac_counter: u8, 31 | 32 | reg02: u8, 33 | } 34 | 35 | impl Default for DAC { 36 | fn default() -> Self { 37 | DAC { 38 | bits: 0, 39 | pel_mask: 0xFF, 40 | pel_index: 0, 41 | state: State::Read, 42 | read_index: 0, 43 | write_index: 0, 44 | first_changed: 0, 45 | combine: [0; 16], 46 | pal: text_palette().to_vec(), 47 | hidac_counter: 0, 48 | reg02: 0, 49 | } 50 | } 51 | } 52 | 53 | impl DAC { 54 | /// (VGA) DAC state register (0x03C7) 55 | pub fn get_state(&mut self) -> u8 { 56 | self.hidac_counter = 0; 57 | let res = self.state.register(); 58 | if DEBUG_DAC { 59 | println!("read port 03C7: get_state = {:02X}", res); 60 | } 61 | res 62 | } 63 | 64 | /// (VGA, MCGA) PEL mask register (0x03C6) 65 | pub fn set_pel_mask(&mut self, val: u8) { 66 | self.pel_mask = val; 67 | } 68 | 69 | /// (VGA,MCGA,CEG-VGA) PEL address register (read mode) (0x03C7) 70 | /// Sets DAC in read mode and assign start of color register 71 | /// index (0..255) for following read accesses to 3C9h. 72 | /// Don't write to 3C9h while in read mode. Next access to 73 | /// 03C8h will stop pending mode immediatly. 74 | pub fn set_pel_read_index(&mut self, val: u8) { 75 | self.state = State::Read; 76 | self.read_index = val; 77 | self.write_index = val + 1; 78 | self.pel_index = 0; 79 | self.hidac_counter = 0; 80 | if DEBUG_DAC { 81 | println!("write port 03C7: set_pel_read_index = {:02X}", val); 82 | } 83 | } 84 | 85 | /// (VGA,MCGA) PEL address register (0x03C8) 86 | pub fn get_pel_write_index(&mut self) -> u8 { 87 | self.hidac_counter = 0; 88 | if DEBUG_DAC { 89 | println!("read port 03C8: get_pel_write_index = {:02X}", self.write_index); 90 | } 91 | self.write_index 92 | } 93 | 94 | /// (VGA,MCGA) PEL address register (write mode) (0x03C8) 95 | /// Sets DAC in write mode and assign start of color register 96 | /// index (0..255) for following write accesses to 3C9h. 97 | /// Next access to 03C8h will stop pending mode immediately. 98 | pub fn set_pel_write_index(&mut self, val: u8) { 99 | self.state = State::Write; 100 | self.write_index = val; 101 | self.pel_index = 0; 102 | self.hidac_counter = 0; 103 | if DEBUG_DAC { 104 | println!("write port 03C8: set_pel_write_index = {:02X}", val); 105 | } 106 | } 107 | 108 | /// (VGA,MCGA) PEL data register (0x03C9) 109 | /// Three consequtive reads (in read mode) in the order: red, green, blue. 110 | /// The internal DAC index is incremented each 3rd access. 111 | pub fn get_pel_data(&mut self) -> u8 { 112 | self.hidac_counter = 0; 113 | let ret = match self.pal[self.read_index as usize] { 114 | RGB(r, g, b) => { 115 | match self.pel_index { 116 | 0 => { 117 | self.pel_index = 1; 118 | r >> 2 119 | } 120 | 1 => { 121 | self.pel_index = 2; 122 | g >> 2 123 | } 124 | 2 => { 125 | self.pel_index = 0; 126 | self.read_index += 1; 127 | b >> 2 128 | } 129 | _ => unreachable!(), 130 | } 131 | } 132 | _ => unreachable!(), 133 | }; 134 | if DEBUG_DAC { 135 | println!("read port 03C9: get_pel_data = {:02X}", ret); 136 | } 137 | ret 138 | } 139 | 140 | /// (VGA,MCGA) PEL data register (0x03C9) 141 | /// Three consecutive writes (in write mode) in the order: red, green, blue. 142 | /// The internal DAC index is incremented on every 3rd access. 143 | pub fn set_pel_data(&mut self, mut val: u8) { 144 | val &= 0x3F; 145 | if DEBUG_DAC { 146 | println!("write port 03C9: set_pel_data = write index {:02X}, pel index {:02X} = {:02X}", self.write_index, self.pel_index, val); 147 | } 148 | // scale 6-bit color into 8 bits 149 | val <<= 2; 150 | 151 | self.hidac_counter = 0; 152 | if let RGB(ref mut r, ref mut g, ref mut b) = self.pal[self.write_index as usize] { 153 | match self.pel_index { 154 | 0 => *r = val, 155 | 1 => *g = val, 156 | 2 => *b = val, 157 | _ => unreachable!(), 158 | } 159 | } 160 | 161 | self.pel_index += 1; 162 | if self.pel_index > 2 { 163 | // println!("self.write_index as usize {} len {}", self.write_index as usize,self.pal.len() ); 164 | if self.write_index as usize >= self.pal.len() - 1 { 165 | // println!("XXX dac write_index wrapped to 0 at {}", self.pal.len()); 166 | self.write_index = 0; 167 | } else { 168 | self.write_index += 1; 169 | } 170 | self.pel_index = 0; 171 | } 172 | } 173 | } 174 | 175 | #[derive(Clone, PartialEq)] 176 | pub enum State { 177 | Read, Write, 178 | } 179 | 180 | impl State { 181 | /// encodes state for the DAC state register (0x03C7) 182 | pub fn register(&self) -> u8 { 183 | match *self { 184 | State::Read => 0b11, 185 | State::Write => 0b00, 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /dustbox/src/gpu/graphic_card.rs: -------------------------------------------------------------------------------- 1 | /// GraphicCard indicates the gfx card generation to emulate 2 | #[derive(Clone, Debug, PartialEq)] 3 | pub enum GraphicCard { 4 | CGA, EGA, VGA, Tandy, PcJr, 5 | } 6 | 7 | impl GraphicCard { 8 | pub fn is_ega_vga(&self) -> bool { 9 | match *self { 10 | GraphicCard::EGA | GraphicCard::VGA => true, 11 | _ => false, 12 | } 13 | } 14 | pub fn is_tandy(&self) -> bool { 15 | match *self { 16 | GraphicCard::Tandy => true, 17 | _ => false, 18 | } 19 | } 20 | pub fn is_pc_jr(&self) -> bool { 21 | match *self { 22 | GraphicCard::PcJr => true, 23 | _ => false, 24 | } 25 | } 26 | pub fn is_cga(&self) -> bool { 27 | match *self { 28 | GraphicCard::CGA => true, 29 | _ => false, 30 | } 31 | } 32 | pub fn is_ega(&self) -> bool { 33 | match *self { 34 | GraphicCard::EGA => true, 35 | _ => false, 36 | } 37 | } 38 | pub fn is_vga(&self) -> bool { 39 | match *self { 40 | GraphicCard::VGA => true, 41 | _ => false, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dustbox/src/gpu/mod.rs: -------------------------------------------------------------------------------- 1 | // these modules are re-exported as a single module 2 | 3 | pub use self::render::*; 4 | mod render; 5 | 6 | pub use self::palette::*; 7 | mod palette; 8 | 9 | pub use self::font::*; 10 | mod font; 11 | 12 | pub use self::video_parameters::*; 13 | mod video_parameters; 14 | 15 | pub use self::modes::*; 16 | mod modes; 17 | 18 | pub use self::graphic_card::*; 19 | mod graphic_card; 20 | 21 | pub use self::crtc::*; 22 | mod crtc; 23 | 24 | pub use self::dac::*; 25 | mod dac; 26 | -------------------------------------------------------------------------------- /dustbox/src/gpu/modes_test.rs: -------------------------------------------------------------------------------- 1 | use crate::gpu::modes::{ega_mode_block, vga_mode_block}; 2 | 3 | 4 | #[test] 5 | fn is_mode_scales_correct() { 6 | // TODO find proper scale factors for the rest of the gfx modes 7 | 8 | for mode in &vga_mode_block() { 9 | let w = (mode.swidth as f32) * mode.scale_x; 10 | let h = (mode.sheight as f32) * mode.scale_y; 11 | 12 | let ar = w / h; 13 | if ar <= 1.32 || ar >= 1.34 { 14 | println!("incorrect ar {} for vga mode {:02X}: {}x{}", ar, mode.mode, mode.swidth, mode.sheight); 15 | } 16 | } 17 | 18 | for mode in &ega_mode_block() { 19 | let w = (mode.swidth as f32) * mode.scale_x; 20 | let h = (mode.sheight as f32) * mode.scale_y; 21 | 22 | let ar = w / h; 23 | if ar <= 1.32 || ar >= 1.34 { 24 | println!("incorrect ar {} for ega mode {:02X}: {}x{}", ar, mode.mode, mode.swidth, mode.sheight); 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /dustbox/src/gpu/render_test.rs: -------------------------------------------------------------------------------- 1 | // this is a collection of graphic tests using classic ms-dos demos 2 | 3 | use std::panic; 4 | 5 | use image::{ImageBuffer, Rgb, Pixel, GenericImage}; 6 | 7 | use crate::cpu::R; 8 | use crate::machine::Machine; 9 | 10 | #[test] 11 | fn can_get_palette_entry() { 12 | let mut machine = Machine::deterministic(); 13 | let code: Vec = vec![ 14 | 0xB3, 0x03, // mov bl,0x3 15 | 0xB8, 0x15, 0x10, // mov ax,0x1015 16 | 0xCD, 0x10, // int 0x10 17 | ]; 18 | machine.load_executable(&code, 0x085F); 19 | 20 | machine.execute_instructions(3); 21 | machine.execute_instruction(); // trigger the interrupt 22 | assert_eq!(0x00, machine.cpu.get_r8(R::DH)); // red 23 | assert_eq!(0x2A, machine.cpu.get_r8(R::CH)); // green 24 | assert_eq!(0x2A, machine.cpu.get_r8(R::CL)); // blue 25 | } 26 | 27 | #[test] 28 | fn can_set_palette_entry() { 29 | let mut machine = Machine::deterministic(); 30 | let code: Vec = vec![ 31 | 0xBB, 0x03, 0x00, // mov bx,0x3 32 | 0xB5, 0x3F, // mov ch,0x3f ; red 33 | 0xB1, 0x3F, // mov cl,0x3f ; green 34 | 0xB6, 0x3F, // mov dh,0x3f ; blue 35 | 0xB8, 0x10, 0x10, // mov ax,0x1010 36 | 0xCD, 0x10, // int 0x10 37 | 38 | 0xB3, 0x03, // mov bl,0x3 39 | 0xB8, 0x15, 0x10, // mov ax,0x1015 40 | 0xCD, 0x10, // int 0x10 41 | ]; 42 | machine.load_executable(&code, 0x085F); 43 | 44 | machine.execute_instructions(6); 45 | machine.execute_instruction(); // trigger the interrupt 46 | machine.execute_instructions(3); 47 | machine.execute_instruction(); // trigger the interrupt 48 | assert_eq!(0x3F, machine.cpu.get_r8(R::DH)); // red 49 | assert_eq!(0x3F, machine.cpu.get_r8(R::CH)); // green 50 | assert_eq!(0x3F, machine.cpu.get_r8(R::CL)); // blue 51 | } 52 | 53 | #[test] 54 | fn can_get_font_info() { 55 | let mut machine = Machine::deterministic(); 56 | let code: Vec = vec![ 57 | 0xB8, 0x30, 0x11, // mov ax,0x1130 ; 1130 = get font info 58 | 0xB7, 0x06, // mov bh,0x6 ; get ROM 8x16 font (MCGA, VGA) 59 | 0xCD, 0x10, // int 0x10 ; es:bp = c000:1700 i dosbox 60 | ]; 61 | machine.load_executable(&code, 0x085F); 62 | 63 | machine.execute_instructions(3); 64 | machine.execute_instruction(); // trigger the interrupt 65 | assert_eq!(0xC000, machine.cpu.get_r16(R::ES)); 66 | assert_eq!(0x1700, machine.cpu.get_r16(R::BP)); 67 | } 68 | 69 | #[test] 70 | fn can_int10_put_pixel() { 71 | let mut machine = Machine::deterministic(); 72 | let code: Vec = vec![ 73 | 0xB8, 0x13, 0x00, // mov ax,0x13 74 | 0xCD, 0x10, // int 0x10 75 | 0xB4, 0x0C, // mov ah,0xc ; int 10h, ah = 0Ch 76 | 0xB7, 0x00, // mov bh,0x0 77 | 0xB0, 0x0D, // mov al,0xd color 78 | 0xB9, 0x01, 0x00, // mov cx,0x1 x 79 | 0xBA, 0x04, 0x00, // mov dx,0x4 y 80 | 0xCD, 0x10, // int 0x10 81 | ]; 82 | machine.load_executable(&code, 0x085F); 83 | 84 | machine.execute_instructions(2); 85 | machine.execute_instruction(); // trigger the interrupt 86 | machine.execute_instructions(6); 87 | machine.execute_instruction(); // trigger the interrupt 88 | assert_eq!(0x0113, machine.cpu.regs.ip); 89 | 90 | let frame = machine.gpu().render_frame(&machine.mmu); 91 | let mut img = frame.draw_image(); 92 | let img = img.sub_image(0, 0, 6, 6).to_image(); 93 | assert_eq!("\ 94 | ...... 95 | ...... 96 | ...... 97 | ...... 98 | .O.... 99 | ...... 100 | ", draw_ascii(&img)); 101 | } 102 | 103 | #[test] 104 | fn can_write_vga_text() { 105 | let mut machine = Machine::deterministic(); 106 | let code: Vec = vec![ 107 | 0xB8, 0x13, 0x00, // mov ax,0x13 108 | 0xCD, 0x10, // int 0x10 109 | 0xB4, 0x0A, // mov ah,0xa ; int 10h, ah = 0Ah 110 | 0xB0, 0x53, // mov al,'S' ; char 111 | 0xB7, 0x00, // mov bh,0x0 ; page 112 | 0xB3, 0x01, // mov bl,0x1 ; attrib 113 | 0xB9, 0x01, 0x00, // mov cx,0x1 ; count 114 | 0xCD, 0x10, // int 0x10 115 | ]; 116 | machine.load_executable(&code, 0x085F); 117 | 118 | machine.execute_instructions(2); 119 | machine.execute_instruction(); // trigger the interrupt 120 | machine.execute_instructions(6); 121 | machine.execute_instruction(); // trigger the interrupt 122 | assert_eq!(0x0112, machine.cpu.regs.ip); 123 | 124 | let frame = machine.gpu().render_frame(&machine.mmu); 125 | let mut img = frame.draw_image(); 126 | let img = img.sub_image(0, 0, 8, 8).to_image(); 127 | assert_eq!("\ 128 | .,,,,... 129 | ,,..,,.. 130 | ,,,..... 131 | .,,,.... 132 | ...,,,.. 133 | ,,..,,.. 134 | .,,,,... 135 | ........ 136 | ", draw_ascii(&img)); 137 | } 138 | 139 | fn draw_ascii(img: &ImageBuffer, Vec>) -> String { 140 | let mut res = String::new(); 141 | for y in 0..img.height() { 142 | for x in 0..img.width() { 143 | let pixel = img.get_pixel(x, y); 144 | res.push(pixel_256_to_ascii(pixel)); 145 | } 146 | res.push('\n'); 147 | } 148 | res 149 | } 150 | 151 | fn pixel_256_to_ascii(v: &Rgb) -> char { 152 | let vals: [char; 9] = ['.', ',', '+', 'o', '5', '6', 'O', '0', '#']; 153 | let Rgb([r, g, b]) = v.to_rgb(); 154 | let avg = (f64::from(r) + f64::from(g) + f64::from(b)) / 3.; 155 | let n = scale(avg, 0., 255., 0., (vals.len() - 1) as f64) as usize; 156 | assert_eq!(true, n <= vals.len()); 157 | 158 | vals[n] 159 | } 160 | 161 | fn scale(value_in:f64, base_min:f64, base_max:f64, limit_min:f64, limit_max:f64) -> f64 { 162 | ((limit_max - limit_min) * (value_in - base_min) / (base_max - base_min)) + limit_min 163 | } 164 | -------------------------------------------------------------------------------- /dustbox/src/hex.rs: -------------------------------------------------------------------------------- 1 | pub fn hex_bytes(data: &[u8]) -> String { 2 | let strs: Vec = data.iter().map(|b| format!("{:02X}", b)).collect(); 3 | strs.join("") 4 | } 5 | 6 | pub fn hex_bytes_separated(data: &[u8], sep: char) -> String { 7 | let strs: Vec = data.iter().map(|b| format!("{:02X}{}", b, sep)).collect(); 8 | strs.join("") 9 | } 10 | -------------------------------------------------------------------------------- /dustbox/src/keyboard_test.rs: -------------------------------------------------------------------------------- 1 | use sdl2::keyboard::{Keycode, Mod}; 2 | 3 | use crate::keyboard::{Keyboard, StatusRegister}; 4 | use crate::machine::Component; 5 | 6 | #[test] 7 | fn test_status_register() { 8 | let sr = StatusRegister::default(); 9 | assert_eq!(0b001_0100, sr.as_u8()); // system 1, unknown4 1 10 | } 11 | 12 | #[test] 13 | fn can_read_keys_from_io_ports() { 14 | let mut keyboard = Keyboard::default(); 15 | 16 | // in al,0x64 17 | assert_eq!(Some(0x14), keyboard.in_u8(0x64)); 18 | 19 | // inject key press 20 | keyboard.add_keypress(Keycode::Escape, Mod::NOMOD); 21 | 22 | // in al,0x64 23 | assert_eq!(Some(0x15), keyboard.in_u8(0x64)); 24 | 25 | // make sure we get the DOS scancode for ESC key 26 | 27 | // in al,0x60 28 | assert_eq!(Some(0x01), keyboard.in_u8(0x60)); 29 | } 30 | 31 | #[test] 32 | fn consumes_keypress_queue() { 33 | let mut keyboard = Keyboard::default(); 34 | 35 | assert_eq!(false, keyboard.has_queued_presses()); 36 | 37 | // inject key press 38 | keyboard.add_keypress(Keycode::Escape, Mod::NOMOD); 39 | keyboard.add_keypress(Keycode::Escape, Mod::NOMOD); 40 | assert_eq!(true, keyboard.has_queued_presses()); 41 | 42 | // read it 43 | let (_, _, keypress) = keyboard.peek_dos_standard_scancode_and_ascii(); 44 | let keypress = keypress.unwrap(); 45 | 46 | // consume 1st 47 | keyboard.consume(&keypress); 48 | assert_eq!(true, keyboard.has_queued_presses()); 49 | 50 | // consume 2nd 51 | keyboard.consume(&keypress); 52 | assert_eq!(false, keyboard.has_queued_presses()); 53 | } 54 | -------------------------------------------------------------------------------- /dustbox/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[macro_use] 4 | extern crate serde_derive; 5 | 6 | #[cfg(test)] 7 | extern crate pretty_assertions; 8 | 9 | pub mod bios; 10 | pub mod cmos; 11 | pub mod codepage; 12 | pub mod cpu; 13 | pub mod debug; 14 | pub mod format; 15 | pub mod gpu; 16 | pub mod hex; 17 | pub mod keyboard; 18 | pub mod machine; 19 | pub mod memory; 20 | pub mod mouse; 21 | pub mod ndisasm; 22 | pub mod pic; 23 | pub mod pit; 24 | pub mod dos; 25 | pub mod storage; 26 | pub mod string; 27 | pub mod tools; 28 | -------------------------------------------------------------------------------- /dustbox/src/memory/flat_memory.rs: -------------------------------------------------------------------------------- 1 | use crate::hex::hex_bytes_separated; 2 | 3 | #[derive(Clone, Default)] 4 | pub struct FlatMemory { 5 | pub data: Vec, 6 | } 7 | 8 | const DEBUG_MEMORY: bool = false; 9 | 10 | impl FlatMemory { 11 | pub fn new() -> Self { 12 | FlatMemory { data: vec![0u8; 0x1_0000 * 64] } 13 | } 14 | 15 | pub fn read_u8(&self, addr: u32) -> u8 { 16 | let val = self.data[addr as usize]; 17 | if DEBUG_MEMORY { 18 | println!("read_u8 from {:06x} = {:02x}", addr, val); 19 | } 20 | val 21 | } 22 | 23 | pub fn read_u16(&self, addr: u32) -> u16 { 24 | u16::from(self.read_u8(addr + 1)) << 8 | u16::from(self.read_u8(addr)) 25 | } 26 | 27 | pub fn write_u8(&mut self, addr: u32, data: u8) { 28 | if DEBUG_MEMORY { 29 | println!("write_u8 to {:06x} = {:02x}", addr, data); 30 | } 31 | self.data[addr as usize] = data; 32 | } 33 | 34 | pub fn write_u16(&mut self, addr: u32, data: u16) { 35 | self.write_u8(addr, data as u8); 36 | self.write_u8(addr + 1, (data >> 8) as u8); 37 | } 38 | 39 | pub fn read_u32(&self, addr: u32) -> u32 { 40 | u32::from(self.read_u16(addr + 2)) << 16 | u32::from(self.read_u16(addr)) 41 | } 42 | 43 | pub fn write_u32(&mut self, addr: u32, data: u32) { 44 | self.write_u16(addr, data as u16); 45 | self.write_u16(addr + 2, (data >> 16) as u16); 46 | } 47 | 48 | pub fn read(&self, addr: u32, length: usize) -> &[u8] { 49 | let addr = addr as usize; 50 | &self.data[addr..addr+length] 51 | } 52 | 53 | pub fn write(&mut self, addr: u32, data: &[u8]) { 54 | let addr = addr as usize; 55 | if DEBUG_MEMORY { 56 | println!("write to {:06x} in {} bytes: {}", addr, data.len(), hex_bytes_separated(data, ' ')); 57 | } 58 | self.data[addr..addr+data.len()].copy_from_slice(data); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dustbox/src/memory/memory_address.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::fmt; 3 | 4 | /// represents a memory address inside the vm 5 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 6 | pub enum MemoryAddress { 7 | /// a real mode segment:offset pair (0x0_0000 - 0xF_FFFF) 8 | RealSegmentOffset(u16, u16), 9 | 10 | /// a long segment:offset pair (0x0000_0000 - 0xFFFF_FFFF) 11 | LongSegmentOffset(u16, u16), 12 | 13 | /// a unknown value 14 | Unset, 15 | } 16 | 17 | 18 | impl fmt::Display for MemoryAddress { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | match *self { 21 | MemoryAddress::RealSegmentOffset(seg, off) => { 22 | write!(f, "{:04X}:{:04X}", seg, off) 23 | } 24 | MemoryAddress::LongSegmentOffset(seg, off) => { 25 | write!(f, "{:08X}:{:08X}", seg, off) 26 | } 27 | _ => unreachable!(), 28 | } 29 | } 30 | } 31 | 32 | impl PartialOrd for MemoryAddress { 33 | fn partial_cmp(&self, other: &MemoryAddress) -> Option { 34 | Some(other.cmp(self)) 35 | } 36 | } 37 | 38 | impl Ord for MemoryAddress { 39 | fn cmp(&self, other: &MemoryAddress) -> cmp::Ordering { 40 | other.value().cmp(&self.value()) 41 | } 42 | } 43 | 44 | impl MemoryAddress { 45 | pub fn default_real() -> MemoryAddress { 46 | MemoryAddress::RealSegmentOffset(0, 0) 47 | } 48 | 49 | /// translates a segment:offset pair to a physical (flat) address 50 | pub fn value(self) -> u32 { 51 | match self { 52 | MemoryAddress::RealSegmentOffset(seg, off) => (u32::from(seg) << 4) + u32::from(off), 53 | MemoryAddress::LongSegmentOffset(seg, off) => (u32::from(seg) << 16) + u32::from(off), 54 | _ => unreachable!(), 55 | } 56 | } 57 | 58 | pub fn segment(self) -> u16 { 59 | match self { 60 | MemoryAddress::RealSegmentOffset(seg, _) | 61 | MemoryAddress::LongSegmentOffset(seg, _) => seg, 62 | _ => unreachable!(), 63 | } 64 | } 65 | 66 | pub fn offset(self) -> u16 { 67 | match self { 68 | MemoryAddress::RealSegmentOffset(_, off) | 69 | MemoryAddress::LongSegmentOffset(_, off) => off, 70 | _ => unreachable!(), 71 | } 72 | } 73 | 74 | /// set offset to `n` 75 | pub fn set_offset(&mut self, n: u16) { 76 | match *self { 77 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 78 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off = n, 79 | _ => unreachable!(), 80 | } 81 | } 82 | 83 | /// add `n` to offset 84 | pub fn add_offset(&mut self, n: u16) { 85 | match *self { 86 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 87 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off += n, 88 | _ => unreachable!(), 89 | } 90 | } 91 | 92 | /// increase offset by 1 93 | pub fn inc_u8(&mut self) { 94 | match *self { 95 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 96 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off += 1, 97 | _ => unreachable!(), 98 | } 99 | } 100 | 101 | /// increase offset by 2 102 | pub fn inc_u16(&mut self) { 103 | match *self { 104 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 105 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off += 2, 106 | _ => unreachable!(), 107 | } 108 | } 109 | 110 | /// increase offset by 4 111 | pub fn inc_u32(&mut self) { 112 | match *self { 113 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 114 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off += 4, 115 | _ => unreachable!(), 116 | } 117 | } 118 | 119 | /// increase offset by n 120 | pub fn inc_n(&mut self, n: u16) { 121 | match *self { 122 | MemoryAddress::RealSegmentOffset(_, ref mut off) | 123 | MemoryAddress::LongSegmentOffset(_, ref mut off) => *off += n, 124 | _ => unreachable!(), 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /dustbox/src/memory/mmu.rs: -------------------------------------------------------------------------------- 1 | use crate::memory::{FlatMemory, MemoryAddress}; 2 | use crate::codepage::cp437; 3 | 4 | #[cfg(test)] 5 | #[path = "./mmu_test.rs"] 6 | mod mmu_test; 7 | 8 | const DEBUG_MMU: bool = false; 9 | const DEBUG_VEC: bool = false; 10 | 11 | #[derive(Clone)] 12 | pub struct MMU { 13 | pub memory: FlatMemory, 14 | 15 | /// the FLAGS register offset on stack while in interrupt 16 | pub flags_address: MemoryAddress, 17 | } 18 | 19 | impl MMU { 20 | pub fn default() -> Self{ 21 | MMU { 22 | memory: FlatMemory::new(), 23 | flags_address: MemoryAddress::Unset, 24 | } 25 | } 26 | 27 | /// manipulates the FLAGS register on stack while in a interrupt 28 | pub fn set_flag(&mut self, flag_mask: u16, flag_value: bool) { 29 | if self.flags_address == MemoryAddress::Unset { 30 | panic!("bios: set_flag with 0 flags_address"); 31 | } 32 | let mut flags = self.memory.read_u16(self.flags_address.value()); 33 | if flag_value { 34 | flags |= flag_mask; 35 | } else { 36 | flags &= !flag_mask; 37 | } 38 | self.memory.write_u16(self.flags_address.value(), flags); 39 | } 40 | 41 | /// reads a sequence of data from memory 42 | pub fn read(&self, seg: u16, offset: u16, length: usize) -> Vec { 43 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 44 | Vec::from(self.memory.read(addr, length)) 45 | } 46 | 47 | /// reads a sequence of data until a NULL byte is found 48 | pub fn readz(&self, seg: u16, offset: u16) -> Vec { 49 | let mut res = Vec::new(); 50 | let mut addr = MemoryAddress::RealSegmentOffset(seg, offset); 51 | loop { 52 | let b = self.memory.read_u8(addr.value()); 53 | if b == 0 { 54 | break; 55 | } 56 | res.push(b); 57 | addr.inc_u8(); 58 | } 59 | res 60 | } 61 | 62 | /// reads a sequence of text until a NULL byte is found 63 | pub fn read_asciiz(&self, seg: u16, offset: u16) -> String { 64 | let mut res = String::new(); 65 | let mut addr = MemoryAddress::RealSegmentOffset(seg, offset); 66 | loop { 67 | let b = self.memory.read_u8(addr.value()); 68 | if b == 0 { 69 | break; 70 | } 71 | res.push(cp437::u8_as_char(b)); 72 | addr.inc_u8(); 73 | } 74 | res 75 | } 76 | 77 | /// reads a sequence of text until a $ terminator is found 78 | pub fn read_asciid(&self, seg: u16, offset: u16) -> String { 79 | let mut res = String::new(); 80 | let mut addr = MemoryAddress::RealSegmentOffset(seg, offset); 81 | loop { 82 | let b = self.memory.read_u8(addr.value()); 83 | if b == b'$' { 84 | break; 85 | } 86 | res.push(cp437::u8_as_char(b)); 87 | addr.inc_u8(); 88 | } 89 | res 90 | } 91 | 92 | pub fn read_u8_addr(&self, addr: MemoryAddress) -> u8 { 93 | let v = self.memory.read_u8(addr.value()); 94 | if DEBUG_MMU { 95 | println!("mmu.read_u8_addr from {} = {:02X}", addr, v); 96 | } 97 | v 98 | } 99 | 100 | pub fn read_u8(&self, seg: u16, offset: u16) -> u8 { 101 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 102 | let v = self.memory.read_u8(addr); 103 | if DEBUG_MMU { 104 | println!("mmu.read_u8 from ({:04X}:{:04X} == {:06X}) = {:02X}", seg, offset, addr, v); 105 | } 106 | v 107 | } 108 | 109 | pub fn read_u16(&self, seg: u16, offset: u16) -> u16 { 110 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 111 | let v = self.memory.read_u16(addr); 112 | if DEBUG_MMU { 113 | println!("mmu.read_u16 from ({:04X}:{:04X} == {:06X}) = {:04X}", seg, offset, addr, v); 114 | } 115 | v 116 | } 117 | 118 | pub fn write_u8(&mut self, seg: u16, offset: u16, data: u8) { 119 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 120 | if DEBUG_MMU { 121 | println!("mmu.write_u8 to ({:04X}:{:04X} == {:06X}) = {:02X}", seg, offset, addr, data); 122 | } 123 | self.memory.write_u8(addr, data); 124 | } 125 | 126 | /// write data and increase addr 127 | pub fn write_u8_inc(&mut self, addr: &mut MemoryAddress, data: u8) { 128 | self.memory.write_u8(addr.value(), data); 129 | if DEBUG_MMU { 130 | println!("mmu.write_u8_inc to {:06X} = {:02X}", addr.value(), data); 131 | } 132 | addr.inc_u8(); 133 | } 134 | 135 | /// writes a sequence of data to memory 136 | pub fn write(&mut self, seg: u16, offset: u16, data: &[u8]) { 137 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 138 | self.memory.write(addr, data); 139 | } 140 | 141 | pub fn write_u16(&mut self, seg: u16, offset: u16, data: u16) { 142 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 143 | if DEBUG_MMU { 144 | println!("mmu.write_u16 to ({:04X}:{:04X} == {:06X}) = {:02X}", seg, offset, addr, data); 145 | } 146 | self.memory.write_u16(addr, data); 147 | } 148 | 149 | /// write data and increase addr 150 | pub fn write_u16_inc(&mut self, addr: &mut MemoryAddress, data: u16) { 151 | self.memory.write_u16(addr.value(), data); 152 | if DEBUG_MMU { 153 | println!("mmu.write_u16_inc to {:06X} = {:08X}", addr.value(), data); 154 | } 155 | addr.inc_u16(); 156 | } 157 | 158 | pub fn read_u32(&self, seg: u16, offset: u16) -> u32 { 159 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 160 | let v = self.memory.read_u32(addr); 161 | if DEBUG_MMU { 162 | println!("mmu.read_u32 from {:06X} = {:04X}", addr, v); 163 | } 164 | v 165 | } 166 | 167 | pub fn write_u32(&mut self, seg: u16, offset: u16, data: u32) { 168 | // TODO take MemoryAddress parameter directly 169 | let addr = MemoryAddress::RealSegmentOffset(seg, offset).value(); 170 | if DEBUG_MMU { 171 | println!("mmu.write_u32 to {:06X} = {:08X}", addr, data); 172 | } 173 | self.memory.write_u32(addr, data); 174 | } 175 | 176 | /// write data and increase addr 177 | pub fn write_u32_inc(&mut self, addr: &mut MemoryAddress, data: u32) { 178 | self.memory.write_u32(addr.value(), data); 179 | if DEBUG_MMU { 180 | println!("mmu.write_u32_inc to {:06X} = {:08X}", addr.value(), data); 181 | } 182 | addr.inc_u32(); 183 | } 184 | 185 | /// read interrupt vector, returns segment, offset 186 | pub fn read_vec(&self, v: u16) -> (u16, u16) { 187 | // XXX better naming 188 | let v_abs = u32::from(v) << 2; 189 | let seg = self.memory.read_u16(v_abs); 190 | let off = self.memory.read_u16(v_abs + 2); 191 | if DEBUG_VEC { 192 | println!("mmu.read_vec: {:04X} = {:04X}:{:04X}", v, seg, off); 193 | } 194 | (seg, off) 195 | } 196 | 197 | /// write interrupt vector 198 | pub fn write_vec(&mut self, v: u16, data: MemoryAddress) { 199 | let v_abs = u32::from(v) << 2; 200 | self.memory.write_u16(v_abs, data.segment()); 201 | self.memory.write_u16(v_abs + 2, data.offset()); 202 | if DEBUG_VEC { 203 | println!("mmu.write_vec: {:04X} = {:04X}:{:04X}", v, data.segment(), data.offset()); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /dustbox/src/memory/mmu_test.rs: -------------------------------------------------------------------------------- 1 | use crate::memory::mmu::MemoryAddress; 2 | 3 | #[test] 4 | fn can_handle_real_mode_addressing() { 5 | let ma = MemoryAddress::RealSegmentOffset(0xC000, 0x0000); 6 | assert_eq!(0xC_0000, ma.value()); 7 | assert_eq!(0xC000, ma.segment()); 8 | assert_eq!(0x0000, ma.offset()); 9 | 10 | assert_eq!(0x86FF, MemoryAddress::RealSegmentOffset(0x085F, 0x10F).value()); 11 | assert_eq!(0x8700, MemoryAddress::RealSegmentOffset(0x085F, 0x110).value()); 12 | } 13 | 14 | #[test] 15 | fn can_convert_to_long_pair() { 16 | let ma = MemoryAddress::LongSegmentOffset(0xC000, 0x0000); 17 | assert_eq!(0xC000_0000, ma.value()); 18 | assert_eq!(0xC000, ma.segment()); 19 | assert_eq!(0x0000, ma.offset()); 20 | } 21 | 22 | #[test] 23 | fn resolve_real_addressing() { 24 | let ma1 = MemoryAddress::RealSegmentOffset(0x0000, 0x046C); 25 | let ma2 = MemoryAddress::RealSegmentOffset(0x0040, 0x006C); 26 | assert_eq!(ma1.value(), ma2.value()); 27 | } 28 | -------------------------------------------------------------------------------- /dustbox/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | // these modules are re-exported as a single module 2 | 3 | pub use self::flat_memory::*; 4 | mod flat_memory; 5 | 6 | pub use self::memory_address::*; 7 | mod memory_address; 8 | 9 | pub use self::mmu::*; 10 | mod mmu; 11 | -------------------------------------------------------------------------------- /dustbox/src/mouse.rs: -------------------------------------------------------------------------------- 1 | /// PS/2 Mouse implementation 2 | /// Exposes a 2D mouse pointer with a left, right and middle buttons 3 | /// 4 | /// https://wiki.osdev.org/Mouse_Input 5 | 6 | use crate::cpu::{CPU, R}; 7 | use crate::machine::Component; 8 | use crate::memory::MMU; 9 | 10 | const DEBUG_MOUSE: bool = false; 11 | 12 | #[derive(Debug)] 13 | pub enum MouseButton { 14 | Left, 15 | Right, 16 | Middle, 17 | } 18 | 19 | pub struct Mouse { 20 | x: i32, 21 | y: i32, 22 | 23 | left: bool, 24 | right: bool, 25 | middle: bool, 26 | 27 | min_x: u16, 28 | max_x: u16, 29 | min_y: u16, 30 | max_y: u16, 31 | } 32 | 33 | impl Component for Mouse { 34 | fn int(&mut self, int: u8, cpu: &mut CPU, _mmu: &mut MMU) -> bool { 35 | if int != 0x33 { 36 | return false; 37 | } 38 | // NOTE: logitech mouse extender use AH too 39 | match cpu.get_r16(R::AX) { 40 | 0x0000 => { 41 | // MS MOUSE - RESET DRIVER AND READ STATUS 42 | cpu.set_r16(R::AX, 0xFFFF); // hardware/driver installed 43 | cpu.set_r16(R::BX, 0x0003); // three-button mouse 44 | } 45 | 0x0003 => { 46 | // MS MOUSE v1.0+ - RETURN POSITION AND BUTTON STATUS 47 | cpu.set_r16(R::BX, self.button_status()); // BX = button status 48 | cpu.set_r16(R::CX, self.x as u16); // CX = column 49 | cpu.set_r16(R::DX, self.y as u16); // DX = row 50 | if DEBUG_MOUSE { 51 | println!("MOUSE - RETURN POSITION AND BUTTON STATUS"); 52 | } 53 | } 54 | 0x0007 => { 55 | // MS MOUSE v1.0+ - DEFINE HORIZONTAL CURSOR RANGE 56 | // CX = minimum column 57 | // DX = maximum column 58 | // Note: In text modes, the minimum and maximum columns are truncated to the next lower multiple of the cell size, typically 8x8 pixels 59 | let cx = cpu.get_r16(R::CX); 60 | let dx = cpu.get_r16(R::DX); 61 | self.min_x = cx; 62 | self.max_x = dx; 63 | if DEBUG_MOUSE { 64 | println!("MOUSE - DEFINE HORIZONTAL CURSOR RANGE min {}, max {}", cx, dx); 65 | } 66 | } 67 | 0x0008 => { 68 | // MS MOUSE v1.0+ - DEFINE VERTICAL CURSOR RANGE 69 | // CX = minimum row 70 | // DX = maximum row 71 | // Note: In text modes, the minimum and maximum rows are truncated to the next lower multiple of the cell size, typically 8x8 pixels 72 | let cx = cpu.get_r16(R::CX); 73 | let dx = cpu.get_r16(R::DX); 74 | self.min_y = cx; 75 | self.max_y = dx; 76 | if DEBUG_MOUSE { 77 | println!("MOUSE - DEFINE VERTICAL CURSOR RANGE min {}, max {}", cx, dx); 78 | } 79 | } 80 | _ => return false 81 | } 82 | true 83 | } 84 | } 85 | 86 | fn scale(value_in: f64, base_min: f64, base_max: f64, limit_min: f64, limit_max: f64) -> f64 { 87 | ((limit_max - limit_min) * (value_in - base_min) / (base_max - base_min)) + limit_min 88 | } 89 | 90 | impl Mouse { 91 | pub fn default() -> Self { 92 | Self { 93 | x: 0, 94 | y: 0, 95 | left: false, 96 | right: false, 97 | middle: false, 98 | min_x: 0, 99 | max_x: 640, 100 | min_y: 0, 101 | max_y: 200, 102 | } 103 | } 104 | 105 | /// Sets the mouse absolute position 106 | pub fn set_position(&mut self, x: i32, y: i32) { 107 | if DEBUG_MOUSE { 108 | // println!("mouse.set_position {}, {}", x, y); 109 | } 110 | // XXX In text modes, all coordinates are specified as multiples of the cell size, typically 8x8 pixels 111 | 112 | if x >= 0 && y >= 0 { 113 | // XXX only works in mode 13h 114 | let screen_w = 320; 115 | let screen_h = 200; 116 | 117 | // XXX first scale x and y from 320 x 240 to 320 x 200 118 | let exact_x = scale(x as f64, 0., 320., 0., screen_w as f64); 119 | let exact_y = scale(y as f64, 0., 240., 0., screen_h as f64); 120 | 121 | self.x = ((self.min_x + exact_x as u16) * (self.max_x / screen_w)) as i32; 122 | self.y = ((self.min_y + exact_y as u16) * (self.max_y / screen_h)) as i32; 123 | } 124 | } 125 | 126 | /// Sets the mouse button pressed state 127 | pub fn set_button(&mut self, button: MouseButton, pressed: bool) { 128 | if DEBUG_MOUSE { 129 | println!("mouse.set_button {:?}, {}", button, pressed); 130 | } 131 | match button { 132 | MouseButton::Left => self.left = pressed, 133 | MouseButton::Right => self.right = pressed, 134 | MouseButton::Middle => self.middle = pressed, 135 | } 136 | } 137 | 138 | /// returns the button status bitmask, used by INT 33, ax=03 139 | fn button_status(&self) -> u16 { 140 | let mut v: u16 = 0; 141 | if self.left { 142 | v |= 0b001; 143 | } else { 144 | v &= 0b110; 145 | } 146 | if self.right { 147 | v |= 0b010; 148 | } else { 149 | v &= 0b101; 150 | } 151 | if self.middle { 152 | v |= 0b100; 153 | } else { 154 | v &= 0b011; 155 | } 156 | v 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /dustbox/src/ndisasm.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | use std::fs::File; 3 | use std::process::Command; 4 | use std::str; 5 | 6 | use tempfile::tempdir; 7 | 8 | use crate::cpu::{Encoder, Instruction}; 9 | 10 | pub fn ndisasm_first_instr(bytes: &[u8]) -> Result { 11 | let rows = ndisasm_bytes(bytes).unwrap(); 12 | // parse syntax "00000000 CD21 int 0x21", return third column 13 | let mut col = 0; 14 | let mut spacing = false; 15 | let mut res = String::new(); 16 | 17 | let s = rows.first().unwrap(); 18 | for c in s.chars() { 19 | if c == ' ' { 20 | if !spacing && col < 2 { 21 | col += 1; 22 | spacing = true; 23 | } 24 | } else { 25 | spacing = false; 26 | } 27 | if col == 2 { 28 | res.push(c); 29 | } 30 | } 31 | 32 | Ok(res.trim().to_owned()) 33 | } 34 | 35 | pub fn ndisasm_bytes(bytes: &[u8]) -> Result, io::Error> { 36 | let tmp_dir = tempdir()?; 37 | let file_path = tmp_dir.path().join("binary.bin"); 38 | let file_str = file_path.to_str().unwrap(); 39 | let mut tmp_file = File::create(&file_path)?; 40 | 41 | tmp_file.write_all(bytes)?; 42 | 43 | let output = Command::new("ndisasm") 44 | .args(&["-b", "16", file_str]) 45 | .output() 46 | .expect("failed to execute process"); 47 | 48 | drop(tmp_file); 49 | tmp_dir.close()?; 50 | 51 | let stdout = output.stdout; 52 | let s = str::from_utf8(&stdout).unwrap(); 53 | 54 | let res: Vec = s.trim().lines().map(|s| s.to_string()).collect(); 55 | Ok(res) 56 | } 57 | 58 | /// encodes an instruction and then disasms the resulting byte sequence with external ndisasm command 59 | fn ndisasm_instruction(op: &Instruction) -> Result, io::Error> { 60 | let encoder = Encoder::new(); 61 | if let Ok(data) = encoder.encode(op) { 62 | ndisasm_bytes(&data) 63 | } else { 64 | panic!("invalid byte sequence"); 65 | } 66 | } 67 | 68 | #[test] 69 | pub fn can_ndisasm_first_instr() { 70 | let data = vec!(0x66, 0x0F, 0xBF, 0xC0, 0x66, 0x50); 71 | assert_eq!("movsx eax,ax", ndisasm_first_instr(&data).unwrap()); 72 | } 73 | 74 | #[test] 75 | pub fn can_ndisasm_bytes() { 76 | let data = vec!(0x66, 0x0F, 0xBF, 0xC0, 0x66, 0x50); 77 | assert_eq!("\ 78 | 00000000 660FBFC0 movsx eax,ax 79 | 00000004 6650 push eax", ndisasm_bytes(&data).unwrap().join("\n")); 80 | } 81 | -------------------------------------------------------------------------------- /dustbox/src/pic.rs: -------------------------------------------------------------------------------- 1 | // Programmable Interrupt Controller (8259A) 2 | // https://wiki.osdev.org/8259_PIC 3 | 4 | // The 8259 PIC controls the CPU's interrupt mechanism, by accepting several 5 | // interrupt requests and feeding them to the processor in order. 6 | 7 | use crate::machine::Component; 8 | 9 | #[cfg(test)] 10 | #[path = "./pic_test.rs"] 11 | mod pic_test; 12 | 13 | const DEBUG_PIC: bool = false; 14 | 15 | #[derive(Clone, Debug)] 16 | enum OperationMode { 17 | Clear, // 0 rotate in auto EOI mode (clear) 18 | NonspecificEOI, // 1 (WORD_A) nonspecific EOI 19 | NoOperation, // 2 (WORD_H) no operation 20 | SpecificEOI, // 3 (WORD_B) specific EOI 21 | Set, // 4 (WORD_F) rotate in auto EOI mode (set) 22 | RotateOnNonspecificEOICommand, // 5 (WORD_C) rotate on nonspecific EOI command 23 | SetPriorityCommand, // 6 (WORD_E) set priority command 24 | RotateOnSpecificEOICommand, // 7 (WORD_D) rotate on specific EOI command 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct PIC { 29 | command: u8, 30 | data: u8, 31 | 32 | /// the base offset for I/O 33 | io_base: u16, 34 | 35 | operation: OperationMode, 36 | } 37 | 38 | impl Component for PIC { 39 | fn in_u8(&mut self, port: u16) -> Option { 40 | match port { 41 | _ if port < self.io_base => None, 42 | _ if port - self.io_base == 0x0000 => Some(self.get_register()), 43 | _ if port - self.io_base == 0x0001 => Some(self.get_ocw1()), 44 | _ => None 45 | } 46 | } 47 | 48 | fn out_u8(&mut self, port: u16, data: u8) -> bool { 49 | match port { 50 | _ if port < self.io_base => return false, 51 | _ if port - self.io_base == 0x0000 => self.set_command(data), 52 | _ if port - self.io_base == 0x0001 => self.set_data(data), 53 | _ => return false 54 | } 55 | true 56 | } 57 | } 58 | 59 | impl PIC { 60 | pub fn new(io_base: u16) -> Self { 61 | PIC { 62 | command: 0, 63 | data: 0, 64 | io_base, 65 | operation: OperationMode::NoOperation, // XXX default? 66 | } 67 | } 68 | 69 | /// io read of port 0021 (pic1) or 00A1 (pic2) 70 | fn get_ocw1(&self) -> u8 { 71 | // read: PIC master interrupt mask register OCW1 72 | if DEBUG_PIC { 73 | println!("PIC {:04x} get_ocw1", self.io_base); 74 | } 75 | 0 // XXX 76 | } 77 | 78 | /// io read of port 0020 (pic1) or 00A0 (pic2) 79 | fn get_register(&self) -> u8 { 80 | if DEBUG_PIC { 81 | println!("PIC {:04x} get_register", self.io_base); 82 | } 83 | /* 84 | 0020 R- PIC interrupt request/in-service registers after OCW3 85 | request register: 86 | bit 7-0 = 0 no active request for the corresponding int. line 87 | = 1 active request for corresponding interrupt line 88 | in-service register: 89 | bit 7-0 = 0 corresponding line not currently being serviced 90 | = 1 corresponding int. line currently being serviced 91 | */ 92 | 0 // XXX 93 | } 94 | 95 | /// PIC - Command register, port 0x0020 96 | fn set_command(&mut self, val: u8) { 97 | if DEBUG_PIC { 98 | println!("PIC {:04X} COMMAND: {:02x} == {:08b}", self.io_base, val, val); 99 | } 100 | // XXX 0x20 == 0b0010_0000 == EOI - End of interrrupt command code 101 | self.command = val; 102 | 103 | /* 104 | 0020 -W PIC initialization command word ICW1 (see #P0010) 105 | Bit(s) Description (Table P0010) 106 | 7-5 0 (only used in 8080/8085 mode) 107 | 4 ICW1 is being issued 108 | 3 (LTIM) 109 | =0 edge triggered mode 110 | =1 level triggered mode 111 | 2 interrupt vector size 112 | =0 successive interrupt vectors use 8 bytes (8080/8085) 113 | =1 successive interrupt vectors use 4 bytes (80x86) 114 | 1 (SNGL) 115 | =0 cascade mode 116 | =1 single mode, no ICW3 needed 117 | 0 ICW4 needed 118 | SeeAlso: #P0011,#P0012,#P0013 119 | */ 120 | let kind = (val >> 3) & 0b11; // bits 4-3: reserved (00 - signals OCW2) 121 | match kind { 122 | 0 => { // 0020 -W PIC output control word OCW2 123 | // SeeAlso: #P0014,#P0016 124 | let operation = (val >> 5) & 0b111; // bits 7-5: operation 125 | self.operation = match operation { 126 | 0 => OperationMode::Clear, 127 | 1 => OperationMode::NonspecificEOI, 128 | 2 => OperationMode::NoOperation, 129 | 3 => OperationMode::SpecificEOI, 130 | 4 => OperationMode::Set, 131 | 5 => OperationMode::RotateOnNonspecificEOICommand, 132 | 6 => OperationMode::SetPriorityCommand, 133 | 7 => OperationMode::RotateOnSpecificEOICommand, 134 | _ => unreachable!(), 135 | }; 136 | 137 | let data = val & 0b11; // bits 0-2: interrupt request to which the command applies 138 | // (only used by WORD_B, WORD_D, and WORD_E) 139 | println!("XXX: pic ocw2 operation {:?}, data {}", self.operation, data); 140 | } 141 | 1 => { // 0020 -W PIC output control word OCW3 (see #P0016) 142 | // Bit(s) Description (Table P0016) 143 | // 7 reserved (0) 144 | // 6-5 special mask 145 | // 0x no operation 146 | // 10 reset special mask 147 | // 11 set special mask mode 148 | // 2 poll command 149 | // 1-0 function 150 | // 0x no operation 151 | // 10 read interrupt request register on next read from PORT 0020h 152 | // 11 read interrupt in-service register on next read from PORT 0020h 153 | // Note: the special mask mode permits all other interrupts (even those with 154 | // lower priority) to be processed while an interrupt is already in 155 | // service, but will not re-issue an interrupt for a particular IRQ 156 | // while it remains in service 157 | } 158 | _ => panic!("unhandled kind {}", kind), 159 | } 160 | } 161 | 162 | /// Master PIC - Data register, port 0x0021 163 | fn set_data(&mut self, val: u8) { 164 | if DEBUG_PIC { 165 | println!("PIC {:04x} set_data = {:02x}", self.io_base, val); 166 | } 167 | 168 | // XXX: one value if written immediately after value to 0020, another otherwise.... 169 | self.data = val; 170 | 171 | // XXX impl, from https://wiki.osdev.org/8259_PIC#Disabling 172 | //If you are going to use the processor local APIC and the IOAPIC, you must first disable the PIC. This is done via: 173 | //mov al, 0xff 174 | //out 0xa1, al 175 | //out 0x21, al 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /dustbox/src/pic_test.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/dustbox/src/pic_test.rs -------------------------------------------------------------------------------- /dustbox/src/pit.rs: -------------------------------------------------------------------------------- 1 | // Programmable Interval Timer 2 | // http://wiki.osdev.org/Programmable_Interval_Timer 3 | // http://www.sat.dundee.ac.uk/psc/dosemu_time_advanced.html#The_BIOS_maintained_counter 4 | // 5 | // A 8253/8254 chip that runs at 18.2065 Hz (or an IRQ every 54.9254 ms) 6 | // with the default divisor of 0x1_0000 7 | 8 | use crate::cpu::{CPU, R}; 9 | use crate::machine::Component; 10 | use crate::memory::MMU; 11 | 12 | #[cfg(test)] 13 | #[path = "./pit_test.rs"] 14 | mod pit_test; 15 | 16 | const DEBUG_PIT: bool = false; 17 | 18 | #[derive(Clone)] 19 | pub struct PIT { 20 | pub timer0: Timer, 21 | pub timer1: Timer, 22 | pub timer2: Timer, 23 | //divisor: u32, // XXX size?!?! 24 | } 25 | 26 | impl Component for PIT { 27 | fn in_u8(&mut self, port: u16) -> Option { 28 | // PORT 0040-005F - PIT - PROGRAMMABLE INTERVAL TIMER (8253, 8254) 29 | match port { 30 | 0x0040 => Some(self.timer0.get_next_u8()), 31 | 0x0041 => Some(self.timer1.get_next_u8()), 32 | 0x0042 => Some(self.timer2.get_next_u8()), 33 | _ => None 34 | } 35 | } 36 | 37 | fn out_u8(&mut self, port: u16, data: u8) -> bool { 38 | match port { 39 | 0x0040 => self.timer0.write_reload_part(data), 40 | 0x0041 => self.timer1.write_reload_part(data), 41 | 0x0042 => self.timer2.write_reload_part(data), 42 | 0x0043 => self.set_mode_command(data), 43 | _ => return false 44 | } 45 | true 46 | } 47 | 48 | fn int(&mut self, int: u8, cpu: &mut CPU, _mmu: &mut MMU) -> bool { 49 | if int != 0x1A { 50 | return false; 51 | } 52 | match cpu.get_r8(R::AH) { 53 | 0x00 => { 54 | // TIME - GET SYSTEM TIME 55 | // Return: 56 | // CX:DX = number of clock ticks since midnight 57 | // AL = midnight flag, nonzero if midnight passed since time last read 58 | if cpu.deterministic { 59 | cpu.set_r16(R::CX, 0); 60 | cpu.set_r16(R::DX, 0); 61 | cpu.set_r8(R::AL, 0); 62 | } else { 63 | // println!("INT 1A GET TIME: get number of clock ticks since midnight, ticks {}", hw.pit.timer0.count); 64 | let cx = (self.timer0.count >> 16) as u16; 65 | let dx = (self.timer0.count & 0xFFFF) as u16; 66 | cpu.set_r16(R::CX, cx); 67 | cpu.set_r16(R::DX, dx); 68 | cpu.set_r8(R::AL, 0); // TODO implement midnight flag 69 | } 70 | } 71 | 0x01 => { 72 | // TIME - SET SYSTEM TIME 73 | // CX:DX = number of clock ticks since midnight 74 | let cx = cpu.get_r16(R::CX); 75 | let dx = cpu.get_r16(R::DX); 76 | let ticks = (u32::from(cx)) << 16 | u32::from(dx); 77 | 78 | self.timer0.count = ticks; 79 | // println!("SET SYSTEM TIME to {}", ticks); 80 | } 81 | _ => return false 82 | } 83 | true 84 | } 85 | } 86 | 87 | impl PIT { 88 | pub fn default() -> Self { 89 | PIT { 90 | timer0: Timer::new(0), 91 | timer1: Timer::new(1), 92 | timer2: Timer::new(2), 93 | //divisor: 0x1_0000, // XXX 94 | } 95 | } 96 | 97 | /// initializes the PIT with current time of day 98 | pub fn init(&mut self) { 99 | // there is approximately 18.2 clock ticks per second, 0x18_00B0 per 24 hrs. one tick is generated every 54.9254ms 100 | let midnight = chrono::Local::now().date().and_hms(0, 0, 0); 101 | let duration = chrono::Local::now().signed_duration_since(midnight).to_std().unwrap(); 102 | self.timer0.count = (((duration.as_secs() as f64 * 1000.) + (f64::from(duration.subsec_nanos()) / 1_000_000.)) / 54.9254) as u32; 103 | } 104 | 105 | // updates PIT internal state 106 | pub fn update(&mut self, mmu: &mut MMU) { 107 | self.timer0.inc(); 108 | // MEM 0040:006C - TIMER TICKS SINCE MIDNIGHT 109 | // Size: DWORD 110 | // Desc: updated approximately every 55 milliseconds by the BIOS INT 08 handler 111 | mmu.write_u32(0x0040, 0x006C, self.timer0.count); 112 | } 113 | 114 | fn counter(&mut self, n: u8) -> &mut Timer { 115 | match n { 116 | 0 => &mut self.timer0, 117 | 1 => &mut self.timer1, 118 | 2 => &mut self.timer2, 119 | _ => unreachable!(), 120 | } 121 | } 122 | 123 | /// port 0043: control word register for counters 0-2 124 | /// called "8253/8254 PIT mode control word" in the interrupt list 125 | pub fn set_mode_command(&mut self, val: u8) { 126 | let channel = (val >> 6) & 0b11; // bits 7-6 127 | let access_mode = (val >> 4) & 0b11; // bits 5-4 128 | let operating_mode = (val >> 1) & 0b111; // bits 3-1 129 | let bcd_mode = val & 1; // bit 0 130 | if channel == 3 { 131 | panic!("TODO channel == 3: Read-back command (8254 only)"); 132 | } 133 | self.counter(channel).set_mode(access_mode, operating_mode, bcd_mode); 134 | if DEBUG_PIT { 135 | println!("PIT set_mode_command channel={}, access_mode={}, operating_mode={}, bcd_mode={}", channel, access_mode, operating_mode, bcd_mode); 136 | } 137 | } 138 | } 139 | 140 | #[derive(Clone)] 141 | pub struct Timer { 142 | pub count: u32, 143 | pub reload: u16, 144 | latch: u32, 145 | hi: bool, 146 | channel: u8, // 0-2, for debugging 147 | 148 | // controlled by write to port 0040: 149 | access_mode: AccessMode, 150 | operating_mode: OperatingMode, 151 | bcd_mode: BcdMode, 152 | } 153 | 154 | impl Timer { 155 | pub fn new(channel: u8) -> Self { 156 | Timer { 157 | count: 0, 158 | reload: 0, 159 | latch: 0, 160 | hi: false, 161 | channel, 162 | access_mode: AccessMode::LoByteHiByte, // XXX default? 163 | operating_mode: OperatingMode::Mode0, // XXX default? 164 | bcd_mode: BcdMode::SixteenBitBinary, // XXX default? 165 | } 166 | } 167 | 168 | pub fn inc(&mut self) { 169 | // XXX channel 0 is connected to interrupt. 170 | self.count += 1; 171 | if DEBUG_PIT { 172 | println!("pit timer inc {}: {:08x}", self.channel, self.count); 173 | } 174 | if self.count >= 0x0018_00B0 { 175 | self.count = 0; 176 | } 177 | } 178 | 179 | pub fn get_next_u8(&mut self) -> u8 { 180 | match self.access_mode { 181 | AccessMode::LatchCountValue => { 182 | // Counter Latch Command 183 | let res = if self.hi { 184 | (self.latch >> 8) as u8 185 | } else { 186 | (self.latch & 0xFF) as u8 187 | }; 188 | self.hi = !self.hi; 189 | res 190 | } 191 | AccessMode::LoByteHiByte => { 192 | let res = if self.hi { 193 | (self.count >> 8) as u8 194 | } else { 195 | (self.count & 0xFF) as u8 196 | }; 197 | self.hi = !self.hi; 198 | res 199 | } 200 | AccessMode::LoByteOnly => { 201 | panic!("AccessMode::LoByteOnly"); 202 | } 203 | AccessMode::HiByteOnly => { 204 | panic!("AccessMode::HiByteOnly"); 205 | } 206 | } 207 | } 208 | 209 | /// sets the reload value for the counter 210 | pub fn write_reload_part(&mut self, val: u8) { 211 | match self.access_mode { 212 | AccessMode::LatchCountValue => { 213 | panic!("AccessMode::LatchCountValue"); 214 | } 215 | AccessMode::LoByteHiByte => { 216 | self.reload = if self.hi { 217 | (self.reload & 0x00FF) | (u16::from(val) << 8) 218 | } else { 219 | (self.reload & 0xFF00) | u16::from(val) 220 | }; 221 | self.hi = !self.hi; 222 | } 223 | AccessMode::LoByteOnly => { 224 | self.reload = (self.reload & 0xFF00) | u16::from(val); 225 | } 226 | AccessMode::HiByteOnly => { 227 | self.reload = (self.reload & 0x00FF) | (u16::from(val) << 8); 228 | } 229 | } 230 | } 231 | 232 | pub fn set_mode(&mut self, access_mode: u8, operating_mode: u8, bcd_mode: u8) { 233 | // println!("pit {}: set_mode_command access {:?}, operating {:?}, bcd {:?}", self.channel, access_mode, operating_mode, bcd_mode); 234 | self.access_mode = match access_mode { 235 | 0 => { 236 | // prepare current count value in the latch register 237 | self.latch = self.count; 238 | AccessMode::LatchCountValue 239 | }, 240 | 1 => AccessMode::LoByteOnly, 241 | 2 => AccessMode::HiByteOnly, 242 | 3 => AccessMode::LoByteHiByte, 243 | _ => panic!("TODO Latch count value command"), 244 | }; 245 | self.operating_mode = match operating_mode { 246 | 0 => OperatingMode::Mode0, 247 | 1 => OperatingMode::Mode1, 248 | 2 | 6 => OperatingMode::Mode2, 249 | 3 | 7 => OperatingMode::Mode3, 250 | 4 => OperatingMode::Mode4, 251 | 5 => OperatingMode::Mode5, 252 | _ => unreachable!(), 253 | }; 254 | self.bcd_mode = match bcd_mode { 255 | 0 => BcdMode::SixteenBitBinary, 256 | //1 => BcdMode::FourDigitBCD, 257 | _ => panic!("TODO BCD mode"), 258 | }; 259 | } 260 | } 261 | 262 | #[derive(Clone, Debug)] 263 | enum AccessMode { 264 | LatchCountValue, 265 | LoByteOnly, 266 | HiByteOnly, 267 | LoByteHiByte, 268 | } 269 | 270 | #[derive(Clone, Debug)] 271 | enum OperatingMode { 272 | Mode0, // Mode 0 (interrupt on terminal count) 273 | Mode1, // Mode 1 (hardware re-triggerable one-shot) 274 | Mode2, // Mode 2 (rate generator) 275 | Mode3, // Mode 3 (square wave generator) 276 | Mode4, // Mode 4 (software triggered strobe) 277 | Mode5, // Mode 5 (hardware triggered strobe) 278 | } 279 | 280 | #[derive(Clone, Debug)] 281 | enum BcdMode { 282 | SixteenBitBinary, // 16-bit binary 283 | FourDigitBCD, // four-digit BCD 284 | } 285 | -------------------------------------------------------------------------------- /dustbox/src/pit_test.rs: -------------------------------------------------------------------------------- 1 | use crate::machine::Component; 2 | use crate::pit::PIT; 3 | 4 | #[test] 5 | fn can_execute_pit_set_reload_value() { 6 | let mut pit = PIT::default(); 7 | 8 | // mov al,0b0011_0100 ; channel 0, lobyte/hibyte, rate generator 9 | // out 0x43,al 10 | pit.out_u8(0x43, 0b0011_0100); 11 | 12 | // mov ax,0x2244 13 | // out 0x40,al ; low byte of PIT reload value = 0x44 14 | pit.out_u8(0x40, 0x44); 15 | 16 | // mov al,ah 17 | // out 0x40,al ; high byte of PIT reload value = 0x22 18 | pit.out_u8(0x40, 0x22); 19 | 20 | assert_eq!(0x2244, pit.timer0.reload); 21 | } 22 | -------------------------------------------------------------------------------- /dustbox/src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::cpu::{CPU, R}; 2 | use crate::machine::Component; 3 | use crate::memory::MMU; 4 | 5 | // mass storage (disk, floppy) 6 | pub struct Storage { 7 | } 8 | 9 | impl Component for Storage { 10 | fn int(&mut self, int: u8, cpu: &mut CPU, _mmu: &mut MMU) -> bool { 11 | if int != 0x13 { 12 | return false; 13 | } 14 | match cpu.get_r8(R::AH) { 15 | 0x00 => { 16 | // DISK - RESET DISK DRIVES 17 | // DL = drive (if bit 7 is set both hard disks and floppy disks reset) 18 | println!("XXX DISK - RESET DISK SYSTEM, dl={:02X}", cpu.get_r8(R::DL)) 19 | // Return: 20 | // AH = status (see #00234) 21 | // CF clear if successful (returned AH=00h) 22 | // CF set on error 23 | } 24 | _ => return false 25 | } 26 | 27 | true 28 | } 29 | } 30 | 31 | impl Storage { 32 | pub fn default() -> Self { 33 | Self { 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dustbox/src/string.rs: -------------------------------------------------------------------------------- 1 | use std::num::ParseIntError; 2 | 3 | #[cfg(test)] 4 | #[path = "./string_test.rs"] 5 | mod string_test; 6 | 7 | pub fn right_pad(s: &str, len: usize) -> String { 8 | let mut res = String::new(); 9 | res.push_str(s); 10 | if s.len() < len { 11 | let padding_len = len - s.len(); 12 | for _ in 0..padding_len { 13 | res.push_str(" "); 14 | } 15 | } 16 | res 17 | } 18 | 19 | /// parses string to a integer. unprefixed values assume base 10, and "0x" prefix indicates base 16. 20 | pub fn parse_number_string(s: &str) -> Result { 21 | let x = &s.replace("_", ""); 22 | if x.len() >= 2 && &x[0..2] == "0x" { 23 | // hex 24 | u32::from_str_radix(&x[2..], 16) 25 | } else { 26 | // decimal 27 | x.parse::() 28 | } 29 | } 30 | 31 | pub fn bytes_to_ascii(data: &[u8]) -> String { 32 | data.iter().map(|b| if *b < 128 && *b > 30 { 33 | *b as char 34 | } else { 35 | '.' 36 | }).collect() 37 | } 38 | -------------------------------------------------------------------------------- /dustbox/src/string_test.rs: -------------------------------------------------------------------------------- 1 | use crate::string::parse_number_string; 2 | 3 | #[test] 4 | fn test_parse_number_string() { 5 | assert_eq!(1234, parse_number_string("1234").unwrap()); 6 | assert_eq!(0xFFFF, parse_number_string("0xFFFF").unwrap()); 7 | } 8 | -------------------------------------------------------------------------------- /dustbox/src/tools.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::io::Error; 4 | 5 | pub fn read_binary(path: &str) -> Result, Error> { 6 | // TODO take Path arg instead 7 | let mut buffer: Vec = Vec::new(); 8 | 9 | let mut f = match File::open(path) { 10 | Ok(x) => x, 11 | Err(why) => return Err(why), 12 | }; 13 | 14 | match f.read_to_end(&mut buffer) { 15 | Ok(_) => Ok(buffer), 16 | Err(why) => Err(why), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /exeinfo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "exeinfo" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "dustbox-exeinfo" 9 | path = "src/bin/exeinfo-main.rs" 10 | 11 | [dependencies] 12 | bincode = "1.2" 13 | clap = "2.33" 14 | dustbox = { path = "../dustbox" } 15 | serde = "1.0" 16 | serde_derive = "1.0" 17 | -------------------------------------------------------------------------------- /exeinfo/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | exeinfo prints file details from a MS-DOS .EXE file header. 4 | -------------------------------------------------------------------------------- /exeinfo/src/bin/exeinfo-main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, App}; 2 | 3 | use dustbox::tools::read_binary; 4 | use dustbox::format::ExeFile; 5 | 6 | const VERSION: &str = "0.1"; 7 | 8 | fn main() { 9 | let matches = App::new("dustbox-exeinfo") 10 | .version(VERSION) 11 | .arg(Arg::with_name("INPUT") 12 | .help("Sets the input file to use") 13 | .required(true) 14 | .index(1)) 15 | .get_matches(); 16 | 17 | let filename = matches.value_of("INPUT").unwrap(); 18 | println!("dustbox-exeinfo {} - {}", VERSION, filename); 19 | 20 | let data = match read_binary(filename) { 21 | Ok(data) => data, 22 | Err(e) => panic!(e), 23 | }; 24 | 25 | match ExeFile::from_data(&data) { 26 | Ok(exe) => exe.print_details(), 27 | Err(e) => panic!(e), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "frontend" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "dustbox-frontend" 9 | path = "src/bin/frontend-main.rs" 10 | 11 | [dependencies] 12 | clap = "2.33" 13 | dustbox = { path = "../dustbox" } 14 | sdl2 = { version = "0.33", default-features = false, features = [ "gfx" ] } 15 | image = { version = "0.22", default-features = false, features = [ "png" ] } 16 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | A light frontend for the emulator, without debugger etc. 4 | 5 | Should be used for easily playing DOS games. 6 | 7 | STATUS: draft 8 | 9 | ## TODO 10 | 11 | - listen for keyboard 12 | -------------------------------------------------------------------------------- /fuzzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzer" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [lib] 8 | path = "src/lib.rs" 9 | 10 | [dependencies] 11 | clap = "2.33" 12 | colored = "1.9" 13 | curl = { version = "0.4", default-features = false } 14 | dustbox = { path = "../dustbox" } 15 | rand = "0.7" 16 | rand_xorshift = "0.2" 17 | tempfile = "3.1" 18 | tera = { version = "1.0", default-features = false } 19 | -------------------------------------------------------------------------------- /fuzzer/README.md: -------------------------------------------------------------------------------- 1 | # dustbox-fuzzer 2 | 3 | -WIP- 4 | 5 | Generates and encodes instruction sequences, and then runs them in 6 | dustbox and a second target, comparing resulting registers and flags. 7 | 8 | Used to verify instruction implementation correctness. 9 | 10 | Currently the following code runners exists: 11 | 12 | supersafe: 13 | 14 | - Connects to an instance of the [supersafe](https://github.com/martinlindhe/supersafe) program running inside a VM. 15 | 16 | vmrun: 17 | 18 | - Uses the `vmrun` command line interface to execute programs inside a VMware Virtual Machine. 19 | - Requires a password to be set in the guest VM in order to function. 20 | 21 | dosbox-x: 22 | 23 | - Uses the `dosbox-x` command line to execute programs inside a Dosbox-X environment. 24 | 25 | ## TODO 26 | 27 | - take prober.com.tpl exact path as arg 28 | - dosbox-x: verify that DosboxX runner works vs original dosbox project 29 | 30 | - mutate 1, 2 and 3 operand forms of instrs 31 | 32 | - LATER: bochs runner 33 | - LATER: qemu runner 34 | 35 | - com: implement superdos - a DOS program that uses the COM serial interface, 36 | and recieves binary data, executes it and sends back STDOUT over the wire, 37 | including checksums and re-transmit for real hardware and to be run inside 38 | dos emulator to speed things up. 39 | make use of https://crates.io/crates/serialport 40 | https://en.wikibooks.org/wiki/Serial_Programming/DOS_Programming 41 | https://www.dosbox.com/wiki/Configuration:SerialPort 42 | 43 | - use winXP + djgpp to build dos .exe ? 44 | - "use unix to build DOS programs" also exists at http://www.delorie.com/djgpp/zip-picker.html 45 | 46 | - supersafe.exe dont run in win98. linked to missing export KERNEL32.DLL:AddVectoredExceptionHandler 47 | - could run serial DOS program in win98 bare bones / vm 48 | -------------------------------------------------------------------------------- /fuzzer/src/bin/fuzzer-main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | use clap::{Arg, App}; 4 | 5 | use rand::prelude::*; 6 | use rand_xorshift::XorShiftRng; 7 | 8 | use dustbox::cpu::Op; 9 | use fuzzer::fuzzer::{fuzz_ops, FuzzConfig, CodeRunner}; 10 | 11 | fn main() { 12 | let matches = App::new("dustbox-fuzzer") 13 | .version("0.1") 14 | .arg(Arg::with_name("RUNNER") 15 | .help("Code runner to use (supersafe, vmrun, dosbox-x)") 16 | .required(true) 17 | .index(1) 18 | .long("runner")) 19 | .arg(Arg::with_name("MUTATIONS") 20 | .help("Number of mutations per instruction") 21 | .takes_value(true) 22 | .long("mutations")) 23 | .arg(Arg::with_name("HOST") 24 | .help("Remote host (supersafe)") 25 | .takes_value(true) 26 | .long("host")) 27 | .arg(Arg::with_name("USERNAME") 28 | .help("VM username (vmrun)") 29 | .takes_value(true) 30 | .long("username")) 31 | .arg(Arg::with_name("PASSWORD") 32 | .help("VM password (vmrun)") 33 | .takes_value(true) 34 | .long("password")) 35 | .arg(Arg::with_name("SEED") 36 | .help("Specify PRNG seed for reproducibility") 37 | .takes_value(true) 38 | .long("seed")) 39 | .arg(Arg::with_name("VMX") 40 | .help("Specify VMX image (vmrun)") 41 | .takes_value(true) 42 | .long("vmx")) 43 | .get_matches(); 44 | 45 | let ops_to_fuzz = vec!( 46 | Op::Shl16, 47 | 48 | //Op::Rol32, // Op::Rcl32, // XXX not implemented in dustbox 49 | //Op::Ror32, // XXX carry flag diff vs WinXP 50 | //Op::Shl32, // XXX carry & overflow differs 51 | 52 | //Op::Ror16, Op::Rol16, // XXX carry flag diff vs WinXP 53 | //Op::Shl16, Op::Rcr32, // XXX overflow flag diff vs WinXP 54 | 55 | //Op::Div32, // XXX MAJOR REG DIFF 56 | 57 | // Op::Loop, // XXX need to keep relative offsets in decoder in order to encode back 58 | 59 | // TODO - EMULATION NOT IMPLEMENTED: 60 | //Op::Adc32, Op::And32, Op::Or32, Op::Sbb32, Op::Test32, Op::Not32 61 | 62 | // TODO - ENCODING NOT IMPLEMENTED: 63 | //Op::Test32, Op::Cmpsw, 64 | 65 | // TODO FUZZ: 66 | // movsb/w, stosb/w 67 | 68 | // Op::Shld, Op::Shrd, // ERROR - regs differ vs dosbox, regs match vs winxp! - overflow flag is wrong in both: 69 | // Op::Rcl16, // ERROR - overflow flag diff vs both dosbox & winxp. algo from bochs 70 | // Op::Shr16, Op::Shr32, // ERROR? - identical to winxp, but overflow flag differs vs dosbox 71 | 72 | // Op::Sar32, // reg diff if shift == 1 in WinXP 73 | 74 | /* 75 | // UNSURE: overflow is identical to bochs and dosbox, but differs in WinXP vm: 76 | Op::Rcl8, Op::Rcr8, Op::Rcr16, Op::Ror8, Op::Shl8, Op::Rol8, 77 | 78 | // SEEMS ALL OK: 79 | Op::Movsx16, Op::Movsx32, Op::Movzx16, Op::Movzx32, 80 | Op::Shr8, Op::Sar8, Op::Sar16, // OK ! 81 | //Op::Div8, Op::Div16, Op::Idiv8, Op::Idiv16, Op::Idiv32, // seems correct. NOTE that winxp crashes with "Divide overflow" on some input 82 | Op::Bt, Op::Bsf, 83 | Op::Aaa, Op::Aad, Op::Aam, Op::Aas, Op::Daa, Op::Das, 84 | 85 | Op::Push16, // NOTE: also tests Op::Pop16 86 | Op::Mov8, Op::Mov16, Op::Mov32, 87 | Op::Cmp8, Op::Cmp16, Op::Cmp32, 88 | Op::And8, Op::And16, 89 | Op::Xor8, Op::Xor16, Op::Xor32, 90 | Op::Or8, Op::Or16, 91 | Op::Add8, Op::Add16, Op::Add32, Op::Adc8, Op::Adc16, 92 | Op::Sub8, Op::Sub16, Op::Sub32, Op::Sbb8, Op::Sbb16, 93 | Op::Test8, Op::Test16, 94 | Op::Not8, Op::Not16, 95 | Op::Neg8, Op::Neg16, Op::Neg32, 96 | Op::Xchg8, Op::Xchg16, 97 | Op::Mul8, Op::Mul16, Op::Mul32, Op::Imul8, Op::Imul16, Op::Imul32, 98 | Op::Lahf, Op::Sahf, Op::Salc, 99 | Op::Nop, Op::Lea16, 100 | Op::Clc, Op::Cld, Op::Cli, Op::Cmc, Op::Stc, Op::Std, Op::Sti, 101 | Op::Cbw, Op::Cwd16, 102 | Op::Inc8, Op::Inc16, Op::Inc32, 103 | Op::Dec8, Op::Dec16, Op::Dec32, 104 | */ 105 | ); 106 | 107 | let cfg = FuzzConfig{ 108 | mutations_per_op: value_t!(matches, "MUTATIONS", usize).unwrap_or(50), 109 | remote_host: matches.value_of("HOST").unwrap_or("127.0.0.1").to_string(), 110 | vmx_path: matches.value_of("VMX").unwrap_or("").to_string(), 111 | 112 | username: matches.value_of("USERNAME").unwrap_or("vmware").to_string(), 113 | password: matches.value_of("PASSWORD").unwrap_or("vmware").to_string(), 114 | }; 115 | 116 | let runner = match matches.value_of("RUNNER").unwrap() { 117 | "supersafe" => CodeRunner::SuperSafe, 118 | "dosbox-x" => CodeRunner::DosboxX, 119 | "vmrun" => CodeRunner::Vmrun, 120 | _ => panic!("unrecognized runner"), 121 | }; 122 | 123 | // seed prng if argument was given 124 | let mut rng: XorShiftRng; 125 | let seed_value = if matches.is_present("SEED") { 126 | value_t!(matches, "SEED", u64).unwrap() 127 | } else { 128 | XorShiftRng::from_entropy().gen() 129 | }; 130 | 131 | rng = XorShiftRng::seed_from_u64(seed_value); 132 | println!("rng seed = {}", seed_value); 133 | 134 | fuzz_ops(&runner, ops_to_fuzz, &cfg, &mut rng); 135 | } 136 | -------------------------------------------------------------------------------- /fuzzer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod fuzzer; 2 | -------------------------------------------------------------------------------- /harness/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "harness" 3 | version = "0.1.0" 4 | authors = ["Martin Lindhe"] 5 | edition = "2018" 6 | 7 | [[bin]] 8 | name = "dustbox-harness" 9 | path = "src/bin/harness-main.rs" 10 | 11 | [dependencies] 12 | clap = "2.33" 13 | colored = "1.9" 14 | dustbox = { path = "../dustbox" } 15 | tera = { version = "1.0", default-features = false } 16 | image = { version = "0.22", features = [ "png" ] } 17 | serde = { version = "1.0", features = ["derive"] } 18 | serde_yaml = "0.8" 19 | -------------------------------------------------------------------------------- /harness/README.md: -------------------------------------------------------------------------------- 1 | Runs test harnesses (a folder of .com files) 2 | and saves rendered graphics to disk. 3 | 4 | # TODO 5 | 6 | - cli switch to scan all rom sets for missing files 7 | -------------------------------------------------------------------------------- /harness/sets/demo-com-16bit.yml: -------------------------------------------------------------------------------- 1 | name: demo_com_16bit 2 | default_instructions: 7000000 3 | root: ../dos-software-decoding/demo-com-16bit/ 4 | 5 | set: 6 | - 1/1.com 7 | - 165plasm/165plasm.com 8 | - 244b/244b.com 9 | - 4sum/4sum.com 10 | - alpc/alpc.com 11 | - basicboy/basicboy.com 12 | - beziesux/beziesux.com 13 | - blah/blah.com 14 | - blaze/blaze5.com 15 | - bmatch/bmatch.com 16 | - bob/bob.com 17 | - chaos/chaos.com 18 | - conf/conf.com 19 | - dreamers_bbs/dreamer.com 20 | - ectotrax/ectotrax.com 21 | - fire/fire.com 22 | - fire/fire.com 23 | - fire17/fire17.com 24 | - fire2/fire2.com 25 | - flame2/flame2.com 26 | - flood/flood.com 27 | - fridge/fridge.com 28 | - hungecek/hungecek.com 29 | - jive/jive.com 30 | - jomppa/jomppa.com 31 | - julia/julia.com 32 | - kintsmef/kintsmef.com 33 | - lava/lava.com 34 | - leaf/leaf.com 35 | - legend/legend.com 36 | - lkccmini/lkccmini.com 37 | - luminous/luminous.com 38 | - lumps/lumps.com 39 | - madness/madness.com 40 | - microsoft_golf_cracktro/mgc.com 41 | - miracle/miracle.com 42 | - mistake/mistake.com 43 | - morales/morales.com 44 | - nicefire/nicefire.com 45 | - optimize/optimize.com 46 | - pack/pack.com 47 | - phong/phong.com 48 | - pikku/pikku.com 49 | - pixelize/pixelize.com 50 | - plasma/plasma.com 51 | - plasmalr/plasmalr.com 52 | - plasmexp/plasmexp.com 53 | - platinum/platinum.com 54 | - proto256/proto256.com 55 | - riddle/riddle.com 56 | - saverave/saverave.com 57 | - skylight/skylight.com 58 | - snow/snow.com 59 | - specifi/specifi.com 60 | - spline/spline.com 61 | - sqwerz3/sqwerz3.com 62 | - static/static.com 63 | - tiled/tiled.com 64 | - unknown/unknown.com 65 | - wamma/wamma.com 66 | - water/water.com 67 | - waves/waves.com 68 | - wd95/wd95.com 69 | - wetwet/wetwet.com 70 | - x/x.com 71 | - zork/zork.com 72 | -------------------------------------------------------------------------------- /harness/sets/demo-com-32bit.yml: -------------------------------------------------------------------------------- 1 | name: demo_com_32bit 2 | default_instructions: 7000000 3 | root: ../dos-software-decoding/demo-com-32bit/ 4 | 5 | set: 6 | - 200h/200h.com 7 | - anding/anding.com 8 | - blobsf/blobsf.com 9 | - bt7/bt7.com 10 | - distant/distant.com 11 | - ems/ems.com 12 | - enchante/enchante.com 13 | - entry2/entry2.com 14 | - fire!/fire!.com 15 | - fire3d/fire3d.com 16 | - fireline/fireline.com 17 | - flame/flame.com 18 | - fountain_of_sparks/fountain_of_sparks.com 19 | - fractal/fractal.com 20 | - frcmirez/frcmirez.com 21 | - glasenapy/glasenapy.com 22 | - gob4k/gob4k.com 23 | - juls/juls.com 24 | - mbl/mbl.com 25 | - noc200/noc200.com 26 | - ripped/ripped.com 27 | - rwater/rwater.com 28 | - sierpins/sierpins.com 29 | - stars/stars.com 30 | - suka/suka.com 31 | - textaroo/textaroo.com 32 | - voronoy/voronoy.com 33 | - wtrfall/wtrfall.com 34 | - xwater/xwater.com 35 | -------------------------------------------------------------------------------- /harness/sets/games-com-commercial-16bit.yml: -------------------------------------------------------------------------------- 1 | name: games_com 2 | default_instructions: 7000000 3 | root: ../dos-software-decoding/games-com-commercial/ 4 | 5 | set: 6 | - 8088 Othello (1985)(Bayley)/8088_othello.com 7 | - Apple Panic (1982)(Broderbund Software Inc)/panic.com 8 | - Astro Dodge (1982)(Digital Marketing Corporation)/astroids.com 9 | - Beast (1984)(Dan Baker)/beast.com 10 | - Blort (1987)(Hennsoft)/blort.com 11 | - Crossfire (1982)(Sierra Online)/cfire.com 12 | - Dig Dug (1982)(Namco)/digdug.com 13 | - F15 Strike Eagle I (1986)(Microprose Software Inc)/f15.com 14 | - Galaxian (1983)(Atari Inc)/galaxian.com 15 | - Gnafu (1986)(Anonymous)/gnafu.com 16 | - Gooku (1987)(Anonymous)/go-moku.com 17 | - Hard Hat Mack (1984)(Electronic Arts Inc)/hhm.com 18 | - Invaders (1995)(Paul Reid)/invaders.com 19 | - Kenguru (1997)(Pig Games)/keng.com 20 | - Logical (1991)(Rainbow Arts)/logctrn1.com 21 | - Madball (1985)(Microtec)/madball.com 22 | - Mind Field (1985)(Everett Kaser)/mine.com 23 | - Ms Pacman (1983)(Atari Inc)/mspacman.com 24 | - Paratrooper (1982)(Orion Software)/ptrooper.com 25 | - Pc Man (1982)(Orion Software)/pcmanv1.com 26 | - Pc Man (1982)(Orion Software)/pcmanv2.com 27 | - Pente (1984)(Michael Leach)/pente.com 28 | - Pipes (1983)(Creative Software)/pipes.com 29 | - Pong (1986)(Imagine)/pong21.com 30 | - Robotron 2084 (1984)(Williams Electronics)/rt2084.com 31 | - Rollo And The Brush Brothers (1983)(Windwill Software)/rollo.com 32 | - Shamus (1984)(Synapse Software)/shamus.com 33 | - Sky Runner (1987)(Anonymous)/sky1.com 34 | - Sky Runner (1987)(Anonymous)/sky2.com 35 | - Snake Game (1992)(Freeware)/snake.com 36 | - Star Chamber (1987)(Russco)/starcham.com 37 | - Triskelion (1987)(Neil Rubenking)/triskel.com 38 | - Turbo Bridge (1985)(Anonymous)/tbridge.com 39 | - Vlak (1993)(Miroslav Nemecek)/vlak.com 40 | - Yatzy (1984)(Jan Ivar Gundersen)/yatzy.com 41 | - Zaxxon (1984)(Sega)/zaxxon.com 42 | - Zyll (1984)(Marshal Linder)/zyll.com 43 | -------------------------------------------------------------------------------- /harness/src/bin/harness-main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::fs; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::Path; 6 | 7 | extern crate clap; 8 | use clap::{Arg, App}; 9 | 10 | use colored::*; 11 | use tera::{Tera, Context}; 12 | use serde::{Serialize, Deserialize}; 13 | 14 | use dustbox::machine::Machine; 15 | 16 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 17 | struct SetDocument { 18 | name: String, 19 | default_instructions: usize, 20 | root: String, 21 | set: Vec, 22 | } 23 | 24 | fn main() { 25 | let matches = App::new("dustbox-harness") 26 | .version("0.1") 27 | .arg(Arg::with_name("INPUT") 28 | .help("Sets the test harness rom set file to use") 29 | .required(true) 30 | .index(1)) 31 | .get_matches(); 32 | 33 | let filename = matches.value_of("INPUT").unwrap(); 34 | 35 | let data = fs::read_to_string(filename).expect("Unable to read file"); 36 | let set: SetDocument = serde_yaml::from_str(&data).unwrap(); 37 | 38 | run_and_save_video_frames(&set); 39 | } 40 | 41 | fn run_and_save_video_frames(set: &SetDocument) { 42 | 43 | let mut out_images = vec![]; 44 | 45 | for bin in &set.set { 46 | println!("{}: {}", set.name.white(), bin.yellow()); 47 | 48 | let mut machine = Machine::deterministic(); 49 | let bin_path = format!("{}{}", set.root, bin); 50 | 51 | if let Some(e) = machine.load_executable_file(&bin_path) { 52 | panic!("error {}", e); 53 | }; 54 | 55 | // XXX allow per-rom override + more properties on a rom basis 56 | machine.execute_instructions(set.default_instructions); 57 | 58 | if !Path::new(&format!("docs/render/{}", set.name)).exists() { 59 | if let Err(e) = fs::create_dir(&format!("docs/render/{}", set.name)) { 60 | panic!("create_dir failed {}", e); 61 | } 62 | } 63 | 64 | let rel_path = Path::new(&bin); 65 | let stem = rel_path.file_stem().unwrap_or_else(|| OsStr::new("")); 66 | let mut filename = OsString::new(); // XXX base on dirname 67 | let outname = &format!("render/{}/{:02x}_", set.name, machine.gpu_mut().mode.mode); 68 | filename.push(format!("docs/{}", outname)); 69 | filename.push(stem.to_os_string()); 70 | filename.push(".png"); 71 | 72 | if write_video_frame_to_disk(&mut machine, filename.to_str().unwrap()) { 73 | let mut pub_filename = String::new(); 74 | pub_filename.push_str(&outname); 75 | pub_filename.push_str(stem.to_str().unwrap()); 76 | pub_filename.push_str(".png"); 77 | out_images.push(pub_filename); 78 | } else { 79 | println!("failed to write {} to disk", filename.to_str().unwrap()); 80 | } 81 | } 82 | 83 | let mut tera = match Tera::new("harness/templates/**/*") { 84 | Ok(t) => t, 85 | Err(e) => { 86 | println!("Parsing error(s): {}", e); 87 | ::std::process::exit(1); 88 | } 89 | }; 90 | 91 | // disable auto-escaping 92 | tera.autoescape_on(vec![]); 93 | 94 | let mut context = Context::new(); 95 | out_images.sort(); 96 | context.insert("out_images", &out_images); 97 | // add stuff to context 98 | match tera.render("test_category.tpl.html", &context) { 99 | Ok(res) => { 100 | let mut f = File::create(format!("docs/{}.html", set.name)).expect("Unable to create file"); 101 | f.write_all(res.as_bytes()).expect("Unable to write data"); 102 | } 103 | Err(why) => panic!(format!("{}", why)), 104 | } 105 | } 106 | 107 | // returns true on success 108 | fn write_video_frame_to_disk(machine: &mut Machine, pngfile: &str) -> bool { 109 | let frame = machine.gpu().render_frame(&machine.mmu); 110 | if frame.data.is_empty() { 111 | println!("ERROR: no frame rendered"); 112 | return false; 113 | } 114 | let img = frame.draw_image(); 115 | if let Err(why) = img.save(pngfile) { 116 | println!("save err: {:?}", why); 117 | return false; 118 | } 119 | true 120 | } 121 | 122 | -------------------------------------------------------------------------------- /harness/templates/test_category.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dustbox - compatibility 6 | 7 | 8 | 9 | {% for img in out_images %} 10 | 11 | {% endfor %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /utils/dos-memory/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o memory.com memory.asm 3 | ndisasm -o 0x100 memory.com 4 | cp memory.com ~/dosbox-x 5 | -------------------------------------------------------------------------------- /utils/dos-memory/memory.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | ;;XXX impl DOS 2+ - ALLOCATE MEMORY. bx=FFFF 5 | mov ah, 0x48 6 | mov bx, 0x1 7 | int 0x21 ; DOS 2+ - ALLOCATE MEMORY 8 | 9 | ret 10 | 11 | ; Return: 12 | ; CF clear if successful 13 | ; AX = segment of allocated block 14 | ; CF set on error 15 | ; AX = error code (07h,08h) (see #01680 at AH=59h/BX=0000h) 16 | ; BX = size of largest available block 17 | -------------------------------------------------------------------------------- /utils/dos-memory/memory.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/dos-memory/memory.com -------------------------------------------------------------------------------- /utils/dos-mouse/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o mouse.com mouse.asm 3 | ndisasm -o 0x100 mouse.com 4 | cp mouse.com ~/dosbox-x 5 | -------------------------------------------------------------------------------- /utils/dos-mouse/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | MS-DOS test program for the mouse 4 | -------------------------------------------------------------------------------- /utils/dos-mouse/mouse.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | 5 | mov ax, 0x13 6 | int 0x10 ; 320x200x256 colors 7 | 8 | mov ax, 0 9 | int 33h ; reset mouse 10 | 11 | ; set up mouse resolution (default is 640x200) 12 | mov ax, 7 13 | mov cx, 0 ; min pos 14 | mov dx, 320 ; max pos 15 | int 33h ; mouse width 0-320 16 | 17 | mov ax, 8 18 | mov cx, 0 19 | mov dx, 200 20 | int 33h ; mouse height 0-200 21 | 22 | 23 | 24 | push 0xA000 25 | pop es ; video segment 26 | 27 | draw_mouse: 28 | mov ax, 0x03 29 | int 0x33 ; get mouse status, CX=X, DX=Y, BX=buttons 30 | 31 | mov word [xVal], cx 32 | mov word [yVal], dx 33 | mov word [buttons], bx 34 | 35 | mov ax, 320 36 | mul word [yVal] ; ax = y base offset 37 | add ax, [xVal] ; exact offset 38 | mov di, ax ; es:di = video offset 39 | 40 | ; draw pixel 41 | mov dx, [buttons] 42 | inc dl 43 | ; with default vga palette, blue = no mouse button, green is left and turqouse is right 44 | mov byte [es:di], dl 45 | 46 | 47 | ; idle for a while 48 | mov bp, 10000 49 | delay: 50 | dec bp 51 | nop 52 | jnz delay 53 | 54 | 55 | jmp draw_mouse 56 | 57 | 58 | mov ax, 0x03 59 | int 0x10 ; text mode 60 | 61 | mov ah, 0x4c 62 | int 0x21 ; exit to dos 63 | 64 | section .data 65 | 66 | section .bss 67 | xVal resw 1 68 | yVal resw 1 69 | buttons resw 1 70 | -------------------------------------------------------------------------------- /utils/dos-mouse/mouse.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/dos-mouse/mouse.com -------------------------------------------------------------------------------- /utils/dos-readfile/FILE.DAT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/dos-readfile/FILE.DAT -------------------------------------------------------------------------------- /utils/dos-readfile/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o readfile.com readfile.asm 3 | ndisasm -o 0x100 readfile.com 4 | cp readfile.com ~/dosbox-x 5 | -------------------------------------------------------------------------------- /utils/dos-readfile/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | MS-DOS test program that OPENs, READs binary file into memory and CLOSEs the handle. 4 | -------------------------------------------------------------------------------- /utils/dos-readfile/readfile.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | 5 | mov ah, 0x3D ; DOS 2+ - OPEN - OPEN EXISTING FILE 6 | mov al, 0; ; mode 7 | push cs 8 | pop ds 9 | mov dx, fileDAT ; DS:DX -> ASCIZ filename 10 | int 0x21 11 | ; ret: AX = file handle 12 | 13 | 14 | mov bx, ax ; file handle 15 | mov cx, 24 ; NUMBER OF BYTES TO READ 16 | ; DS:DX -> buffer for data 17 | mov dx, 0x400 ; point to after program code 18 | mov ah, 0x3F ; DOS 2+ - READ - READ FROM FILE OR DEVICE 19 | int 0x21 20 | ; ret: AX = bytes read 21 | 22 | 23 | mov ah, 0x3E ; CLOSE - CLOSE FILE (BX=handle) 24 | int 0x21 25 | 26 | 27 | int 0x20 ; EXIT TO DOS 28 | 29 | section .data 30 | fileDAT db 'FILE.DAT',0 31 | -------------------------------------------------------------------------------- /utils/dos-readfile/readfile.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/dos-readfile/readfile.com -------------------------------------------------------------------------------- /utils/dumpcs/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | nasm -f bin -o dumpcs.com dumpcs.asm 3 | -------------------------------------------------------------------------------- /utils/dumpcs/README.md: -------------------------------------------------------------------------------- 1 | Writes 64k ram from CS:0 - CS:FFFF to CS.BIN 2 | -------------------------------------------------------------------------------- /utils/dumpcs/dumpcs.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | 5 | ; create file 6 | mov ah, 3ch 7 | mov cx, 0 8 | mov dx, filename 9 | int 21h 10 | mov [handle], ax 11 | 12 | ; write data 13 | mov ah, 40h 14 | mov bx, [handle] 15 | mov cx, 0xFFFF ; length 16 | mov dx, 0 ; start 17 | int 21h 18 | 19 | ; close file 20 | mov ah, 3eh 21 | mov bx, [handle] 22 | int 21h 23 | 24 | ; exit 25 | mov ax,4c00h 26 | int 21h 27 | 28 | 29 | section .data 30 | filename db "cs.bin",0 31 | 32 | section .bss 33 | handle resw 1 34 | -------------------------------------------------------------------------------- /utils/dumpcs/dumpcs.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/dumpcs/dumpcs.com -------------------------------------------------------------------------------- /utils/palette/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o palette.com palette.asm 3 | -------------------------------------------------------------------------------- /utils/palette/README.md: -------------------------------------------------------------------------------- 1 | Draws the default VGA palette on screen 2 | 3 | Source: https://stackoverflow.com/a/38270279 4 | -------------------------------------------------------------------------------- /utils/palette/palette.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | start: 5 | mov ax,13h 6 | int 10h 7 | 8 | ; draw palette in 32x8 squares, each square 5x5 pixels big (so 160x40px) 9 | push 0a000h 10 | pop es 11 | xor di,di 12 | xor ax,ax ; color 13 | mov cx,8 ; big rows (each having 32 5x5 squares) 14 | bigRowLoop: 15 | mov bx,5 ; pixel height of single row 16 | rowLoop: 17 | mov dx,32 ; squares per row 18 | push ax 19 | push di 20 | squareLoop: 21 | ; draw 5 pixels with "ah:al" color, ++color, di += 5 22 | mov [es:di],ax 23 | mov [es:di+2],ax 24 | mov [es:di+4],al 25 | add ax,0101h 26 | add di,5 27 | dec dx 28 | jnz squareLoop 29 | pop di 30 | pop ax ; restore color for first square 31 | add di,320 ; move di to start of next line 32 | dec bx ; do next single pixel line 33 | jnz rowLoop 34 | 35 | ; one row of color squares is drawn, now next 32 colors 36 | add ax,02020h ; color += 32 37 | dec cx 38 | jnz bigRowLoop 39 | 40 | ; wait for any key and exit 41 | xor ah,ah 42 | int 16h 43 | ret 44 | -------------------------------------------------------------------------------- /utils/palette/palette.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/palette/palette.com -------------------------------------------------------------------------------- /utils/prober/.gitignore: -------------------------------------------------------------------------------- 1 | prober.com 2 | prober.asm 3 | 4 | -------------------------------------------------------------------------------- /utils/prober/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o prober.com prober.asm 3 | ndisasm -o 0x100 prober.com | more 4 | cp prober.com ../../../dos-software-decoding 5 | cp prober.com ~/vm-share 6 | cp prober.com ~/dosbox-x 7 | -------------------------------------------------------------------------------- /utils/prober/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | A tool that runs some code and outputs the register values 4 | in an external environment (emulator or real hardware). 5 | 6 | -------------------------------------------------------------------------------- /utils/prober/print.inc.asm: -------------------------------------------------------------------------------- 1 | section .text 2 | 3 | ; prints hex 4 | ; params: 5 | ; ax = u16 value to print 6 | ; dx = pointer to prefixed $-string 7 | prefixed_print_hex_u16: 8 | push ax 9 | call print_dollar_dx ; in = dx 10 | pop ax 11 | call print_hex_u16 ; in = ax 12 | ret 13 | 14 | ; prints hex 15 | ; params: 16 | ; ax = u16 value to print 17 | ; dx = pointer to prefixed $-string 18 | prefixed_print_hex_u32: 19 | push ax 20 | call print_dollar_dx ; in = dx 21 | pop ax 22 | call print_hex_u32 ; in = ax 23 | ret 24 | 25 | 26 | ; in: dx: pointer to $-string 27 | print_dollar_dx: 28 | mov ah, 0x09 ; DOS 1+ WRITE STRING TO STANDARD OUTPUT 29 | int 0x21 30 | ret 31 | 32 | 33 | ; in: ax 34 | print_hex_u16: 35 | ;----------------------- 36 | ; convert the value in AX to hexadecimal ASCIIs 37 | ;----------------------- 38 | mov di, hexTemp16 ; get the offset address 39 | mov cl, 4 ; number of ASCII 40 | P1_16: rol ax, 4 ; 1 Nibble (start with highest byte) 41 | mov bl, al 42 | and bl, 0Fh ; only low-Nibble 43 | add bl, 30h ; convert to ASCII 44 | cmp bl, 39h ; above 9? 45 | jna short P2_16 46 | add bl, 7 ; "A" to "F" 47 | P2_16: mov [di], bl ; store ASCII in buffer 48 | inc di ; increase target address 49 | dec cl ; decrease loop counter 50 | jnz P1_16 ; jump if cl is not equal 0 (zeroflag is not set) 51 | ;----------------------- 52 | ; Print string 53 | ;----------------------- 54 | mov dx, hexTemp16 55 | call print_dollar_dx 56 | ret 57 | 58 | 59 | ; in: eax 60 | print_hex_u32: 61 | ;----------------------- 62 | ; convert the value in AX to hexadecimal ASCIIs 63 | ;----------------------- 64 | mov di, hexTemp32 ; get the offset address 65 | mov cl, 8 ; number of ASCII 66 | P1_32: rol eax, 4 ; 1 Nibble (start with highest byte) 67 | mov bl, al 68 | and bl, 0Fh ; only low-Nibble 69 | add bl, 30h ; convert to ASCII 70 | cmp bl, 39h ; above 9? 71 | jna short P2_32 72 | add bl, 7 ; "A" to "F" 73 | P2_32: mov [di], bl ; store ASCII in buffer 74 | inc di ; increase target address 75 | dec cl ; decrease loop counter 76 | jnz P1_32 ; jump if cl is not equal 0 (zeroflag is not set) 77 | ;----------------------- 78 | ; Print string 79 | ;----------------------- 80 | mov dx, hexTemp32 81 | call print_dollar_dx 82 | ret 83 | 84 | 85 | section .data 86 | hexTemp16 db '0000',0xD,0xA,'$' ; buffer for 16-bit hex string 87 | hexTemp32 db '00000000',0xD,0xA,'$' ; buffer for 32-bit hex string 88 | -------------------------------------------------------------------------------- /utils/prober/prober.tpl.asm: -------------------------------------------------------------------------------- 1 | org 0x100 2 | 3 | section .text 4 | start: 5 | {{ snippet }} 6 | 7 | call save_regs 8 | call print_regs 9 | 10 | mov ax, 0x4c00 ; exit to dos 11 | int 0x21 12 | 13 | %include "regs.inc.asm" 14 | %include "print.inc.asm" 15 | -------------------------------------------------------------------------------- /utils/prober/regs.inc.asm: -------------------------------------------------------------------------------- 1 | section .text 2 | save_regs: 3 | ; save reg states after instruction executes 4 | mov [_eax], eax 5 | mov [_ebx], ebx 6 | mov [_ecx], ecx 7 | mov [_edx], edx 8 | mov [_esp], esp 9 | mov [_ebp], ebp 10 | mov [_esi], esi 11 | mov [_edi], edi 12 | 13 | mov [_es], es 14 | mov [_cs], cs 15 | mov [_ss], ss 16 | mov [_ds], ds 17 | mov [_fs], fs 18 | mov [_gs], gs 19 | 20 | ; read FLAGS 16bit reg 21 | pushf 22 | pop ax 23 | mov [_flags], ax 24 | ret 25 | 26 | print_flags: 27 | mov dx, flagsIs 28 | call print_dollar_dx 29 | mov ax, [_flags] 30 | call print_hex_u16 31 | ret 32 | 33 | 34 | 35 | print_regs: 36 | ; ----------- 37 | ; 32 BIT REGS 38 | ; ----------- 39 | mov dx, eaxIs 40 | mov eax, [_eax] 41 | call prefixed_print_hex_u32 42 | 43 | mov dx, ebxIs 44 | mov eax, [_ebx] 45 | call prefixed_print_hex_u32 46 | 47 | mov dx, ecxIs 48 | mov eax, [_ecx] 49 | call prefixed_print_hex_u32 50 | 51 | mov dx, edxIs 52 | mov eax, [_edx] 53 | call prefixed_print_hex_u32 54 | 55 | mov dx, ebpIs 56 | mov eax, [_ebp] 57 | call prefixed_print_hex_u32 58 | 59 | mov dx, espIs 60 | mov eax, [_esp] 61 | call prefixed_print_hex_u32 62 | 63 | mov dx, esiIs 64 | mov eax, [_esi] 65 | call prefixed_print_hex_u32 66 | 67 | mov dx, ediIs 68 | mov eax, [_edi] 69 | call prefixed_print_hex_u32 70 | 71 | ; ----------- 72 | ; 16 BIT REGS 73 | ; ----------- 74 | mov dx, esIs 75 | mov ax, [_es] 76 | call prefixed_print_hex_u16 77 | 78 | mov dx, csIs 79 | mov ax, [_cs] 80 | call prefixed_print_hex_u16 81 | 82 | mov dx, ssIs 83 | mov ax, [_ss] 84 | call prefixed_print_hex_u16 85 | 86 | mov dx, dsIs 87 | mov ax, [_ds] 88 | call prefixed_print_hex_u16 89 | 90 | mov dx, fsIs 91 | mov ax, [_fs] 92 | call prefixed_print_hex_u16 93 | 94 | mov dx, gsIs 95 | mov ax, [_gs] 96 | call prefixed_print_hex_u16 97 | 98 | call print_flags 99 | ret 100 | 101 | 102 | section .data 103 | eaxIs db 'eax=$' 104 | ebxIs db 'ebx=$' 105 | ecxIs db 'ecx=$' 106 | edxIs db 'edx=$' 107 | ebpIs db 'ebp=$' 108 | espIs db 'esp=$' 109 | esiIs db 'esi=$' 110 | ediIs db 'edi=$' 111 | esIs db 'es=$' 112 | csIs db 'cs=$' 113 | ssIs db 'ss=$' 114 | dsIs db 'ds=$' 115 | fsIs db 'fs=$' 116 | gsIs db 'gs=$' 117 | flagsIs db 'flag=$' 118 | _eax dd 0 119 | _ebx dd 0 120 | _ecx dd 0 121 | _edx dd 0 122 | _esp dd 0 123 | _ebp dd 0 124 | _esi dd 0 125 | _edi dd 0 126 | _es dw 0 127 | _cs dw 0 128 | _ss dw 0 129 | _ds dw 0 130 | _fs dw 0 131 | _gs dw 0 132 | _flags dw 0 133 | -------------------------------------------------------------------------------- /utils/realtest/.gitignore: -------------------------------------------------------------------------------- 1 | cc 2 | ex1 3 | test 4 | test.dSYM 5 | -------------------------------------------------------------------------------- /utils/realtest/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test ex1 cc 2 | 3 | ex1: 4 | nasm -f elf64 -o ex1 ex1.asm && gobjdump -d ./ex1 5 | 6 | test: 7 | gcc test.c -g -o test 8 | ./test 9 | 10 | cc: 11 | gcc cc.c -o cc 12 | gobjdump -d ./cc 13 | -------------------------------------------------------------------------------- /utils/realtest/cc.c: -------------------------------------------------------------------------------- 1 | // some code to objdump, to study calling conventions 2 | 3 | #include 4 | 5 | void a_void_func() { 6 | int a = 10; 7 | printf("%d", a); 8 | } 9 | 10 | void b_void_func() { 11 | int a = 10; 12 | } 13 | 14 | int main(int argc, char **argv) { 15 | a_void_func(); 16 | b_void_func(); 17 | } 18 | -------------------------------------------------------------------------------- /utils/realtest/ex1.asm: -------------------------------------------------------------------------------- 1 | ; for testing instruction decoding 2 | 3 | BITS 64 4 | ;GLOBAL _start 5 | ;SECTION .text 6 | 7 | mov rax, 0x1313131344 8 | ret 9 | 10 | -------------------------------------------------------------------------------- /utils/realtest/test.c: -------------------------------------------------------------------------------- 1 | // execute a instruction, record resulting register values 2 | // tested on macOS, Apple LLVM version 8.1.0 (clang-802.0.41) 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | char code[] = { 10 | // llvm, osx void func begin (4 bytes) 11 | 0x55, // push %rbp 12 | 0x48, 0x89, 0xE5, // mov %rsp,%rbp 13 | 14 | // instruction to test 15 | // 0xB8, 0x13, 0x13, 0x00, 0x00, // mov eax, 0x1313 16 | 0x66, 0xB8, 0x13, 0x13, // mov ax, 0x1313 NOTE: in 64-bit mode we must use 0x66 prefix for 16bit op 17 | 18 | // llvm, osx void func end (2 bytes) 19 | 0x5D, // pop %rbp 20 | 0xC3, // retq 21 | 22 | // pad to even 16-byte length 23 | 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // nop 24 | }; 25 | 26 | int main(int argc, char **argv) { 27 | // copy code to executable buffer 28 | void *buf = mmap(0, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC, 29 | MAP_PRIVATE|MAP_ANON, -1, 0); 30 | memcpy(buf, code, sizeof(code)); 31 | 32 | uint32_t eax, ebx, ecx, edx; 33 | uint64_t flags; 34 | 35 | // clear registers and flags 36 | asm("movl $0, %eax"); 37 | asm("movl $0, %ebx"); 38 | asm("movl $0, %ecx"); 39 | asm("movl $0, %edx"); 40 | //asm("pushq $0"); 41 | //asm("popfq"); // XXX flags returned as 0000202 so seems to fail??? 42 | 43 | // run code 44 | // ((void (*) (void))buf)(); 45 | 46 | // read registers and flag 47 | asm("movl %%eax, %0" : "=r"(eax)); 48 | asm("movl %%ebx, %0" : "=r"(ebx)); 49 | asm("movl %%ecx, %0" : "=r"(ecx)); 50 | asm("movl %%edx, %0" : "=r"(edx)); 51 | asm("pushfq"); // push flags (32 bits) 52 | asm("pop %rax"); 53 | asm("movq %%rax, %0" : "=r"(flags)); // XXX bug?.. generates "mov rax,rcx" 54 | 55 | //printf("eax %08x ebx %08x ecx %08x edx %08x\n", eax, ebx, ecx, edx); 56 | //printf("flag %08llx\n", flags); 57 | } 58 | -------------------------------------------------------------------------------- /utils/scroll/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | nasm -f bin -o scroll.com scroll.asm 3 | cp scroll.com ~/dosbox-x 4 | -------------------------------------------------------------------------------- /utils/scroll/scroll.asm: -------------------------------------------------------------------------------- 1 | ; scrolls screen in gfx mode 2 | 3 | org 0x100 4 | 5 | section .text 6 | start: 7 | mov ax,13h 8 | int 10h 9 | 10 | mov ah, 0xc ; draw a pixel 11 | mov bh, 0 ; page 0 12 | mov al, 13 ; pixel color 13 | mov cx, 50 ; x 14 | mov dx, 50 ; y 15 | int 0x10 16 | 17 | 18 | mov ah, 0x06 ; scroll up 19 | mov ch, 10 ; upper_y 20 | mov cl, 10 ; upper_x 21 | mov dh, 100 ; lower_y 22 | mov dl, 100 ; lower_x 23 | mov al, 5 ; lines 24 | mov bh, 1 ; attr 25 | int 0x10 26 | 27 | ; wait for any key and exit 28 | xor ah,ah 29 | int 16h 30 | ret 31 | -------------------------------------------------------------------------------- /utils/scroll/scroll.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinlindhe/dustbox-rs/6dad38d8db2946cc5539b96c0fae796aa6f6b6b6/utils/scroll/scroll.com --------------------------------------------------------------------------------