├── bios ├── fd.img ├── docs ├── images │ ├── logo.gif │ ├── button_active.gif │ ├── button_default.gif │ ├── templatemo_bg.gif │ ├── templatemo_top_bg.gif │ ├── templatemo_footer_bg.gif │ ├── templatemo_header_bg.gif │ ├── templatemo_side_bg.gif │ └── templatemo_content_bg.gif ├── 8086tiny.css └── doc.html ├── runme ├── Makefile ├── license.txt ├── README.md ├── 8086tiny.c └── bios_source └── bios.asm /bios: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/bios -------------------------------------------------------------------------------- /fd.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/fd.img -------------------------------------------------------------------------------- /docs/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/logo.gif -------------------------------------------------------------------------------- /docs/images/button_active.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/button_active.gif -------------------------------------------------------------------------------- /docs/images/button_default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/button_default.gif -------------------------------------------------------------------------------- /docs/images/templatemo_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_top_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_top_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_footer_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_footer_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_header_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_header_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_side_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_side_bg.gif -------------------------------------------------------------------------------- /docs/images/templatemo_content_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adriancable/8086tiny/HEAD/docs/images/templatemo_content_bg.gif -------------------------------------------------------------------------------- /runme: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | clear 3 | stty cbreak raw -echo min 0 4 | if [ -f hd.img ] 5 | then 6 | ./8086tiny bios fd.img hd.img 7 | else 8 | ./8086tiny bios fd.img 9 | fi 10 | stty cooked echo 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 8086tiny: a tiny, highly functional, highly portable PC emulator/VM 2 | # Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | # 4 | # This work is licensed under the MIT License. See included LICENSE.TXT. 5 | 6 | # 8086tiny builds with graphics and sound support 7 | # 8086tiny_slowcpu improves graphics performance on slow platforms (e.g. Raspberry Pi) 8 | # no_graphics compiles without SDL graphics/sound 9 | 10 | OPTS_ALL=-O3 -fsigned-char -std=c99 11 | OPTS_SDL=`sdl-config --cflags --libs` 12 | OPTS_NOGFX=-DNO_GRAPHICS 13 | OPTS_SLOWCPU=-DGRAPHICS_UPDATE_DELAY=25000 14 | 15 | 8086tiny: 8086tiny.c 16 | ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} -o 8086tiny 17 | strip 8086tiny 18 | 19 | 8086tiny_slowcpu: 8086tiny.c 20 | ${CC} 8086tiny.c ${OPTS_SDL} ${OPTS_ALL} ${OPTS_SLOWCPU} -o 8086tiny 21 | strip 8086tiny 22 | 23 | no_graphics: 8086tiny.c 24 | ${CC} 8086tiny.c ${OPTS_NOGFX} ${OPTS_ALL} -o 8086tiny 25 | strip 8086tiny 26 | 27 | clean: 28 | rm 8086tiny 29 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Adrian Cable - http://www.megalith.co.uk/8086tiny 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 8086tiny 2 | ======== 3 | 4 | 8086tiny is a completely free (MIT License) open source PC XT-compatible emulator/virtual machine written in C. It is, we believe, the smallest of its kind (the fully-commented source is under 25K). Despite its size, 8086tiny provides a highly accurate 8086 CPU emulation, together with support for PC peripherals including XT-style keyboard, floppy/hard disk, clock, audio, and Hercules/CGA graphics. 8086tiny is powerful enough to run software like AutoCAD, Windows 3.0, and legacy PC games: the 8086tiny distribution includes Alley Cat, the author's favorite PC game of all time. 5 | 6 | 8086tiny is highly portable and runs on practically any little endian machine, from simple 32-bit MCUs upwards. 8086tiny has successfully been deployed on 32-bit/64-bit Intel machines (Windows, Mac OS X and Linux), Nexus 4/ARM (Android), iPad 3 and iPhone 5S (iOS), and Raspberry Pi (Linux). 7 | 8 | The philosophy of 8086tiny is to keep the code base as small as possible, and through the open source license encourage individual developers to tune and extend it as per their specific requirements, adding support, for example, for more complex instruction sets (e.g. Pentium) or peripherals (e.g. mouse). Forking this repository is highly encouraged! 9 | 10 | Any questions, comments or suggestions are very welcome in our forum at 8086tiny.freeforums.net. 11 | -------------------------------------------------------------------------------- /docs/8086tiny.css: -------------------------------------------------------------------------------- 1 | /* 2 | CSS Credit: http://www.templatemo.com/ 3 | */ 4 | body { 5 | margin:0; 6 | padding:0; 7 | line-height: 1.5em; 8 | font-family: "Trebuchet MS", Verdana, Helvetica, Arial; 9 | font-size: 12px; 10 | color: #000000; 11 | background: #999999 url(images/templatemo_bg.gif); 12 | } 13 | a:link, a:visited { color: #0066CC; text-decoration: none} 14 | a:active, a:hover { color: #008800; text-decoration: underline} 15 | 16 | #templatemo_container_wrapper { 17 | background: url(images/templatemo_side_bg.gif) repeat-x; 18 | padding-left: 2px; 19 | } 20 | #templatemo_container { 21 | width: 809px; 22 | margin: 0px auto; 23 | background: url(images/templatemo_content_bg.gif); 24 | } 25 | #templatemo_top { 26 | clear: left; 27 | height: 25px; /* 'padding-top' + 'height' must be equal to the 'background image height' */ 28 | padding-top: 18px; 29 | padding-left: 30px; 30 | color: #0000bf; 31 | background: url(images/templatemo_top_bg.gif) no-repeat bottom; 32 | } 33 | #templatemo_header { 34 | clear: left; 35 | height: 113px; 36 | text-align: center; 37 | background: url(images/templatemo_header_bg.gif) no-repeat; 38 | } 39 | #inner_header { 40 | height: 88px; 41 | background: url(images/templatemo_header.jpg) no-repeat center center; 42 | } 43 | #templatemo_left_column { 44 | clear: left; 45 | float: left; 46 | width: 540px; 47 | padding-left: 20px; 48 | } 49 | #templatemo_right_column { 50 | float: right; 51 | width: 216px; 52 | padding-right: 15px; 53 | } 54 | #templatemo_footer { 55 | clear: both; 56 | padding-top: 18px; 57 | height: 37px; 58 | text-align: center; 59 | font-size: 11px; 60 | background: url(images/templatemo_footer_bg.gif) no-repeat; 61 | color: #666666; 62 | } 63 | #templatemo_footer a { 64 | color: #666666; 65 | } 66 | #templatemo_site_title { 67 | padding-top: 65px; 68 | font-weight: bold; 69 | font-size: 32px; 70 | color: #FFFFFF; 71 | } 72 | .site_slogan_center { 73 | float:left; 74 | display:block; 75 | width:50px; 76 | height:25px; 77 | text-indent:-999px; 78 | cursor:default; 79 | } 80 | #templatemo_site_slogan { 81 | padding-top: 14px; 82 | font-weight: bold; 83 | font-size: 13px; 84 | color: #AAFFFF; 85 | } 86 | .templatemo_spacer { 87 | clear: left; 88 | height: 18px; 89 | } 90 | .templatemo_pic { 91 | float: left; 92 | margin-right: 10px; 93 | margin-bottom: 10px; 94 | border: 1px solid #000000; 95 | } 96 | .section_box { 97 | margin: 10px; 98 | padding: 10px; 99 | border: 1px dashed #CCCCCC; 100 | background: #F2F2F2; 101 | } 102 | .section_box2 { 103 | clear: left; 104 | margin-top: 10px; 105 | background: #F6F6F6; 106 | color: #000000; 107 | } 108 | .text_area { 109 | padding: 10px; 110 | } 111 | .publish_date { 112 | clear: both; 113 | margin-top: 10px; 114 | color: #999999; 115 | font-size: 11px; 116 | font-weight: bold; 117 | } 118 | .title { 119 | padding-bottom: 12px; 120 | font-size: 18px; 121 | font-weight: bold; 122 | color: #0066CC; 123 | } 124 | .subtitle { 125 | padding-bottom: 6px; 126 | font-size: 14px; 127 | font-weight: bold; 128 | color: #666666; 129 | } 130 | .post_title { 131 | padding: 6px; 132 | padding-left: 10px; 133 | background: #DDEEFF; 134 | font-size: 14px; 135 | font-weight: bold; 136 | color: #0066CC; 137 | } 138 | .templatemo_menu { 139 | list-style-type: none; 140 | margin: 10px; 141 | margin-top: 0px; 142 | padding: 0px; 143 | width: 195px; 144 | } 145 | .templatemo_menu li a{ 146 | background: #F4F4F4 url(images/button_default.gif) no-repeat; 147 | font-size: 13px; 148 | font-weight: bold; 149 | color: #0066CC; 150 | display: block; 151 | width: auto; 152 | margin-bottom: 2px; 153 | padding: 5px; 154 | padding-left: 12px; 155 | text-decoration: none; 156 | } 157 | * html .templatemo_menu li a{ 158 | width: 190px; 159 | } 160 | .templatemo_menu li a:visited, .templatemo_menu li a:active{ 161 | color: #0066CC; 162 | } 163 | .templatemo_menu li a:hover{ 164 | background: #EEEEEE url(images/button_active.gif) no-repeat; 165 | color: #FF3333; 166 | } -------------------------------------------------------------------------------- /docs/doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8086tiny: a tiny PC emulator/virtual machine - Documentation 6 | 7 | 8 | 9 | 10 | 11 | 14 |
15 |
16 |
17 |
18 | 8086tiny · Documentation 19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 |
Documentation
28 | 29 |

8086tiny is a tiny, free, open source, portable Intel PC emulator/VM, powerful enough to run DOS, Windows 3.0, Excel, MS Flight Simulator, AutoCAD, Lotus 1-2-3, and similar applications. 8086tiny emulates a "late 80's era" PC XT-type machine with the following features:

30 | 31 |
    32 |
  • Intel 8086/186 CPU
  • 33 |
  • 1MB RAM
  • 34 |
  • 3.5" floppy disk controller (1.44MB/720KB)
  • 35 |
  • Fixed disk controller (supports a single hard drive up to 528MB)
  • 36 |
  • CGA/Hercules graphics card with 720x348 2-color and 320x200 4-color graphics (64KB video RAM), and CGA 80 x 25 16-color text mode support
  • 37 |
  • Accurate programmable interrupt timer (PIT)
  • 38 |
  • Keyboard controller with 83-key XT-style keyboard
  • 39 |
  • Real-time clock
  • 40 |
  • PC speaker
  • 41 |
42 | 43 |

The emulator uses the SDL graphics library for portability, and compiles under a range of platforms (Windows, Mac OS X, Linux, Android, iOS, Raspberry Pi).

44 |

While 8086tiny as supplied implements only the 8086 instruction set, it can be extended to more complex, modern instruction sets with relative ease.

45 | 46 |
47 |
Build Instructions
48 |
49 |

50 | The 8086tiny distribution includes a Makefile that will compile unchanged under most UNIX platforms. The 8086tiny source also compiles unchanged under Microsoft Visual Studio C/C++. 51 |

52 |
    53 |
  • Running make compiles the full 8086tiny distribution, which includes audio and CGA/Hercules graphics support via SDL.
  • 54 |
  • To compile for slower platforms (e.g. Raspberry Pi), build with make 8086tiny_slowcpu to increase the graphics emulation frame rate.
  • 55 |
  • If your platform lacks SDL and/or you do not need support for graphics-mode applications, you can compile without graphics and audio support by running make no_graphics to produce a smaller binary.
  • 56 |
57 |
58 |
59 | 60 |
61 |
Usage
62 |
63 |

8086tiny bios-image-file floppy-image-file [@][harddisk-image-file]

64 | 65 |

If harddisk-image-file is prefixed with @ then 8086tiny will boot from the hard disk image. Otherwise, 8086tiny will boot from the floppy disk image.

66 | 67 |

Under UNIXes, the keyboard must be set to raw mode using stty for the emulator to run. The distribution includes a script called runme which sets the keyboard mode appropriately and runs the emulator with floppy and/or hard disk images as appropriate:

68 | 69 | #!/bin/sh
70 | clear
71 | stty cbreak raw -echo min 0
72 | if [ -f hd.img ]
73 | then
74 |   ./8086tiny bios fd.img hd.img
75 | else
76 |   ./8086tiny bios fd.img
77 | fi
78 | stty cooked echo 79 |
80 |
81 |
82 | 83 |
84 |
Building a Hard Disk Image
85 |
86 |

87 | To create a hard disk image for use with the emulator, start by generating a flat file called, for example, hd.img of the required size (under 528MB), filled with zero bytes, made using mkfile or a similar tool. 88 |

89 |

90 | Preparing the hard disk image for use with the emulator under DOS is done just like a real PC: 91 |

    92 |
  • Boot the emulator, and use FDISK to partition the hard disk. When it's done FDISK will reboot the emulator.
  • 93 |
  • Use FORMAT C: (or FORMAT C: /S to create a bootable disk) to format the disk image, and you are done.
  • 94 |
95 |

The resulting disk image is in the right format to be mounted on a real Windows PC using e.g. OSFMount, on a Mac using hdiutil, or on Linux using mount, providing an easy way to copy files and programs to and from the disk image. Or, you can install programs from within the emulator itself using regular floppy disk images (see "Floppy Drive Emulation" below). 96 |

97 |
98 |
99 | 100 |
101 |
Keyboard Support
102 |
103 |

104 | The emulator simulates an XT-style keyboard controlled by an Intel 8042 chip on I/O port 0x60, generating interrupt 9 on each keypress. Because a real 8042 returns scan codes rather than the ASCII characters, for portability, the emulator BIOS does the reverse of a real PC BIOS and converts ASCII characters to scancodes, simulating press/release of the modifier keys (e.g. shift) as necessary to work like a "real" keyboard. The OS (DOS/Windows) then converts them back to ASCII characters and normally this process works seamlessly. 105 |

106 |

107 | The scan code table in the BIOS maps your local keyboard layout onto scan codes matching a US-layout QWERTY keyboard. If you are using an international keyboard layout everything will work fine with no changes, provided "United States 83-key XT keyboard" or similar is selected if your OS (e.g. Windows 3.0) gives the option. 108 |

109 | For console (text) applications, there are special key sequences to get Alt+xxx, Fxx and some Ctrl+xxx keys, since these are not returned directly by the standard C I/O functions: 110 |

    111 |
  • To send an Alt+xxx key combination, press Ctrl+A then the key, so for example to type Alt+F, press Ctrl+A then F.
  • 112 |
  • To send an Fxx key, press Ctrl+F then a number key. For example, to get the F4 key, press Ctrl+F then 4. To get F10, press Ctrl+F then 0.
  • 113 |
  • To send Ctrl+A, press Ctrl+F then Ctrl+A. To send Ctrl+F, press Ctrl+F then Ctrl+F.
  • 114 |
  • To send a Page Down key, press Ctrl+F then O. To send a Page Up key, press Ctrl+F then Q.
  • 115 |
116 |

For graphics (SDL) applications, all keys will work as per a "real" PC without needing the special sequences above. 117 |

The keyboard is polled every KEYBOARD_TIMER_UPDATE_DELAY instructions. Decreasing this value will increase keyboard responsiveness, at the expense of emulated CPU speed, and vice versa. The default value of 20000 should be suitable for most platforms.

118 |
119 |
120 | 121 |
122 |
Floppy Drive Emulation
123 |
124 |

125 | Emulates a 3.5" high-density floppy drive. Can read, write and format 1.44MB disks (18 sectors per track, 2 heads) and 720KB disks (9 sectors per track, 2 heads). 126 |

127 | If you want to install your own software from a set of multiple floppy images (downloaded from e.g. Vetusware), the easiest way to "change disks" is to copy each disk image in turn over the floppy image file you specified on the command line, from a terminal other than the one running 8086tiny. Don't forget to put your original boot disk back at the end! 128 |

129 |
130 |
131 | 132 |
133 |
Hard Drive Emulation
134 |
135 |

136 | Supports up to 1023 cylinders, 63 sectors per track, 63 heads for disks up to 528MB. 137 |

138 | Disk image format used is a subset of the standard "raw" format used by most disk image mount tools. In general, disk images prepared by the emulator will work with disk image tools and other emulators, but not the other way around. 139 |

140 | The emulator uses an overly simplistic algorithm to derive a simulated cylinder/sector/head geometry from the disk image file's size. This algorithm often results in not all the space in the image file being available for disk partitions. For example, creating a 40,000,000 byte image file results in DOS FDISK seeing only 31.9MB as the volume size. 141 |

142 | 8086tiny will boot from a hard disk image if the HD image filename is prefixed with @ on the command line. For example: ./8086tiny bios fd.img @hd.img 143 |

144 |
145 |
146 | 147 |
148 |
Text Mode Support
149 |
150 |

151 | The emulator supports text output via the standard BIOS interrupt 0x10 interface, and also direct video memory access (one page, 4KB video RAM at segment B800) in 80 x 25 CGA 16-color text mode. 152 |

153 | BIOS text output calls are converted to simple writes to stdout. Direct video memory accesses for the 80 x 25 CGA color text mode are converted to ANSI terminal escape sequences. If you are using a terminal which does not support ANSI (e.g. you are compiling the emulator with MS VC++ and running in a Windows console window) then PC applications that directly write to video memory in text mode may be unusable. Please make sure your terminal window is at least 80 x 25 characters in size. 154 |

155 | Video memory in text mode is rendered to the terminal every 8 * KEYBOARD_TIMER_UPDATE_DELAY instructions. Decreasing this value will increase the text update rate, at the expense of emulated CPU speed, and vice versa. The default value of 20000 should be suitable for most platforms.

156 |

157 | The regular PC character code page (437) includes various extended ASCII characters for things like line drawing. You might want to set the font in your terminal program to something that includes these (for example, on Mac OS X there is a suitable freeware font called Perfect DOS VGA 437). Otherwise, extended characters may render incorrectly (for example as question mark symbols). 158 |

159 | Occasionally a DOS application on exit will leave the video hardware in an odd state which confuses the emulator, resulting in subsequent text output being invisible. If this happens, just use the DOS CLS command to clear the screen and all will be well again. 160 |

161 |
162 |
163 | 164 |
165 |
Graphics Mode Support
166 |
167 |

168 | Hercules 720x348 monochrome graphics mode and CGA 320x200 4-color graphics mode are emulated using SDL. Most CGA/Hercules features are supported via the normal I/O interface on ports 0x3Dx and 0x3Bx including video memory bank switching (segments B000/B800), which some games use for double-buffered graphics. Resolution reprogramming via the CRTC register is supported by 8086tiny 1.03 and later, as required by, for example, the ETEN Chinese System (which uses 640 x 408). The CGA 640x200 2-color mode is currently not supported. 169 |

170 | When an application enters graphics mode, the emulator will open an SDL window (which will be closed when the application goes back to text mode). On UNIXes, SDL will automatically output graphics via X11 if the DISPLAY environment variable is set up. 171 |

172 | The graphics display is updated every GRAPHICS_UPDATE_DELAY instructions. Decreasing this value will increase the graphics update rate, at the expense of emulated CPU speed, and vice versa. By default, GRAPHICS_UPDATE_DELAY is set to 360000 instructions, which gives good performance for faster platforms. On slower platforms like Raspberry Pi, a smaller value is suitable: building with make 8086tiny_slowcpu reduces GRAPHICS_UPDATE_DELAY to 50000 instructions. 173 |

174 | Some applications (e.g. AutoCAD) support a PC configuration with a CGA card and a Hercules card, for simultaneous text and graphics output on different displays. The emulator simulates this configuration, too, using separate windows for the (terminal) text and (SDL) graphics displays. 175 |

176 | If your application only requires text mode, you can compile 8086tiny without SDL by defining NO_GRAPHICS. 177 |

178 |
179 |
180 | 181 |
182 |
Real-Time Clock Support
183 |
184 |

185 | Reading the RTC (both time and date) is emulated via the standard BIOS clock interface, pulling the time/date from the host computer. Setting the time or date is not supported. 186 |

187 |
188 |
189 | 190 |
191 |
Hardware Timer Support
192 |
193 |

194 | Programmable interrupt timer channels 0 and 2 are emulated through the usual I/O port 0x40-0x43 interface. Only mode 3 (square wave generator) is currently supported, but this is what most applications use. Software that uses timers to control execution speed such as games should run at an accurate pace. 195 |

196 |
197 |
198 | 199 |
200 |
PC Speaker Support
201 |
202 |

203 | The PC speaker is emulated through the usual port 0x61 interface. The only PC speaker mode supported is via PIT channel 2, so you will hear most music but not non-musical sound effects. 204 |

205 |
206 |
207 | 208 |
209 |
BIOS
210 |
211 |

212 | Like a real PC, the emulator needs a BIOS to implement boot functionality and the standard interrupt interfaces. The 8086tiny BIOS was written from scratch using documentation in the public domain. It is around 6KB in size and assembles using NASM. Full source code and a pre-built binary are provided. 213 |

214 | The BIOS binary comprises a code section and a data section. The code section implements the standard interrupt interfaces for video, disk, timer, clock and so on, much as a "real" PC BIOS does, and also a small timer-controlled video driver to convert video memory formatting into ANSI escape sequences when the emulator is in text mode. 215 | The data section includes typical BIOS structures like a scan code table and the BIOS data area, but also a number of look-up tables to assist the emulator with instruction decoding. Full detail is provided in the "CPU, Memory and Register Emulation" section below. 216 |

217 |
218 |
219 | 220 |
221 |
Memory Map and Register Emulation
222 |
223 |

224 | The emulator simulates a hardware configuration with A20 address line wraparound disabled, making just over 1MB of RAM available to applications. 225 |

226 | Memory map is largely as per a real PC, with interrupt vector table at 0:0, BIOS data area including keyboard buffer at 40:0, CGA text video memory at B800:0, Hercules/CGA graphics memory at B000/B800:0, and BIOS at F000:0100. Unlike a real PC, in the emulator the CPU registers are memory-mapped (at F000:0), which enables considerable optimisation of the emulator's instruction execution unit by permitting the unification of memory and register operations, while remaining invisible to the running software. 227 |

228 |
229 |
230 | 231 |
232 |
CPU, Memory and Register Emulation
233 |
234 |

235 | CPU supports the full 8086 instruction set (plus some 80186 extensions), including undocumented instructions (e.g. SALC) and flag behaviors (e.g. MUL/DIV), opcode bugs which some applications rely on (e.g. PUSH SP), and the trap flag for debugger support. 236 |

237 | The focus of 8086tiny is on minimizing code size without comproming emulation accuracy. Due to the complexities of the highly irregular Intel x86 instruction format, instruction decoding is assisted by a number of lookup tables which form part of the BIOS binary. For example, there are many different ways to encode a MOV instruction, depending on the types of the source and destination operands (immediate, register, or memory). There are sometimes even multiple ways to encode the same instruction (e.g. MOV AX, [1234] usually encodes as A1 34 12 but can also encode as 8B 06 34 12). To avoid having to implement similar functionality in the emulator multiple times for each instruction or encoding variant, look-up tables are used to map each instruction to an internal function and subfunction number. 238 |

239 | As an example, we illustrate how the emulator executes the instruction ADD AX, BX, which encodes as hex 01 D8. 240 |

241 |
    242 |
  • The emulator begins by retrieving index 01 hex (the first byte of the instruction) from TABLE_XLAT_OPCODE and TABLE_XLAT_SUBFUNCTION, giving a translated opcode ID of decimal 9 (which corresponds to the Intel instruction template arithmetic_function reg, r/m) and a subfunction ID of 0 (which corresponds to the ADD function), respectively.
  • 243 |
  • The OPCODE chain in the source uses the translated opcode ID and subfunction ID to determine the operation to execute, in this case calling the OP(+=) macro followed by set_CF() to set the carry flag in accordance with the result of the addition.
  • 244 |
  • Next, instruction length is computed. Because Intel x86 instructions are of arbitrary length (and, sometimes, multiple encodings of the same instruction can have different lengths), tables are used to determine the instruction length to move IP to the next instruction. The opcode index 01 hex is used as an index into TABLE_BASE_INST_SIZE, TABLE_I_MOD_SIZE, and TABLE_I_W_SIZE and these numbers are added to compute the total instruction length.
  • 245 |
  • Finally, flags are set. The opcode index 01 hex is then used as an index into TABLE_STD_FLAGS to give a bitmask of 3, which is FLAGS_UPDATE_SZP | FLAGS_UPDATE_AO_ARITH.
  • 246 |
    • FLAGS_UPDATE_SZP (1) signifies that this instruction sets the sign, zero and parity flags according to the operation's result in the standard way. Sign and zero flags are set directly from the result, and the parity flag is set by looking up the result in TABLE_PARITY_FLAG.
    • 247 |
    • FLAGS_UPDATE_AO_ARITH (2) signifies that this instruction sets the auxiliary and overflow flags as standard for arithmetic operations.
    • 248 |
    • If FLAGS_UPDATE_OC_LOGIC (4) were set in the bitmask (it is not here), the overflow and carry flags would be set to 0, as standard for logic operations.
    249 |
