├── .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 | [](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
--------------------------------------------------------------------------------