250 | 251 |

The CPU also implements some "special" two-byte opcodes to help the emulator talk with the outside world. These are: 252 |

    253 |
  • 0F 00 (PUTCHAR_AL) - output character in register AL to terminal
  • 254 |
  • 0F 01 (GET_RTC) - write real-time clock data (as returned by localtime) to memory location ES:BX
  • 255 |
  • 0F 02 (READ_DISK) - read AX bytes from disk at offset 512*(16*SI+BP) into memory location ES:BX. Disk is specified in DL (0 = hard disk, 1 = floppy disk)
  • 256 |
  • 0F 03 (WRITE_DISK) - write AX bytes at memory location ES:BX to disk at offset 512*(16*SI+BP). Disk is specified in DL as per 0F 02
  • 257 |
258 | 259 |

Emulator exit is triggered by a JMP 0:0 instruction, to allow the user to easily quit the emulator without shutting down the terminal.

260 |

Extension of the instruction set supported by 8086tiny can be implemented by appropriate modification to the tables described above in the BIOS source, and addition of a corresponding new OPCODE block in the C source.

261 |

262 |
263 |
264 | 265 |
266 |
Supported Application Software
267 |
268 |

269 | The emulator will run practically any software a real PC (of the spec listed at the top of this page) can. 270 |

271 | The author has successfully tested a range of software on the emulator. 272 |

    273 |
  • OSes/GUIs
  • 274 |
    • MS-DOS 6.22
    • 275 |
    • FreeDOS 0.82pl3
    • 276 |
    • Linux/ELKS 0.1.4
    • 277 |
    • Windows 3.0
    • 278 |
    • DESQview 2.8
    • 279 |
    • ETEN Chinese System
    280 |
  • Professional software
  • 281 |
    • Lotus 1-2-3 R2.4
    • 282 |
    • AsEasyAs 5.7
    • 283 |
    • Excel 2.1 for Windows
    • 284 |
    • AutoCAD 2.5
    • 285 |
    • WordStar 4
    286 |
  • Programming languages
  • 287 |
    • QBASIC
    • 288 |
    • GWBASIC
    • 289 |
    • Turbo C++
    290 |
  • Games
  • 291 |
    • Carrier Command
    • 292 |
    • Police Quest
    • 293 |
    • SimCity
    • 294 |
    • Alley Cat
    • 295 |
    • MS Flight Simulator 4
    • 296 |
    • Lots of freeware Windows games
    297 |
  • Diagnostic/benchmark software
  • 298 |
    • Manifest
    • 299 |
    • Microsoft MSD
    • 300 |
    • InfoSpot
    • 301 |
    • CheckIt
    302 |
303 |

304 |
305 |
306 | 307 |
308 |
309 | 310 |
311 | 312 | 318 | 319 |
320 |
Author Contact
321 | Adrian Cable
322 | adrian.cable@gmail.com
323 | 324 |

If 8086tiny brings you joy or profit, the author welcomes modest donations as a token of appreciation.

325 | 326 |
327 | 328 | 329 | 330 | 331 |
332 | 333 |
334 |
335 | 336 | 339 | 340 |
341 |
342 |
343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /8086tiny.c: -------------------------------------------------------------------------------- 1 | // 8086tiny: a tiny, highly functional, highly portable PC emulator/VM 2 | // Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | // 4 | // Revision 1.25 5 | // 6 | // This work is licensed under the MIT License. See included LICENSE.TXT. 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef _WIN32 13 | #include 14 | #include 15 | #endif 16 | 17 | #ifndef NO_GRAPHICS 18 | #include "SDL.h" 19 | #endif 20 | 21 | // Emulator system constants 22 | #define IO_PORT_COUNT 0x10000 23 | #define RAM_SIZE 0x10FFF0 24 | #define REGS_BASE 0xF0000 25 | #define VIDEO_RAM_SIZE 0x10000 26 | 27 | // Graphics/timer/keyboard update delays (explained later) 28 | #ifndef GRAPHICS_UPDATE_DELAY 29 | #define GRAPHICS_UPDATE_DELAY 360000 30 | #endif 31 | #define KEYBOARD_TIMER_UPDATE_DELAY 20000 32 | 33 | // 16-bit register decodes 34 | #define REG_AX 0 35 | #define REG_CX 1 36 | #define REG_DX 2 37 | #define REG_BX 3 38 | #define REG_SP 4 39 | #define REG_BP 5 40 | #define REG_SI 6 41 | #define REG_DI 7 42 | 43 | #define REG_ES 8 44 | #define REG_CS 9 45 | #define REG_SS 10 46 | #define REG_DS 11 47 | 48 | #define REG_ZERO 12 49 | #define REG_SCRATCH 13 50 | 51 | // 8-bit register decodes 52 | #define REG_AL 0 53 | #define REG_AH 1 54 | #define REG_CL 2 55 | #define REG_CH 3 56 | #define REG_DL 4 57 | #define REG_DH 5 58 | #define REG_BL 6 59 | #define REG_BH 7 60 | 61 | // FLAGS register decodes 62 | #define FLAG_CF 40 63 | #define FLAG_PF 41 64 | #define FLAG_AF 42 65 | #define FLAG_ZF 43 66 | #define FLAG_SF 44 67 | #define FLAG_TF 45 68 | #define FLAG_IF 46 69 | #define FLAG_DF 47 70 | #define FLAG_OF 48 71 | 72 | // Lookup tables in the BIOS binary 73 | #define TABLE_XLAT_OPCODE 8 74 | #define TABLE_XLAT_SUBFUNCTION 9 75 | #define TABLE_STD_FLAGS 10 76 | #define TABLE_PARITY_FLAG 11 77 | #define TABLE_BASE_INST_SIZE 12 78 | #define TABLE_I_W_SIZE 13 79 | #define TABLE_I_MOD_SIZE 14 80 | #define TABLE_COND_JUMP_DECODE_A 15 81 | #define TABLE_COND_JUMP_DECODE_B 16 82 | #define TABLE_COND_JUMP_DECODE_C 17 83 | #define TABLE_COND_JUMP_DECODE_D 18 84 | #define TABLE_FLAGS_BITFIELDS 19 85 | 86 | // Bitfields for TABLE_STD_FLAGS values 87 | #define FLAGS_UPDATE_SZP 1 88 | #define FLAGS_UPDATE_AO_ARITH 2 89 | #define FLAGS_UPDATE_OC_LOGIC 4 90 | 91 | // Helper macros 92 | 93 | // Decode mod, r_m and reg fields in instruction 94 | #define DECODE_RM_REG scratch2_uint = 4 * !i_mod, \ 95 | op_to_addr = rm_addr = i_mod < 3 ? SEGREG(seg_override_en ? seg_override : bios_table_lookup[scratch2_uint + 3][i_rm], bios_table_lookup[scratch2_uint][i_rm], regs16[bios_table_lookup[scratch2_uint + 1][i_rm]] + bios_table_lookup[scratch2_uint + 2][i_rm] * i_data1+) : GET_REG_ADDR(i_rm), \ 96 | op_from_addr = GET_REG_ADDR(i_reg), \ 97 | i_d && (scratch_uint = op_from_addr, op_from_addr = rm_addr, op_to_addr = scratch_uint) 98 | 99 | // Return memory-mapped register location (offset into mem array) for register #reg_id 100 | #define GET_REG_ADDR(reg_id) (REGS_BASE + (i_w ? 2 * reg_id : 2 * reg_id + reg_id / 4 & 7)) 101 | 102 | // Returns number of top bit in operand (i.e. 8 for 8-bit operands, 16 for 16-bit operands) 103 | #define TOP_BIT 8*(i_w + 1) 104 | 105 | // Opcode execution unit helpers 106 | #define OPCODE ;break; case 107 | #define OPCODE_CHAIN ; case 108 | 109 | // [I]MUL/[I]DIV/DAA/DAS/ADC/SBB helpers 110 | #define MUL_MACRO(op_data_type,out_regs) (set_opcode(0x10), \ 111 | out_regs[i_w + 1] = (op_result = CAST(op_data_type)mem[rm_addr] * (op_data_type)*out_regs) >> 16, \ 112 | regs16[REG_AX] = op_result, \ 113 | set_OF(set_CF(op_result - (op_data_type)op_result))) 114 | #define DIV_MACRO(out_data_type,in_data_type,out_regs) (scratch_int = CAST(out_data_type)mem[rm_addr]) && !(scratch2_uint = (in_data_type)(scratch_uint = (out_regs[i_w+1] << 16) + regs16[REG_AX]) / scratch_int, scratch2_uint - (out_data_type)scratch2_uint) ? out_regs[i_w+1] = scratch_uint - scratch_int * (*out_regs = scratch2_uint) : pc_interrupt(0) 115 | #define DAA_DAS(op1,op2,mask,min) set_AF((((scratch2_uint = regs8[REG_AL]) & 0x0F) > 9) || regs8[FLAG_AF]) && (op_result = regs8[REG_AL] op1 6, set_CF(regs8[FLAG_CF] || (regs8[REG_AL] op2 scratch2_uint))), \ 116 | set_CF((((mask & 1 ? scratch2_uint : regs8[REG_AL]) & mask) > min) || regs8[FLAG_CF]) && (op_result = regs8[REG_AL] op1 0x60) 117 | #define ADC_SBB_MACRO(a) OP(a##= regs8[FLAG_CF] +), \ 118 | set_CF(regs8[FLAG_CF] && (op_result == op_dest) || (a op_result < a(int)op_dest)), \ 119 | set_AF_OF_arith() 120 | 121 | // Execute arithmetic/logic operations in emulator memory/registers 122 | #define R_M_OP(dest,op,src) (i_w ? op_dest = CAST(unsigned short)dest, op_result = CAST(unsigned short)dest op (op_source = CAST(unsigned short)src) \ 123 | : (op_dest = dest, op_result = dest op (op_source = CAST(unsigned char)src))) 124 | #define MEM_OP(dest,op,src) R_M_OP(mem[dest],op,mem[src]) 125 | #define OP(op) MEM_OP(op_to_addr,op,op_from_addr) 126 | 127 | // Increment or decrement a register #reg_id (usually SI or DI), depending on direction flag and operand size (given by i_w) 128 | #define INDEX_INC(reg_id) (regs16[reg_id] -= (2 * regs8[FLAG_DF] - 1)*(i_w + 1)) 129 | 130 | // Helpers for stack operations 131 | #define R_M_PUSH(a) (i_w = 1, R_M_OP(mem[SEGREG(REG_SS, REG_SP, --)], =, a)) 132 | #define R_M_POP(a) (i_w = 1, regs16[REG_SP] += 2, R_M_OP(a, =, mem[SEGREG(REG_SS, REG_SP, -2+)])) 133 | 134 | // Convert segment:offset to linear address in emulator memory space 135 | #define SEGREG(reg_seg,reg_ofs,op) 16 * regs16[reg_seg] + (unsigned short)(op regs16[reg_ofs]) 136 | 137 | // Returns sign bit of an 8-bit or 16-bit operand 138 | #define SIGN_OF(a) (1 & (i_w ? CAST(short)a : a) >> (TOP_BIT - 1)) 139 | 140 | // Reinterpretation cast 141 | #define CAST(a) *(a*)& 142 | 143 | // Keyboard driver for console. This may need changing for UNIX/non-UNIX platforms 144 | #ifdef _WIN32 145 | #define KEYBOARD_DRIVER kbhit() && (mem[0x4A6] = getch(), pc_interrupt(7)) 146 | #else 147 | #define KEYBOARD_DRIVER read(0, mem + 0x4A6, 1) && (int8_asap = (mem[0x4A6] == 0x1B), pc_interrupt(7)) 148 | #endif 149 | 150 | // Keyboard driver for SDL 151 | #ifdef NO_GRAPHICS 152 | #define SDL_KEYBOARD_DRIVER KEYBOARD_DRIVER 153 | #else 154 | #define SDL_KEYBOARD_DRIVER sdl_screen ? SDL_PollEvent(&sdl_event) && (sdl_event.type == SDL_KEYDOWN || sdl_event.type == SDL_KEYUP) && (scratch_uint = sdl_event.key.keysym.unicode, scratch2_uint = sdl_event.key.keysym.mod, CAST(short)mem[0x4A6] = 0x400 + 0x800*!!(scratch2_uint & KMOD_ALT) + 0x1000*!!(scratch2_uint & KMOD_SHIFT) + 0x2000*!!(scratch2_uint & KMOD_CTRL) + 0x4000*(sdl_event.type == SDL_KEYUP) + ((!scratch_uint || scratch_uint > 0x7F) ? sdl_event.key.keysym.sym : scratch_uint), pc_interrupt(7)) : (KEYBOARD_DRIVER) 155 | #endif 156 | 157 | // Global variable definitions 158 | unsigned char mem[RAM_SIZE], io_ports[IO_PORT_COUNT], *opcode_stream, *regs8, i_rm, i_w, i_reg, i_mod, i_mod_size, i_d, i_reg4bit, raw_opcode_id, xlat_opcode_id, extra, rep_mode, seg_override_en, rep_override_en, trap_flag, int8_asap, scratch_uchar, io_hi_lo, *vid_mem_base, spkr_en, bios_table_lookup[20][256]; 159 | unsigned short *regs16, reg_ip, seg_override, file_index, wave_counter; 160 | unsigned int op_source, op_dest, rm_addr, op_to_addr, op_from_addr, i_data0, i_data1, i_data2, scratch_uint, scratch2_uint, inst_counter, set_flags_type, GRAPHICS_X, GRAPHICS_Y, pixel_colors[16], vmem_ctr; 161 | int op_result, disk[3], scratch_int; 162 | time_t clock_buf; 163 | struct timeb ms_clock; 164 | 165 | #ifndef NO_GRAPHICS 166 | SDL_AudioSpec sdl_audio = {44100, AUDIO_U8, 1, 0, 128}; 167 | SDL_Surface *sdl_screen; 168 | SDL_Event sdl_event; 169 | unsigned short vid_addr_lookup[VIDEO_RAM_SIZE], cga_colors[4] = {0 /* Black */, 0x1F1F /* Cyan */, 0xE3E3 /* Magenta */, 0xFFFF /* White */}; 170 | #endif 171 | 172 | // Helper functions 173 | 174 | // Set carry flag 175 | char set_CF(int new_CF) 176 | { 177 | return regs8[FLAG_CF] = !!new_CF; 178 | } 179 | 180 | // Set auxiliary flag 181 | char set_AF(int new_AF) 182 | { 183 | return regs8[FLAG_AF] = !!new_AF; 184 | } 185 | 186 | // Set overflow flag 187 | char set_OF(int new_OF) 188 | { 189 | return regs8[FLAG_OF] = !!new_OF; 190 | } 191 | 192 | // Set auxiliary and overflow flag after arithmetic operations 193 | char set_AF_OF_arith() 194 | { 195 | set_AF((op_source ^= op_dest ^ op_result) & 0x10); 196 | if (op_result == op_dest) 197 | return set_OF(0); 198 | else 199 | return set_OF(1 & (regs8[FLAG_CF] ^ op_source >> (TOP_BIT - 1))); 200 | } 201 | 202 | // Assemble and return emulated CPU FLAGS register in scratch_uint 203 | void make_flags() 204 | { 205 | scratch_uint = 0xF002; // 8086 has reserved and unused flags set to 1 206 | for (int i = 9; i--;) 207 | scratch_uint += regs8[FLAG_CF + i] << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i]; 208 | } 209 | 210 | // Set emulated CPU FLAGS register from regs8[FLAG_xx] values 211 | void set_flags(int new_flags) 212 | { 213 | for (int i = 9; i--;) 214 | regs8[FLAG_CF + i] = !!(1 << bios_table_lookup[TABLE_FLAGS_BITFIELDS][i] & new_flags); 215 | } 216 | 217 | // Convert raw opcode to translated opcode index. This condenses a large number of different encodings of similar 218 | // instructions into a much smaller number of distinct functions, which we then execute 219 | void set_opcode(unsigned char opcode) 220 | { 221 | xlat_opcode_id = bios_table_lookup[TABLE_XLAT_OPCODE][raw_opcode_id = opcode]; 222 | extra = bios_table_lookup[TABLE_XLAT_SUBFUNCTION][opcode]; 223 | i_mod_size = bios_table_lookup[TABLE_I_MOD_SIZE][opcode]; 224 | set_flags_type = bios_table_lookup[TABLE_STD_FLAGS][opcode]; 225 | } 226 | 227 | // Execute INT #interrupt_num on the emulated machine 228 | char pc_interrupt(unsigned char interrupt_num) 229 | { 230 | set_opcode(0xCD); // Decode like INT 231 | 232 | make_flags(); 233 | R_M_PUSH(scratch_uint); 234 | R_M_PUSH(regs16[REG_CS]); 235 | R_M_PUSH(reg_ip); 236 | MEM_OP(REGS_BASE + 2 * REG_CS, =, 4 * interrupt_num + 2); 237 | R_M_OP(reg_ip, =, mem[4 * interrupt_num]); 238 | 239 | return regs8[FLAG_TF] = regs8[FLAG_IF] = 0; 240 | } 241 | 242 | // AAA and AAS instructions - which_operation is +1 for AAA, and -1 for AAS 243 | int AAA_AAS(char which_operation) 244 | { 245 | return (regs16[REG_AX] += 262 * which_operation*set_AF(set_CF(((regs8[REG_AL] & 0x0F) > 9) || regs8[FLAG_AF])), regs8[REG_AL] &= 0x0F); 246 | } 247 | 248 | #ifndef NO_GRAPHICS 249 | void audio_callback(void *data, unsigned char *stream, int len) 250 | { 251 | for (int i = 0; i < len; i++) 252 | stream[i] = (spkr_en == 3) && CAST(unsigned short)mem[0x4AA] ? -((54 * wave_counter++ / CAST(unsigned short)mem[0x4AA]) & 1) : sdl_audio.silence; 253 | 254 | spkr_en = io_ports[0x61] & 3; 255 | } 256 | #endif 257 | 258 | // Emulator entry point 259 | int main(int argc, char **argv) 260 | { 261 | #ifndef NO_GRAPHICS 262 | // Initialise SDL 263 | SDL_Init(SDL_INIT_AUDIO); 264 | sdl_audio.callback = audio_callback; 265 | #ifdef _WIN32 266 | sdl_audio.samples = 512; 267 | #endif 268 | SDL_OpenAudio(&sdl_audio, 0); 269 | #endif 270 | 271 | // regs16 and reg8 point to F000:0, the start of memory-mapped registers. CS is initialised to F000 272 | regs16 = (unsigned short *)(regs8 = mem + REGS_BASE); 273 | regs16[REG_CS] = 0xF000; 274 | 275 | // Trap flag off 276 | regs8[FLAG_TF] = 0; 277 | 278 | // Set DL equal to the boot device: 0 for the FD, or 0x80 for the HD. Normally, boot from the FD. 279 | // But, if the HD image file is prefixed with @, then boot from the HD 280 | regs8[REG_DL] = ((argc > 3) && (*argv[3] == '@')) ? argv[3]++, 0x80 : 0; 281 | 282 | // Open BIOS (file id disk[2]), floppy disk image (disk[1]), and hard disk image (disk[0]) if specified 283 | for (file_index = 3; file_index;) 284 | disk[--file_index] = *++argv ? open(*argv, 32898) : 0; 285 | 286 | // Set CX:AX equal to the hard disk image size, if present 287 | CAST(unsigned)regs16[REG_AX] = *disk ? lseek(*disk, 0, 2) >> 9 : 0; 288 | 289 | // Load BIOS image into F000:0100, and set IP to 0100 290 | read(disk[2], regs8 + (reg_ip = 0x100), 0xFF00); 291 | 292 | // Load instruction decoding helper table 293 | for (int i = 0; i < 20; i++) 294 | for (int j = 0; j < 256; j++) 295 | bios_table_lookup[i][j] = regs8[regs16[0x81 + i] + j]; 296 | 297 | // Instruction execution loop. Terminates if CS:IP = 0:0 298 | for (; opcode_stream = mem + 16 * regs16[REG_CS] + reg_ip, opcode_stream != mem;) 299 | { 300 | // Set up variables to prepare for decoding an opcode 301 | set_opcode(*opcode_stream); 302 | 303 | // Extract i_w and i_d fields from instruction 304 | i_w = (i_reg4bit = raw_opcode_id & 7) & 1; 305 | i_d = i_reg4bit / 2 & 1; 306 | 307 | // Extract instruction data fields 308 | i_data0 = CAST(short)opcode_stream[1]; 309 | i_data1 = CAST(short)opcode_stream[2]; 310 | i_data2 = CAST(short)opcode_stream[3]; 311 | 312 | // seg_override_en and rep_override_en contain number of instructions to hold segment override and REP prefix respectively 313 | if (seg_override_en) 314 | seg_override_en--; 315 | if (rep_override_en) 316 | rep_override_en--; 317 | 318 | // i_mod_size > 0 indicates that opcode uses i_mod/i_rm/i_reg, so decode them 319 | if (i_mod_size) 320 | { 321 | i_mod = (i_data0 & 0xFF) >> 6; 322 | i_rm = i_data0 & 7; 323 | i_reg = i_data0 / 8 & 7; 324 | 325 | if ((!i_mod && i_rm == 6) || (i_mod == 2)) 326 | i_data2 = CAST(short)opcode_stream[4]; 327 | else if (i_mod != 1) 328 | i_data2 = i_data1; 329 | else // If i_mod is 1, operand is (usually) 8 bits rather than 16 bits 330 | i_data1 = (char)i_data1; 331 | 332 | DECODE_RM_REG; 333 | } 334 | 335 | // Instruction execution unit 336 | switch (xlat_opcode_id) 337 | { 338 | OPCODE_CHAIN 0: // Conditional jump (JAE, JNAE, etc.) 339 | // i_w is the invert flag, e.g. i_w == 1 means JNAE, whereas i_w == 0 means JAE 340 | scratch_uchar = raw_opcode_id / 2 & 7; 341 | reg_ip += (char)i_data0 * (i_w ^ (regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_A][scratch_uchar]] || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_B][scratch_uchar]] || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_C][scratch_uchar]] ^ regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_D][scratch_uchar]])) 342 | OPCODE 1: // MOV reg, imm 343 | i_w = !!(raw_opcode_id & 8); 344 | R_M_OP(mem[GET_REG_ADDR(i_reg4bit)], =, i_data0) 345 | OPCODE 3: // PUSH regs16 346 | R_M_PUSH(regs16[i_reg4bit]) 347 | OPCODE 4: // POP regs16 348 | R_M_POP(regs16[i_reg4bit]) 349 | OPCODE 2: // INC|DEC regs16 350 | i_w = 1; 351 | i_d = 0; 352 | i_reg = i_reg4bit; 353 | DECODE_RM_REG; 354 | i_reg = extra 355 | OPCODE_CHAIN 5: // INC|DEC|JMP|CALL|PUSH 356 | if (i_reg < 2) // INC|DEC 357 | MEM_OP(op_from_addr, += 1 - 2 * i_reg +, REGS_BASE + 2 * REG_ZERO), 358 | op_source = 1, 359 | set_AF_OF_arith(), 360 | set_OF(op_dest + 1 - i_reg == 1 << (TOP_BIT - 1)), 361 | (xlat_opcode_id == 5) && (set_opcode(0x10), 0); // Decode like ADC 362 | else if (i_reg != 6) // JMP|CALL 363 | i_reg - 3 || R_M_PUSH(regs16[REG_CS]), // CALL (far) 364 | i_reg & 2 && R_M_PUSH(reg_ip + 2 + i_mod*(i_mod != 3) + 2*(!i_mod && i_rm == 6)), // CALL (near or far) 365 | i_reg & 1 && (regs16[REG_CS] = CAST(short)mem[op_from_addr + 2]), // JMP|CALL (far) 366 | R_M_OP(reg_ip, =, mem[op_from_addr]), 367 | set_opcode(0x9A); // Decode like CALL 368 | else // PUSH 369 | R_M_PUSH(mem[rm_addr]) 370 | OPCODE 6: // TEST r/m, imm16 / NOT|NEG|MUL|IMUL|DIV|IDIV reg 371 | op_to_addr = op_from_addr; 372 | 373 | switch (i_reg) 374 | { 375 | OPCODE_CHAIN 0: // TEST 376 | set_opcode(0x20); // Decode like AND 377 | reg_ip += i_w + 1; 378 | R_M_OP(mem[op_to_addr], &, i_data2) 379 | OPCODE 2: // NOT 380 | OP(=~) 381 | OPCODE 3: // NEG 382 | OP(=-); 383 | op_dest = 0; 384 | set_opcode(0x28); // Decode like SUB 385 | set_CF(op_result > op_dest) 386 | OPCODE 4: // MUL 387 | i_w ? MUL_MACRO(unsigned short, regs16) : MUL_MACRO(unsigned char, regs8) 388 | OPCODE 5: // IMUL 389 | i_w ? MUL_MACRO(short, regs16) : MUL_MACRO(char, regs8) 390 | OPCODE 6: // DIV 391 | i_w ? DIV_MACRO(unsigned short, unsigned, regs16) : DIV_MACRO(unsigned char, unsigned short, regs8) 392 | OPCODE 7: // IDIV 393 | i_w ? DIV_MACRO(short, int, regs16) : DIV_MACRO(char, short, regs8); 394 | } 395 | OPCODE 7: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP AL/AX, immed 396 | rm_addr = REGS_BASE; 397 | i_data2 = i_data0; 398 | i_mod = 3; 399 | i_reg = extra; 400 | reg_ip--; 401 | OPCODE_CHAIN 8: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP reg, immed 402 | op_to_addr = rm_addr; 403 | regs16[REG_SCRATCH] = (i_d |= !i_w) ? (char)i_data2 : i_data2; 404 | op_from_addr = REGS_BASE + 2 * REG_SCRATCH; 405 | reg_ip += !i_d + 1; 406 | set_opcode(0x08 * (extra = i_reg)); 407 | OPCODE_CHAIN 9: // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP|MOV reg, r/m 408 | switch (extra) 409 | { 410 | OPCODE_CHAIN 0: // ADD 411 | OP(+=), 412 | set_CF(op_result < op_dest) 413 | OPCODE 1: // OR 414 | OP(|=) 415 | OPCODE 2: // ADC 416 | ADC_SBB_MACRO(+) 417 | OPCODE 3: // SBB 418 | ADC_SBB_MACRO(-) 419 | OPCODE 4: // AND 420 | OP(&=) 421 | OPCODE 5: // SUB 422 | OP(-=), 423 | set_CF(op_result > op_dest) 424 | OPCODE 6: // XOR 425 | OP(^=) 426 | OPCODE 7: // CMP 427 | OP(-), 428 | set_CF(op_result > op_dest) 429 | OPCODE 8: // MOV 430 | OP(=); 431 | } 432 | OPCODE 10: // MOV sreg, r/m | POP r/m | LEA reg, r/m 433 | if (!i_w) // MOV 434 | i_w = 1, 435 | i_reg += 8, 436 | DECODE_RM_REG, 437 | OP(=); 438 | else if (!i_d) // LEA 439 | seg_override_en = 1, 440 | seg_override = REG_ZERO, 441 | DECODE_RM_REG, 442 | R_M_OP(mem[op_from_addr], =, rm_addr); 443 | else // POP 444 | R_M_POP(mem[rm_addr]) 445 | OPCODE 11: // MOV AL/AX, [loc] 446 | i_mod = i_reg = 0; 447 | i_rm = 6; 448 | i_data1 = i_data0; 449 | DECODE_RM_REG; 450 | MEM_OP(op_from_addr, =, op_to_addr) 451 | OPCODE 12: // ROL|ROR|RCL|RCR|SHL|SHR|???|SAR reg/mem, 1/CL/imm (80186) 452 | scratch2_uint = SIGN_OF(mem[rm_addr]), 453 | scratch_uint = extra ? // xxx reg/mem, imm 454 | ++reg_ip, 455 | (char)i_data1 456 | : // xxx reg/mem, CL 457 | i_d 458 | ? 31 & regs8[REG_CL] 459 | : // xxx reg/mem, 1 460 | 1; 461 | if (scratch_uint) 462 | { 463 | if (i_reg < 4) // Rotate operations 464 | scratch_uint %= i_reg / 2 + TOP_BIT, 465 | R_M_OP(scratch2_uint, =, mem[rm_addr]); 466 | if (i_reg & 1) // Rotate/shift right operations 467 | R_M_OP(mem[rm_addr], >>=, scratch_uint); 468 | else // Rotate/shift left operations 469 | R_M_OP(mem[rm_addr], <<=, scratch_uint); 470 | if (i_reg > 3) // Shift operations 471 | set_opcode(0x10); // Decode like ADC 472 | if (i_reg > 4) // SHR or SAR 473 | set_CF(op_dest >> (scratch_uint - 1) & 1); 474 | } 475 | 476 | switch (i_reg) 477 | { 478 | OPCODE_CHAIN 0: // ROL 479 | R_M_OP(mem[rm_addr], += , scratch2_uint >> (TOP_BIT - scratch_uint)); 480 | set_OF(SIGN_OF(op_result) ^ set_CF(op_result & 1)) 481 | OPCODE 1: // ROR 482 | scratch2_uint &= (1 << scratch_uint) - 1, 483 | R_M_OP(mem[rm_addr], += , scratch2_uint << (TOP_BIT - scratch_uint)); 484 | set_OF(SIGN_OF(op_result * 2) ^ set_CF(SIGN_OF(op_result))) 485 | OPCODE 2: // RCL 486 | R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (scratch_uint - 1)) + , scratch2_uint >> (1 + TOP_BIT - scratch_uint)); 487 | set_OF(SIGN_OF(op_result) ^ set_CF(scratch2_uint & 1 << (TOP_BIT - scratch_uint))) 488 | OPCODE 3: // RCR 489 | R_M_OP(mem[rm_addr], += (regs8[FLAG_CF] << (TOP_BIT - scratch_uint)) + , scratch2_uint << (1 + TOP_BIT - scratch_uint)); 490 | set_CF(scratch2_uint & 1 << (scratch_uint - 1)); 491 | set_OF(SIGN_OF(op_result) ^ SIGN_OF(op_result * 2)) 492 | OPCODE 4: // SHL 493 | set_OF(SIGN_OF(op_result) ^ set_CF(SIGN_OF(op_dest << (scratch_uint - 1)))) 494 | OPCODE 5: // SHR 495 | set_OF(SIGN_OF(op_dest)) 496 | OPCODE 7: // SAR 497 | scratch_uint < TOP_BIT || set_CF(scratch2_uint); 498 | set_OF(0); 499 | R_M_OP(mem[rm_addr], +=, scratch2_uint *= ~(((1 << TOP_BIT) - 1) >> scratch_uint)); 500 | } 501 | OPCODE 13: // LOOPxx|JCZX 502 | scratch_uint = !!--regs16[REG_CX]; 503 | 504 | switch(i_reg4bit) 505 | { 506 | OPCODE_CHAIN 0: // LOOPNZ 507 | scratch_uint &= !regs8[FLAG_ZF] 508 | OPCODE 1: // LOOPZ 509 | scratch_uint &= regs8[FLAG_ZF] 510 | OPCODE 3: // JCXXZ 511 | scratch_uint = !++regs16[REG_CX]; 512 | } 513 | reg_ip += scratch_uint*(char)i_data0 514 | OPCODE 14: // JMP | CALL short/near 515 | reg_ip += 3 - i_d; 516 | if (!i_w) 517 | { 518 | if (i_d) // JMP far 519 | reg_ip = 0, 520 | regs16[REG_CS] = i_data2; 521 | else // CALL 522 | R_M_PUSH(reg_ip); 523 | } 524 | reg_ip += i_d && i_w ? (char)i_data0 : i_data0 525 | OPCODE 15: // TEST reg, r/m 526 | MEM_OP(op_from_addr, &, op_to_addr) 527 | OPCODE 16: // XCHG AX, regs16 528 | i_w = 1; 529 | op_to_addr = REGS_BASE; 530 | op_from_addr = GET_REG_ADDR(i_reg4bit); 531 | OPCODE_CHAIN 24: // NOP|XCHG reg, r/m 532 | if (op_to_addr != op_from_addr) 533 | OP(^=), 534 | MEM_OP(op_from_addr, ^=, op_to_addr), 535 | OP(^=) 536 | OPCODE 17: // MOVSx (extra=0)|STOSx (extra=1)|LODSx (extra=2) 537 | scratch2_uint = seg_override_en ? seg_override : REG_DS; 538 | 539 | for (scratch_uint = rep_override_en ? regs16[REG_CX] : 1; scratch_uint; scratch_uint--) 540 | { 541 | MEM_OP(extra < 2 ? SEGREG(REG_ES, REG_DI,) : REGS_BASE, =, extra & 1 ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,)), 542 | extra & 1 || INDEX_INC(REG_SI), 543 | extra & 2 || INDEX_INC(REG_DI); 544 | } 545 | 546 | if (rep_override_en) 547 | regs16[REG_CX] = 0 548 | OPCODE 18: // CMPSx (extra=0)|SCASx (extra=1) 549 | scratch2_uint = seg_override_en ? seg_override : REG_DS; 550 | 551 | if ((scratch_uint = rep_override_en ? regs16[REG_CX] : 1)) 552 | { 553 | for (; scratch_uint; rep_override_en || scratch_uint--) 554 | { 555 | MEM_OP(extra ? REGS_BASE : SEGREG(scratch2_uint, REG_SI,), -, SEGREG(REG_ES, REG_DI,)), 556 | extra || INDEX_INC(REG_SI), 557 | INDEX_INC(REG_DI), rep_override_en && !(--regs16[REG_CX] && (!op_result == rep_mode)) && (scratch_uint = 0); 558 | } 559 | 560 | set_flags_type = FLAGS_UPDATE_SZP | FLAGS_UPDATE_AO_ARITH; // Funge to set SZP/AO flags 561 | set_CF(op_result > op_dest); 562 | } 563 | OPCODE 19: // RET|RETF|IRET 564 | i_d = i_w; 565 | R_M_POP(reg_ip); 566 | if (extra) // IRET|RETF|RETF imm16 567 | R_M_POP(regs16[REG_CS]); 568 | if (extra & 2) // IRET 569 | set_flags(R_M_POP(scratch_uint)); 570 | else if (!i_d) // RET|RETF imm16 571 | regs16[REG_SP] += i_data0 572 | OPCODE 20: // MOV r/m, immed 573 | R_M_OP(mem[op_from_addr], =, i_data2) 574 | OPCODE 21: // IN AL/AX, DX/imm8 575 | io_ports[0x20] = 0; // PIC EOI 576 | io_ports[0x42] = --io_ports[0x40]; // PIT channel 0/2 read placeholder 577 | io_ports[0x3DA] ^= 9; // CGA refresh 578 | scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; 579 | scratch_uint == 0x60 && (io_ports[0x64] = 0); // Scancode read flag 580 | scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 7) && (io_ports[0x3D5] = ((mem[0x49E]*80 + mem[0x49D] + CAST(short)mem[0x4AD]) & (io_ports[0x3D4] & 1 ? 0xFF : 0xFF00)) >> (io_ports[0x3D4] & 1 ? 0 : 8)); // CRT cursor position 581 | R_M_OP(regs8[REG_AL], =, io_ports[scratch_uint]); 582 | OPCODE 22: // OUT DX/imm8, AL/AX 583 | scratch_uint = extra ? regs16[REG_DX] : (unsigned char)i_data0; 584 | R_M_OP(io_ports[scratch_uint], =, regs8[REG_AL]); 585 | scratch_uint == 0x61 && (io_hi_lo = 0, spkr_en |= regs8[REG_AL] & 3); // Speaker control 586 | (scratch_uint == 0x40 || scratch_uint == 0x42) && (io_ports[0x43] & 6) && (mem[0x469 + scratch_uint - (io_hi_lo ^= 1)] = regs8[REG_AL]); // PIT rate programming 587 | #ifndef NO_GRAPHICS 588 | scratch_uint == 0x43 && (io_hi_lo = 0, regs8[REG_AL] >> 6 == 2) && (SDL_PauseAudio((regs8[REG_AL] & 0xF7) != 0xB6), 0); // Speaker enable 589 | #endif 590 | scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 6) && (mem[0x4AD + !(io_ports[0x3D4] & 1)] = regs8[REG_AL]); // CRT video RAM start offset 591 | scratch_uint == 0x3D5 && (io_ports[0x3D4] >> 1 == 7) && (scratch2_uint = ((mem[0x49E]*80 + mem[0x49D] + CAST(short)mem[0x4AD]) & (io_ports[0x3D4] & 1 ? 0xFF00 : 0xFF)) + (regs8[REG_AL] << (io_ports[0x3D4] & 1 ? 0 : 8)) - CAST(short)mem[0x4AD], mem[0x49D] = scratch2_uint % 80, mem[0x49E] = scratch2_uint / 80); // CRT cursor position 592 | scratch_uint == 0x3B5 && io_ports[0x3B4] == 1 && (GRAPHICS_X = regs8[REG_AL] * 16); // Hercules resolution reprogramming. Defaults are set in the BIOS 593 | scratch_uint == 0x3B5 && io_ports[0x3B4] == 6 && (GRAPHICS_Y = regs8[REG_AL] * 4); 594 | OPCODE 23: // REPxx 595 | rep_override_en = 2; 596 | rep_mode = i_w; 597 | seg_override_en && seg_override_en++ 598 | OPCODE 25: // PUSH reg 599 | R_M_PUSH(regs16[extra]) 600 | OPCODE 26: // POP reg 601 | R_M_POP(regs16[extra]) 602 | OPCODE 27: // xS: segment overrides 603 | seg_override_en = 2; 604 | seg_override = extra; 605 | rep_override_en && rep_override_en++ 606 | OPCODE 28: // DAA/DAS 607 | i_w = 0; 608 | extra ? DAA_DAS(-=, >=, 0xFF, 0x99) : DAA_DAS(+=, <, 0xF0, 0x90) // extra = 0 for DAA, 1 for DAS 609 | OPCODE 29: // AAA/AAS 610 | op_result = AAA_AAS(extra - 1) 611 | OPCODE 30: // CBW 612 | regs8[REG_AH] = -SIGN_OF(regs8[REG_AL]) 613 | OPCODE 31: // CWD 614 | regs16[REG_DX] = -SIGN_OF(regs16[REG_AX]) 615 | OPCODE 32: // CALL FAR imm16:imm16 616 | R_M_PUSH(regs16[REG_CS]); 617 | R_M_PUSH(reg_ip + 5); 618 | regs16[REG_CS] = i_data2; 619 | reg_ip = i_data0 620 | OPCODE 33: // PUSHF 621 | make_flags(); 622 | R_M_PUSH(scratch_uint) 623 | OPCODE 34: // POPF 624 | set_flags(R_M_POP(scratch_uint)) 625 | OPCODE 35: // SAHF 626 | make_flags(); 627 | set_flags((scratch_uint & 0xFF00) + regs8[REG_AH]) 628 | OPCODE 36: // LAHF 629 | make_flags(), 630 | regs8[REG_AH] = scratch_uint 631 | OPCODE 37: // LES|LDS reg, r/m 632 | i_w = i_d = 1; 633 | DECODE_RM_REG; 634 | OP(=); 635 | MEM_OP(REGS_BASE + extra, =, rm_addr + 2) 636 | OPCODE 38: // INT 3 637 | ++reg_ip; 638 | pc_interrupt(3) 639 | OPCODE 39: // INT imm8 640 | reg_ip += 2; 641 | pc_interrupt(i_data0) 642 | OPCODE 40: // INTO 643 | ++reg_ip; 644 | regs8[FLAG_OF] && pc_interrupt(4) 645 | OPCODE 41: // AAM 646 | if (i_data0 &= 0xFF) 647 | regs8[REG_AH] = regs8[REG_AL] / i_data0, 648 | op_result = regs8[REG_AL] %= i_data0; 649 | else // Divide by zero 650 | pc_interrupt(0) 651 | OPCODE 42: // AAD 652 | i_w = 0; 653 | regs16[REG_AX] = op_result = 0xFF & regs8[REG_AL] + i_data0 * regs8[REG_AH] 654 | OPCODE 43: // SALC 655 | regs8[REG_AL] = -regs8[FLAG_CF] 656 | OPCODE 44: // XLAT 657 | regs8[REG_AL] = mem[SEGREG(seg_override_en ? seg_override : REG_DS, REG_BX, regs8[REG_AL] +)] 658 | OPCODE 45: // CMC 659 | regs8[FLAG_CF] ^= 1 660 | OPCODE 46: // CLC|STC|CLI|STI|CLD|STD 661 | regs8[extra / 2] = extra & 1 662 | OPCODE 47: // TEST AL/AX, immed 663 | R_M_OP(regs8[REG_AL], &, i_data0) 664 | OPCODE 48: // Emulator-specific 0F xx opcodes 665 | switch ((char)i_data0) 666 | { 667 | OPCODE_CHAIN 0: // PUTCHAR_AL 668 | write(1, regs8, 1) 669 | OPCODE 1: // GET_RTC 670 | time(&clock_buf); 671 | ftime(&ms_clock); 672 | memcpy(mem + SEGREG(REG_ES, REG_BX,), localtime(&clock_buf), sizeof(struct tm)); 673 | CAST(short)mem[SEGREG(REG_ES, REG_BX, 36+)] = ms_clock.millitm; 674 | OPCODE 2: // DISK_READ 675 | OPCODE_CHAIN 3: // DISK_WRITE 676 | regs8[REG_AL] = ~lseek(disk[regs8[REG_DL]], CAST(unsigned)regs16[REG_BP] << 9, 0) 677 | ? ((char)i_data0 == 3 ? (int(*)())write : (int(*)())read)(disk[regs8[REG_DL]], mem + SEGREG(REG_ES, REG_BX,), regs16[REG_AX]) 678 | : 0; 679 | } 680 | } 681 | 682 | // Increment instruction pointer by computed instruction length. Tables in the BIOS binary 683 | // help us here. 684 | reg_ip += (i_mod*(i_mod != 3) + 2*(!i_mod && i_rm == 6))*i_mod_size + bios_table_lookup[TABLE_BASE_INST_SIZE][raw_opcode_id] + bios_table_lookup[TABLE_I_W_SIZE][raw_opcode_id]*(i_w + 1); 685 | 686 | // If instruction needs to update SF, ZF and PF, set them as appropriate 687 | if (set_flags_type & FLAGS_UPDATE_SZP) 688 | { 689 | regs8[FLAG_SF] = SIGN_OF(op_result); 690 | regs8[FLAG_ZF] = !op_result; 691 | regs8[FLAG_PF] = bios_table_lookup[TABLE_PARITY_FLAG][(unsigned char)op_result]; 692 | 693 | // If instruction is an arithmetic or logic operation, also set AF/OF/CF as appropriate. 694 | if (set_flags_type & FLAGS_UPDATE_AO_ARITH) 695 | set_AF_OF_arith(); 696 | if (set_flags_type & FLAGS_UPDATE_OC_LOGIC) 697 | set_CF(0), set_OF(0); 698 | } 699 | 700 | // Poll timer/keyboard every KEYBOARD_TIMER_UPDATE_DELAY instructions 701 | if (!(++inst_counter % KEYBOARD_TIMER_UPDATE_DELAY)) 702 | int8_asap = 1; 703 | 704 | #ifndef NO_GRAPHICS 705 | // Update the video graphics display every GRAPHICS_UPDATE_DELAY instructions 706 | if (!(inst_counter % GRAPHICS_UPDATE_DELAY)) 707 | { 708 | // Video card in graphics mode? 709 | if (io_ports[0x3B8] & 2) 710 | { 711 | // If we don't already have an SDL window open, set it up and compute color and video memory translation tables 712 | if (!sdl_screen) 713 | { 714 | for (int i = 0; i < 16; i++) 715 | pixel_colors[i] = mem[0x4AC] ? // CGA? 716 | cga_colors[(i & 12) >> 2] + (cga_colors[i & 3] << 16) // CGA -> RGB332 717 | : 0xFF*(((i & 1) << 24) + ((i & 2) << 15) + ((i & 4) << 6) + ((i & 8) >> 3)); // Hercules -> RGB332 718 | 719 | for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) 720 | vid_addr_lookup[i] = i / GRAPHICS_X * (GRAPHICS_X / 8) + (i / 2) % (GRAPHICS_X / 8) + 0x2000*(mem[0x4AC] ? (2 * i / GRAPHICS_X) % 2 : (4 * i / GRAPHICS_X) % 4); 721 | 722 | SDL_Init(SDL_INIT_VIDEO); 723 | sdl_screen = SDL_SetVideoMode(GRAPHICS_X, GRAPHICS_Y, 8, 0); 724 | SDL_EnableUNICODE(1); 725 | SDL_EnableKeyRepeat(500, 30); 726 | } 727 | 728 | // Refresh SDL display from emulated graphics card video RAM 729 | vid_mem_base = mem + 0xB0000 + 0x8000*(mem[0x4AC] ? 1 : io_ports[0x3B8] >> 7); // B800:0 for CGA/Hercules bank 2, B000:0 for Hercules bank 1 730 | for (int i = 0; i < GRAPHICS_X * GRAPHICS_Y / 4; i++) 731 | ((unsigned *)sdl_screen->pixels)[i] = pixel_colors[15 & (vid_mem_base[vid_addr_lookup[i]] >> 4*!(i & 1))]; 732 | 733 | SDL_Flip(sdl_screen); 734 | } 735 | else if (sdl_screen) // Application has gone back to text mode, so close the SDL window 736 | { 737 | SDL_QuitSubSystem(SDL_INIT_VIDEO); 738 | sdl_screen = 0; 739 | } 740 | SDL_PumpEvents(); 741 | } 742 | #endif 743 | 744 | // Application has set trap flag, so fire INT 1 745 | if (trap_flag) 746 | pc_interrupt(1); 747 | 748 | trap_flag = regs8[FLAG_TF]; 749 | 750 | // If a timer tick is pending, interrupts are enabled, and no overrides/REP are active, 751 | // then process the tick and check for new keystrokes 752 | if (int8_asap && !seg_override_en && !rep_override_en && regs8[FLAG_IF] && !regs8[FLAG_TF]) 753 | pc_interrupt(0xA), int8_asap = 0, SDL_KEYBOARD_DRIVER; 754 | } 755 | 756 | #ifndef NO_GRAPHICS 757 | SDL_Quit(); 758 | #endif 759 | return 0; 760 | } 761 | -------------------------------------------------------------------------------- /bios_source/bios.asm: -------------------------------------------------------------------------------- 1 | ; BIOS source for 8086tiny IBM PC emulator (revision 1.21 and above). Compiles with NASM. 2 | ; Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny 3 | ; 4 | ; Revision 1.61 5 | ; 6 | ; This work is licensed under the MIT License. See included LICENSE.TXT. 7 | 8 | cpu 8086 9 | 10 | ; Here we define macros for some custom instructions that help the emulator talk with the outside 11 | ; world. They are described in detail in the hint.html file, which forms part of the emulator 12 | ; distribution. 13 | 14 | %macro extended_putchar_al 0 15 | db 0x0f, 0x00 16 | %endmacro 17 | 18 | %macro extended_get_rtc 0 19 | db 0x0f, 0x01 20 | %endmacro 21 | 22 | %macro extended_read_disk 0 23 | db 0x0f, 0x02 24 | %endmacro 25 | 26 | %macro extended_write_disk 0 27 | db 0x0f, 0x03 28 | %endmacro 29 | 30 | org 100h ; BIOS loads at offset 0x0100 31 | 32 | main: 33 | 34 | jmp bios_entry 35 | 36 | ; Here go pointers to the different data tables used for instruction decoding 37 | 38 | dw rm_mode12_reg1 ; Table 0: R/M mode 1/2 "register 1" lookup 39 | dw rm_mode012_reg2 ; Table 1: R/M mode 1/2 "register 2" lookup 40 | dw rm_mode12_disp ; Table 2: R/M mode 1/2 "DISP multiplier" lookup 41 | dw rm_mode12_dfseg ; Table 3: R/M mode 1/2 "default segment" lookup 42 | dw rm_mode0_reg1 ; Table 4: R/M mode 0 "register 1" lookup 43 | dw rm_mode012_reg2 ; Table 5: R/M mode 0 "register 2" lookup 44 | dw rm_mode0_disp ; Table 6: R/M mode 0 "DISP multiplier" lookup 45 | dw rm_mode0_dfseg ; Table 7: R/M mode 0 "default segment" lookup 46 | dw xlat_ids ; Table 8: Translation of raw opcode index ("Raw ID") to function number ("Xlat'd ID") 47 | dw ex_data ; Table 9: Translation of Raw ID to Extra Data 48 | dw std_flags ; Table 10: How each Raw ID sets the flags (bit 1 = sets SZP, bit 2 = sets AF/OF for arithmetic, bit 3 = sets OF/CF for logic) 49 | dw parity ; Table 11: Parity flag loop-up table (256 entries) 50 | dw base_size ; Table 12: Translation of Raw ID to base instruction size (bytes) 51 | dw i_w_adder ; Table 13: Translation of Raw ID to i_w size adder yes/no 52 | dw i_mod_adder ; Table 14: Translation of Raw ID to i_mod size adder yes/no 53 | dw jxx_dec_a ; Table 15: Jxx decode table A 54 | dw jxx_dec_b ; Table 16: Jxx decode table B 55 | dw jxx_dec_c ; Table 17: Jxx decode table C 56 | dw jxx_dec_d ; Table 18: Jxx decode table D 57 | dw flags_mult ; Table 19: FLAGS multipliers 58 | 59 | ; These values (BIOS ID string, BIOS date and so forth) go at the very top of memory 60 | 61 | biosstr db '8086tiny BIOS Revision 1.61!', 0, 0 ; Why not? 62 | mem_top db 0xea, 0, 0x01, 0, 0xf0, '03/08/14', 0, 0xfe, 0 63 | 64 | bios_entry: 65 | 66 | ; Set up initial stack to F000:F000 67 | 68 | mov sp, 0xf000 69 | mov ss, sp 70 | 71 | push cs 72 | pop es 73 | 74 | push ax 75 | 76 | ; The emulator requires a few control registers in memory to always be zero for correct 77 | ; instruction decoding (in particular, register look-up operations). These are the 78 | ; emulator's zero segment (ZS) and always-zero flag (XF). Because the emulated memory 79 | ; space is uninitialised, we need to be sure these values are zero before doing anything 80 | ; else. The instructions we need to use to set them must not rely on look-up operations. 81 | ; So e.g. MOV to memory is out but string operations are fine. 82 | 83 | cld 84 | 85 | xor ax, ax 86 | mov di, 24 87 | stosw ; Set ZS = 0 88 | mov di, 49 89 | stosb ; Set XF = 0 90 | 91 | ; Now we can do whatever we want! DL starts off being the boot disk. 92 | 93 | mov [cs:boot_device], dl 94 | 95 | ; Set up Hercules graphics support. We start with the adapter in text mode 96 | 97 | push dx 98 | 99 | mov dx, 0x3b8 100 | mov al, 0 101 | out dx, al ; Set Hercules support to text mode 102 | 103 | mov dx, 0x3b4 104 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 105 | out dx, al 106 | mov dx, 0x3b5 107 | mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) 108 | out dx, al 109 | mov dx, 0x3b4 110 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 111 | out dx, al 112 | mov dx, 0x3b5 113 | mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) 114 | out dx, al 115 | 116 | pop dx 117 | 118 | pop ax 119 | 120 | ; Check cold boot/warm boot. We initialise disk parameters on cold boot only 121 | 122 | cmp byte [cs:boot_state], 0 ; Cold boot? 123 | jne boot 124 | 125 | mov byte [cs:boot_state], 1 ; Set flag so next boot will be warm boot 126 | 127 | ; First, set up the disk subsystem. Only do this on the very first startup, when 128 | ; the emulator sets up the CX/AX registers with disk information. 129 | 130 | ; Compute the cylinder/head/sector count for the HD disk image, if present. 131 | ; Total number of sectors is in CX:AX, or 0 if there is no HD image. First, 132 | ; we put it in DX:CX. 133 | 134 | mov dx, cx 135 | mov cx, ax 136 | 137 | mov [cs:hd_secs_hi], dx 138 | mov [cs:hd_secs_lo], cx 139 | 140 | cmp cx, 0 141 | je maybe_no_hd 142 | 143 | mov word [cs:num_disks], 2 144 | jmp calc_hd 145 | 146 | maybe_no_hd: 147 | 148 | cmp dx, 0 149 | je no_hd 150 | 151 | mov word [cs:num_disks], 2 152 | jmp calc_hd 153 | 154 | no_hd: 155 | 156 | mov word [cs:num_disks], 1 157 | 158 | calc_hd: 159 | 160 | mov ax, cx 161 | mov word [cs:hd_max_track], 1 162 | mov word [cs:hd_max_head], 1 163 | 164 | cmp dx, 0 ; More than 63 total sectors? If so, we have more than 1 track. 165 | ja sect_overflow 166 | cmp ax, 63 167 | ja sect_overflow 168 | 169 | mov [cs:hd_max_sector], ax 170 | jmp calc_heads 171 | 172 | sect_overflow: 173 | 174 | mov cx, 63 ; Calculate number of tracks 175 | div cx 176 | mov [cs:hd_max_track], ax 177 | mov word [cs:hd_max_sector], 63 178 | 179 | calc_heads: 180 | 181 | mov dx, 0 ; More than 1024 tracks? If so, we have more than 1 head. 182 | mov ax, [cs:hd_max_track] 183 | cmp ax, 1024 184 | ja track_overflow 185 | 186 | jmp calc_end 187 | 188 | track_overflow: 189 | 190 | mov cx, 1024 191 | div cx 192 | mov [cs:hd_max_head], ax 193 | mov word [cs:hd_max_track], 1024 194 | 195 | calc_end: 196 | 197 | ; Convert number of tracks into maximum track (0-based) and then store in INT 41 198 | ; HD parameter table 199 | 200 | mov ax, [cs:hd_max_head] 201 | mov [cs:int41_max_heads], al 202 | mov ax, [cs:hd_max_track] 203 | mov [cs:int41_max_cyls], ax 204 | mov ax, [cs:hd_max_sector] 205 | mov [cs:int41_max_sect], al 206 | 207 | dec word [cs:hd_max_track] 208 | dec word [cs:hd_max_head] 209 | 210 | ; Main BIOS entry point. Zero the flags, and set up registers. 211 | 212 | boot: mov ax, 0 213 | push ax 214 | popf 215 | 216 | push cs 217 | push cs 218 | pop ds 219 | pop ss 220 | mov sp, 0xf000 221 | 222 | ; Set up the IVT. First we zero out the table 223 | 224 | cld 225 | 226 | xor ax, ax 227 | mov es, ax 228 | xor di, di 229 | mov cx, 512 230 | rep stosw 231 | 232 | ; Then we load in the pointers to our interrupt handlers 233 | 234 | mov di, 0 235 | mov si, int_table 236 | mov cx, [itbl_size] 237 | rep movsb 238 | 239 | ; Set pointer to INT 41 table for hard disk 240 | 241 | mov cx, int41 242 | mov word [es:4*0x41], cx 243 | mov cx, 0xf000 244 | mov word [es:4*0x41 + 2], cx 245 | 246 | ; Set up last 16 bytes of memory, including boot jump, BIOS date, machine ID byte 247 | 248 | mov ax, 0xffff 249 | mov es, ax 250 | mov di, 0 251 | mov si, mem_top 252 | mov cx, 16 253 | rep movsb 254 | 255 | ; Set up the BIOS data area 256 | 257 | mov ax, 0x40 258 | mov es, ax 259 | mov di, 0 260 | mov si, bios_data 261 | mov cx, 0x100 262 | rep movsb 263 | 264 | ; Clear video memory 265 | 266 | mov ax, 0xb800 267 | mov es, ax 268 | mov di, 0 269 | mov cx, 80*25 270 | mov ax, 0x0700 271 | rep stosw 272 | 273 | ; Clear video memory shadow buffer 274 | 275 | mov ax, 0xc800 276 | mov es, ax 277 | mov di, 0 278 | mov cx, 80*25 279 | mov ax, 0x0700 280 | rep stosw 281 | 282 | ; Set up some I/O ports, between 0 and FFF. Most of them we set to 0xFF, to indicate no device present 283 | 284 | mov dx, 0x61 285 | mov al, 0 286 | out dx, al ; Make sure the speaker is off 287 | 288 | mov dx, 0x60 289 | out dx, al ; No scancode 290 | 291 | mov dx, 0x64 292 | out dx, al ; No key waiting 293 | 294 | mov dx, 0 295 | mov al, 0xFF 296 | 297 | next_out: 298 | 299 | inc dx 300 | 301 | cmp dx, 0x40 ; We deal with the PIT channel 0 later 302 | je next_out 303 | cmp dx, 0x42 ; We deal with the PIT channel 2 later 304 | je next_out 305 | cmp dx, 0x3B8 ; We deal with the Hercules port later, too 306 | je next_out 307 | cmp dx, 0x60 ; Keyboard scancode 308 | je next_out 309 | cmp dx, 0x61 ; Sound output 310 | je next_out 311 | cmp dx, 0x64 ; Keyboard status 312 | je next_out 313 | 314 | out dx, al 315 | 316 | cmp dx, 0xFFF 317 | jl next_out 318 | 319 | mov al, 0 320 | 321 | mov dx, 0x3DA ; CGA refresh port 322 | out dx, al 323 | 324 | mov dx, 0x3BA ; Hercules detection port 325 | out dx, al 326 | 327 | mov dx, 0x3B8 ; Hercules video mode port 328 | out dx, al 329 | 330 | mov dx, 0x3BC ; LPT1 331 | out dx, al 332 | 333 | mov dx, 0x62 ; PPI - needed for memory parity checks 334 | out dx, al 335 | 336 | ; Get initial RTC value 337 | 338 | push cs 339 | pop es 340 | mov bx, timetable 341 | extended_get_rtc 342 | mov ax, [es:tm_msec] 343 | mov [cs:last_int8_msec], ax 344 | 345 | ; Read boot sector from FDD, and load it into 0:7C00 346 | 347 | mov ax, 0 348 | mov es, ax 349 | 350 | mov ax, 0x0201 351 | mov dh, 0 352 | mov dl, [cs:boot_device] 353 | mov cx, 1 354 | mov bx, 0x7c00 355 | int 13h 356 | 357 | ; Jump to boot sector 358 | 359 | jmp 0:0x7c00 360 | 361 | ; ************************* INT 7h handler - keyboard driver (8086tiny internal) 362 | 363 | int7: ; Whenever the user presses a key, INT 7 is called by the emulator. 364 | ; ASCII character of the keystroke is at 0040:this_keystroke 365 | 366 | push ds 367 | push es 368 | push ax 369 | push bx 370 | push bp 371 | 372 | push cs 373 | pop ds 374 | 375 | mov bx, 0x40 ; Set segment to BIOS data area segment (0x40) 376 | mov es, bx 377 | 378 | ; Retrieve the keystroke 379 | 380 | mov ax, [es:this_keystroke-bios_data] 381 | mov byte [es:this_keystroke+1-bios_data], 0 382 | 383 | real_key: 384 | 385 | mov byte [cs:last_key_sdl], 0 386 | 387 | test ah, 4 ; This key doesn't come from SDL 388 | jz check_linux_bksp 389 | 390 | mov byte [es:keyflags1-bios_data], 0 391 | mov byte [es:keyflags2-bios_data], 0 392 | 393 | mov byte [cs:last_key_sdl], 1 ; Key down from SDL 394 | 395 | test ah, 0x40 ; Key up 396 | jz sdl_check_specials 397 | 398 | mov byte [cs:last_key_sdl], 2 ; Key up from SDL 399 | 400 | sdl_check_specials: 401 | 402 | mov bx, ax 403 | and bh, 7 ; If key is between 52F and 534 (Shift/Ctrl/Alt), ignore the key state flags 404 | cmp bx, 0x52f 405 | je sdl_just_press_shift 406 | cmp bx, 0x530 407 | je sdl_just_press_shift 408 | cmp bx, 0x533 409 | je sdl_just_press_alt 410 | cmp bx, 0x534 411 | je sdl_just_press_alt 412 | cmp bx, 0x531 413 | je sdl_just_press_ctrl 414 | cmp bx, 0x532 415 | je sdl_just_press_ctrl 416 | jmp sdl_check_alt 417 | 418 | sdl_just_press_shift: 419 | 420 | mov al, 0x36 ; Shift 421 | and ah, 0x40 ; Key up? 422 | add al, ah 423 | add al, ah 424 | call io_key_available 425 | jmp i2_dne 426 | 427 | sdl_just_press_alt: 428 | 429 | mov al, 0x38 ; Alt 430 | and ah, 0x40 ; Key up? 431 | add al, ah 432 | add al, ah 433 | call io_key_available 434 | jmp i2_dne 435 | 436 | sdl_just_press_ctrl: 437 | 438 | mov al, 0x1d ; Ctrl 439 | and ah, 0x40 ; Key up? 440 | add al, ah 441 | add al, ah 442 | call io_key_available 443 | jmp i2_dne 444 | 445 | sdl_check_alt: 446 | 447 | test ah, 8 ; Alt+something? 448 | jz sdl_no_alt 449 | add byte [es:keyflags1-bios_data], 8 450 | add byte [es:keyflags2-bios_data], 2 451 | 452 | sdl_no_alt: 453 | 454 | test ah, 0x20 ; Ctrl+something? 455 | jz sdl_no_ctrl 456 | add byte [es:keyflags1-bios_data], 4 457 | 458 | sdl_no_ctrl: 459 | 460 | test ah, 0x10 ; Shift+something? 461 | jz sdl_no_mods 462 | add byte [es:keyflags1-bios_data], 1 463 | 464 | sdl_no_mods: 465 | 466 | and ah, 1 ; We have processed all SDL modifiers, so remove them 467 | 468 | ;cmp ax, 160 ; Alt+Space? 469 | ;jne next_sdl_alt_keys 470 | ;mov al, ' ' 471 | ;mov byte [es:this_keystroke-bios_data], al 472 | 473 | check_sdl_f_keys: 474 | 475 | cmp ax, 0x125 476 | ja i2_dne ; Unknown key 477 | 478 | cmp ax, 0x11a 479 | jb check_sdl_pgup_pgdn_keys 480 | 481 | sub ax, 0xdf ; F1 - F10 482 | cmp ax, 0x45 483 | jb check_sdl_f_keys2 484 | add ax, 0x12 ; F11 - F12 485 | 486 | check_sdl_f_keys2: 487 | 488 | mov bh, al 489 | mov al, 0 490 | jmp sdl_scancode_xlat_done 491 | 492 | check_sdl_pgup_pgdn_keys: 493 | 494 | cmp ax, 0x116 495 | jb check_sdl_cursor_keys 496 | cmp ax, 0x119 497 | ja check_sdl_cursor_keys 498 | 499 | sub ax, 0x116 500 | mov bx, pgup_pgdn_xlt 501 | cs xlat 502 | 503 | mov bh, al 504 | mov al, 0 505 | jmp sdl_scancode_xlat_done 506 | 507 | check_sdl_cursor_keys: 508 | 509 | cmp ax, 0x111 ; SDL cursor keys 510 | jb sdl_process_key ; No special handling for other keys yet 511 | 512 | sub ax, 0x111 513 | mov bx, unix_cursor_xlt 514 | xlat ; Convert SDL cursor keys to scancode 515 | 516 | mov bh, al 517 | mov al, 0 518 | mov byte [es:this_keystroke-bios_data], 0 519 | jmp sdl_scancode_xlat_done 520 | 521 | sdl_process_key: 522 | 523 | cmp ax, 0x100 524 | jae i2_dne ; Unsupported key 525 | cmp al, 0x7f ; SDL 0x7F backspace? Convert to 0x08 526 | jne sdl_process_key2 527 | mov al, 8 528 | 529 | sdl_process_key2: 530 | 531 | push ax 532 | mov bx, a2scan_tbl ; ASCII to scancode table 533 | xlat 534 | mov bh, al 535 | pop ax ; Scancode in BH, keycode in AL 536 | 537 | sdl_scancode_xlat_done: 538 | 539 | add bh, 0x80 ; Key up scancode 540 | cmp byte [cs:last_key_sdl], 2 ; Key up? 541 | je sdl_not_in_buf 542 | 543 | sub bh, 0x80 ; Key down scancode 544 | 545 | sdl_key_down: 546 | 547 | mov [es:this_keystroke-bios_data], al 548 | 549 | sdl_not_in_buf: 550 | 551 | mov al, bh 552 | call io_key_available 553 | jmp i2_dne 554 | 555 | check_linux_bksp: 556 | 557 | cmp al, 0 ; Null keystroke - ignore 558 | je i2_dne 559 | 560 | cmp al, 0x7f ; Linux code for backspace - change to 8 561 | jne after_check_bksp 562 | 563 | mov al, 8 564 | mov byte [es:this_keystroke-bios_data], 8 565 | 566 | after_check_bksp: 567 | 568 | cmp byte [es:next_key_fn-bios_data], 1 ; If previous keypress was Ctrl+F (signifying this key is is Fxx), skip checks for Ctrl+A (Alt+xx) and Ctrl+F (Fxx) 569 | je i2_n 570 | 571 | cmp al, 0x01 ; Ctrl+A pressed - this is the sequence for "next key is Alt+" 572 | jne i2_not_alt 573 | 574 | mov byte [es:keyflags1-bios_data], 8 ; Alt flag down 575 | mov byte [es:keyflags2-bios_data], 2 ; Alt flag down 576 | mov al, 0x38 ; Simulated Alt by Ctrl+A prefix? 577 | call io_key_available 578 | 579 | mov byte [es:next_key_alt-bios_data], 1 580 | jmp i2_dne 581 | 582 | i2_not_alt: 583 | 584 | cmp al, 0x06 ; Ctrl+F pressed - this is the sequence for "next key is Fxx" 585 | jne i2_not_fn 586 | 587 | mov byte [es:next_key_fn-bios_data], 1 588 | jmp i2_dne 589 | 590 | i2_not_fn: 591 | 592 | cmp byte [es:notranslate_flg-bios_data], 1 ; If no translation mode is on, just pass through the scan code. ASCII key is zero. 593 | mov byte [es:notranslate_flg-bios_data], 0 594 | jne need_to_translate 595 | 596 | mov byte [es:this_keystroke-bios_data], 0 597 | jmp after_translate 598 | 599 | need_to_translate: 600 | 601 | cmp al, 0xe0 ; Some OSes return scan codes after 0xE0 for things like cursor moves. So, if we find it, set a flag saying the next code received should not be translated. 602 | mov byte [es:notranslate_flg-bios_data], 1 603 | je i2_dne ; Don't add the 0xE0 to the keyboard buffer 604 | 605 | mov byte [es:notranslate_flg-bios_data], 0 606 | 607 | cmp al, 0x1b ; ESC key pressed. Either this a "real" escape, or it is UNIX cursor keys. In either case, we do nothing now, except set a flag 608 | jne i2_escnext 609 | 610 | ; If the last key pressed was ESC, then we need to stuff it 611 | cmp byte [es:escape_flag-bios_data], 1 612 | jne i2_sf 613 | 614 | ; Stuff an ESC character 615 | 616 | mov byte [es:this_keystroke-bios_data], 0x1b 617 | 618 | mov al, 0x01 619 | call keypress_release 620 | 621 | i2_sf: 622 | 623 | mov byte [es:escape_flag-bios_data], 1 624 | jmp i2_dne 625 | 626 | i2_escnext: 627 | 628 | ; Check if the last key was an escape character 629 | cmp byte [es:escape_flag-bios_data], 1 630 | jne i2_noesc 631 | 632 | ; It is, so check if this key is a [ control character 633 | cmp al, '[' ; [ key pressed 634 | je i2_esc 635 | 636 | ; It isn't, so stuff an ESC character plus this key 637 | 638 | mov byte [es:this_keystroke-bios_data], 0x1b 639 | 640 | mov al, 0x01 641 | call keypress_release 642 | 643 | ; Now actually process this key 644 | mov byte [es:escape_flag-bios_data], 0 645 | mov al, [es:this_keystroke-bios_data] 646 | jmp i2_noesc 647 | 648 | i2_esc: 649 | 650 | ; Last + this characters are ESC ] - do nothing now, but set escape flag 651 | mov byte [es:escape_flag-bios_data], 2 652 | jmp i2_dne 653 | 654 | i2_noesc: 655 | 656 | cmp byte [es:escape_flag-bios_data], 2 657 | jne i2_regular_key 658 | 659 | ; No shifts or Alt for cursor keys 660 | mov byte [es:keyflags1-bios_data], 0 661 | mov byte [es:keyflags2-bios_data], 0 662 | 663 | ; Last + this characters are ESC ] xxx - cursor key, so translate and stuff it 664 | sub al, 'A' 665 | mov bx, unix_cursor_xlt 666 | xlat 667 | 668 | mov byte [es:this_keystroke-bios_data], 0 669 | jmp after_translate 670 | 671 | i2_regular_key: 672 | 673 | mov byte [es:notranslate_flg-bios_data], 0 674 | 675 | mov bx, a2shift_tbl ; ASCII to shift code table 676 | xlat 677 | 678 | ; Now, BL is 1 if shift is down, 0 otherwise. If shift is down, put a shift down scan code 679 | ; in port 0x60. Then call int 9. Otherwise, put a shift up scan code in, and call int 9. 680 | 681 | push ax 682 | 683 | ; Put shift flags in BIOS, 0040:0017. Add 8 to shift flags if Alt is down. 684 | mov ah, [es:next_key_alt-bios_data] 685 | cpu 186 686 | shl ah, 3 687 | cpu 8086 688 | add al, ah 689 | 690 | cmp byte [es:this_keystroke-bios_data], 0x1A ; Ctrl+A to Ctrl+Z? Then add Ctrl to BIOS key flags 691 | ja i2_no_ctrl 692 | cmp byte [es:this_keystroke-bios_data], 0 693 | je i2_no_ctrl 694 | cmp byte [es:this_keystroke-bios_data], 0xD ; CR 695 | je i2_no_ctrl 696 | cmp byte [es:this_keystroke-bios_data], 0xA ; LF 697 | je i2_no_ctrl 698 | cmp byte [es:this_keystroke-bios_data], 0x8 ; Backspace 699 | je i2_no_ctrl 700 | cmp byte [es:this_keystroke-bios_data], 0x9 ; Tab 701 | je i2_no_ctrl 702 | add al, 4 ; Ctrl in key flags 703 | 704 | push ax 705 | mov al, 0x1d ; Ctrl key down 706 | call io_key_available 707 | pop ax 708 | 709 | i2_no_ctrl: 710 | 711 | mov [es:keyflags1-bios_data], al 712 | 713 | cpu 186 714 | shr ah, 2 715 | cpu 8086 716 | mov [es:keyflags2-bios_data], ah 717 | 718 | pop ax 719 | 720 | test al, 1 ; Shift down? 721 | jz i2_n 722 | 723 | mov al, 0x36 ; Right shift down 724 | call io_key_available 725 | 726 | i2_n: 727 | 728 | mov al, [es:this_keystroke-bios_data] 729 | 730 | mov bx, a2scan_tbl ; ASCII to scan code table 731 | xlat 732 | 733 | cmp byte [es:next_key_fn-bios_data], 1 ; Fxx? 734 | jne after_translate 735 | 736 | cmp byte [es:this_keystroke-bios_data], 1 ; Ctrl+F then Ctrl+A outputs code for Ctrl+A 737 | je after_translate 738 | 739 | cmp byte [es:this_keystroke-bios_data], 6 ; Ctrl+F then Ctrl+F outputs code for Ctrl+F 740 | je after_translate 741 | 742 | mov byte [es:this_keystroke-bios_data], 0 ; Fxx key, so zero out ASCII code 743 | add al, 0x39 744 | 745 | after_translate: 746 | 747 | mov byte [es:escape_flag-bios_data], 0 748 | mov byte [es:escape_flag_last-bios_data], 0 749 | 750 | ; If the key is actually an Alt+ key we use an ASCII code of 0 instead of the real value. 751 | 752 | cmp byte [es:next_key_alt-bios_data], 1 753 | jne skip_ascii_zero 754 | 755 | mov byte [es:this_keystroke-bios_data], 0 756 | 757 | skip_ascii_zero: 758 | 759 | ; Output key down/key up event (scancode in AL) to keyboard port 760 | call keypress_release 761 | 762 | ; If scan code is not 0xE0, then also send right shift up if necessary 763 | cmp al, 0xe0 764 | je i2_dne 765 | 766 | test byte [es:keyflags1-bios_data], 1 767 | jz check_ctrl 768 | 769 | mov al, 0xb6 ; Right shift up 770 | call io_key_available 771 | 772 | check_ctrl: 773 | 774 | test byte [es:keyflags1-bios_data], 4 775 | jz check_alt 776 | 777 | mov al, 0x9d ; Right Ctrl up 778 | call io_key_available 779 | 780 | check_alt: 781 | 782 | mov al, byte [es:next_key_alt-bios_data] 783 | mov byte [es:next_key_alt-bios_data], 0 784 | mov byte [es:next_key_fn-bios_data], 0 785 | 786 | cmp al, 1 787 | je endalt 788 | 789 | jmp i2_dne 790 | 791 | endalt: 792 | 793 | mov al, 0xb8 ; Left Alt up 794 | call io_key_available 795 | 796 | i2_dne: 797 | 798 | pop bp 799 | pop bx 800 | pop ax 801 | pop es 802 | pop ds 803 | iret 804 | 805 | ; ************************* INT 9h handler - keyboard (PC BIOS standard) 806 | 807 | int9: 808 | 809 | push es 810 | push ax 811 | push bx 812 | push bp 813 | 814 | in al, 0x60 815 | 816 | cmp al, 0x80 ; Key up? 817 | jae no_add_buf 818 | cmp al, 0x36 ; Shift? 819 | je no_add_buf 820 | cmp al, 0x38 ; Alt? 821 | je no_add_buf 822 | cmp al, 0x1d ; Ctrl? 823 | je no_add_buf 824 | 825 | mov bx, 0x40 826 | mov es, bx 827 | 828 | mov bh, al 829 | mov al, [es:this_keystroke-bios_data] 830 | 831 | ; Tail of the BIOS keyboard buffer goes in BP. This is where we add new keystrokes 832 | 833 | mov bp, [es:kbbuf_tail-bios_data] 834 | mov byte [es:bp], al ; ASCII code 835 | mov byte [es:bp+1], bh ; Scan code 836 | 837 | ; ESC keystroke is in the buffer now 838 | add word [es:kbbuf_tail-bios_data], 2 839 | call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large 840 | 841 | no_add_buf: 842 | 843 | mov al, 1 844 | out 0x64, al 845 | 846 | pop bp 847 | pop bx 848 | pop ax 849 | pop es 850 | 851 | iret 852 | 853 | ; ************************* INT Ah handler - timer (8086tiny internal) 854 | 855 | inta: 856 | ; 8086tiny called interrupt 0xA frequently, at a rate dependent on the speed of your computer. 857 | ; This interrupt handler scales down the call rate and calls INT 8 at 18.2 times per second, 858 | ; as per a real PC. 859 | 860 | ; See if there is an ESC waiting from a previous INT 7h. If so, put it in the keyboard buffer 861 | ; (because by now - 1/18.2 secs on - we know it can't be part of an escape key sequence). 862 | ; Also handle CGA refresh register. Also release any keys that are still marked as down. 863 | 864 | push ax 865 | push bx 866 | push dx 867 | push bp 868 | push es 869 | 870 | push cx 871 | push di 872 | push ds 873 | push si 874 | 875 | call vmem_driver_entry ; CGA text mode driver - documented later 876 | 877 | ; Increment 32-bit BIOS timer tick counter, once every 18.2 ms 878 | 879 | push cs 880 | pop es 881 | mov bx, timetable 882 | extended_get_rtc 883 | 884 | mov ax, [cs:tm_msec] 885 | sub ax, [cs:last_int8_msec] 886 | 887 | make_ctr_positive: 888 | 889 | cmp ax, 0 890 | jge no_add_1000 891 | 892 | add ax, 1000 893 | jmp make_ctr_positive 894 | 895 | no_add_1000: 896 | 897 | mov bx, 0x40 898 | mov es, bx 899 | 900 | mov dx, 0 901 | mov bx, 1193 902 | mul bx 903 | 904 | mov bx, [es:timer0_freq-bios_data] 905 | 906 | cmp bx, 0 ; 0 actually means FFFF 907 | jne no_adjust_10000 908 | 909 | mov bx, 0xffff 910 | 911 | no_adjust_10000: 912 | 913 | div bx ; AX now contains number of timer ticks since last int 8 (DX is remainder) 914 | 915 | cmp ax, 0 916 | je i8_end 917 | 918 | add word [es:0x6C], ax 919 | adc word [es:0x6E], 0 920 | 921 | inta_call_int8: 922 | 923 | push ax ; Workaround for CPM-86 - INT 1C destroys AX!! 924 | int 8 925 | pop ax 926 | 927 | dec ax 928 | cmp ax, 0 929 | jne inta_call_int8 930 | 931 | mov ax, [cs:tm_msec] 932 | mov [cs:last_int8_msec], ax 933 | 934 | skip_timer_increment: 935 | 936 | ; If last key was from SDL, don't simulate key up events (SDL will do it for us) 937 | cmp byte [cs:last_key_sdl], 0 938 | jne i8_end 939 | 940 | ; See if we have any keys down. If so, release them 941 | cmp byte [es:key_now_down-bios_data], 0 942 | je i8_no_key_down 943 | 944 | mov al, [es:key_now_down-bios_data] 945 | mov byte [es:key_now_down-bios_data], 0 946 | add al, 0x80 947 | call io_key_available 948 | 949 | i8_no_key_down: 950 | 951 | ; See if we have a waiting ESC flag 952 | cmp byte [es:escape_flag-bios_data], 1 953 | jne i8_end 954 | 955 | ; Did we have one last two cycles as well? 956 | cmp byte [es:escape_flag_last-bios_data], 1 957 | je i8_stuff_esc 958 | 959 | inc byte [es:escape_flag_last-bios_data] 960 | jmp i8_end 961 | 962 | i8_stuff_esc: 963 | 964 | ; Yes, clear the ESC flag and put it in the keyboard buffer 965 | mov byte [es:escape_flag-bios_data], 0 966 | mov byte [es:escape_flag_last-bios_data], 0 967 | 968 | ; mov bp, [es:kbbuf_tail-bios_data] 969 | ; mov byte [es:bp], 0x1b ; ESC ASCII code 970 | ; mov byte [es:bp+1], 0x01 ; ESC scan code 971 | 972 | ; ESC keystroke is in the buffer now 973 | ; add word [es:kbbuf_tail-bios_data], 2 974 | ; call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large 975 | 976 | mov byte [es:this_keystroke-bios_data], 0x1b 977 | 978 | ; Push out ESC keypress/release 979 | mov al, 0x01 980 | call keypress_release 981 | 982 | i8_end: 983 | 984 | ; A Hercules graphics adapter flips bit 7 of I/O port 3BA on refresh 985 | mov dx, 0x3BA 986 | in al, dx 987 | xor al, 0x80 988 | out dx, al 989 | 990 | pop si 991 | pop ds 992 | pop di 993 | pop cx 994 | 995 | pop es 996 | pop bp 997 | pop dx 998 | pop bx 999 | pop ax 1000 | 1001 | iret 1002 | 1003 | ; ************************* INT 8h handler - timer 1004 | 1005 | int8: 1006 | 1007 | int 0x1c 1008 | iret 1009 | 1010 | ; ************************* INT 10h handler - video services 1011 | 1012 | int10: 1013 | 1014 | cmp ah, 0x00 ; Set video mode 1015 | je int10_set_vm 1016 | cmp ah, 0x01 ; Set cursor shape 1017 | je int10_set_cshape 1018 | cmp ah, 0x02 ; Set cursor position 1019 | je int10_set_cursor 1020 | cmp ah, 0x03 ; Get cursur position 1021 | je int10_get_cursor 1022 | cmp ah, 0x06 ; Scroll up window 1023 | je int10_scrollup 1024 | cmp ah, 0x07 ; Scroll down window 1025 | je int10_scrolldown 1026 | cmp ah, 0x08 ; Get character at cursor 1027 | je int10_charatcur 1028 | cmp ah, 0x09 ; Write char and attribute 1029 | je int10_write_char_attrib 1030 | cmp ah, 0x0e ; Write character at cursor position 1031 | je int10_write_char 1032 | cmp ah, 0x0f ; Get video mode 1033 | je int10_get_vm 1034 | ; cmp ah, 0x1a ; Feature check 1035 | ; je int10_features 1036 | 1037 | iret 1038 | 1039 | int10_set_vm: 1040 | 1041 | push dx 1042 | push cx 1043 | push bx 1044 | push es 1045 | 1046 | cmp al, 4 ; CGA mode 4 1047 | je int10_switch_to_cga_gfx 1048 | cmp al, 5 1049 | je int10_switch_to_cga_gfx 1050 | cmp al, 6 1051 | je int10_switch_to_cga_gfx 1052 | 1053 | push ax 1054 | 1055 | mov dx, 0x3b8 1056 | mov al, 0 1057 | out dx, al 1058 | 1059 | mov dx, 0x3b4 1060 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 1061 | out dx, al 1062 | mov dx, 0x3b5 1063 | mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) 1064 | out dx, al 1065 | mov dx, 0x3b4 1066 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 1067 | out dx, al 1068 | mov dx, 0x3b5 1069 | mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) 1070 | out dx, al 1071 | 1072 | mov dx, 0x40 1073 | mov es, dx 1074 | 1075 | mov byte [es:0xac], 0 ; Tell emulator we are in Hercules mode 1076 | 1077 | pop ax 1078 | 1079 | cmp al, 7 ; If an app tries to set Hercules text mode 7, actually set mode 3 (we do not support mode 7's video memory buffer at B000:0) 1080 | je int10_set_vm_3 1081 | cmp al, 2 ; Same for text mode 2 (mono) 1082 | je int10_set_vm_3 1083 | 1084 | jmp int10_set_vm_continue 1085 | 1086 | int10_switch_to_cga_gfx: 1087 | 1088 | ; Switch to CGA-like graphics mode (with Hercules CRTC set for 640 x 400) 1089 | 1090 | mov dx, 0x40 1091 | mov es, dx 1092 | 1093 | mov [es:0x49], al ; Current video mode 1094 | mov byte [es:0xac], 1 ; Tell emulator we are in CGA mode 1095 | 1096 | mov dx, 0x3b4 1097 | mov al, 1 ; Hercules CRTC "horizontal displayed" register select 1098 | out dx, al 1099 | mov dx, 0x3b5 1100 | mov al, 0x28 ; 0x28 = 40 (* 16) = 640 pixels wide (GRAPHICS_X) 1101 | out dx, al 1102 | mov dx, 0x3b4 1103 | mov al, 6 ; Hercules CRTC "vertical displayed" register select 1104 | out dx, al 1105 | mov dx, 0x3b5 1106 | mov al, 0x64 ; 0x64 = 100 (* 4) = 400 pixels high (GRAPHICS_Y) 1107 | out dx, al 1108 | 1109 | mov dx, 0x3b8 1110 | mov al, 0x8a 1111 | out dx, al 1112 | 1113 | mov bh, 7 1114 | call clear_screen 1115 | 1116 | mov ax, 0x30 1117 | jmp svmn_exit 1118 | 1119 | int10_set_vm_3: 1120 | 1121 | mov al, 3 1122 | 1123 | int10_set_vm_continue: 1124 | 1125 | mov bx, 0x40 1126 | mov es, bx 1127 | 1128 | mov [es:vidmode-bios_data], al 1129 | 1130 | mov bh, 7 ; Black background, white foreground 1131 | call clear_screen ; ANSI clear screen 1132 | 1133 | cmp byte [es:vidmode-bios_data], 6 1134 | je set6 1135 | mov al, 0x30 1136 | jmp svmn 1137 | 1138 | set6: 1139 | 1140 | mov al, 0x3f 1141 | 1142 | svmn: 1143 | 1144 | ; Take Hercules adapter out of graphics mode when resetting video mode via int 10 1145 | push ax 1146 | mov dx, 0x3B8 1147 | mov al, 0 1148 | out dx, al 1149 | pop ax 1150 | 1151 | svmn_exit: 1152 | 1153 | pop es 1154 | pop bx 1155 | pop cx 1156 | pop dx 1157 | iret 1158 | 1159 | int10_set_cshape: 1160 | 1161 | push ds 1162 | push ax 1163 | push cx 1164 | 1165 | mov ax, 0x40 1166 | mov ds, ax 1167 | 1168 | mov byte [cursor_visible-bios_data], 1 ; Show cursor 1169 | 1170 | and ch, 01100000b 1171 | cmp ch, 00100000b 1172 | jne cur_visible 1173 | 1174 | mov byte [cursor_visible-bios_data], 0 ; Hide cursor 1175 | call ansi_hide_cursor 1176 | jmp cur_done 1177 | 1178 | cur_visible: 1179 | 1180 | call ansi_show_cursor 1181 | 1182 | cur_done: 1183 | 1184 | pop cx 1185 | pop ax 1186 | pop ds 1187 | iret 1188 | 1189 | int10_set_cursor: 1190 | 1191 | push ds 1192 | push ax 1193 | 1194 | mov ax, 0x40 1195 | mov ds, ax 1196 | 1197 | mov [curpos_y-bios_data], dh 1198 | mov [crt_curpos_y-bios_data], dh 1199 | mov [curpos_x-bios_data], dl 1200 | mov [crt_curpos_x-bios_data], dl 1201 | 1202 | cmp dh, 24 1203 | jbe skip_set_cur_row_max 1204 | 1205 | ; If cursor is moved off the screen, then hide it 1206 | call ansi_hide_cursor 1207 | jmp skip_set_cur_ansi 1208 | 1209 | skip_set_cur_row_max: 1210 | 1211 | cmp dl, 79 1212 | jbe skip_set_cur_col_max 1213 | 1214 | ; If cursor is moved off the screen, then hide it 1215 | call ansi_hide_cursor 1216 | jmp skip_set_cur_ansi 1217 | 1218 | skip_set_cur_col_max: 1219 | 1220 | mov al, 0x1B ; ANSI 1221 | extended_putchar_al 1222 | mov al, '[' ; ANSI 1223 | extended_putchar_al 1224 | mov al, dh ; Row number 1225 | inc al 1226 | call puts_decimal_al 1227 | mov al, ';' ; ANSI 1228 | extended_putchar_al 1229 | mov al, dl ; Column number 1230 | inc al 1231 | call puts_decimal_al 1232 | mov al, 'H' ; Set cursor position command 1233 | extended_putchar_al 1234 | 1235 | cmp byte [cursor_visible-bios_data], 1 1236 | jne skip_set_cur_ansi 1237 | call ansi_show_cursor 1238 | 1239 | skip_set_cur_ansi: 1240 | 1241 | pop ax 1242 | pop ds 1243 | iret 1244 | 1245 | int10_get_cursor: 1246 | 1247 | push es 1248 | 1249 | mov cx, 0x40 1250 | mov es, cx 1251 | 1252 | mov cx, 0x0607 1253 | mov dl, [es:curpos_x-bios_data] 1254 | mov dh, [es:curpos_y-bios_data] 1255 | 1256 | pop es 1257 | 1258 | iret 1259 | 1260 | int10_scrollup: 1261 | 1262 | push bx 1263 | push cx 1264 | push bp 1265 | push ax 1266 | 1267 | mov bp, bx ; Convert from CGA to ANSI 1268 | mov cl, 12 1269 | ror bp, cl 1270 | and bp, 7 1271 | mov bl, byte [cs:bp+colour_table] 1272 | add bl, 10 1273 | 1274 | mov al, 0x1B ; Escape 1275 | extended_putchar_al 1276 | mov al, '[' ; ANSI 1277 | extended_putchar_al 1278 | mov al, bl ; Background colour 1279 | call puts_decimal_al 1280 | mov al, 'm' ; Set cursor position command 1281 | extended_putchar_al 1282 | 1283 | pop ax 1284 | pop bp 1285 | pop cx 1286 | pop bx 1287 | 1288 | cmp al, 0 ; Clear window 1289 | jne cls_partial 1290 | 1291 | cmp cx, 0 ; Start of screen 1292 | jne cls_partial 1293 | 1294 | cmp dl, 0x4f ; Clearing columns 0-79 1295 | jb cls_partial 1296 | 1297 | cmp dh, 0x18 ; Clearing rows 0-24 (or more) 1298 | jb cls_partial 1299 | 1300 | call clear_screen 1301 | iret 1302 | 1303 | cls_partial: 1304 | 1305 | push ax 1306 | push bx 1307 | 1308 | mov bl, al ; Number of rows to scroll are now in bl 1309 | cmp bl, 0 ; Clear whole window? 1310 | jne cls_partial_up_whole 1311 | 1312 | mov bl, 25 ; 25 rows 1313 | 1314 | cls_partial_up_whole: 1315 | 1316 | mov al, 0x1B ; Escape 1317 | extended_putchar_al 1318 | mov al, '[' ; ANSI 1319 | extended_putchar_al 1320 | 1321 | cmp ch, 0 ; Start row 1? Maybe full screen 1322 | je cls_maybe_fs 1323 | jmp cls_not_fs 1324 | 1325 | cls_maybe_fs: 1326 | 1327 | cmp dh, 24 ; End row 25? Full screen for sure 1328 | je cls_fs 1329 | 1330 | cls_not_fs: 1331 | 1332 | mov al, ch ; Start row 1333 | inc al 1334 | call puts_decimal_al 1335 | mov al, ';' ; ANSI 1336 | extended_putchar_al 1337 | mov al, dh ; End row 1338 | inc al 1339 | call puts_decimal_al 1340 | 1341 | cls_fs: 1342 | 1343 | mov al, 'r' ; Set scrolling window 1344 | extended_putchar_al 1345 | 1346 | mov al, 0x1B ; Escape 1347 | extended_putchar_al 1348 | mov al, '[' ; ANSI 1349 | extended_putchar_al 1350 | 1351 | cmp bl, 1 1352 | jne cls_fs_multiline 1353 | 1354 | mov al, 'M' 1355 | jmp cs_fs_ml_out 1356 | 1357 | cls_fs_multiline: 1358 | 1359 | mov al, bl ; Number of rows 1360 | call puts_decimal_al 1361 | mov al, 'S' ; Scroll up 1362 | 1363 | cs_fs_ml_out: 1364 | 1365 | extended_putchar_al 1366 | 1367 | pop bx 1368 | pop ax 1369 | 1370 | ; Update "actual" cursor position with expected value - different ANSI terminals do different things 1371 | ; to the cursor position when you scroll 1372 | 1373 | push ax 1374 | push bx 1375 | push dx 1376 | push es 1377 | 1378 | mov ax, 0x40 1379 | mov es, ax 1380 | 1381 | mov ah, 2 1382 | mov bh, 0 1383 | mov dh, [es:curpos_y-bios_data] 1384 | mov dl, [es:curpos_x-bios_data] 1385 | int 10h 1386 | 1387 | pop es 1388 | pop dx 1389 | pop bx 1390 | pop ax 1391 | 1392 | int10_scroll_up_vmem_update: 1393 | 1394 | ; Now, we need to update video memory 1395 | 1396 | push bx 1397 | push ax 1398 | 1399 | push ds 1400 | push es 1401 | push cx 1402 | push dx 1403 | push si 1404 | push di 1405 | 1406 | mov byte [cs:vram_dirty], 1 1407 | 1408 | push bx 1409 | 1410 | mov bx, 0xb800 1411 | mov es, bx 1412 | mov ds, bx 1413 | 1414 | pop bx 1415 | mov bl, al 1416 | 1417 | cls_vmem_scroll_up_next_line: 1418 | 1419 | cmp bl, 0 1420 | je cls_vmem_scroll_up_done 1421 | 1422 | cls_vmem_scroll_up_one: 1423 | 1424 | push bx 1425 | push dx 1426 | 1427 | mov ax, 0 1428 | mov al, ch ; Start row number is now in AX 1429 | mov bx, 80 1430 | mul bx 1431 | add al, cl 1432 | adc ah, 0 ; Character number is now in AX 1433 | mov bx, 2 1434 | mul bx ; Memory location is now in AX 1435 | 1436 | pop dx 1437 | pop bx 1438 | 1439 | mov di, ax 1440 | mov si, ax 1441 | add si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI 1442 | 1443 | mov ax, 0 1444 | add al, dl 1445 | adc ah, 0 1446 | inc ax 1447 | sub al, cl 1448 | sbb ah, 0 ; AX now contains the number of characters from the row to copy 1449 | 1450 | cmp ch, dh 1451 | jae cls_vmem_scroll_up_one_done 1452 | 1453 | vmem_scroll_up_copy_next_row: 1454 | 1455 | push cx 1456 | mov cx, ax ; CX is now the length (in words) of the row to copy 1457 | cld 1458 | rep movsw ; Scroll the line up 1459 | pop cx 1460 | 1461 | inc ch ; Move onto the next row 1462 | jmp cls_vmem_scroll_up_one 1463 | 1464 | cls_vmem_scroll_up_one_done: 1465 | 1466 | push cx 1467 | mov cx, ax ; CX is now the length (in words) of the row to copy 1468 | mov ah, bh ; Attribute for new line 1469 | mov al, 0 ; Write 0 to video memory for new characters 1470 | cld 1471 | rep stosw 1472 | pop cx 1473 | 1474 | dec bl ; Scroll whole text block another line 1475 | jmp cls_vmem_scroll_up_next_line 1476 | 1477 | cls_vmem_scroll_up_done: 1478 | 1479 | ;mov al, 0x1B ; Escape 1480 | ;extended_putchar_al 1481 | ;mov al, '[' ; ANSI 1482 | ;extended_putchar_al 1483 | ;mov al, '0' ; Reset attributes 1484 | ;extended_putchar_al 1485 | ;mov al, 'm' 1486 | ;extended_putchar_al 1487 | 1488 | pop di 1489 | pop si 1490 | pop dx 1491 | pop cx 1492 | pop es 1493 | pop ds 1494 | 1495 | pop ax 1496 | pop bx 1497 | 1498 | iret 1499 | 1500 | int10_scrolldown: 1501 | 1502 | push bx 1503 | push cx 1504 | push bp 1505 | push ax 1506 | 1507 | mov bp, bx ; Convert from CGA to ANSI 1508 | mov cl, 12 1509 | ror bp, cl 1510 | and bp, 7 1511 | mov bl, byte [cs:bp+colour_table] 1512 | add bl, 10 1513 | 1514 | mov al, 0x1B ; Escape 1515 | extended_putchar_al 1516 | mov al, '[' ; ANSI 1517 | extended_putchar_al 1518 | mov al, bl ; Background colour 1519 | call puts_decimal_al 1520 | mov al, 'm' ; Set cursor position command 1521 | extended_putchar_al 1522 | 1523 | pop ax 1524 | pop bp 1525 | pop cx 1526 | pop bx 1527 | 1528 | cmp al, 0 ; Clear window 1529 | jne cls_partial_down 1530 | 1531 | cmp cx, 0 ; Start of screen 1532 | jne cls_partial_down 1533 | 1534 | cmp dl, 0x4f ; Clearing columns 0-79 1535 | jne cls_partial_down 1536 | 1537 | cmp dh, 0x18 ; Clearing rows 0-24 (or more) 1538 | jl cls_partial_down 1539 | 1540 | call clear_screen 1541 | iret 1542 | 1543 | cls_partial_down: 1544 | 1545 | push ax 1546 | push bx 1547 | 1548 | mov bx, 0 1549 | mov bl, al ; Number of rows to scroll are now in bl 1550 | 1551 | cmp bl, 0 ; Clear whole window? 1552 | jne cls_partial_down_whole 1553 | 1554 | mov bl, 25 ; 25 rows 1555 | 1556 | cls_partial_down_whole: 1557 | 1558 | mov al, 0x1B ; Escape 1559 | extended_putchar_al 1560 | mov al, '[' ; ANSI 1561 | extended_putchar_al 1562 | 1563 | cmp ch, 0 ; Start row 1? Maybe full screen 1564 | je cls_maybe_fs_down 1565 | jmp cls_not_fs_down 1566 | 1567 | cls_maybe_fs_down: 1568 | 1569 | cmp dh, 24 ; End row 25? Full screen for sure 1570 | je cls_fs_down 1571 | 1572 | cls_not_fs_down: 1573 | 1574 | mov al, ch ; Start row 1575 | inc al 1576 | call puts_decimal_al 1577 | mov al, ';' ; ANSI 1578 | extended_putchar_al 1579 | mov al, dh ; End row 1580 | inc al 1581 | call puts_decimal_al 1582 | 1583 | cls_fs_down: 1584 | 1585 | mov al, 'r' ; Set scrolling window 1586 | extended_putchar_al 1587 | 1588 | mov al, 0x1B ; Escape 1589 | extended_putchar_al 1590 | mov al, '[' ; ANSI 1591 | extended_putchar_al 1592 | 1593 | cmp bl, 1 1594 | jne cls_fs_down_multiline 1595 | 1596 | mov al, 'D' 1597 | jmp cs_fs_down_ml_out 1598 | 1599 | cls_fs_down_multiline: 1600 | 1601 | mov al, bl ; Number of rows 1602 | call puts_decimal_al 1603 | mov al, 'T' ; Scroll down 1604 | 1605 | cs_fs_down_ml_out: 1606 | 1607 | extended_putchar_al 1608 | 1609 | ; Update "actual" cursor position with expected value - different ANSI terminals do different things 1610 | ; to the cursor position when you scroll 1611 | 1612 | pop bx 1613 | pop ax 1614 | 1615 | push ax 1616 | push bx 1617 | push dx 1618 | push es 1619 | 1620 | mov ax, 0x40 1621 | mov es, ax 1622 | 1623 | mov ah, 2 1624 | mov bh, 0 1625 | mov dh, [es:curpos_y-bios_data] 1626 | mov dl, [es:curpos_x-bios_data] 1627 | int 10h 1628 | 1629 | pop es 1630 | pop dx 1631 | pop bx 1632 | pop ax 1633 | 1634 | int10_scroll_down_vmem_update: 1635 | 1636 | ; Now, we need to update video memory 1637 | 1638 | push ax 1639 | push bx 1640 | 1641 | push ds 1642 | push es 1643 | push cx 1644 | push dx 1645 | push si 1646 | push di 1647 | 1648 | mov byte [cs:vram_dirty], 1 1649 | 1650 | push bx 1651 | 1652 | mov bx, 0xb800 1653 | mov es, bx 1654 | mov ds, bx 1655 | 1656 | pop bx 1657 | mov bl, al 1658 | 1659 | cls_vmem_scroll_down_next_line: 1660 | 1661 | cmp bl, 0 1662 | je cls_vmem_scroll_down_done 1663 | 1664 | cls_vmem_scroll_down_one: 1665 | 1666 | push bx 1667 | push dx 1668 | 1669 | mov ax, 0 1670 | mov al, dh ; End row number is now in AX 1671 | mov bx, 80 1672 | mul bx 1673 | add al, cl 1674 | adc ah, 0 ; Character number is now in AX 1675 | mov bx, 2 1676 | mul bx ; Memory location (start of final row) is now in AX 1677 | 1678 | pop dx 1679 | pop bx 1680 | 1681 | mov di, ax 1682 | mov si, ax 1683 | sub si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI 1684 | 1685 | mov ax, 0 1686 | add al, dl 1687 | adc ah, 0 1688 | inc ax 1689 | sub al, cl 1690 | sbb ah, 0 ; AX now contains the number of characters from the row to copy 1691 | 1692 | cmp ch, dh 1693 | jae cls_vmem_scroll_down_one_done 1694 | 1695 | push cx 1696 | mov cx, ax ; CX is now the length (in words) of the row to copy 1697 | rep movsw ; Scroll the line down 1698 | pop cx 1699 | 1700 | dec dh ; Move onto the next row 1701 | jmp cls_vmem_scroll_down_one 1702 | 1703 | cls_vmem_scroll_down_one_done: 1704 | 1705 | push cx 1706 | mov cx, ax ; CX is now the length (in words) of the row to copy 1707 | mov ah, bh ; Attribute for new line 1708 | mov al, 0 ; Write 0 to video memory for new characters 1709 | rep stosw 1710 | pop cx 1711 | 1712 | dec bl ; Scroll whole text block another line 1713 | jmp cls_vmem_scroll_down_next_line 1714 | 1715 | cls_vmem_scroll_down_done: 1716 | 1717 | pop di 1718 | pop si 1719 | pop dx 1720 | pop cx 1721 | pop es 1722 | pop ds 1723 | 1724 | ;mov al, 0x1B ; Escape 1725 | ;extended_putchar_al 1726 | ;mov al, '[' ; ANSI 1727 | ;extended_putchar_al 1728 | ;mov al, '0' ; Reset attributes 1729 | ;extended_putchar_al 1730 | ;mov al, 'm' 1731 | ;extended_putchar_al 1732 | 1733 | pop bx 1734 | pop ax 1735 | iret 1736 | 1737 | int10_charatcur: 1738 | 1739 | ; This returns the character at the cursor. It is completely dysfunctional, 1740 | ; and only works at all if the character has previously been written following 1741 | ; an int 10/ah = 2 call to set the cursor position. Added just to support 1742 | ; GWBASIC. 1743 | 1744 | push ds 1745 | push es 1746 | push bx 1747 | push dx 1748 | 1749 | mov bx, 0x40 1750 | mov es, bx 1751 | 1752 | mov bx, 0xc000 1753 | mov ds, bx 1754 | 1755 | mov bx, 160 1756 | mov ax, 0 1757 | mov al, [es:curpos_y-bios_data] 1758 | mul bx 1759 | 1760 | mov bx, 0 1761 | mov bl, [es:curpos_x-bios_data] 1762 | add ax, bx 1763 | add ax, bx 1764 | mov bx, ax 1765 | 1766 | mov ah, 7 1767 | mov al, [bx] 1768 | 1769 | pop dx 1770 | pop bx 1771 | pop es 1772 | pop ds 1773 | 1774 | iret 1775 | 1776 | i10_unsup: 1777 | 1778 | iret 1779 | 1780 | int10_write_char: 1781 | 1782 | ; First write the character to a buffer at C000:0. This is so that 1783 | ; we can later retrieve it using the get character at cursor function, 1784 | ; which GWBASIC uses. 1785 | 1786 | push ds 1787 | push es 1788 | push cx 1789 | push dx 1790 | push ax 1791 | push bp 1792 | push bx 1793 | 1794 | push ax 1795 | 1796 | mov cl, al 1797 | mov ch, 7 1798 | 1799 | mov bx, 0x40 1800 | mov es, bx 1801 | 1802 | mov bx, 0xc000 1803 | mov ds, bx 1804 | 1805 | mov bx, 160 1806 | mov ax, 0 1807 | mov al, [es:curpos_y-bios_data] 1808 | mul bx 1809 | 1810 | mov bx, 0 1811 | mov bl, [es:curpos_x-bios_data] 1812 | shl bx, 1 1813 | add bx, ax 1814 | 1815 | mov [bx], cx 1816 | 1817 | pop ax 1818 | push ax 1819 | 1820 | extended_putchar_al 1821 | 1822 | jmp int10_write_char_skip_lines 1823 | 1824 | int10_write_char_attrib: 1825 | 1826 | ; First write the character to a buffer at C000:0. This is so that 1827 | ; we can later retrieve it using the get character at cursor function, 1828 | ; which GWBASIC uses. 1829 | 1830 | push ds 1831 | push es 1832 | push cx 1833 | push dx 1834 | push ax 1835 | push bp 1836 | push bx 1837 | 1838 | push ax 1839 | push cx 1840 | 1841 | mov cl, al 1842 | mov ch, bl 1843 | 1844 | mov bx, 0x40 1845 | mov es, bx 1846 | 1847 | mov bx, 0xc000 1848 | mov ds, bx 1849 | 1850 | mov bx, 160 1851 | mov ax, 0 1852 | mov al, [es:curpos_y-bios_data] 1853 | mul bx 1854 | 1855 | mov bx, 0 1856 | mov bl, [es:curpos_x-bios_data] 1857 | shl bx, 1 1858 | add bx, ax 1859 | 1860 | mov [bx], cx 1861 | 1862 | mov bl, ch 1863 | 1864 | mov bh, bl 1865 | and bl, 7 ; Foreground colour now in bl 1866 | 1867 | mov bp, bx ; Convert from CGA to ANSI 1868 | and bp, 0xff 1869 | mov bl, byte [cs:bp+colour_table] 1870 | 1871 | and bh, 8 ; Bright attribute now in bh 1872 | cpu 186 1873 | shr bh, 3 1874 | cpu 8086 1875 | 1876 | mov al, 0x1B ; Escape 1877 | extended_putchar_al 1878 | mov al, '[' ; ANSI 1879 | extended_putchar_al 1880 | mov al, bh ; Bright attribute 1881 | call puts_decimal_al 1882 | mov al, ';' 1883 | extended_putchar_al 1884 | mov al, bl ; Foreground colour 1885 | call puts_decimal_al 1886 | 1887 | mov bl, ch 1888 | 1889 | mov bh, bl 1890 | cpu 186 1891 | shr bl, 4 1892 | cpu 8086 1893 | and bl, 7 ; Background colour now in bl 1894 | 1895 | mov bp, bx ; Convert from CGA to ANSI 1896 | and bp, 0xff 1897 | mov bl, byte [cs:bp+colour_table] 1898 | 1899 | add bl, 10 1900 | ; rol bh, 1 1901 | ; and bh, 1 ; Bright attribute now in bh (not used right now) 1902 | 1903 | mov al, ';' 1904 | extended_putchar_al 1905 | mov al, bl ; Background colour 1906 | call puts_decimal_al 1907 | mov al, 'm' ; Set cursor position command 1908 | extended_putchar_al 1909 | 1910 | pop cx 1911 | pop ax 1912 | push ax 1913 | 1914 | out_another_char: 1915 | 1916 | extended_putchar_al 1917 | dec cx 1918 | cmp cx, 0 1919 | jne out_another_char 1920 | 1921 | mov al, 0x1B ; Escape 1922 | extended_putchar_al 1923 | mov al, '[' ; ANSI 1924 | extended_putchar_al 1925 | mov al, '0' ; Reset attributes 1926 | extended_putchar_al 1927 | mov al, 'm' 1928 | extended_putchar_al 1929 | 1930 | int10_write_char_skip_lines: 1931 | 1932 | pop ax 1933 | 1934 | push es 1935 | pop ds 1936 | 1937 | cmp al, 0x08 1938 | jne int10_write_char_attrib_inc_x 1939 | 1940 | dec byte [curpos_x-bios_data] 1941 | dec byte [crt_curpos_x-bios_data] 1942 | cmp byte [curpos_x-bios_data], 0 1943 | jg int10_write_char_attrib_done 1944 | 1945 | mov byte [curpos_x-bios_data], 0 1946 | mov byte [crt_curpos_x-bios_data], 0 1947 | jmp int10_write_char_attrib_done 1948 | 1949 | int10_write_char_attrib_inc_x: 1950 | 1951 | cmp al, 0x0A ; New line? 1952 | je int10_write_char_attrib_newline 1953 | 1954 | cmp al, 0x0D ; Carriage return? 1955 | jne int10_write_char_attrib_not_cr 1956 | 1957 | mov byte [curpos_x-bios_data], 0 1958 | mov byte [crt_curpos_x-bios_data], 0 1959 | jmp int10_write_char_attrib_done 1960 | 1961 | int10_write_char_attrib_not_cr: 1962 | 1963 | inc byte [curpos_x-bios_data] 1964 | inc byte [crt_curpos_x-bios_data] 1965 | cmp byte [curpos_x-bios_data], 80 1966 | jge int10_write_char_attrib_newline 1967 | jmp int10_write_char_attrib_done 1968 | 1969 | int10_write_char_attrib_newline: 1970 | 1971 | mov byte [curpos_x-bios_data], 0 1972 | mov byte [crt_curpos_x-bios_data], 0 1973 | inc byte [curpos_y-bios_data] 1974 | inc byte [crt_curpos_y-bios_data] 1975 | 1976 | cmp byte [curpos_y-bios_data], 25 1977 | jb int10_write_char_attrib_done 1978 | mov byte [curpos_y-bios_data], 24 1979 | mov byte [crt_curpos_y-bios_data], 24 1980 | 1981 | mov bh, 7 1982 | mov al, 1 1983 | mov cx, 0 1984 | mov dx, 0x184f 1985 | 1986 | pushf 1987 | push cs 1988 | call int10_scroll_up_vmem_update 1989 | 1990 | int10_write_char_attrib_done: 1991 | 1992 | pop bx 1993 | pop bp 1994 | pop ax 1995 | pop dx 1996 | pop cx 1997 | pop es 1998 | pop ds 1999 | 2000 | iret 2001 | 2002 | int10_get_vm: 2003 | 2004 | push es 2005 | 2006 | mov ax, 0x40 2007 | mov es, ax 2008 | 2009 | mov ah, 80 ; Number of columns 2010 | mov al, [es:vidmode-bios_data] 2011 | mov bh, 0 2012 | 2013 | pop es 2014 | 2015 | iret 2016 | 2017 | int10_features: 2018 | 2019 | ; Signify we have CGA display 2020 | 2021 | ; mov al, 0x1a 2022 | ; mov bx, 0x0202 2023 | ; iret 2024 | 2025 | ; ************************* INT 11h - get equipment list 2026 | 2027 | int11: 2028 | mov ax, [cs:equip] 2029 | iret 2030 | 2031 | ; ************************* INT 12h - return memory size 2032 | 2033 | int12: 2034 | mov ax, 0x280 ; 640K conventional memory 2035 | iret 2036 | 2037 | ; ************************* INT 13h handler - disk services 2038 | 2039 | int13: 2040 | cmp ah, 0x00 ; Reset disk 2041 | je int13_reset_disk 2042 | cmp ah, 0x01 ; Get last status 2043 | je int13_last_status 2044 | 2045 | cmp dl, 0x80 ; Hard disk being queried? 2046 | jne i13_diskok 2047 | 2048 | ; Now, need to check an HD is installed 2049 | cmp word [cs:num_disks], 2 2050 | jge i13_diskok 2051 | 2052 | ; No HD, so return an error 2053 | mov ah, 15 ; Report no such drive 2054 | jmp reach_stack_stc 2055 | 2056 | i13_diskok: 2057 | 2058 | cmp ah, 0x02 ; Read disk 2059 | je int13_read_disk 2060 | cmp ah, 0x03 ; Write disk 2061 | je int13_write_disk 2062 | cmp ah, 0x04 ; Verify disk 2063 | je int13_verify 2064 | cmp ah, 0x05 ; Format track - does nothing here 2065 | je int13_format 2066 | cmp ah, 0x08 ; Get drive parameters (hard disk) 2067 | je int13_getparams 2068 | cmp ah, 0x0c ; Seek (hard disk) 2069 | je int13_seek 2070 | cmp ah, 0x10 ; Check if drive ready (hard disk) 2071 | je int13_hdready 2072 | cmp ah, 0x15 ; Get disk type 2073 | je int13_getdisktype 2074 | cmp ah, 0x16 ; Detect disk change 2075 | je int13_diskchange 2076 | 2077 | mov ah, 1 ; Invalid function 2078 | jmp reach_stack_stc 2079 | 2080 | iret 2081 | 2082 | int13_reset_disk: 2083 | 2084 | jmp reach_stack_clc 2085 | 2086 | int13_last_status: 2087 | 2088 | mov ah, [cs:disk_laststatus] 2089 | je ls_no_error 2090 | 2091 | stc 2092 | iret 2093 | 2094 | ls_no_error: 2095 | 2096 | clc 2097 | iret 2098 | 2099 | int13_read_disk: 2100 | 2101 | push dx 2102 | 2103 | cmp dl, 0 ; Floppy 0 2104 | je i_flop_rd 2105 | cmp dl, 0x80 ; HD 2106 | je i_hd_rd 2107 | 2108 | pop dx 2109 | mov ah, 1 2110 | jmp reach_stack_stc 2111 | 2112 | i_flop_rd: 2113 | 2114 | push si 2115 | push bp 2116 | 2117 | cmp cl, [cs:int1e_spt] 2118 | ja rd_error 2119 | 2120 | pop bp 2121 | pop si 2122 | 2123 | mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator 2124 | jmp i_rd 2125 | 2126 | i_hd_rd: 2127 | 2128 | mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator 2129 | 2130 | i_rd: 2131 | 2132 | push si 2133 | push bp 2134 | 2135 | ; Convert head/cylinder/sector number to byte offset in disk image 2136 | 2137 | call chs_to_abs 2138 | 2139 | ; Now, SI:BP contains the absolute sector offset of the block. We then multiply by 512 to get the offset into the disk image 2140 | 2141 | mov ah, 0 2142 | cpu 186 2143 | shl ax, 9 2144 | extended_read_disk 2145 | shr ax, 9 2146 | cpu 8086 2147 | mov ah, 0x02 ; Put read code back 2148 | 2149 | cmp al, 0 2150 | je rd_error 2151 | 2152 | ; Read was successful. Now, check if we have read the boot sector. If so, we want to update 2153 | ; our internal table of sectors/track to match the disk format 2154 | 2155 | cmp dx, 1 ; FDD? 2156 | jne rd_noerror 2157 | cmp cx, 1 ; First sector? 2158 | jne rd_noerror 2159 | 2160 | push ax 2161 | 2162 | mov al, [es:bx+24] ; Number of SPT in floppy disk BPB 2163 | 2164 | ; cmp al, 0 ; If disk is unformatted, do not update the table 2165 | ; jne rd_update_spt 2166 | cmp al, 9 ; 9 SPT, i.e. 720K disk, so update the table 2167 | je rd_update_spt 2168 | cmp al, 18 2169 | je rd_update_spt ; 18 SPT, i.e. 1.44MB disk, so update the table 2170 | 2171 | pop ax 2172 | 2173 | jmp rd_noerror 2174 | 2175 | rd_update_spt: 2176 | 2177 | mov [cs:int1e_spt], al 2178 | pop ax 2179 | 2180 | rd_noerror: 2181 | 2182 | clc 2183 | mov ah, 0 ; No error 2184 | jmp rd_finish 2185 | 2186 | rd_error: 2187 | 2188 | stc 2189 | mov ah, 4 ; Sector not found 2190 | 2191 | rd_finish: 2192 | 2193 | pop bp 2194 | pop si 2195 | pop dx 2196 | 2197 | mov [cs:disk_laststatus], ah 2198 | jmp reach_stack_carry 2199 | 2200 | int13_write_disk: 2201 | 2202 | push dx 2203 | 2204 | cmp dl, 0 ; Floppy 0 2205 | je i_flop_wr 2206 | cmp dl, 0x80 ; HD 2207 | je i_hd_wr 2208 | 2209 | pop dx 2210 | mov ah, 1 2211 | jmp reach_stack_stc 2212 | 2213 | i_flop_wr: 2214 | 2215 | mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator 2216 | jmp i_wr 2217 | 2218 | i_hd_wr: 2219 | 2220 | mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator 2221 | 2222 | i_wr: 2223 | 2224 | push si 2225 | push bp 2226 | push cx 2227 | push di 2228 | 2229 | ; Convert head/cylinder/sector number to byte offset in disk image 2230 | 2231 | call chs_to_abs 2232 | 2233 | ; Signal an error if we are trying to write beyond the end of the disk 2234 | 2235 | cmp dl, 0 ; Hard disk? 2236 | jne wr_fine ; No - no need for disk sector valid check - NOTE: original submission was JNAE which caused write problems on floppy disk 2237 | 2238 | ; First, we add the number of sectors we are trying to write from the absolute 2239 | ; sector number returned by chs_to_abs. We need to have at least this many 2240 | ; sectors on the disk, otherwise return a sector not found error. 2241 | 2242 | mov cx, bp 2243 | mov di, si 2244 | 2245 | mov ah, 0 2246 | add cx, ax 2247 | adc di, 0 2248 | 2249 | cmp di, [cs:hd_secs_hi] 2250 | ja wr_error 2251 | jb wr_fine 2252 | cmp cx, [cs:hd_secs_lo] 2253 | ja wr_error 2254 | 2255 | wr_fine: 2256 | 2257 | mov ah, 0 2258 | cpu 186 2259 | shl ax, 9 2260 | extended_write_disk 2261 | shr ax, 9 2262 | cpu 8086 2263 | mov ah, 0x03 ; Put write code back 2264 | 2265 | cmp al, 0 2266 | je wr_error 2267 | 2268 | clc 2269 | mov ah, 0 ; No error 2270 | jmp wr_finish 2271 | 2272 | wr_error: 2273 | 2274 | stc 2275 | mov ah, 4 ; Sector not found 2276 | 2277 | wr_finish: 2278 | 2279 | pop di 2280 | pop cx 2281 | pop bp 2282 | pop si 2283 | pop dx 2284 | 2285 | mov [cs:disk_laststatus], ah 2286 | jmp reach_stack_carry 2287 | 2288 | int13_verify: 2289 | 2290 | mov ah, 0 2291 | jmp reach_stack_clc 2292 | 2293 | int13_getparams: 2294 | 2295 | cmp dl, 0 2296 | je i_gp_fl 2297 | cmp dl, 0x80 2298 | je i_gp_hd 2299 | 2300 | mov ah, 0x01 2301 | mov [cs:disk_laststatus], ah 2302 | jmp reach_stack_stc 2303 | 2304 | i_gp_fl: 2305 | 2306 | push cs 2307 | pop es 2308 | mov di, int1e ; ES:DI now points to floppy parameters table (INT 1E) 2309 | 2310 | mov ax, 0 2311 | mov bx, 4 2312 | mov ch, 0x4f 2313 | mov cl, [cs:int1e_spt] 2314 | mov dx, 0x0101 2315 | 2316 | mov byte [cs:disk_laststatus], 0 2317 | jmp reach_stack_clc 2318 | 2319 | i_gp_hd: 2320 | 2321 | mov ax, 0 2322 | mov bx, 0 2323 | mov dl, 1 2324 | mov dh, [cs:hd_max_head] 2325 | mov cx, [cs:hd_max_track] 2326 | ror ch, 1 2327 | ror ch, 1 2328 | add ch, [cs:hd_max_sector] 2329 | xchg ch, cl 2330 | 2331 | mov byte [cs:disk_laststatus], 0 2332 | jmp reach_stack_clc 2333 | 2334 | int13_seek: 2335 | 2336 | mov ah, 0 2337 | jmp reach_stack_clc 2338 | 2339 | int13_hdready: 2340 | 2341 | cmp byte [cs:num_disks], 2 ; HD present? 2342 | jne int13_hdready_nohd 2343 | cmp dl, 0x80 ; Checking first HD? 2344 | jne int13_hdready_nohd 2345 | 2346 | mov ah, 0 2347 | jmp reach_stack_clc 2348 | 2349 | int13_hdready_nohd: 2350 | 2351 | jmp reach_stack_stc 2352 | 2353 | int13_format: 2354 | 2355 | mov ah, 0 2356 | jmp reach_stack_clc 2357 | 2358 | int13_getdisktype: 2359 | 2360 | cmp dl, 0 ; Floppy 2361 | je gdt_flop 2362 | cmp dl, 0x80 ; HD 2363 | je gdt_hd 2364 | 2365 | mov ah, 15 ; Report no such drive 2366 | mov [cs:disk_laststatus], ah 2367 | jmp reach_stack_stc 2368 | 2369 | gdt_flop: 2370 | 2371 | mov ah, 1 2372 | jmp reach_stack_clc 2373 | 2374 | gdt_hd: 2375 | 2376 | mov ah, 3 2377 | mov cx, [cs:hd_secs_hi] 2378 | mov dx, [cs:hd_secs_lo] 2379 | jmp reach_stack_clc 2380 | 2381 | int13_diskchange: 2382 | 2383 | mov ah, 0 ; Disk not changed 2384 | jmp reach_stack_clc 2385 | 2386 | ; ************************* INT 14h - serial port functions 2387 | 2388 | int14: 2389 | cmp ah, 0 2390 | je int14_init 2391 | 2392 | jmp reach_stack_stc 2393 | 2394 | int14_init: 2395 | 2396 | mov ax, 0 2397 | jmp reach_stack_stc 2398 | 2399 | ; ************************* INT 15h - get system configuration 2400 | 2401 | int15: ; Here we do not support any of the functions, and just return 2402 | ; a function not supported code - like the original IBM PC/XT does. 2403 | 2404 | ; cmp ah, 0xc0 2405 | ; je int15_sysconfig 2406 | ; cmp ah, 0x41 2407 | ; je int15_waitevent 2408 | ; cmp ah, 0x4f 2409 | ; je int15_intercept 2410 | ; cmp ah, 0x88 2411 | ; je int15_getextmem 2412 | 2413 | ; Otherwise, function not supported 2414 | 2415 | mov ah, 0x86 2416 | 2417 | jmp reach_stack_stc 2418 | 2419 | ; int15_sysconfig: ; Return address of system configuration table in ROM 2420 | ; 2421 | ; mov bx, 0xf000 2422 | ; mov es, bx 2423 | ; mov bx, rom_config 2424 | ; mov ah, 0 2425 | ; 2426 | ; jmp reach_stack_clc 2427 | ; 2428 | ; int15_waitevent: ; Events not supported 2429 | ; 2430 | ; mov ah, 0x86 2431 | ; 2432 | ; jmp reach_stack_stc 2433 | ; 2434 | ; int15_intercept: ; Keyboard intercept 2435 | ; 2436 | ; jmp reach_stack_stc 2437 | ; 2438 | ; int15_getextmem: ; Extended memory not supported 2439 | ; 2440 | ; mov ah,0x86 2441 | ; 2442 | ; jmp reach_stack_stc 2443 | 2444 | ; ************************* INT 16h handler - keyboard 2445 | 2446 | int16: 2447 | cmp ah, 0x00 ; Get keystroke (remove from buffer) 2448 | je kb_getkey 2449 | cmp ah, 0x01 ; Check for keystroke (do not remove from buffer) 2450 | je kb_checkkey 2451 | cmp ah, 0x02 ; Check shift flags 2452 | je kb_shiftflags 2453 | cmp ah, 0x12 ; Check shift flags 2454 | je kb_extshiftflags 2455 | 2456 | iret 2457 | 2458 | kb_getkey: 2459 | 2460 | push es 2461 | push bx 2462 | push cx 2463 | push dx 2464 | 2465 | mov bx, 0x40 2466 | mov es, bx 2467 | 2468 | kb_gkblock: 2469 | 2470 | cli 2471 | 2472 | mov cx, [es:kbbuf_tail-bios_data] 2473 | mov bx, [es:kbbuf_head-bios_data] 2474 | mov dx, [es:bx] 2475 | 2476 | sti 2477 | 2478 | ; Wait until there is a key in the buffer 2479 | cmp cx, bx 2480 | je kb_gkblock 2481 | 2482 | add word [es:kbbuf_head-bios_data], 2 2483 | call kb_adjust_buf 2484 | 2485 | mov ah, dh 2486 | mov al, dl 2487 | 2488 | pop dx 2489 | pop cx 2490 | pop bx 2491 | pop es 2492 | 2493 | iret 2494 | 2495 | kb_checkkey: 2496 | 2497 | push es 2498 | push bx 2499 | push cx 2500 | push dx 2501 | 2502 | mov bx, 0x40 2503 | mov es, bx 2504 | 2505 | mov cx, [es:kbbuf_tail-bios_data] 2506 | mov bx, [es:kbbuf_head-bios_data] 2507 | mov dx, [es:bx] 2508 | 2509 | sti 2510 | 2511 | ; Check if there is a key in the buffer. ZF is set if there is none. 2512 | cmp cx, bx 2513 | 2514 | mov ah, dh 2515 | mov al, dl 2516 | 2517 | pop dx 2518 | pop cx 2519 | pop bx 2520 | pop es 2521 | 2522 | retf 2 ; NEED TO FIX THIS!! 2523 | 2524 | kb_shiftflags: 2525 | 2526 | push es 2527 | push bx 2528 | 2529 | mov bx, 0x40 2530 | mov es, bx 2531 | 2532 | mov al, [es:keyflags1-bios_data] 2533 | 2534 | pop bx 2535 | pop es 2536 | 2537 | iret 2538 | 2539 | kb_extshiftflags: 2540 | 2541 | push es 2542 | push bx 2543 | 2544 | mov bx, 0x40 2545 | mov es, bx 2546 | 2547 | mov al, [es:keyflags1-bios_data] 2548 | mov ah, al 2549 | 2550 | pop bx 2551 | pop es 2552 | 2553 | iret 2554 | 2555 | ; ************************* INT 17h handler - printer 2556 | 2557 | int17: 2558 | cmp ah, 0x01 2559 | je int17_initprint ; Initialise printer 2560 | 2561 | jmp reach_stack_stc 2562 | 2563 | int17_initprint: 2564 | 2565 | mov ah, 1 ; No printer 2566 | jmp reach_stack_stc 2567 | 2568 | ; ************************* INT 19h = reboot 2569 | 2570 | int19: 2571 | jmp boot 2572 | 2573 | ; ************************* INT 1Ah - clock 2574 | 2575 | int1a: 2576 | cmp ah, 0 2577 | je int1a_getsystime ; Get ticks since midnight (used for RTC time) 2578 | cmp ah, 2 2579 | je int1a_gettime ; Get RTC time (not actually used by DOS) 2580 | cmp ah, 4 2581 | je int1a_getdate ; Get RTC date 2582 | cmp ah, 0x0f 2583 | je int1a_init ; Initialise RTC 2584 | 2585 | iret 2586 | 2587 | int1a_getsystime: 2588 | 2589 | push ax 2590 | push bx 2591 | push ds 2592 | push es 2593 | 2594 | push cs 2595 | push cs 2596 | pop ds 2597 | pop es 2598 | 2599 | mov bx, timetable 2600 | 2601 | extended_get_rtc 2602 | 2603 | mov ax, 182 ; Clock ticks in 10 seconds 2604 | mul word [tm_msec] 2605 | mov bx, 10000 2606 | div bx ; AX now contains clock ticks in milliseconds counter 2607 | mov [tm_msec], ax 2608 | 2609 | mov ax, 182 ; Clock ticks in 10 seconds 2610 | mul word [tm_sec] 2611 | mov bx, 10 2612 | mov dx, 0 2613 | div bx ; AX now contains clock ticks in seconds counter 2614 | mov [tm_sec], ax 2615 | 2616 | mov ax, 1092 ; Clock ticks in a minute 2617 | mul word [tm_min] ; AX now contains clock ticks in minutes counter 2618 | mov [tm_min], ax 2619 | 2620 | mov ax, 65520 ; Clock ticks in an hour 2621 | mul word [tm_hour] ; DX:AX now contains clock ticks in hours counter 2622 | 2623 | add ax, [tm_msec] ; Add milliseconds in to AX 2624 | adc dx, 0 ; Carry into DX if necessary 2625 | add ax, [tm_sec] ; Add seconds in to AX 2626 | adc dx, 0 ; Carry into DX if necessary 2627 | add ax, [tm_min] ; Add minutes in to AX 2628 | adc dx, 0 ; Carry into DX if necessary 2629 | 2630 | push dx 2631 | push ax 2632 | pop dx 2633 | pop cx 2634 | 2635 | pop es 2636 | pop ds 2637 | pop bx 2638 | pop ax 2639 | 2640 | mov al, 0 2641 | iret 2642 | 2643 | int1a_gettime: 2644 | 2645 | ; Return the system time in BCD format. DOS doesn't use this, but we need to return 2646 | ; something or the system thinks there is no RTC. 2647 | 2648 | push ds 2649 | push es 2650 | push ax 2651 | push bx 2652 | 2653 | push cs 2654 | push cs 2655 | pop ds 2656 | pop es 2657 | 2658 | mov bx, timetable 2659 | 2660 | extended_get_rtc 2661 | 2662 | mov ax, 0 2663 | mov cx, [tm_hour] 2664 | call hex_to_bcd 2665 | mov bh, al ; Hour in BCD is in BH 2666 | 2667 | mov ax, 0 2668 | mov cx, [tm_min] 2669 | call hex_to_bcd 2670 | mov bl, al ; Minute in BCD is in BL 2671 | 2672 | mov ax, 0 2673 | mov cx, [tm_sec] 2674 | call hex_to_bcd 2675 | mov dh, al ; Second in BCD is in DH 2676 | 2677 | mov dl, 0 ; Daylight saving flag = 0 always 2678 | 2679 | mov cx, bx ; Hour:minute now in CH:CL 2680 | 2681 | pop bx 2682 | pop ax 2683 | pop es 2684 | pop ds 2685 | 2686 | jmp reach_stack_clc 2687 | 2688 | int1a_getdate: 2689 | 2690 | ; Return the system date in BCD format. 2691 | 2692 | push ds 2693 | push es 2694 | push bx 2695 | push ax 2696 | 2697 | push cs 2698 | push cs 2699 | pop ds 2700 | pop es 2701 | 2702 | mov bx, timetable 2703 | 2704 | extended_get_rtc 2705 | 2706 | mov ax, 0x1900 2707 | mov cx, [tm_year] 2708 | call hex_to_bcd 2709 | mov cx, ax 2710 | push cx 2711 | 2712 | mov ax, 1 2713 | mov cx, [tm_mon] 2714 | call hex_to_bcd 2715 | mov dh, al 2716 | 2717 | mov ax, 0 2718 | mov cx, [tm_mday] 2719 | call hex_to_bcd 2720 | mov dl, al 2721 | 2722 | pop cx 2723 | pop ax 2724 | pop bx 2725 | pop es 2726 | pop ds 2727 | 2728 | jmp reach_stack_clc 2729 | 2730 | int1a_init: 2731 | 2732 | jmp reach_stack_clc 2733 | 2734 | ; ************************* INT 1Ch - the other timer interrupt 2735 | 2736 | int1c: 2737 | 2738 | iret 2739 | 2740 | ; ************************* INT 1Eh - diskette parameter table 2741 | 2742 | int1e: 2743 | 2744 | db 0xdf ; Step rate 2ms, head unload time 240ms 2745 | db 0x02 ; Head load time 4 ms, non-DMA mode 0 2746 | db 0x25 ; Byte delay until motor turned off 2747 | db 0x02 ; 512 bytes per sector 2748 | int1e_spt db 18 ; 18 sectors per track (1.44MB) 2749 | db 0x1B ; Gap between sectors for 3.5" floppy 2750 | db 0xFF ; Data length (ignored) 2751 | db 0x54 ; Gap length when formatting 2752 | db 0xF6 ; Format filler byte 2753 | db 0x0F ; Head settle time (1 ms) 2754 | db 0x08 ; Motor start time in 1/8 seconds 2755 | 2756 | ; ************************* INT 41h - hard disk parameter table 2757 | 2758 | int41: 2759 | 2760 | int41_max_cyls dw 0 2761 | int41_max_heads db 0 2762 | dw 0 2763 | dw 0 2764 | db 0 2765 | db 11000000b 2766 | db 0 2767 | db 0 2768 | db 0 2769 | dw 0 2770 | int41_max_sect db 0 2771 | db 0 2772 | 2773 | ; ************************* ROM configuration table 2774 | 2775 | rom_config dw 16 ; 16 bytes following 2776 | db 0xfe ; Model 2777 | db 'A' ; Submodel 2778 | db 'C' ; BIOS revision 2779 | db 0b00100000 ; Feature 1 2780 | db 0b00000000 ; Feature 2 2781 | db 0b00000000 ; Feature 3 2782 | db 0b00000000 ; Feature 4 2783 | db 0b00000000 ; Feature 5 2784 | db 0, 0, 0, 0, 0, 0 2785 | 2786 | ; Internal state variables 2787 | 2788 | num_disks dw 0 ; Number of disks present 2789 | hd_secs_hi dw 0 ; Total sectors on HD (high word) 2790 | hd_secs_lo dw 0 ; Total sectors on HD (low word) 2791 | hd_max_sector dw 0 ; Max sector number on HD 2792 | hd_max_track dw 0 ; Max track number on HD 2793 | hd_max_head dw 0 ; Max head number on HD 2794 | drive_tracks_temp dw 0 2795 | drive_sectors_temp dw 0 2796 | drive_heads_temp dw 0 2797 | drive_num_temp dw 0 2798 | boot_state db 0 2799 | cga_refresh_reg db 0 2800 | 2801 | ; Default interrupt handlers 2802 | 2803 | int0: 2804 | int1: 2805 | int2: 2806 | int3: 2807 | int4: 2808 | int5: 2809 | int6: 2810 | intb: 2811 | intc: 2812 | intd: 2813 | inte: 2814 | intf: 2815 | int18: 2816 | int1b: 2817 | int1d: 2818 | 2819 | iret 2820 | 2821 | ; ************ Function call library ************ 2822 | 2823 | ; Hex to BCD routine. Input is AX in hex (can be 0), and adds CX in hex to it, forming a BCD output in AX. 2824 | 2825 | hex_to_bcd: 2826 | 2827 | push bx 2828 | 2829 | jcxz h2bfin 2830 | 2831 | h2bloop: 2832 | 2833 | inc ax 2834 | 2835 | ; First process the low nibble of AL 2836 | mov bh, al 2837 | and bh, 0x0f 2838 | cmp bh, 0x0a 2839 | jne c1 2840 | add ax, 0x0006 2841 | 2842 | ; Then the high nibble of AL 2843 | c1: 2844 | mov bh, al 2845 | and bh, 0xf0 2846 | cmp bh, 0xa0 2847 | jne c2 2848 | add ax, 0x0060 2849 | 2850 | ; Then the low nibble of AH 2851 | c2: 2852 | mov bh, ah 2853 | and bh, 0x0f 2854 | cmp bh, 0x0a 2855 | jne c3 2856 | add ax, 0x0600 2857 | 2858 | c3: 2859 | loop h2bloop 2860 | h2bfin: 2861 | pop bx 2862 | ret 2863 | 2864 | ; Takes a number in AL (from 0 to 99), and outputs the value in decimal using extended_putchar_al. 2865 | 2866 | puts_decimal_al: 2867 | 2868 | push ax 2869 | 2870 | aam 2871 | add ax, 0x3030 ; '00' 2872 | 2873 | cmp ah, 0x30 2874 | je pda_2nd ; First digit is zero, so print only 2nd digit 2875 | 2876 | xchg ah, al ; First digit is now in AL 2877 | extended_putchar_al ; Print first digit 2878 | xchg ah, al ; Second digit is now in AL 2879 | 2880 | pda_2nd: 2881 | 2882 | extended_putchar_al ; Print second digit 2883 | 2884 | pop ax 2885 | ret 2886 | 2887 | ; Keyboard adjust buffer head and tail. If either head or the tail are at the end of the buffer, reset them 2888 | ; back to the start, since it is a circular buffer. 2889 | 2890 | kb_adjust_buf: 2891 | 2892 | push ax 2893 | push bx 2894 | 2895 | ; Check to see if the head is at the end of the buffer (or beyond). If so, bring it back 2896 | ; to the start 2897 | 2898 | mov ax, [es:kbbuf_end_ptr-bios_data] 2899 | cmp [es:kbbuf_head-bios_data], ax 2900 | jnge kb_adjust_tail 2901 | 2902 | mov bx, [es:kbbuf_start_ptr-bios_data] 2903 | mov [es:kbbuf_head-bios_data], bx 2904 | 2905 | kb_adjust_tail: 2906 | 2907 | ; Check to see if the tail is at the end of the buffer (or beyond). If so, bring it back 2908 | ; to the start 2909 | 2910 | mov ax, [es:kbbuf_end_ptr-bios_data] 2911 | cmp [es:kbbuf_tail-bios_data], ax 2912 | jnge kb_adjust_done 2913 | 2914 | mov bx, [es:kbbuf_start_ptr-bios_data] 2915 | mov [es:kbbuf_tail-bios_data], bx 2916 | 2917 | kb_adjust_done: 2918 | 2919 | pop bx 2920 | pop ax 2921 | ret 2922 | 2923 | ; Convert CHS disk position (in CH, CL and DH) to absolute sector number in BP:SI 2924 | ; Floppy disks have 512 bytes per sector, 9/18 sectors per track, 2 heads. DH is head number (1 or 0), CH bits 5..0 is 2925 | ; sector number, CL7..6 + CH7..0 is 10-bit cylinder/track number. Hard disks have 512 bytes per sector, but a variable 2926 | ; number of tracks and heads. 2927 | 2928 | chs_to_abs: 2929 | 2930 | push ax 2931 | push bx 2932 | push cx 2933 | push dx 2934 | 2935 | mov [cs:drive_num_temp], dl 2936 | 2937 | ; First, we extract the track number from CH and CL. 2938 | 2939 | push cx 2940 | mov bh, cl 2941 | mov cl, 6 2942 | shr bh, cl 2943 | mov bl, ch 2944 | 2945 | ; Multiply track number (now in BX) by the number of heads 2946 | 2947 | cmp byte [cs:drive_num_temp], 1 ; Floppy disk? 2948 | 2949 | push dx 2950 | 2951 | mov dx, 0 2952 | xchg ax, bx 2953 | 2954 | jne chs_hd 2955 | 2956 | shl ax, 1 ; Multiply by 2 (number of heads on FD) 2957 | push ax 2958 | xor ax, ax 2959 | mov al, [cs:int1e_spt] 2960 | mov [cs:drive_sectors_temp], ax ; Retrieve sectors per track from INT 1E table 2961 | pop ax 2962 | 2963 | jmp chs_continue 2964 | 2965 | chs_hd: 2966 | 2967 | mov bp, [cs:hd_max_head] 2968 | inc bp 2969 | mov [cs:drive_heads_temp], bp 2970 | 2971 | mul word [cs:drive_heads_temp] ; HD, so multiply by computed head count 2972 | 2973 | mov bp, [cs:hd_max_sector] ; We previously calculated maximum HD track, so number of tracks is 1 more 2974 | mov [cs:drive_sectors_temp], bp 2975 | 2976 | chs_continue: 2977 | 2978 | xchg ax, bx 2979 | 2980 | pop dx 2981 | 2982 | xchg dh, dl 2983 | mov dh, 0 2984 | add bx, dx 2985 | 2986 | mov ax, [cs:drive_sectors_temp] 2987 | mul bx 2988 | 2989 | ; Now we extract the sector number (from 1 to 63) - for some reason they start from 1 2990 | 2991 | pop cx 2992 | mov ch, 0 2993 | and cl, 0x3F 2994 | dec cl 2995 | 2996 | add ax, cx 2997 | adc dx, 0 2998 | mov bp, ax 2999 | mov si, dx 3000 | 3001 | ; Now, SI:BP contains offset into disk image file (FD or HD) 3002 | 3003 | pop dx 3004 | pop cx 3005 | pop bx 3006 | pop ax 3007 | ret 3008 | 3009 | ; Clear screen using ANSI codes. Also clear video memory with attribute in BH 3010 | 3011 | clear_screen: 3012 | 3013 | push ax 3014 | 3015 | mov al, 0x1B ; Escape 3016 | extended_putchar_al 3017 | mov al, '[' ; ANSI 3018 | extended_putchar_al 3019 | mov al, 'r' ; Set scrolling window 3020 | extended_putchar_al 3021 | 3022 | mov al, 0x1B ; Escape 3023 | extended_putchar_al 3024 | mov al, '[' ; ANSI 3025 | extended_putchar_al 3026 | mov al, '0' ; Reset attributes 3027 | extended_putchar_al 3028 | mov al, 'm' ; Reset attributes 3029 | extended_putchar_al 3030 | 3031 | push bx 3032 | push cx 3033 | push bp 3034 | push ax 3035 | push es 3036 | 3037 | mov bp, bx ; Convert from CGA to ANSI 3038 | mov cl, 12 3039 | ror bp, cl 3040 | and bp, 7 3041 | mov bl, byte [cs:bp+colour_table] 3042 | add bl, 10 3043 | 3044 | mov al, 0x1B ; Escape 3045 | extended_putchar_al 3046 | mov al, '[' ; ANSI 3047 | extended_putchar_al 3048 | mov al, bl ; Background colour 3049 | call puts_decimal_al 3050 | mov al, 'm' ; Set cursor position command 3051 | extended_putchar_al 3052 | 3053 | mov ax, 0x40 3054 | mov es, ax 3055 | mov byte [es:curpos_x-bios_data], 0 3056 | mov byte [es:crt_curpos_x-bios_data], 0 3057 | mov byte [es:curpos_y-bios_data], 0 3058 | mov byte [es:crt_curpos_y-bios_data], 0 3059 | 3060 | pop es 3061 | pop ax 3062 | pop bp 3063 | pop cx 3064 | pop bx 3065 | 3066 | mov al, 0x1B ; Escape 3067 | extended_putchar_al 3068 | mov al, '[' ; ANSI 3069 | extended_putchar_al 3070 | mov al, '2' ; Clear screen 3071 | extended_putchar_al 3072 | mov al, 'J' 3073 | extended_putchar_al 3074 | 3075 | mov al, 0x1B ; Escape 3076 | extended_putchar_al 3077 | mov al, '[' ; ANSI 3078 | extended_putchar_al 3079 | mov al, '1' ; Cursor row 1 3080 | extended_putchar_al 3081 | mov al, ';' 3082 | extended_putchar_al 3083 | mov al, '1' ; Cursor column 1 3084 | extended_putchar_al 3085 | mov al, 'H' ; Set cursor 3086 | extended_putchar_al 3087 | 3088 | push es 3089 | push di 3090 | push cx 3091 | 3092 | cld 3093 | mov ax, 0xb800 3094 | mov es, ax 3095 | mov di, 0 3096 | mov al, 0 3097 | mov ah, bh 3098 | mov cx, 80*25 3099 | rep stosw 3100 | 3101 | cld 3102 | mov di, 0xc800 3103 | mov es, di 3104 | mov di, 0 3105 | mov cx, 80*25 3106 | rep stosw 3107 | 3108 | cld 3109 | mov di, 0xc000 3110 | mov es, di 3111 | mov di, 0 3112 | mov cx, 80*25 3113 | rep stosw 3114 | 3115 | pop cx 3116 | pop di 3117 | pop es 3118 | 3119 | pop ax 3120 | 3121 | mov byte [cs:vram_dirty], 1 3122 | 3123 | ret 3124 | 3125 | ; Pushes a key press, followed by a key release, event to I/O port 0x60 and calls 3126 | ; INT 9. 3127 | 3128 | keypress_release: 3129 | 3130 | push ax 3131 | 3132 | cmp byte [es:key_now_down-bios_data], 0 3133 | je kpr_no_prev_release 3134 | 3135 | mov al, [es:key_now_down-bios_data] 3136 | add al, 0x80 3137 | call io_key_available 3138 | 3139 | pop ax 3140 | push ax 3141 | 3142 | kpr_no_prev_release: 3143 | 3144 | mov [es:key_now_down-bios_data], al 3145 | call io_key_available 3146 | 3147 | pop ax 3148 | 3149 | ret 3150 | 3151 | ; Sets key available flag on I/O port 0x64, outputs key scan code in AL to I/O port 0x60, and calls INT 9 3152 | 3153 | io_key_available: 3154 | 3155 | push ax 3156 | mov al, 1 3157 | out 0x64, al 3158 | pop ax 3159 | 3160 | out 0x60, al 3161 | int 9 3162 | ret 3163 | 3164 | ; Reaches up into the stack before the end of an interrupt handler, and sets the carry flag 3165 | 3166 | reach_stack_stc: 3167 | 3168 | xchg bp, sp 3169 | or word [bp+4], 1 3170 | xchg bp, sp 3171 | iret 3172 | 3173 | ; Reaches up into the stack before the end of an interrupt handler, and clears the carry flag 3174 | 3175 | reach_stack_clc: 3176 | 3177 | xchg bp, sp 3178 | and word [bp+4], 0xfffe 3179 | xchg bp, sp 3180 | iret 3181 | 3182 | ; Reaches up into the stack before the end of an interrupt handler, and returns with the current 3183 | ; setting of the carry flag 3184 | 3185 | reach_stack_carry: 3186 | 3187 | jc reach_stack_stc 3188 | jmp reach_stack_clc 3189 | 3190 | ; This is the VMEM driver, to support direct video memory access in 80x25 colour CGA mode. 3191 | ; It scans through CGA video memory at address B800:0, and if there is anything there (i.e. 3192 | ; applications are doing direct video memory writes), converts the buffer to a sequence of 3193 | ; ANSI terminal codes to render the screen output. 3194 | ; 3195 | ; Note: this destroys all registers. It is the responsibility of the caller to save/restore 3196 | ; them. 3197 | 3198 | vmem_driver_entry: 3199 | 3200 | cmp byte [cs:in_update], 1 3201 | je just_finish ; If we are already in the middle of an update, skip. Needed for re-entrancy 3202 | 3203 | inc byte [cs:int8_ctr] 3204 | cmp byte [cs:int8_ctr], 8 ; Only do this once every 8 timer ticks 3205 | jne just_finish 3206 | 3207 | gmode_test: 3208 | 3209 | mov byte [cs:int8_ctr], 0 3210 | mov dx, 0x3b8 ; Do not update if in Hercules graphics mode 3211 | in al, dx 3212 | test al, 2 3213 | jz vram_zero_check 3214 | 3215 | just_finish: 3216 | 3217 | ret 3218 | 3219 | vram_zero_check: ; Check if video memory is blank - if so, do nothing 3220 | 3221 | mov byte [cs:in_update], 1 3222 | 3223 | sti 3224 | 3225 | mov bx, 0x40 3226 | mov ds, bx 3227 | 3228 | mov di, [vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3229 | shl di, 1 3230 | push di 3231 | 3232 | mov bx, 0xb800 3233 | mov es, bx 3234 | mov cx, 0x7d0 3235 | mov ax, 0x0700 3236 | 3237 | cld 3238 | repz scasw 3239 | pop di 3240 | je vmem_done ; Nothing has been written to video RAM - no need to update 3241 | 3242 | cmp byte [cs:vram_dirty], 1 ; Cleared screen so always need to update 3243 | je vram_update 3244 | 3245 | mov bx, 0xc800 3246 | mov ds, bx 3247 | mov si, 0 3248 | mov cx, 0x7d0 3249 | 3250 | cld 3251 | repz cmpsw 3252 | jne vram_update ; Video RAM is changed - need to update 3253 | 3254 | mov bx, 0x40 3255 | mov ds, bx 3256 | mov bh, [crt_curpos_y-bios_data] 3257 | mov bl, [crt_curpos_x-bios_data] 3258 | 3259 | cmp bh, [cs:crt_curpos_y_last] 3260 | jne restore_cursor ; Cursor position changed (but nothing else) so update just that 3261 | cmp bl, [cs:crt_curpos_x_last] 3262 | jne restore_cursor 3263 | 3264 | jmp vmem_done 3265 | 3266 | vram_update: 3267 | 3268 | mov bx, 0x40 3269 | mov es, bx 3270 | 3271 | push cs 3272 | pop ds 3273 | 3274 | mov byte [int_curpos_x], 0xff 3275 | mov byte [int_curpos_y], 0xff 3276 | 3277 | cmp byte [es:cursor_visible-bios_data], 0 3278 | je dont_hide_cursor 3279 | 3280 | call ansi_hide_cursor 3281 | 3282 | dont_hide_cursor: 3283 | 3284 | mov byte [last_attrib], 0xff 3285 | 3286 | mov bx, 0x40 3287 | mov es, bx 3288 | 3289 | mov di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3290 | shl di, 1 3291 | sub di, 2 ; Combined offset 3292 | 3293 | mov bx, 0xb800 3294 | mov es, bx 3295 | 3296 | ; Set up the initial cursor coordinates. Since the first thing we do is increment the cursor 3297 | ; position, this initial position is actually off the screen 3298 | 3299 | mov bp, -1 ; Row number 3300 | mov si, 79 ; Column number 3301 | 3302 | disp_loop: 3303 | 3304 | ; Advance to next column 3305 | 3306 | add di, 2 3307 | inc si 3308 | cmp si, 80 3309 | jne cont 3310 | 3311 | ; Column is 80, so set to 0 and advance a line 3312 | 3313 | loop_next_line: 3314 | 3315 | mov si, 0 3316 | inc bp 3317 | 3318 | ; Bottom of the screen reached already? If so, we're done 3319 | 3320 | cmp bp, 25 3321 | je restore_attrib 3322 | 3323 | ; See if this line has changed in video RAM 3324 | 3325 | cmp byte [cs:vram_dirty], 1 3326 | je cont 3327 | 3328 | push si 3329 | push di 3330 | 3331 | mov bx, 0xb800 3332 | mov ds, bx 3333 | mov bx, 0xc800 3334 | mov es, bx 3335 | mov si, di 3336 | 3337 | push es 3338 | mov bx, 0x40 3339 | mov es, bx 3340 | sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3341 | sub di, [es:vmem_offset-bios_data] 3342 | pop es 3343 | 3344 | mov cx, 80 ; One row's worth of characters 3345 | 3346 | cld 3347 | repz cmpsw 3348 | pop di 3349 | pop si 3350 | 3351 | je vmem_next_line ; This line is unchanged in video RAM, so do not update 3352 | 3353 | vmem_copy_buf: 3354 | 3355 | ; Copy the changed line to our double buffer at C800:0 3356 | 3357 | push cx 3358 | push si 3359 | push di 3360 | 3361 | push es 3362 | mov bx, 0x40 3363 | mov es, bx 3364 | mov si, di 3365 | sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register 3366 | sub di, [es:vmem_offset-bios_data] 3367 | pop es 3368 | 3369 | mov cx, 80 ; One row's worth of characters 3370 | cld 3371 | rep movsw 3372 | 3373 | pop di 3374 | pop si 3375 | pop cx 3376 | 3377 | ; We want to start the update at the first character which differs - so calculate its position. 3378 | 3379 | mov bx, 79 3380 | sub bx, cx 3381 | 3382 | add di, bx 3383 | add di, bx 3384 | add si, bx 3385 | 3386 | push ds 3387 | pop es ; Set ES back to B800 3388 | 3389 | jmp cont 3390 | 3391 | vmem_next_line: 3392 | 3393 | add di, 160 3394 | jmp loop_next_line ; Line is unchanged in video RAM 3395 | 3396 | cont: 3397 | push cs 3398 | pop ds 3399 | 3400 | cmp byte [es:di], 0 ; Ignore null characters in video memory 3401 | je disp_loop 3402 | 3403 | mov ax, bp 3404 | mov bx, si 3405 | mov dh, al 3406 | mov dl, bl 3407 | 3408 | cmp dh, [int_curpos_y] ; Same row as the last time? 3409 | jne ansi_set_cur_pos 3410 | push dx 3411 | dec dl 3412 | cmp dl, [int_curpos_x] ; One column to the right since the last time? 3413 | pop dx 3414 | je skip_set_cur_pos 3415 | 3416 | ansi_set_cur_pos: 3417 | 3418 | mov al, 0x1B ; Escape 3419 | extended_putchar_al 3420 | mov al, '[' ; ANSI 3421 | extended_putchar_al 3422 | mov al, dh ; Row number 3423 | inc al 3424 | call puts_decimal_al 3425 | mov al, ';' ; ANSI 3426 | extended_putchar_al 3427 | mov al, dl ; Column number 3428 | inc al 3429 | call puts_decimal_al 3430 | mov al, 'H' ; Set cursor position command 3431 | extended_putchar_al 3432 | 3433 | mov [int_curpos_y], dh 3434 | 3435 | skip_set_cur_pos: 3436 | 3437 | mov [int_curpos_x], dl 3438 | 3439 | mov dl, [es:di+1] 3440 | cmp dl, [last_attrib] 3441 | je skip_attrib 3442 | 3443 | mov [last_attrib], dl 3444 | 3445 | mov al, 0x1B ; Escape 3446 | extended_putchar_al 3447 | mov al, '[' ; ANSI 3448 | extended_putchar_al 3449 | 3450 | mov al, dl 3451 | and al, 8 ; Bright attribute now in AL 3452 | cpu 186 3453 | shr al, 3 3454 | cpu 8086 3455 | 3456 | call puts_decimal_al 3457 | mov al, ';' 3458 | extended_putchar_al 3459 | 3460 | push dx 3461 | 3462 | and dl, 7 ; Foreground colour now in DL 3463 | mov bx, colour_table 3464 | mov al, dl 3465 | xlat 3466 | 3467 | call puts_decimal_al 3468 | mov al, ';' 3469 | extended_putchar_al 3470 | 3471 | pop dx 3472 | 3473 | cpu 186 3474 | shr dl, 4 3475 | cpu 8086 3476 | and dl, 7 ; Background colour now in DL 3477 | 3478 | mov al, dl 3479 | xlat 3480 | 3481 | add al, 10 3482 | call puts_decimal_al 3483 | mov al, 'm' ; Set cursor attribute command 3484 | extended_putchar_al 3485 | 3486 | skip_attrib: 3487 | 3488 | mov al, [es:di] 3489 | 3490 | cmp al, 32 ; Non-printable ASCII? (< 32 decimal) 3491 | jae just_show_it 3492 | 3493 | mov bx, low_ascii_conv 3494 | cs xlat ; Convert to printable representation (mostly spaces) 3495 | 3496 | just_show_it: 3497 | 3498 | extended_putchar_al 3499 | 3500 | jmp disp_loop 3501 | 3502 | restore_attrib: 3503 | 3504 | mov al, 0x1B ; Escape 3505 | extended_putchar_al 3506 | mov al, '[' ; ANSI 3507 | extended_putchar_al 3508 | mov al, '0' ; Reset attributes 3509 | extended_putchar_al 3510 | mov al, 'm' 3511 | extended_putchar_al 3512 | 3513 | restore_cursor: 3514 | 3515 | ; On a real PC, the 6845 CRT cursor position registers take place over the BIOS 3516 | ; Data Area ones. So, if the cursor is not off the screen, set it to the CRT 3517 | ; position. 3518 | 3519 | mov bx, 0x40 3520 | mov ds, bx 3521 | 3522 | mov bh, [crt_curpos_y-bios_data] 3523 | mov bl, [crt_curpos_x-bios_data] 3524 | mov [cs:crt_curpos_y_last], bh 3525 | mov [cs:crt_curpos_x_last], bl 3526 | 3527 | cmp bh, 24 3528 | ja vmem_end_hidden_cursor 3529 | cmp bl, 79 3530 | ja vmem_end_hidden_cursor 3531 | 3532 | mov al, 0x1B ; ANSI 3533 | extended_putchar_al 3534 | mov al, '[' ; ANSI 3535 | extended_putchar_al 3536 | mov al, bh ; Row number 3537 | inc al 3538 | call puts_decimal_al 3539 | mov al, ';' ; ANSI 3540 | extended_putchar_al 3541 | mov al, bl ; Column number 3542 | inc al 3543 | call puts_decimal_al 3544 | mov al, 'H' ; Set cursor position command 3545 | extended_putchar_al 3546 | 3547 | restore_cursor_visible: 3548 | 3549 | cmp byte [cursor_visible-bios_data], 1 3550 | jne vmem_end_hidden_cursor 3551 | 3552 | call ansi_show_cursor 3553 | jmp vmem_done 3554 | 3555 | vmem_end_hidden_cursor: 3556 | 3557 | call ansi_hide_cursor 3558 | 3559 | vmem_done: 3560 | 3561 | mov byte [cs:vram_dirty], 0 3562 | mov byte [cs:in_update], 0 3563 | ret 3564 | 3565 | ; Show cursor using ANSI codes 3566 | 3567 | ansi_show_cursor: 3568 | 3569 | mov al, 0x1B 3570 | extended_putchar_al 3571 | mov al, '[' 3572 | extended_putchar_al 3573 | mov al, '?' 3574 | extended_putchar_al 3575 | mov al, '2' 3576 | extended_putchar_al 3577 | mov al, '5' 3578 | extended_putchar_al 3579 | mov al, 'h' 3580 | extended_putchar_al 3581 | 3582 | ret 3583 | 3584 | ; Hide cursor using ANSI codes 3585 | 3586 | ansi_hide_cursor: 3587 | 3588 | mov al, 0x1B 3589 | extended_putchar_al 3590 | mov al, '[' 3591 | extended_putchar_al 3592 | mov al, '?' 3593 | extended_putchar_al 3594 | mov al, '2' 3595 | extended_putchar_al 3596 | mov al, '5' 3597 | extended_putchar_al 3598 | mov al, 'l' 3599 | extended_putchar_al 3600 | 3601 | ret 3602 | 3603 | ; **************************************************************************************** 3604 | ; That's it for the code. Now, the data tables follow. 3605 | ; **************************************************************************************** 3606 | 3607 | ; Standard PC-compatible BIOS data area - to copy to 40:0 3608 | 3609 | bios_data: 3610 | 3611 | com1addr dw 0 3612 | com2addr dw 0 3613 | com3addr dw 0 3614 | com4addr dw 0 3615 | lpt1addr dw 0 3616 | lpt2addr dw 0 3617 | lpt3addr dw 0 3618 | lpt4addr dw 0 3619 | equip dw 0b0000000000100001 3620 | ;equip dw 0b0000000100100001 3621 | db 0 3622 | memsize dw 0x280 3623 | db 0 3624 | db 0 3625 | keyflags1 db 0 3626 | keyflags2 db 0 3627 | db 0 3628 | kbbuf_head dw kbbuf-bios_data 3629 | kbbuf_tail dw kbbuf-bios_data 3630 | kbbuf: times 32 db 'X' 3631 | drivecal db 0 3632 | diskmotor db 0 3633 | motorshutoff db 0x07 3634 | disk_laststatus db 0 3635 | times 7 db 0 3636 | vidmode db 0x03 3637 | vid_cols dw 80 3638 | page_size dw 0x1000 3639 | dw 0 3640 | curpos_x db 0 3641 | curpos_y db 0 3642 | times 7 dw 0 3643 | cur_v_end db 7 3644 | cur_v_start db 6 3645 | disp_page db 0 3646 | crtport dw 0x3d4 3647 | db 10 3648 | db 0 3649 | times 5 db 0 3650 | clk_dtimer dd 0 3651 | clk_rollover db 0 3652 | ctrl_break db 0 3653 | soft_rst_flg dw 0x1234 3654 | db 0 3655 | num_hd db 0 3656 | db 0 3657 | db 0 3658 | dd 0 3659 | dd 0 3660 | kbbuf_start_ptr dw 0x001e 3661 | kbbuf_end_ptr dw 0x003e 3662 | vid_rows db 25 ; at 40:84 3663 | db 0 3664 | db 0 3665 | vidmode_opt db 0 ; 0x70 3666 | db 0 ; 0x89 3667 | db 0 ; 0x51 3668 | db 0 ; 0x0c 3669 | db 0 3670 | db 0 3671 | db 0 3672 | db 0 3673 | db 0 3674 | db 0 3675 | db 0 3676 | db 0 3677 | db 0 3678 | db 0 3679 | db 0 3680 | kb_mode db 0 3681 | kb_led db 0 3682 | db 0 3683 | db 0 3684 | db 0 3685 | db 0 3686 | boot_device db 0 3687 | crt_curpos_x db 0 3688 | crt_curpos_y db 0 3689 | key_now_down db 0 3690 | next_key_fn db 0 3691 | cursor_visible db 1 3692 | escape_flag_last db 0 3693 | next_key_alt db 0 3694 | escape_flag db 0 3695 | notranslate_flg db 0 3696 | this_keystroke db 0 3697 | this_keystroke_ext db 0 3698 | timer0_freq dw 0xffff ; PIT channel 0 (55ms) 3699 | timer2_freq dw 0 ; PIT channel 2 3700 | cga_vmode db 0 3701 | vmem_offset dw 0 ; Video RAM offset 3702 | ending: times (0xff-($-com1addr)) db 0 3703 | 3704 | ; Keyboard scan code tables 3705 | 3706 | a2scan_tbl db 0xFF, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x0E, 0x0F, 0x24, 0x25, 0x26, 0x1C, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x39, 0x02, 0x28, 0x04, 0x05, 0x06, 0x08, 0x28, 0x0A, 0x0B, 0x09, 0x0D, 0x33, 0x0C, 0x34, 0x35, 0x0B, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x27, 0x27, 0x33, 0x0D, 0x34, 0x35, 0x03, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x1A, 0x2B, 0x1B, 0x07, 0x0C, 0x29, 0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, 0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, 0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, 0x15, 0x2C, 0x1A, 0x2B, 0x1B, 0x29, 0x0E 3707 | a2shift_tbl db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0 3708 | 3709 | ; Interrupt vector table - to copy to 0:0 3710 | 3711 | int_table dw int0 3712 | dw 0xf000 3713 | dw int1 3714 | dw 0xf000 3715 | dw int2 3716 | dw 0xf000 3717 | dw int3 3718 | dw 0xf000 3719 | dw int4 3720 | dw 0xf000 3721 | dw int5 3722 | dw 0xf000 3723 | dw int6 3724 | dw 0xf000 3725 | dw int7 3726 | dw 0xf000 3727 | dw int8 3728 | dw 0xf000 3729 | dw int9 3730 | dw 0xf000 3731 | dw inta 3732 | dw 0xf000 3733 | dw intb 3734 | dw 0xf000 3735 | dw intc 3736 | dw 0xf000 3737 | dw intd 3738 | dw 0xf000 3739 | dw inte 3740 | dw 0xf000 3741 | dw intf 3742 | dw 0xf000 3743 | dw int10 3744 | dw 0xf000 3745 | dw int11 3746 | dw 0xf000 3747 | dw int12 3748 | dw 0xf000 3749 | dw int13 3750 | dw 0xf000 3751 | dw int14 3752 | dw 0xf000 3753 | dw int15 3754 | dw 0xf000 3755 | dw int16 3756 | dw 0xf000 3757 | dw int17 3758 | dw 0xf000 3759 | dw int18 3760 | dw 0xf000 3761 | dw int19 3762 | dw 0xf000 3763 | dw int1a 3764 | dw 0xf000 3765 | dw int1b 3766 | dw 0xf000 3767 | dw int1c 3768 | dw 0xf000 3769 | dw int1d 3770 | dw 0xf000 3771 | dw int1e 3772 | 3773 | itbl_size dw $-int_table 3774 | 3775 | ; Conversion from CGA video memory colours to ANSI colours 3776 | 3777 | colour_table db 30, 34, 32, 36, 31, 35, 33, 37 3778 | 3779 | ; Conversion from non-printable low ASCII to printable 3780 | 3781 | low_ascii_conv db ' ', 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, '><|!|$', 250, '|^v><--^v' 3782 | 3783 | ; Conversion from UNIX cursor keys/SDL keycodes to scancodes 3784 | 3785 | unix_cursor_xlt db 0x48, 0x50, 0x4d, 0x4b 3786 | 3787 | ; Conversion from SDL keycodes to Home/End/PgUp/PgDn scancodes 3788 | 3789 | pgup_pgdn_xlt db 0x47, 0x4f, 0x49, 0x51 3790 | 3791 | ; Internal variables for VMEM driver 3792 | 3793 | int8_ctr db 0 3794 | in_update db 0 3795 | vram_dirty db 0 3796 | last_attrib db 0 3797 | int_curpos_x db 0 3798 | int_curpos_y db 0 3799 | crt_curpos_x_last db 0 3800 | crt_curpos_y_last db 0 3801 | 3802 | ; INT 8 millisecond counter 3803 | 3804 | last_int8_msec dw 0 3805 | last_key_sdl db 0 3806 | 3807 | ; Now follow the tables for instruction decode helping 3808 | 3809 | ; R/M mode tables 3810 | 3811 | rm_mode0_reg1 db 3, 3, 5, 5, 6, 7, 12, 3 3812 | rm_mode012_reg2 db 6, 7, 6, 7, 12, 12, 12, 12 3813 | rm_mode0_disp db 0, 0, 0, 0, 0, 0, 1, 0 3814 | rm_mode0_dfseg db 11, 11, 10, 10, 11, 11, 11, 11 3815 | 3816 | rm_mode12_reg1 db 3, 3, 5, 5, 6, 7, 5, 3 3817 | rm_mode12_disp db 1, 1, 1, 1, 1, 1, 1, 1 3818 | rm_mode12_dfseg db 11, 11, 10, 10, 11, 11, 10, 11 3819 | 3820 | ; Opcode decode tables 3821 | 3822 | xlat_ids db 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 25, 48, 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 25, 26, 9, 9, 9, 9, 7, 7, 27, 28, 9, 9, 9, 9, 7, 7, 27, 28, 9, 9, 9, 9, 7, 7, 27, 29, 9, 9, 9, 9, 7, 7, 27, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 51, 54, 52, 52, 52, 52, 52, 52, 55, 55, 55, 55, 52, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 15, 15, 24, 24, 9, 9, 9, 9, 10, 10, 10, 10, 16, 16, 16, 16, 16, 16, 16, 16, 30, 31, 32, 53, 33, 34, 35, 36, 11, 11, 11, 11, 17, 17, 18, 18, 47, 47, 17, 17, 17, 17, 18, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 12, 19, 19, 37, 37, 20, 20, 49, 50, 19, 19, 38, 39, 40, 19, 12, 12, 12, 12, 41, 42, 43, 44, 53, 53, 53, 53, 53, 53, 53, 53, 13, 13, 13, 13, 21, 21, 22, 22, 14, 14, 14, 14, 21, 21, 22, 22, 53, 0, 23, 23, 53, 45, 6, 6, 46, 46, 46, 46, 46, 46, 5, 5 3823 | ex_data db 0, 0, 0, 0, 0, 0, 8, 8, 1, 1, 1, 1, 1, 1, 9, 36, 2, 2, 2, 2, 2, 2, 10, 10, 3, 3, 3, 3, 3, 3, 11, 11, 4, 4, 4, 4, 4, 4, 8, 0, 5, 5, 5, 5, 5, 5, 9, 1, 6, 6, 6, 6, 6, 6, 10, 2, 7, 7, 7, 7, 7, 7, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 16, 22, 0, 0, 0, 0, 1, 1, 0, 255, 48, 2, 0, 0, 0, 0, 255, 255, 40, 11, 3, 3, 3, 3, 3, 3, 3, 3, 43, 43, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 21, 0, 0, 2, 40, 21, 21, 80, 81, 92, 93, 94, 95, 0, 0 3824 | std_flags db 3, 3, 3, 3, 3, 3, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 5, 5, 5, 5, 5, 5, 0, 1, 3, 3, 3, 3, 3, 3, 0, 1, 5, 5, 5, 5, 5, 5, 0, 1, 3, 3, 3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 3825 | base_size db 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 0, 0, 2, 2, 2, 2, 4, 1, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2 3826 | i_w_adder db 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 3827 | i_mod_adder db 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1 3828 | 3829 | flags_mult db 0, 2, 4, 6, 7, 8, 9, 10, 11 3830 | 3831 | jxx_dec_a db 48, 40, 43, 40, 44, 41, 49, 49 3832 | jxx_dec_b db 49, 49, 49, 43, 49, 49, 49, 43 3833 | jxx_dec_c db 49, 49, 49, 49, 49, 49, 44, 44 3834 | jxx_dec_d db 49, 49, 49, 49, 49, 49, 48, 48 3835 | 3836 | parity db 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 3837 | 3838 | ; This is the format of the 36-byte tm structure, returned by the emulator's RTC query call 3839 | 3840 | timetable: 3841 | 3842 | tm_sec equ $ 3843 | tm_min equ $+4 3844 | tm_hour equ $+8 3845 | tm_mday equ $+12 3846 | tm_mon equ $+16 3847 | tm_year equ $+20 3848 | tm_wday equ $+24 3849 | tm_yday equ $+28 3850 | tm_dst equ $+32 3851 | tm_msec equ $+36 --------------------------------------------------------------------------------