From 856dd9dcedd445fef310546db4ed600798df9036 Mon Sep 17 00:00:00 2001 From: Cody Boone Ferguson <53008573+xexyl@users.noreply.github.com> Date: Mon, 9 Oct 2023 03:18:17 -0700 Subject: [PATCH] Add alt code to 2013/cable3 + other fixes The alt code _should_ (keyword :-) ) run in Windows. This code is based on the author's remarks. It is done by (at the compiler line) undefining KB and defining it in the source code (cable3.alt.c) as the author suggested. The cable3.c now #includes unistd.h: this is because the author suggested in some systems it might be necessary though the example the author gave no longer exists for current versions (the last 32-bit macOS to support 32-bit binaries was version 0.14 Mojave and even that was 64-bit and Mojave was released in 2018 with a new version released each year since then). This does trigger a warning in macOS but we now silence that warning as well. The runme script now passes shellcheck (make '[ .. ]' '[[ .. ]]'). Format fixes in README.md including a few links where this seemed like a good idea (for instance websites). Updated .gitignore: added cable3.alt. As as of yesterday there is no need to #undef localtime any more (if there ever was: the fix I made to let it compile did not in the end need it but it was the first thing I had done so I kept it in) this has been removed as well. The screenshots provided by the author have been linked into the README.md file so that they should display in the html version. At least one file was changed in name (in the README.md file) to what I believe is the correct name (it was a non-existent file). Updated bugs.md to refer to a file that is not found that the author referred to. Add bios.asm that author referred to: found at the GitHub repo for the entry which the README.md now links to (was another website that no longer exists). --- 2013/cable3/.gitignore | 1 + 2013/cable3/Makefile | 9 +- 2013/cable3/README.md | 251 ++- 2013/cable3/bios.asm | 3851 ++++++++++++++++++++++++++++++++++++++ 2013/cable3/cable3.alt.c | 28 + 2013/cable3/cable3.c | 2 +- 2013/cable3/runme | 2 +- bugs.md | 10 + thanks-for-fixes.md | 19 +- 9 files changed, 4112 insertions(+), 61 deletions(-) create mode 100644 2013/cable3/bios.asm create mode 100644 2013/cable3/cable3.alt.c diff --git a/2013/cable3/.gitignore b/2013/cable3/.gitignore index ee6fe2bcb..8ee26aab3 100644 --- a/2013/cable3/.gitignore +++ b/2013/cable3/.gitignore @@ -1,3 +1,4 @@ cable3 +cable.alt cable3.orig prog.orig diff --git a/2013/cable3/Makefile b/2013/cable3/Makefile index e5bc1d75a..29691e1aa 100644 --- a/2013/cable3/Makefile +++ b/2013/cable3/Makefile @@ -44,7 +44,7 @@ CSILENCE= -Wno-bitwise-op-parentheses -Wno-bool-operation \ -Wno-incompatible-pointer-types -Wno-logical-not-parentheses \ -Wno-logical-op-parentheses -Wno-parentheses -Wno-shift-op-parentheses \ -Wno-sign-compare -Wno-unsequenced -Wno-unused-parameter \ - -Wno-deprecated-non-prototype -Wno-return-type + -Wno-deprecated-non-prototype -Wno-return-type -Wno-pointer-type-mismatch # Common C compiler warning flags # @@ -123,8 +123,8 @@ DATA= bios fd.img runme sc-ioccc.terminal screenshot_autocad.png \ screenshot_simcity.png screenshot_win3_on_macosx.png TARGET= ${PROG} # -ALT_OBJ= -ALT_TARGET= +ALT_OBJ= ${PROG}.alt.o +ALT_TARGET= ${PROG}.alt ################# @@ -146,6 +146,9 @@ ${PROG}: ${PROG}.c alt: data ${ALT_TARGET} @${TRUE} +${PROG}.alt: ${PROG}.alt.c + ${CC} ${CFLAGS} -UKB $< -o $@ ${LDFLAGS} + # data files # data: ${DATA} diff --git a/2013/cable3/README.md b/2013/cable3/README.md index 8199e12b2..31d18b213 100644 --- a/2013/cable3/README.md +++ b/2013/cable3/README.md @@ -10,6 +10,8 @@ Adrian Cable
make ``` +An alternate version that should compile in Windows/MS Visual Studio is +available. See the [Alternate code](#alternate-code) section below. ## To run: @@ -26,6 +28,17 @@ make NOTE: to quit the program type `QUITEMU`. +### Alternate code: + +An alternate version, which should compile in Windows, is provided. To use try: + + +```sh +make alt +``` + +Use `cable3.alt` as you would `cable3` above. + ## Judges' remarks: @@ -34,8 +47,14 @@ bits). It manages to implement most of the hardware in a 1980's era IBM-PC using a few hundred fewer bits than the total number of transistors used to implement the original 8086 CPU. -If you are using macOS, the included sc-ioccc.terminal configuration file will -correctly display console applications that use ANSI graphics. +If you are using macOS, the included [sc-ioccc.terminal](sc-ioccc.terminal) +configuration file will correctly display console applications that use ANSI +graphics. + +NOTE: the author remarked that in some systems one might need to `#include +`. This has been done in the entry but not the alternate version. This +should not affect the usability of the program but will trigger a warning which +we have disabled in the Makefile. Update: This entry now has its own website: @@ -56,7 +75,8 @@ IBM-compatible PC, consisting of: - 1MB RAM - 8072A 3.5" floppy disk controller (1.44MB/720KB) - Fixed disk controller (supports a single hard drive up to 528MB) -- Hercules graphics card with 720x348 2-color graphics (64KB video RAM), and CGA +- Hercules graphics card with 720x348 2-color graphics (64KB video RAM), and +[CGA](https://en.wikipedia.org/wiki/Color_Graphics_Adapter) 80x25 16-color text mode support - 8253 programmable interval timer (PIT) - 8259 programmable interrupt controller (PIC) @@ -64,24 +84,25 @@ IBM-compatible PC, consisting of: - MC146818 real-time clock - PC speaker -The emulator uses the SDL graphics library for portability, and compiles for -Windows, macOS, Linux and probably most other 32-bit/64-bit systems too. +The emulator uses the [SDL graphics library](https://www.libsdl.org) for +portability, and compiles for Windows, macOS, Linux and probably most other +32-bit/64-bit systems too. If you like living on the edge you can try building the emulator on a big endian machine, and you will get an emulation of a big endian 8086, a rather bizarre and somewhat useless beast. For everyone else, please run the emulator on a little endian machine. -*RULE 2 ABUSE DISCLAIMER* +### RULE 2 ABUSE DISCLAIMER -- `cable3.c` is 4043 bytes in length (half an 8086) +- [cable3.c](cable3.c) is 4043 bytes in length (half an 8086). - `iocccsize -i < cable3.c` returns 1977 (the year the 4.77 MHz 8086 CPU was announced) -- Therefore, any suspicions the judges may have regarding rule 2 non-compliance may be well-intentioned but are groundless. +- Therefore, any suspicions the judges may have regarding rule 2 non-compliance +might be well-intentioned but are groundless. - Nonetheless, the author would like to apologise to the judges for the one-big-block-of-code nature of this entry, which turned out to be unavoidable. Hopefully the joys of this entry will make up for its shortcomings. -*/RULE 2 ABUSE DISCLAIMER* ### Why is this entry obfuscated/interesting? @@ -121,15 +142,15 @@ competition (see above). This entry has been tested on Windows (compiled with MS Visual Studio 2010 and 2013), Mac OS X (clang and gcc), and Linux (clang and gcc). The Makefile -supplied is good for Mac OS X, Linux and probably other UNIXes. I have received +supplied is good for Mac OS X, Linux and probably other Unices. I have received reports that the emulator works on Raspberry Pi/Android/ARM (you will need to -compile with -fsigned-char) and iOS. You will need to adjust the Makefile if -your system lacks sdl-config to correctly point to the SDL libraries and header -files. +compile with `-fsigned-char`) and iOS. You will need to adjust the +[Makefile](Makefile) if your system lacks `sdl-config` to correctly point to the +SDL libraries and header files. On UNIX-based systems we can get raw keystrokes using `stty`. However Windows has no `stty`. Therefore the Makefile includes a `-D` option to define a -`"keyboard driver"` KB which as it stands is suitable for UNIXes, but maybe not +"keyboard driver" `KB` which as it stands is suitable for Unices, but maybe not non-UNIX platforms. For example, for Windows/MS Visual Studio, instead of the Makefile definition of KB, use something slightly different - add the following entry to the Preprocessor Definitions list in the Project Properties page: @@ -138,6 +159,8 @@ entry to the Preprocessor Definitions list in the Project Properties page: KB=(kb=H(8),kbhit())&&(r[1190]=getch(),H(7)) ``` +NOTE: this is done in the [Alternate code](#alternate-code). + ### POSIX portability note The code as supplied uses implicit function declarations for POSIX file I/O and @@ -151,13 +174,15 @@ include to the top of the source: #include ``` +NOTE: this has been done in [cable3.c](cable3.c). + ### Usage ```sh ./cable3 bios-image-file floppy-image-file [harddisk-image-file] ``` -PLEASE NOTE that under UNIXes the keyboard must be in raw mode for the emulator +PLEASE NOTE that under Unices the keyboard must be in raw mode for the emulator to work properly. Therefore the emulator is best run from a shell script that looks something like: @@ -191,7 +216,7 @@ Easiest to start with is to try a ready-made 40MB hard disk image containing a whole bunch of software: For the more adventurous, you can start off with (for example) a blank 40MB -image file called hd.img made using e.g. mkfile. Then use: +image file called hd.img made using e.g. Makefile. Then use: ```sh stty cbreak raw -echo min 0 @@ -199,80 +224,171 @@ stty cbreak raw -echo min 0 stty cooked echo ``` -Preparing the hard disk for use in the emulator is done just like a real PC. Boot the emulator, and use FDISK to partition the hard disk. When it's done FDISK will reboot the emulator. Then you can use FORMAT C: and you are done. 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 regular floppy disk images (see "Floppy disk support" below). +Preparing the hard disk for use in the emulator is done just like a real PC. +Boot the emulator, and use `FDISK` to partition the hard disk. When it's done +`FDISK` will reboot the emulator. Then you can use `FORMAT C:` and you are done. The +resulting disk image is in the right format to be mounted on a real Windows PC +using e.g. `OSFMount` (Windows), `hdiutil` (macOS), or `mount` (linux/unix), providing +an easy way to copy files and programs to and from the disk image. Or, you can +install programs from regular floppy disk images (see [Floppy disk +support](#floppy-disk-support) below). ### Keyboard emulation -The emulator simulates an XT-style keyboard controlled by an Intel 8042 chip on I/O port 0x60, generating IRQ1 and then interrupt 9 on each keypress. This is harder than it sounds because a real 8042 returns scan codes rather than the ASCII characters which the C standard I/O functions return. Rather than make the emulator less portable and use ioctl or platform-dependent equivalents to obtain real scan codes from the keyboard, 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 (although don't be surprised if there are issues, for example, with non-QWERTY e.g. international keyboards). +The emulator simulates an XT-style keyboard controlled by an Intel 8042 chip on +I/O port 0x60, generating IRQ1 and then interrupt 9 on each key press. This is +harder than it sounds because a real 8042 returns scan codes rather than the +ASCII characters which the C standard I/O functions return. Rather than make the +emulator less portable and use `ioctl(2)` or platform-dependent equivalents to obtain +real scan codes from the keyboard, the emulator BIOS does the reverse of a real +PC BIOS and converts ASCII characters to scan codes, simulating depress/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 although this +process normally works seamlessly don't be surprised if there are issues, for +example, with non-QWERTY e.g. international keyboards. -Most of the time you can just type normally, but there are special sequences to get Alt+xxx and Fxxx. +Most of the time you can just type normally, but there are special sequences to +get `Alt+xxx` and `Fxxx`. -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. +To send an `Alt+XXX` key combination, press (`^A` or `Ctrl+A`) then the key, so for example to +type `Alt+F`, press (`^A` / `Ctrl+A`) then `F`. -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. +To send an `Fxx` key, press `^F` / `Ctrl+F` then a number key. For example, to +get the `F4` key, press `^F` / `Ctrl+F` then `4`. To get `F10`, press `^F` / +`Ctrl+F` then `0`. -To send a Page Down key, press Ctrl+F then O. To send a Page Up key, press Ctrl+F then E. Other key combinations are left for the discovery of the user. +To send a `Page Down` key, press `^F` / `Ctrl+F` then `O` (letter O, not digit +zero). To send a `Page Up` key, press `^F` / `Ctrl+F` then `E`. Other key +combinations are left for the discovery of the user. ### Text mode support -The emulator supports both text output via the standard BIOS interrupt 0x10 interface, and also direct video memory access (one page, 4KB video RAM at segment B800) in 80x25 CGA 16-color text mode. +The emulator supports both text output via the standard BIOS interrupt 0x10 +interface, and also direct video memory access (one page, 4KB video RAM at +segment `B800`) in `80x25 CGA 16-color` text mode. -BIOS text output calls are converted to simple writes to stdout. Direct video memory accesses for the 80x25 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. +BIOS text output calls are converted to simple writes to `stdout`. Direct video +memory accesses for the `80x25 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 might be unusable. -Most CGA I/O ports are not supported except for the CGA refresh register at 0x3DA, which some applications use for timing or synchronisation. +Most `CGA I/O` ports are not supported except for the `CGA refresh` register at +`0x3DA`, which some applications use for timing or synchronisation. -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 (e.g. on Mac OS X there is a freeware font called Perfect DOS VGA 437 which does the trick). +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 (e.g. on Mac OS X there is a +freeware font called [Perfect DOS VGA +437](https://www.dafont.com/perfect-dos-vga-437.font) which does the trick). -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. +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. ### Graphics mode support -Hercules 720x348 monochrome graphics mode emulation is implemented using SDL. Most Hercules features are supported via the normal I/O interface on ports 0x3B8 and 0x3BA including video memory bank switching (segments B000/B800), which some games use for double-buffered graphics. CGA graphics modes are not supported. +[Hercules 720x348 monochrome graphics +mode](https://en.wikipedia.org/wiki/Hercules_Graphics_Card) emulation is +implemented using SDL. Most Hercules features are supported via the normal I/O +interface on ports `0x3B8` and `0x3BA` including video memory bank switching +(segments `B000`/`B800`), which some games use for double-buffered graphics. CGA +graphics modes are not supported. -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). Including code to redirect keystrokes from the SDL window to the main terminal window would have busted the IOCCC size limits, so you need to keep the main emulator terminal window in focus at all times even when you are doing graphics (sounds a little odd but you will get used to it). +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). Including +code to redirect keystrokes from the SDL window to the main terminal window +would have busted the IOCCC size limits, so you need to keep the main emulator +terminal window in focus at all times even when you are doing graphics (sounds a +little odd but you will get used to it). -On UNIXes, SDL will automatically output graphics via X11 if the DISPLAY environment variable is set up. +On Unices, SDL will automatically output graphics via X11 if the DISPLAY +environment variable is set up. ### Dual graphics card support -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. +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. ### BIOS -Like a real PC, the emulator needs a BIOS to do anything useful. Here we use a custom BIOS, written from scratch specifically for the emulator. Source code for the BIOS (written in 8086 assembly language) which compiles with the freely-available NASM x86 assembler is available from the author on request. +Like a real PC, the emulator needs a BIOS to do anything useful. Here we use a +custom BIOS, written from scratch specifically for the emulator. Source code for +the BIOS (written in 8086 assembly language) which compiles with the +freely-available NASM x86 assembler is in [bios.asm](bios.asm). -The BIOS 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. +The BIOS 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. ### CPU and memory emulation -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 dual-bank graphics memory at B000/B800:0, and BIOS at F000:100. 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. +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 dual-bank graphics memory at `B000`/`B800:0`, and BIOS at +`F000:100`. 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. -CPU supports the full 8086/186 instruction set. Due to the complexities of the 8086's arbitrary-length instruction decoding and flags, 8086 instructions are first converted to a simpler intermediate format before being executed. This conversion, along with instruction lengths and how each instruction modifies the flags, is assisted by some lookup tables which form part of the BIOS binary. +CPU supports the full 8086/186 instruction set. Due to the complexities of the +8086's arbitrary-length instruction decoding and flags, 8086 instructions are +first converted to a simpler intermediate format before being executed. This +conversion, along with instruction lengths and how each instruction modifies the +flags, is assisted by some lookup tables which form part of the BIOS binary. The CPU also implements some "special" two-byte opcodes to help the emulator talk with the outside world. These are: - 0F 00 - output character in register AL to terminal - 0F 01 - write real-time clock data (as returned by localtime) to memory location ES:BX - 0F 02 - 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) - 0F 03 - 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 -Emulator exit is triggered if CS:IP == 0:0 (which would be nonsensical in real software since this is where the interrupt vector table lives). The supplied Dos6.22.img disk includes a small program QUITEMU.COM which contains a single JMP 0:0 instruction, to allow the user to easily quit the emulator without shutting down the terminal. +- `0F 00` - output character in register AL to terminal. +- `0F 01` - write real-time clock data (as returned by `localtime()`) to memory +location `ES:BX`. +- `0F 02` - 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). +- `0F 03` - 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`. + +Emulator exit is triggered if `CS:IP == 0:0` (which would be nonsensical in real +software since this is where the interrupt vector table lives). The supplied +`fd.img` disk includes a small program `QUITEMU.COM` which contains a single +`JMP 0:0` instruction, to allow the user to easily quit the emulator without +shutting down the terminal. ### Floppy disk support -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). +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). -If you want to install your own software from 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 specify on the command line. Don't forget to put your original boot disk back at the end! +If you want to install your own software from floppy images (downloaded from +e.g. [Vetusware](https://vetusware.com)), the easiest way to "change disks" is +to copy each disk image in turn over the floppy image file you specify on the +command line. Don't forget to put your original boot disk back at the end! ### Hard disk support -Supports up to 1023 cylinders, 63 sectors per track, 63 heads for disks up to 528MB. +Supports up to 1023 cylinders, 63 sectors per track, 63 heads for disks up to +528MB. -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. +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. -The emulator uses a particularly dumb 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. +The emulator uses a particularly dumb 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. -Note that unlike a real PC, the emulator cannot boot from a hard disk (image). Therefore, you will always need to use a bootable floppy image, even if after boot everything runs from the HD. +Note that unlike a real PC, the emulator cannot boot from a hard disk (image). +Therefore, you will always need to use a bootable floppy image, even if after +boot everything runs from the HD. ### Mouse @@ -280,13 +396,21 @@ No mouse is emulated. ### Real-time clock -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. +Reading the [RTC](https://en.wikipedia.org/wiki/Real-time_clock) (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. ### Timers -A countdown timer on I/O port 0x40 is simulated in a broken way which is good enough for most software. On a real PC this has a default period of 55ms and is programmable. No programmability is supported in the emulator and the period may be about right or completely wrong depending on the actual speed of your computer. +A countdown timer on I/O port `0x40` is simulated in a broken way which is good +enough for most software. On a real PC this has a default period of 55ms and is +programmable. No programmability is supported in the emulator and the period may +be about right or completely wrong depending on the actual speed of your +computer. -On a real PC, IRQ0 and interrupt 8 are fired every 55ms. The emulator tries to do the same but again, the delay period is uncalibrated so you get what you get. +On a real PC, IRQ 0 and interrupt 8 are fired every 55ms. The emulator tries to +do the same but again, the delay period is not calibrated so you get what you +get. ### PC speaker @@ -294,13 +418,36 @@ Beeps only, through the console. ### Software supported -The emulator will run practically any software a real PC (of the spec listed at the top of this file) can. The author has tested a number of OSes/GUIs (MS-DOS 6.22, FreeDOS 0.82pl3, Windows 3.0, DESQview 2.8), professional software (Lotus 1-2-3 2.4 and AsEasyAs 5.7 for DOS, Excel 2.1 for Windows, AutoCAD 2.5, WordStar 4), programming languages (QBASIC, GWBASIC, Turbo C++), games (Carrier Command, Police Quest, and a bunch of freeware Windows games), and diagnostic/benchmark software (Manifest, Microsoft MSD, InfoSpot, CheckIt) and all of them run well. +The emulator will run practically any software a real PC (of the spec listed at +the top of this file) can. The author has tested a number of OSes/GUIs (MS-DOS +6.22, FreeDOS 0.82pl3, Windows 3.0, DESQview 2.8), professional software (Lotus +1-2-3 2.4 and AsEasyAs 5.7 for DOS, Excel 2.1 for Windows, AutoCAD 2.5, WordStar +4), programming languages (QBASIC, GWBASIC, Turbo C++), games (Carrier Command, +Police Quest, and a bunch of freeware Windows games), and diagnostic/benchmark +software (Manifest, Microsoft MSD, InfoSpot, CheckIt) and all of them run well. + +Screenshots of some of these applications running (on Mac OS X) are provided for +the impatient. + +![AutoCAD](screenshot_autocad.png) + +![Flight Simulator 4](screenshot_flightsim4.png) + +![Lotus 123](screenshot_lotus123.png) + +![QBASIC](screenshot_qbasic.png) + +![SimCity](screenshot_simcity.png) + +![Windows 3 on macOS](screenshot_win3_on_macosx.png) -Screenshots of some of these applications running (on Mac OS X) are provided for the impatient. ### Compiler warnings -A lot of compiler warnings are produced by clang. Missing type specifiers, control reaching the end of functions without returning values, incompatible pointer type assignments, and some precedence warnings, all necessary to keep the source size down. Other compilers are likely to produce similar warnings. +A lot of compiler warnings are produced by clang. Missing type specifiers, +control reaching the end of functions without returning values, incompatible +pointer type assignments, and some precedence warnings, all necessary to keep +the source size down. Other compilers are likely to produce similar warnings. ## Copyright and CC BY-SA 4.0 License: diff --git a/2013/cable3/bios.asm b/2013/cable3/bios.asm new file mode 100644 index 000000000..93eca8521 --- /dev/null +++ b/2013/cable3/bios.asm @@ -0,0 +1,3851 @@ +; BIOS source for 8086tiny IBM PC emulator (revision 1.21 and above). Compiles with NASM. +; Copyright 2013-14, Adrian Cable (adrian.cable@gmail.com) - http://www.megalith.co.uk/8086tiny +; +; Revision 1.61 +; +; This work is licensed under the MIT License. See included LICENSE.TXT. + + cpu 8086 + +; Here we define macros for some custom instructions that help the emulator talk with the outside +; world. They are described in detail in the hint.html file, which forms part of the emulator +; distribution. + +%macro extended_putchar_al 0 + db 0x0f, 0x00 +%endmacro + +%macro extended_get_rtc 0 + db 0x0f, 0x01 +%endmacro + +%macro extended_read_disk 0 + db 0x0f, 0x02 +%endmacro + +%macro extended_write_disk 0 + db 0x0f, 0x03 +%endmacro + +org 100h ; BIOS loads at offset 0x0100 + +main: + + jmp bios_entry + +; Here go pointers to the different data tables used for instruction decoding + + dw rm_mode12_reg1 ; Table 0: R/M mode 1/2 "register 1" lookup + dw rm_mode012_reg2 ; Table 1: R/M mode 1/2 "register 2" lookup + dw rm_mode12_disp ; Table 2: R/M mode 1/2 "DISP multiplier" lookup + dw rm_mode12_dfseg ; Table 3: R/M mode 1/2 "default segment" lookup + dw rm_mode0_reg1 ; Table 4: R/M mode 0 "register 1" lookup + dw rm_mode012_reg2 ; Table 5: R/M mode 0 "register 2" lookup + dw rm_mode0_disp ; Table 6: R/M mode 0 "DISP multiplier" lookup + dw rm_mode0_dfseg ; Table 7: R/M mode 0 "default segment" lookup + dw xlat_ids ; Table 8: Translation of raw opcode index ("Raw ID") to function number ("Xlat'd ID") + dw ex_data ; Table 9: Translation of Raw ID to Extra Data + 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) + dw parity ; Table 11: Parity flag loop-up table (256 entries) + dw base_size ; Table 12: Translation of Raw ID to base instruction size (bytes) + dw i_w_adder ; Table 13: Translation of Raw ID to i_w size adder yes/no + dw i_mod_adder ; Table 14: Translation of Raw ID to i_mod size adder yes/no + dw jxx_dec_a ; Table 15: Jxx decode table A + dw jxx_dec_b ; Table 16: Jxx decode table B + dw jxx_dec_c ; Table 17: Jxx decode table C + dw jxx_dec_d ; Table 18: Jxx decode table D + dw flags_mult ; Table 19: FLAGS multipliers + +; These values (BIOS ID string, BIOS date and so forth) go at the very top of memory + +biosstr db '8086tiny BIOS Revision 1.61!', 0, 0 ; Why not? +mem_top db 0xea, 0, 0x01, 0, 0xf0, '03/08/14', 0, 0xfe, 0 + +bios_entry: + + ; Set up initial stack to F000:F000 + + mov sp, 0xf000 + mov ss, sp + + push cs + pop es + + push ax + + ; The emulator requires a few control registers in memory to always be zero for correct + ; instruction decoding (in particular, register look-up operations). These are the + ; emulator's zero segment (ZS) and always-zero flag (XF). Because the emulated memory + ; space is uninitialised, we need to be sure these values are zero before doing anything + ; else. The instructions we need to use to set them must not rely on look-up operations. + ; So e.g. MOV to memory is out but string operations are fine. + + cld + + xor ax, ax + mov di, 24 + stosw ; Set ZS = 0 + mov di, 49 + stosb ; Set XF = 0 + + ; Now we can do whatever we want! DL starts off being the boot disk. + + mov [cs:boot_device], dl + + ; Set up Hercules graphics support. We start with the adapter in text mode + + push dx + + mov dx, 0x3b8 + mov al, 0 + out dx, al ; Set Hercules support to text mode + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) + out dx, al + + pop dx + + pop ax + + ; Check cold boot/warm boot. We initialise disk parameters on cold boot only + + cmp byte [cs:boot_state], 0 ; Cold boot? + jne boot + + mov byte [cs:boot_state], 1 ; Set flag so next boot will be warm boot + + ; First, set up the disk subsystem. Only do this on the very first startup, when + ; the emulator sets up the CX/AX registers with disk information. + + ; Compute the cylinder/head/sector count for the HD disk image, if present. + ; Total number of sectors is in CX:AX, or 0 if there is no HD image. First, + ; we put it in DX:CX. + + mov dx, cx + mov cx, ax + + mov [cs:hd_secs_hi], dx + mov [cs:hd_secs_lo], cx + + cmp cx, 0 + je maybe_no_hd + + mov word [cs:num_disks], 2 + jmp calc_hd + +maybe_no_hd: + + cmp dx, 0 + je no_hd + + mov word [cs:num_disks], 2 + jmp calc_hd + +no_hd: + + mov word [cs:num_disks], 1 + +calc_hd: + + mov ax, cx + mov word [cs:hd_max_track], 1 + mov word [cs:hd_max_head], 1 + + cmp dx, 0 ; More than 63 total sectors? If so, we have more than 1 track. + ja sect_overflow + cmp ax, 63 + ja sect_overflow + + mov [cs:hd_max_sector], ax + jmp calc_heads + +sect_overflow: + + mov cx, 63 ; Calculate number of tracks + div cx + mov [cs:hd_max_track], ax + mov word [cs:hd_max_sector], 63 + +calc_heads: + + mov dx, 0 ; More than 1024 tracks? If so, we have more than 1 head. + mov ax, [cs:hd_max_track] + cmp ax, 1024 + ja track_overflow + + jmp calc_end + +track_overflow: + + mov cx, 1024 + div cx + mov [cs:hd_max_head], ax + mov word [cs:hd_max_track], 1024 + +calc_end: + + ; Convert number of tracks into maximum track (0-based) and then store in INT 41 + ; HD parameter table + + mov ax, [cs:hd_max_head] + mov [cs:int41_max_heads], al + mov ax, [cs:hd_max_track] + mov [cs:int41_max_cyls], ax + mov ax, [cs:hd_max_sector] + mov [cs:int41_max_sect], al + + dec word [cs:hd_max_track] + dec word [cs:hd_max_head] + +; Main BIOS entry point. Zero the flags, and set up registers. + +boot: mov ax, 0 + push ax + popf + + push cs + push cs + pop ds + pop ss + mov sp, 0xf000 + +; Set up the IVT. First we zero out the table + + cld + + xor ax, ax + mov es, ax + xor di, di + mov cx, 512 + rep stosw + +; Then we load in the pointers to our interrupt handlers + + mov di, 0 + mov si, int_table + mov cx, [itbl_size] + rep movsb + +; Set pointer to INT 41 table for hard disk + + mov cx, int41 + mov word [es:4*0x41], cx + mov cx, 0xf000 + mov word [es:4*0x41 + 2], cx + +; Set up last 16 bytes of memory, including boot jump, BIOS date, machine ID byte + + mov ax, 0xffff + mov es, ax + mov di, 0 + mov si, mem_top + mov cx, 16 + rep movsb + +; Set up the BIOS data area + + mov ax, 0x40 + mov es, ax + mov di, 0 + mov si, bios_data + mov cx, 0x100 + rep movsb + +; Clear video memory + + mov ax, 0xb800 + mov es, ax + mov di, 0 + mov cx, 80*25 + mov ax, 0x0700 + rep stosw + +; Clear video memory shadow buffer + + mov ax, 0xc800 + mov es, ax + mov di, 0 + mov cx, 80*25 + mov ax, 0x0700 + rep stosw + +; Set up some I/O ports, between 0 and FFF. Most of them we set to 0xFF, to indicate no device present + + mov dx, 0x61 + mov al, 0 + out dx, al ; Make sure the speaker is off + + mov dx, 0x60 + out dx, al ; No scancode + + mov dx, 0x64 + out dx, al ; No key waiting + + mov dx, 0 + mov al, 0xFF + +next_out: + + inc dx + + cmp dx, 0x40 ; We deal with the PIT channel 0 later + je next_out + cmp dx, 0x42 ; We deal with the PIT channel 2 later + je next_out + cmp dx, 0x3B8 ; We deal with the Hercules port later, too + je next_out + cmp dx, 0x60 ; Keyboard scancode + je next_out + cmp dx, 0x61 ; Sound output + je next_out + cmp dx, 0x64 ; Keyboard status + je next_out + + out dx, al + + cmp dx, 0xFFF + jl next_out + + mov al, 0 + + mov dx, 0x3DA ; CGA refresh port + out dx, al + + mov dx, 0x3BA ; Hercules detection port + out dx, al + + mov dx, 0x3B8 ; Hercules video mode port + out dx, al + + mov dx, 0x3BC ; LPT1 + out dx, al + + mov dx, 0x62 ; PPI - needed for memory parity checks + out dx, al + +; Get initial RTC value + + push cs + pop es + mov bx, timetable + extended_get_rtc + mov ax, [es:tm_msec] + mov [cs:last_int8_msec], ax + +; Read boot sector from FDD, and load it into 0:7C00 + + mov ax, 0 + mov es, ax + + mov ax, 0x0201 + mov dh, 0 + mov dl, [cs:boot_device] + mov cx, 1 + mov bx, 0x7c00 + int 13h + +; Jump to boot sector + + jmp 0:0x7c00 + +; ************************* INT 7h handler - keyboard driver (8086tiny internal) + +int7: ; Whenever the user presses a key, INT 7 is called by the emulator. + ; ASCII character of the keystroke is at 0040:this_keystroke + + push ds + push es + push ax + push bx + push bp + + push cs + pop ds + + mov bx, 0x40 ; Set segment to BIOS data area segment (0x40) + mov es, bx + + ; Retrieve the keystroke + + mov ax, [es:this_keystroke-bios_data] + mov byte [es:this_keystroke+1-bios_data], 0 + + real_key: + + mov byte [cs:last_key_sdl], 0 + + test ah, 4 ; This key doesn't come from SDL + jz check_linux_bksp + + mov byte [es:keyflags1-bios_data], 0 + mov byte [es:keyflags2-bios_data], 0 + + mov byte [cs:last_key_sdl], 1 ; Key down from SDL + + test ah, 0x40 ; Key up + jz sdl_check_specials + + mov byte [cs:last_key_sdl], 2 ; Key up from SDL + + sdl_check_specials: + + mov bx, ax + and bh, 7 ; If key is between 52F and 534 (Shift/Ctrl/Alt), ignore the key state flags + cmp bx, 0x52f + je sdl_just_press_shift + cmp bx, 0x530 + je sdl_just_press_shift + cmp bx, 0x533 + je sdl_just_press_alt + cmp bx, 0x534 + je sdl_just_press_alt + cmp bx, 0x531 + je sdl_just_press_ctrl + cmp bx, 0x532 + je sdl_just_press_ctrl + jmp sdl_check_alt + + sdl_just_press_shift: + + mov al, 0x36 ; Shift + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_just_press_alt: + + mov al, 0x38 ; Alt + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_just_press_ctrl: + + mov al, 0x1d ; Ctrl + and ah, 0x40 ; Key up? + add al, ah + add al, ah + call io_key_available + jmp i2_dne + + sdl_check_alt: + + test ah, 8 ; Alt+something? + jz sdl_no_alt + add byte [es:keyflags1-bios_data], 8 + add byte [es:keyflags2-bios_data], 2 + + sdl_no_alt: + + test ah, 0x20 ; Ctrl+something? + jz sdl_no_ctrl + add byte [es:keyflags1-bios_data], 4 + + sdl_no_ctrl: + + test ah, 0x10 ; Shift+something? + jz sdl_no_mods + add byte [es:keyflags1-bios_data], 1 + + sdl_no_mods: + + and ah, 1 ; We have processed all SDL modifiers, so remove them + + ;cmp ax, 160 ; Alt+Space? + ;jne next_sdl_alt_keys + ;mov al, ' ' + ;mov byte [es:this_keystroke-bios_data], al + + check_sdl_f_keys: + + cmp ax, 0x125 + ja i2_dne ; Unknown key + + cmp ax, 0x11a + jb check_sdl_pgup_pgdn_keys + + sub ax, 0xdf ; F1 - F10 + cmp ax, 0x45 + jb check_sdl_f_keys2 + add ax, 0x12 ; F11 - F12 + + check_sdl_f_keys2: + + mov bh, al + mov al, 0 + jmp sdl_scancode_xlat_done + + check_sdl_pgup_pgdn_keys: + + cmp ax, 0x116 + jb check_sdl_cursor_keys + cmp ax, 0x119 + ja check_sdl_cursor_keys + + sub ax, 0x116 + mov bx, pgup_pgdn_xlt + cs xlat + + mov bh, al + mov al, 0 + jmp sdl_scancode_xlat_done + + check_sdl_cursor_keys: + + cmp ax, 0x111 ; SDL cursor keys + jb sdl_process_key ; No special handling for other keys yet + + sub ax, 0x111 + mov bx, unix_cursor_xlt + xlat ; Convert SDL cursor keys to scancode + + mov bh, al + mov al, 0 + mov byte [es:this_keystroke-bios_data], 0 + jmp sdl_scancode_xlat_done + + sdl_process_key: + + cmp ax, 0x100 + jae i2_dne ; Unsupported key + cmp al, 0x7f ; SDL 0x7F backspace? Convert to 0x08 + jne sdl_process_key2 + mov al, 8 + + sdl_process_key2: + + push ax + mov bx, a2scan_tbl ; ASCII to scancode table + xlat + mov bh, al + pop ax ; Scancode in BH, keycode in AL + + sdl_scancode_xlat_done: + + add bh, 0x80 ; Key up scancode + cmp byte [cs:last_key_sdl], 2 ; Key up? + je sdl_not_in_buf + + sub bh, 0x80 ; Key down scancode + + sdl_key_down: + + mov [es:this_keystroke-bios_data], al + + sdl_not_in_buf: + + mov al, bh + call io_key_available + jmp i2_dne + + check_linux_bksp: + + cmp al, 0 ; Null keystroke - ignore + je i2_dne + + cmp al, 0x7f ; Linux code for backspace - change to 8 + jne after_check_bksp + + mov al, 8 + mov byte [es:this_keystroke-bios_data], 8 + + after_check_bksp: + + 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) + je i2_n + + cmp al, 0x01 ; Ctrl+A pressed - this is the sequence for "next key is Alt+" + jne i2_not_alt + + mov byte [es:keyflags1-bios_data], 8 ; Alt flag down + mov byte [es:keyflags2-bios_data], 2 ; Alt flag down + mov al, 0x38 ; Simulated Alt by Ctrl+A prefix? + call io_key_available + + mov byte [es:next_key_alt-bios_data], 1 + jmp i2_dne + + i2_not_alt: + + cmp al, 0x06 ; Ctrl+F pressed - this is the sequence for "next key is Fxx" + jne i2_not_fn + + mov byte [es:next_key_fn-bios_data], 1 + jmp i2_dne + + i2_not_fn: + + cmp byte [es:notranslate_flg-bios_data], 1 ; If no translation mode is on, just pass through the scan code. ASCII key is zero. + mov byte [es:notranslate_flg-bios_data], 0 + jne need_to_translate + + mov byte [es:this_keystroke-bios_data], 0 + jmp after_translate + + need_to_translate: + + 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. + mov byte [es:notranslate_flg-bios_data], 1 + je i2_dne ; Don't add the 0xE0 to the keyboard buffer + + mov byte [es:notranslate_flg-bios_data], 0 + + 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 + jne i2_escnext + + ; If the last key pressed was ESC, then we need to stuff it + cmp byte [es:escape_flag-bios_data], 1 + jne i2_sf + + ; Stuff an ESC character + + mov byte [es:this_keystroke-bios_data], 0x1b + + mov al, 0x01 + call keypress_release + + i2_sf: + + mov byte [es:escape_flag-bios_data], 1 + jmp i2_dne + + i2_escnext: + + ; Check if the last key was an escape character + cmp byte [es:escape_flag-bios_data], 1 + jne i2_noesc + + ; It is, so check if this key is a [ control character + cmp al, '[' ; [ key pressed + je i2_esc + + ; It isn't, so stuff an ESC character plus this key + + mov byte [es:this_keystroke-bios_data], 0x1b + + mov al, 0x01 + call keypress_release + + ; Now actually process this key + mov byte [es:escape_flag-bios_data], 0 + mov al, [es:this_keystroke-bios_data] + jmp i2_noesc + + i2_esc: + + ; Last + this characters are ESC ] - do nothing now, but set escape flag + mov byte [es:escape_flag-bios_data], 2 + jmp i2_dne + + i2_noesc: + + cmp byte [es:escape_flag-bios_data], 2 + jne i2_regular_key + + ; No shifts or Alt for cursor keys + mov byte [es:keyflags1-bios_data], 0 + mov byte [es:keyflags2-bios_data], 0 + + ; Last + this characters are ESC ] xxx - cursor key, so translate and stuff it + sub al, 'A' + mov bx, unix_cursor_xlt + xlat + + mov byte [es:this_keystroke-bios_data], 0 + jmp after_translate + + i2_regular_key: + + mov byte [es:notranslate_flg-bios_data], 0 + + mov bx, a2shift_tbl ; ASCII to shift code table + xlat + + ; Now, BL is 1 if shift is down, 0 otherwise. If shift is down, put a shift down scan code + ; in port 0x60. Then call int 9. Otherwise, put a shift up scan code in, and call int 9. + + push ax + + ; Put shift flags in BIOS, 0040:0017. Add 8 to shift flags if Alt is down. + mov ah, [es:next_key_alt-bios_data] + cpu 186 + shl ah, 3 + cpu 8086 + add al, ah + + cmp byte [es:this_keystroke-bios_data], 0x1A ; Ctrl+A to Ctrl+Z? Then add Ctrl to BIOS key flags + ja i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0 + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0xD ; CR + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0xA ; LF + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0x8 ; Backspace + je i2_no_ctrl + cmp byte [es:this_keystroke-bios_data], 0x9 ; Tab + je i2_no_ctrl + add al, 4 ; Ctrl in key flags + + push ax + mov al, 0x1d ; Ctrl key down + call io_key_available + pop ax + + i2_no_ctrl: + + mov [es:keyflags1-bios_data], al + + cpu 186 + shr ah, 2 + cpu 8086 + mov [es:keyflags2-bios_data], ah + + pop ax + + test al, 1 ; Shift down? + jz i2_n + + mov al, 0x36 ; Right shift down + call io_key_available + + i2_n: + + mov al, [es:this_keystroke-bios_data] + + mov bx, a2scan_tbl ; ASCII to scan code table + xlat + + cmp byte [es:next_key_fn-bios_data], 1 ; Fxx? + jne after_translate + + cmp byte [es:this_keystroke-bios_data], 1 ; Ctrl+F then Ctrl+A outputs code for Ctrl+A + je after_translate + + cmp byte [es:this_keystroke-bios_data], 6 ; Ctrl+F then Ctrl+F outputs code for Ctrl+F + je after_translate + + mov byte [es:this_keystroke-bios_data], 0 ; Fxx key, so zero out ASCII code + add al, 0x39 + + after_translate: + + mov byte [es:escape_flag-bios_data], 0 + mov byte [es:escape_flag_last-bios_data], 0 + + ; If the key is actually an Alt+ key we use an ASCII code of 0 instead of the real value. + + cmp byte [es:next_key_alt-bios_data], 1 + jne skip_ascii_zero + + mov byte [es:this_keystroke-bios_data], 0 + + skip_ascii_zero: + + ; Output key down/key up event (scancode in AL) to keyboard port + call keypress_release + + ; If scan code is not 0xE0, then also send right shift up if necessary + cmp al, 0xe0 + je i2_dne + + test byte [es:keyflags1-bios_data], 1 + jz check_ctrl + + mov al, 0xb6 ; Right shift up + call io_key_available + + check_ctrl: + + test byte [es:keyflags1-bios_data], 4 + jz check_alt + + mov al, 0x9d ; Right Ctrl up + call io_key_available + + check_alt: + + mov al, byte [es:next_key_alt-bios_data] + mov byte [es:next_key_alt-bios_data], 0 + mov byte [es:next_key_fn-bios_data], 0 + + cmp al, 1 + je endalt + + jmp i2_dne + + endalt: + + mov al, 0xb8 ; Left Alt up + call io_key_available + + i2_dne: + + pop bp + pop bx + pop ax + pop es + pop ds + iret + +; ************************* INT 9h handler - keyboard (PC BIOS standard) + +int9: + + push es + push ax + push bx + push bp + + in al, 0x60 + + cmp al, 0x80 ; Key up? + jae no_add_buf + cmp al, 0x36 ; Shift? + je no_add_buf + cmp al, 0x38 ; Alt? + je no_add_buf + cmp al, 0x1d ; Ctrl? + je no_add_buf + + mov bx, 0x40 + mov es, bx + + mov bh, al + mov al, [es:this_keystroke-bios_data] + + ; Tail of the BIOS keyboard buffer goes in BP. This is where we add new keystrokes + + mov bp, [es:kbbuf_tail-bios_data] + mov byte [es:bp], al ; ASCII code + mov byte [es:bp+1], bh ; Scan code + + ; ESC keystroke is in the buffer now + add word [es:kbbuf_tail-bios_data], 2 + call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large + + no_add_buf: + + mov al, 1 + out 0x64, al + + pop bp + pop bx + pop ax + pop es + + iret + +; ************************* INT Ah handler - timer (8086tiny internal) + +inta: + ; 8086tiny called interrupt 0xA frequently, at a rate dependent on the speed of your computer. + ; This interrupt handler scales down the call rate and calls INT 8 at 18.2 times per second, + ; as per a real PC. + + ; See if there is an ESC waiting from a previous INT 7h. If so, put it in the keyboard buffer + ; (because by now - 1/18.2 secs on - we know it can't be part of an escape key sequence). + ; Also handle CGA refresh register. Also release any keys that are still marked as down. + + push ax + push bx + push dx + push bp + push es + + push cx + push di + push ds + push si + + call vmem_driver_entry ; CGA text mode driver - documented later + + ; Increment 32-bit BIOS timer tick counter, once every 18.2 ms + + push cs + pop es + mov bx, timetable + extended_get_rtc + + mov ax, [cs:tm_msec] + sub ax, [cs:last_int8_msec] + + make_ctr_positive: + + cmp ax, 0 + jge no_add_1000 + + add ax, 1000 + jmp make_ctr_positive + + no_add_1000: + + mov bx, 0x40 + mov es, bx + + mov dx, 0 + mov bx, 1193 + mul bx + + mov bx, [es:timer0_freq-bios_data] + + cmp bx, 0 ; 0 actually means FFFF + jne no_adjust_10000 + + mov bx, 0xffff + + no_adjust_10000: + + div bx ; AX now contains number of timer ticks since last int 8 (DX is remainder) + + cmp ax, 0 + je i8_end + + add word [es:0x6C], ax + adc word [es:0x6E], 0 + +inta_call_int8: + + push ax ; Workaround for CPM-86 - INT 1C destroys AX!! + int 8 + pop ax + + dec ax + cmp ax, 0 + jne inta_call_int8 + + mov ax, [cs:tm_msec] + mov [cs:last_int8_msec], ax + +skip_timer_increment: + + ; If last key was from SDL, don't simulate key up events (SDL will do it for us) + cmp byte [cs:last_key_sdl], 0 + jne i8_end + + ; See if we have any keys down. If so, release them + cmp byte [es:key_now_down-bios_data], 0 + je i8_no_key_down + + mov al, [es:key_now_down-bios_data] + mov byte [es:key_now_down-bios_data], 0 + add al, 0x80 + call io_key_available + + i8_no_key_down: + + ; See if we have a waiting ESC flag + cmp byte [es:escape_flag-bios_data], 1 + jne i8_end + + ; Did we have one last two cycles as well? + cmp byte [es:escape_flag_last-bios_data], 1 + je i8_stuff_esc + + inc byte [es:escape_flag_last-bios_data] + jmp i8_end + +i8_stuff_esc: + + ; Yes, clear the ESC flag and put it in the keyboard buffer + mov byte [es:escape_flag-bios_data], 0 + mov byte [es:escape_flag_last-bios_data], 0 + + ; mov bp, [es:kbbuf_tail-bios_data] + ; mov byte [es:bp], 0x1b ; ESC ASCII code + ; mov byte [es:bp+1], 0x01 ; ESC scan code + + ; ESC keystroke is in the buffer now + ; add word [es:kbbuf_tail-bios_data], 2 + ; call kb_adjust_buf ; Wrap the tail around the head if the buffer gets too large + + mov byte [es:this_keystroke-bios_data], 0x1b + + ; Push out ESC keypress/release + mov al, 0x01 + call keypress_release + +i8_end: + + ; A Hercules graphics adapter flips bit 7 of I/O port 3BA on refresh + mov dx, 0x3BA + in al, dx + xor al, 0x80 + out dx, al + + pop si + pop ds + pop di + pop cx + + pop es + pop bp + pop dx + pop bx + pop ax + + iret + +; ************************* INT 8h handler - timer + +int8: + + int 0x1c + iret + +; ************************* INT 10h handler - video services + +int10: + + cmp ah, 0x00 ; Set video mode + je int10_set_vm + cmp ah, 0x01 ; Set cursor shape + je int10_set_cshape + cmp ah, 0x02 ; Set cursor position + je int10_set_cursor + cmp ah, 0x03 ; Get cursur position + je int10_get_cursor + cmp ah, 0x06 ; Scroll up window + je int10_scrollup + cmp ah, 0x07 ; Scroll down window + je int10_scrolldown + cmp ah, 0x08 ; Get character at cursor + je int10_charatcur + cmp ah, 0x09 ; Write char and attribute + je int10_write_char_attrib + cmp ah, 0x0e ; Write character at cursor position + je int10_write_char + cmp ah, 0x0f ; Get video mode + je int10_get_vm + ; cmp ah, 0x1a ; Feature check + ; je int10_features + + iret + + int10_set_vm: + + push dx + push cx + push bx + push es + + cmp al, 4 ; CGA mode 4 + je int10_switch_to_cga_gfx + cmp al, 5 + je int10_switch_to_cga_gfx + cmp al, 6 + je int10_switch_to_cga_gfx + + push ax + + mov dx, 0x3b8 + mov al, 0 + out dx, al + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x2d ; 0x2D = 45 (* 16) = 720 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x57 ; 0x57 = 87 (* 4) = 348 pixels high (GRAPHICS_Y) + out dx, al + + mov dx, 0x40 + mov es, dx + + mov byte [es:0xac], 0 ; Tell emulator we are in Hercules mode + + pop ax + + 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) + je int10_set_vm_3 + cmp al, 2 ; Same for text mode 2 (mono) + je int10_set_vm_3 + + jmp int10_set_vm_continue + + int10_switch_to_cga_gfx: + + ; Switch to CGA-like graphics mode (with Hercules CRTC set for 640 x 400) + + mov dx, 0x40 + mov es, dx + + mov [es:0x49], al ; Current video mode + mov byte [es:0xac], 1 ; Tell emulator we are in CGA mode + + mov dx, 0x3b4 + mov al, 1 ; Hercules CRTC "horizontal displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x28 ; 0x28 = 40 (* 16) = 640 pixels wide (GRAPHICS_X) + out dx, al + mov dx, 0x3b4 + mov al, 6 ; Hercules CRTC "vertical displayed" register select + out dx, al + mov dx, 0x3b5 + mov al, 0x64 ; 0x64 = 100 (* 4) = 400 pixels high (GRAPHICS_Y) + out dx, al + + mov dx, 0x3b8 + mov al, 0x8a + out dx, al + + mov bh, 7 + call clear_screen + + mov ax, 0x30 + jmp svmn_exit + + int10_set_vm_3: + + mov al, 3 + + int10_set_vm_continue: + + mov bx, 0x40 + mov es, bx + + mov [es:vidmode-bios_data], al + + mov bh, 7 ; Black background, white foreground + call clear_screen ; ANSI clear screen + + cmp byte [es:vidmode-bios_data], 6 + je set6 + mov al, 0x30 + jmp svmn + + set6: + + mov al, 0x3f + + svmn: + + ; Take Hercules adapter out of graphics mode when resetting video mode via int 10 + push ax + mov dx, 0x3B8 + mov al, 0 + out dx, al + pop ax + + svmn_exit: + + pop es + pop bx + pop cx + pop dx + iret + + int10_set_cshape: + + push ds + push ax + push cx + + mov ax, 0x40 + mov ds, ax + + mov byte [cursor_visible-bios_data], 1 ; Show cursor + + and ch, 01100000b + cmp ch, 00100000b + jne cur_visible + + mov byte [cursor_visible-bios_data], 0 ; Hide cursor + call ansi_hide_cursor + jmp cur_done + + cur_visible: + + call ansi_show_cursor + + cur_done: + + pop cx + pop ax + pop ds + iret + + int10_set_cursor: + + push ds + push ax + + mov ax, 0x40 + mov ds, ax + + mov [curpos_y-bios_data], dh + mov [crt_curpos_y-bios_data], dh + mov [curpos_x-bios_data], dl + mov [crt_curpos_x-bios_data], dl + + cmp dh, 24 + jbe skip_set_cur_row_max + + ; If cursor is moved off the screen, then hide it + call ansi_hide_cursor + jmp skip_set_cur_ansi + + skip_set_cur_row_max: + + cmp dl, 79 + jbe skip_set_cur_col_max + + ; If cursor is moved off the screen, then hide it + call ansi_hide_cursor + jmp skip_set_cur_ansi + + skip_set_cur_col_max: + + mov al, 0x1B ; ANSI + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, dh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + + cmp byte [cursor_visible-bios_data], 1 + jne skip_set_cur_ansi + call ansi_show_cursor + + skip_set_cur_ansi: + + pop ax + pop ds + iret + + int10_get_cursor: + + push es + + mov cx, 0x40 + mov es, cx + + mov cx, 0x0607 + mov dl, [es:curpos_x-bios_data] + mov dh, [es:curpos_y-bios_data] + + pop es + + iret + + int10_scrollup: + + push bx + push cx + push bp + push ax + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop ax + pop bp + pop cx + pop bx + + cmp al, 0 ; Clear window + jne cls_partial + + cmp cx, 0 ; Start of screen + jne cls_partial + + cmp dl, 0x4f ; Clearing columns 0-79 + jb cls_partial + + cmp dh, 0x18 ; Clearing rows 0-24 (or more) + jb cls_partial + + call clear_screen + iret + + cls_partial: + + push ax + push bx + + mov bl, al ; Number of rows to scroll are now in bl + cmp bl, 0 ; Clear whole window? + jne cls_partial_up_whole + + mov bl, 25 ; 25 rows + + cls_partial_up_whole: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp ch, 0 ; Start row 1? Maybe full screen + je cls_maybe_fs + jmp cls_not_fs + + cls_maybe_fs: + + cmp dh, 24 ; End row 25? Full screen for sure + je cls_fs + + cls_not_fs: + + mov al, ch ; Start row + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dh ; End row + inc al + call puts_decimal_al + + cls_fs: + + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp bl, 1 + jne cls_fs_multiline + + mov al, 'M' + jmp cs_fs_ml_out + +cls_fs_multiline: + + mov al, bl ; Number of rows + call puts_decimal_al + mov al, 'S' ; Scroll up + +cs_fs_ml_out: + + extended_putchar_al + + pop bx + pop ax + + ; Update "actual" cursor position with expected value - different ANSI terminals do different things + ; to the cursor position when you scroll + + push ax + push bx + push dx + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 2 + mov bh, 0 + mov dh, [es:curpos_y-bios_data] + mov dl, [es:curpos_x-bios_data] + int 10h + + pop es + pop dx + pop bx + pop ax + +int10_scroll_up_vmem_update: + + ; Now, we need to update video memory + + push bx + push ax + + push ds + push es + push cx + push dx + push si + push di + + mov byte [cs:vram_dirty], 1 + + push bx + + mov bx, 0xb800 + mov es, bx + mov ds, bx + + pop bx + mov bl, al + + cls_vmem_scroll_up_next_line: + + cmp bl, 0 + je cls_vmem_scroll_up_done + + cls_vmem_scroll_up_one: + + push bx + push dx + + mov ax, 0 + mov al, ch ; Start row number is now in AX + mov bx, 80 + mul bx + add al, cl + adc ah, 0 ; Character number is now in AX + mov bx, 2 + mul bx ; Memory location is now in AX + + pop dx + pop bx + + mov di, ax + mov si, ax + add si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI + + mov ax, 0 + add al, dl + adc ah, 0 + inc ax + sub al, cl + sbb ah, 0 ; AX now contains the number of characters from the row to copy + + cmp ch, dh + jae cls_vmem_scroll_up_one_done + +vmem_scroll_up_copy_next_row: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + cld + rep movsw ; Scroll the line up + pop cx + + inc ch ; Move onto the next row + jmp cls_vmem_scroll_up_one + + cls_vmem_scroll_up_one_done: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + mov ah, bh ; Attribute for new line + mov al, 0 ; Write 0 to video memory for new characters + cld + rep stosw + pop cx + + dec bl ; Scroll whole text block another line + jmp cls_vmem_scroll_up_next_line + + cls_vmem_scroll_up_done: + + ;mov al, 0x1B ; Escape + ;extended_putchar_al + ;mov al, '[' ; ANSI + ;extended_putchar_al + ;mov al, '0' ; Reset attributes + ;extended_putchar_al + ;mov al, 'm' + ;extended_putchar_al + + pop di + pop si + pop dx + pop cx + pop es + pop ds + + pop ax + pop bx + + iret + + int10_scrolldown: + + push bx + push cx + push bp + push ax + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop ax + pop bp + pop cx + pop bx + + cmp al, 0 ; Clear window + jne cls_partial_down + + cmp cx, 0 ; Start of screen + jne cls_partial_down + + cmp dl, 0x4f ; Clearing columns 0-79 + jne cls_partial_down + + cmp dh, 0x18 ; Clearing rows 0-24 (or more) + jl cls_partial_down + + call clear_screen + iret + + cls_partial_down: + + push ax + push bx + + mov bx, 0 + mov bl, al ; Number of rows to scroll are now in bl + + cmp bl, 0 ; Clear whole window? + jne cls_partial_down_whole + + mov bl, 25 ; 25 rows + + cls_partial_down_whole: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp ch, 0 ; Start row 1? Maybe full screen + je cls_maybe_fs_down + jmp cls_not_fs_down + + cls_maybe_fs_down: + + cmp dh, 24 ; End row 25? Full screen for sure + je cls_fs_down + + cls_not_fs_down: + + mov al, ch ; Start row + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dh ; End row + inc al + call puts_decimal_al + + cls_fs_down: + + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + cmp bl, 1 + jne cls_fs_down_multiline + + mov al, 'D' + jmp cs_fs_down_ml_out + + cls_fs_down_multiline: + + mov al, bl ; Number of rows + call puts_decimal_al + mov al, 'T' ; Scroll down + + cs_fs_down_ml_out: + + extended_putchar_al + + ; Update "actual" cursor position with expected value - different ANSI terminals do different things + ; to the cursor position when you scroll + + pop bx + pop ax + + push ax + push bx + push dx + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 2 + mov bh, 0 + mov dh, [es:curpos_y-bios_data] + mov dl, [es:curpos_x-bios_data] + int 10h + + pop es + pop dx + pop bx + pop ax + +int10_scroll_down_vmem_update: + + ; Now, we need to update video memory + + push ax + push bx + + push ds + push es + push cx + push dx + push si + push di + + mov byte [cs:vram_dirty], 1 + + push bx + + mov bx, 0xb800 + mov es, bx + mov ds, bx + + pop bx + mov bl, al + + cls_vmem_scroll_down_next_line: + + cmp bl, 0 + je cls_vmem_scroll_down_done + + cls_vmem_scroll_down_one: + + push bx + push dx + + mov ax, 0 + mov al, dh ; End row number is now in AX + mov bx, 80 + mul bx + add al, cl + adc ah, 0 ; Character number is now in AX + mov bx, 2 + mul bx ; Memory location (start of final row) is now in AX + + pop dx + pop bx + + mov di, ax + mov si, ax + sub si, 2*80 ; In a moment we will copy CX words from DS:SI to ES:DI + + mov ax, 0 + add al, dl + adc ah, 0 + inc ax + sub al, cl + sbb ah, 0 ; AX now contains the number of characters from the row to copy + + cmp ch, dh + jae cls_vmem_scroll_down_one_done + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + rep movsw ; Scroll the line down + pop cx + + dec dh ; Move onto the next row + jmp cls_vmem_scroll_down_one + + cls_vmem_scroll_down_one_done: + + push cx + mov cx, ax ; CX is now the length (in words) of the row to copy + mov ah, bh ; Attribute for new line + mov al, 0 ; Write 0 to video memory for new characters + rep stosw + pop cx + + dec bl ; Scroll whole text block another line + jmp cls_vmem_scroll_down_next_line + + cls_vmem_scroll_down_done: + + pop di + pop si + pop dx + pop cx + pop es + pop ds + + ;mov al, 0x1B ; Escape + ;extended_putchar_al + ;mov al, '[' ; ANSI + ;extended_putchar_al + ;mov al, '0' ; Reset attributes + ;extended_putchar_al + ;mov al, 'm' + ;extended_putchar_al + + pop bx + pop ax + iret + + int10_charatcur: + + ; This returns the character at the cursor. It is completely dysfunctional, + ; and only works at all if the character has previously been written following + ; an int 10/ah = 2 call to set the cursor position. Added just to support + ; GWBASIC. + + push ds + push es + push bx + push dx + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + add ax, bx + add ax, bx + mov bx, ax + + mov ah, 7 + mov al, [bx] + + pop dx + pop bx + pop es + pop ds + + iret + + i10_unsup: + + iret + + int10_write_char: + + ; First write the character to a buffer at C000:0. This is so that + ; we can later retrieve it using the get character at cursor function, + ; which GWBASIC uses. + + push ds + push es + push cx + push dx + push ax + push bp + push bx + + push ax + + mov cl, al + mov ch, 7 + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + shl bx, 1 + add bx, ax + + mov [bx], cx + + pop ax + push ax + + extended_putchar_al + + jmp int10_write_char_skip_lines + + int10_write_char_attrib: + + ; First write the character to a buffer at C000:0. This is so that + ; we can later retrieve it using the get character at cursor function, + ; which GWBASIC uses. + + push ds + push es + push cx + push dx + push ax + push bp + push bx + + push ax + push cx + + mov cl, al + mov ch, bl + + mov bx, 0x40 + mov es, bx + + mov bx, 0xc000 + mov ds, bx + + mov bx, 160 + mov ax, 0 + mov al, [es:curpos_y-bios_data] + mul bx + + mov bx, 0 + mov bl, [es:curpos_x-bios_data] + shl bx, 1 + add bx, ax + + mov [bx], cx + + mov bl, ch + + mov bh, bl + and bl, 7 ; Foreground colour now in bl + + mov bp, bx ; Convert from CGA to ANSI + and bp, 0xff + mov bl, byte [cs:bp+colour_table] + + and bh, 8 ; Bright attribute now in bh +cpu 186 + shr bh, 3 +cpu 8086 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bh ; Bright attribute + call puts_decimal_al + mov al, ';' + extended_putchar_al + mov al, bl ; Foreground colour + call puts_decimal_al + + mov bl, ch + + mov bh, bl +cpu 186 + shr bl, 4 +cpu 8086 + and bl, 7 ; Background colour now in bl + + mov bp, bx ; Convert from CGA to ANSI + and bp, 0xff + mov bl, byte [cs:bp+colour_table] + + add bl, 10 + ; rol bh, 1 + ; and bh, 1 ; Bright attribute now in bh (not used right now) + + mov al, ';' + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + pop cx + pop ax + push ax + + out_another_char: + + extended_putchar_al + dec cx + cmp cx, 0 + jne out_another_char + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' + extended_putchar_al + + int10_write_char_skip_lines: + + pop ax + + push es + pop ds + + cmp al, 0x08 + jne int10_write_char_attrib_inc_x + + dec byte [curpos_x-bios_data] + dec byte [crt_curpos_x-bios_data] + cmp byte [curpos_x-bios_data], 0 + jg int10_write_char_attrib_done + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + jmp int10_write_char_attrib_done + + int10_write_char_attrib_inc_x: + + cmp al, 0x0A ; New line? + je int10_write_char_attrib_newline + + cmp al, 0x0D ; Carriage return? + jne int10_write_char_attrib_not_cr + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + jmp int10_write_char_attrib_done + + int10_write_char_attrib_not_cr: + + inc byte [curpos_x-bios_data] + inc byte [crt_curpos_x-bios_data] + cmp byte [curpos_x-bios_data], 80 + jge int10_write_char_attrib_newline + jmp int10_write_char_attrib_done + + int10_write_char_attrib_newline: + + mov byte [curpos_x-bios_data], 0 + mov byte [crt_curpos_x-bios_data], 0 + inc byte [curpos_y-bios_data] + inc byte [crt_curpos_y-bios_data] + + cmp byte [curpos_y-bios_data], 25 + jb int10_write_char_attrib_done + mov byte [curpos_y-bios_data], 24 + mov byte [crt_curpos_y-bios_data], 24 + + mov bh, 7 + mov al, 1 + mov cx, 0 + mov dx, 0x184f + + pushf + push cs + call int10_scroll_up_vmem_update + + int10_write_char_attrib_done: + + pop bx + pop bp + pop ax + pop dx + pop cx + pop es + pop ds + + iret + + int10_get_vm: + + push es + + mov ax, 0x40 + mov es, ax + + mov ah, 80 ; Number of columns + mov al, [es:vidmode-bios_data] + mov bh, 0 + + pop es + + iret + + int10_features: + + ; Signify we have CGA display + + ; mov al, 0x1a + ; mov bx, 0x0202 + ; iret + +; ************************* INT 11h - get equipment list + +int11: + mov ax, [cs:equip] + iret + +; ************************* INT 12h - return memory size + +int12: + mov ax, 0x280 ; 640K conventional memory + iret + +; ************************* INT 13h handler - disk services + +int13: + cmp ah, 0x00 ; Reset disk + je int13_reset_disk + cmp ah, 0x01 ; Get last status + je int13_last_status + + cmp dl, 0x80 ; Hard disk being queried? + jne i13_diskok + + ; Now, need to check an HD is installed + cmp word [cs:num_disks], 2 + jge i13_diskok + + ; No HD, so return an error + mov ah, 15 ; Report no such drive + jmp reach_stack_stc + + i13_diskok: + + cmp ah, 0x02 ; Read disk + je int13_read_disk + cmp ah, 0x03 ; Write disk + je int13_write_disk + cmp ah, 0x04 ; Verify disk + je int13_verify + cmp ah, 0x05 ; Format track - does nothing here + je int13_format + cmp ah, 0x08 ; Get drive parameters (hard disk) + je int13_getparams + cmp ah, 0x0c ; Seek (hard disk) + je int13_seek + cmp ah, 0x10 ; Check if drive ready (hard disk) + je int13_hdready + cmp ah, 0x15 ; Get disk type + je int13_getdisktype + cmp ah, 0x16 ; Detect disk change + je int13_diskchange + + mov ah, 1 ; Invalid function + jmp reach_stack_stc + + iret + + int13_reset_disk: + + jmp reach_stack_clc + + int13_last_status: + + mov ah, [cs:disk_laststatus] + je ls_no_error + + stc + iret + + ls_no_error: + + clc + iret + + int13_read_disk: + + push dx + + cmp dl, 0 ; Floppy 0 + je i_flop_rd + cmp dl, 0x80 ; HD + je i_hd_rd + + pop dx + mov ah, 1 + jmp reach_stack_stc + + i_flop_rd: + + push si + push bp + + cmp cl, [cs:int1e_spt] + ja rd_error + + pop bp + pop si + + mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator + jmp i_rd + + i_hd_rd: + + mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator + + i_rd: + + push si + push bp + + ; Convert head/cylinder/sector number to byte offset in disk image + + call chs_to_abs + + ; Now, SI:BP contains the absolute sector offset of the block. We then multiply by 512 to get the offset into the disk image + + mov ah, 0 + cpu 186 + shl ax, 9 + extended_read_disk + shr ax, 9 + cpu 8086 + mov ah, 0x02 ; Put read code back + + cmp al, 0 + je rd_error + + ; Read was successful. Now, check if we have read the boot sector. If so, we want to update + ; our internal table of sectors/track to match the disk format + + cmp dx, 1 ; FDD? + jne rd_noerror + cmp cx, 1 ; First sector? + jne rd_noerror + + push ax + + mov al, [es:bx+24] ; Number of SPT in floppy disk BPB + + ; cmp al, 0 ; If disk is unformatted, do not update the table + ; jne rd_update_spt + cmp al, 9 ; 9 SPT, i.e. 720K disk, so update the table + je rd_update_spt + cmp al, 18 + je rd_update_spt ; 18 SPT, i.e. 1.44MB disk, so update the table + + pop ax + + jmp rd_noerror + + rd_update_spt: + + mov [cs:int1e_spt], al + pop ax + + rd_noerror: + + clc + mov ah, 0 ; No error + jmp rd_finish + + rd_error: + + stc + mov ah, 4 ; Sector not found + + rd_finish: + + pop bp + pop si + pop dx + + mov [cs:disk_laststatus], ah + jmp reach_stack_carry + + int13_write_disk: + + push dx + + cmp dl, 0 ; Floppy 0 + je i_flop_wr + cmp dl, 0x80 ; HD + je i_hd_wr + + pop dx + mov ah, 1 + jmp reach_stack_stc + + i_flop_wr: + + mov dl, 1 ; Floppy disk file handle is stored at j[1] in emulator + jmp i_wr + + i_hd_wr: + + mov dl, 0 ; Hard disk file handle is stored at j[0] in emulator + + i_wr: + + push si + push bp + push cx + push di + + ; Convert head/cylinder/sector number to byte offset in disk image + + call chs_to_abs + + ; Signal an error if we are trying to write beyond the end of the disk + + cmp dl, 0 ; Hard disk? + jne wr_fine ; No - no need for disk sector valid check - NOTE: original submission was JNAE which caused write problems on floppy disk + + ; First, we add the number of sectors we are trying to write from the absolute + ; sector number returned by chs_to_abs. We need to have at least this many + ; sectors on the disk, otherwise return a sector not found error. + + mov cx, bp + mov di, si + + mov ah, 0 + add cx, ax + adc di, 0 + + cmp di, [cs:hd_secs_hi] + ja wr_error + jb wr_fine + cmp cx, [cs:hd_secs_lo] + ja wr_error + +wr_fine: + + mov ah, 0 + cpu 186 + shl ax, 9 + extended_write_disk + shr ax, 9 + cpu 8086 + mov ah, 0x03 ; Put write code back + + cmp al, 0 + je wr_error + + clc + mov ah, 0 ; No error + jmp wr_finish + + wr_error: + + stc + mov ah, 4 ; Sector not found + + wr_finish: + + pop di + pop cx + pop bp + pop si + pop dx + + mov [cs:disk_laststatus], ah + jmp reach_stack_carry + + int13_verify: + + mov ah, 0 + jmp reach_stack_clc + + int13_getparams: + + cmp dl, 0 + je i_gp_fl + cmp dl, 0x80 + je i_gp_hd + + mov ah, 0x01 + mov [cs:disk_laststatus], ah + jmp reach_stack_stc + + i_gp_fl: + + push cs + pop es + mov di, int1e ; ES:DI now points to floppy parameters table (INT 1E) + + mov ax, 0 + mov bx, 4 + mov ch, 0x4f + mov cl, [cs:int1e_spt] + mov dx, 0x0101 + + mov byte [cs:disk_laststatus], 0 + jmp reach_stack_clc + + i_gp_hd: + + mov ax, 0 + mov bx, 0 + mov dl, 1 + mov dh, [cs:hd_max_head] + mov cx, [cs:hd_max_track] + ror ch, 1 + ror ch, 1 + add ch, [cs:hd_max_sector] + xchg ch, cl + + mov byte [cs:disk_laststatus], 0 + jmp reach_stack_clc + + int13_seek: + + mov ah, 0 + jmp reach_stack_clc + + int13_hdready: + + cmp byte [cs:num_disks], 2 ; HD present? + jne int13_hdready_nohd + cmp dl, 0x80 ; Checking first HD? + jne int13_hdready_nohd + + mov ah, 0 + jmp reach_stack_clc + + int13_hdready_nohd: + + jmp reach_stack_stc + + int13_format: + + mov ah, 0 + jmp reach_stack_clc + + int13_getdisktype: + + cmp dl, 0 ; Floppy + je gdt_flop + cmp dl, 0x80 ; HD + je gdt_hd + + mov ah, 15 ; Report no such drive + mov [cs:disk_laststatus], ah + jmp reach_stack_stc + + gdt_flop: + + mov ah, 1 + jmp reach_stack_clc + + gdt_hd: + + mov ah, 3 + mov cx, [cs:hd_secs_hi] + mov dx, [cs:hd_secs_lo] + jmp reach_stack_clc + + int13_diskchange: + + mov ah, 0 ; Disk not changed + jmp reach_stack_clc + +; ************************* INT 14h - serial port functions + +int14: + cmp ah, 0 + je int14_init + + jmp reach_stack_stc + + int14_init: + + mov ax, 0 + jmp reach_stack_stc + +; ************************* INT 15h - get system configuration + +int15: ; Here we do not support any of the functions, and just return + ; a function not supported code - like the original IBM PC/XT does. + + ; cmp ah, 0xc0 + ; je int15_sysconfig + ; cmp ah, 0x41 + ; je int15_waitevent + ; cmp ah, 0x4f + ; je int15_intercept + ; cmp ah, 0x88 + ; je int15_getextmem + +; Otherwise, function not supported + + mov ah, 0x86 + + jmp reach_stack_stc + +; int15_sysconfig: ; Return address of system configuration table in ROM +; +; mov bx, 0xf000 +; mov es, bx +; mov bx, rom_config +; mov ah, 0 +; +; jmp reach_stack_clc +; +; int15_waitevent: ; Events not supported +; +; mov ah, 0x86 +; +; jmp reach_stack_stc +; +; int15_intercept: ; Keyboard intercept +; +; jmp reach_stack_stc +; +; int15_getextmem: ; Extended memory not supported +; +; mov ah,0x86 +; +; jmp reach_stack_stc + +; ************************* INT 16h handler - keyboard + +int16: + cmp ah, 0x00 ; Get keystroke (remove from buffer) + je kb_getkey + cmp ah, 0x01 ; Check for keystroke (do not remove from buffer) + je kb_checkkey + cmp ah, 0x02 ; Check shift flags + je kb_shiftflags + cmp ah, 0x12 ; Check shift flags + je kb_extshiftflags + + iret + + kb_getkey: + + push es + push bx + push cx + push dx + + mov bx, 0x40 + mov es, bx + + kb_gkblock: + + cli + + mov cx, [es:kbbuf_tail-bios_data] + mov bx, [es:kbbuf_head-bios_data] + mov dx, [es:bx] + + sti + + ; Wait until there is a key in the buffer + cmp cx, bx + je kb_gkblock + + add word [es:kbbuf_head-bios_data], 2 + call kb_adjust_buf + + mov ah, dh + mov al, dl + + pop dx + pop cx + pop bx + pop es + + iret + + kb_checkkey: + + push es + push bx + push cx + push dx + + mov bx, 0x40 + mov es, bx + + mov cx, [es:kbbuf_tail-bios_data] + mov bx, [es:kbbuf_head-bios_data] + mov dx, [es:bx] + + sti + + ; Check if there is a key in the buffer. ZF is set if there is none. + cmp cx, bx + + mov ah, dh + mov al, dl + + pop dx + pop cx + pop bx + pop es + + retf 2 ; NEED TO FIX THIS!! + + kb_shiftflags: + + push es + push bx + + mov bx, 0x40 + mov es, bx + + mov al, [es:keyflags1-bios_data] + + pop bx + pop es + + iret + + kb_extshiftflags: + + push es + push bx + + mov bx, 0x40 + mov es, bx + + mov al, [es:keyflags1-bios_data] + mov ah, al + + pop bx + pop es + + iret + +; ************************* INT 17h handler - printer + +int17: + cmp ah, 0x01 + je int17_initprint ; Initialise printer + + jmp reach_stack_stc + + int17_initprint: + + mov ah, 1 ; No printer + jmp reach_stack_stc + +; ************************* INT 19h = reboot + +int19: + jmp boot + +; ************************* INT 1Ah - clock + +int1a: + cmp ah, 0 + je int1a_getsystime ; Get ticks since midnight (used for RTC time) + cmp ah, 2 + je int1a_gettime ; Get RTC time (not actually used by DOS) + cmp ah, 4 + je int1a_getdate ; Get RTC date + cmp ah, 0x0f + je int1a_init ; Initialise RTC + + iret + + int1a_getsystime: + + push ax + push bx + push ds + push es + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 182 ; Clock ticks in 10 seconds + mul word [tm_msec] + mov bx, 10000 + div bx ; AX now contains clock ticks in milliseconds counter + mov [tm_msec], ax + + mov ax, 182 ; Clock ticks in 10 seconds + mul word [tm_sec] + mov bx, 10 + mov dx, 0 + div bx ; AX now contains clock ticks in seconds counter + mov [tm_sec], ax + + mov ax, 1092 ; Clock ticks in a minute + mul word [tm_min] ; AX now contains clock ticks in minutes counter + mov [tm_min], ax + + mov ax, 65520 ; Clock ticks in an hour + mul word [tm_hour] ; DX:AX now contains clock ticks in hours counter + + add ax, [tm_msec] ; Add milliseconds in to AX + adc dx, 0 ; Carry into DX if necessary + add ax, [tm_sec] ; Add seconds in to AX + adc dx, 0 ; Carry into DX if necessary + add ax, [tm_min] ; Add minutes in to AX + adc dx, 0 ; Carry into DX if necessary + + push dx + push ax + pop dx + pop cx + + pop es + pop ds + pop bx + pop ax + + mov al, 0 + iret + + int1a_gettime: + + ; Return the system time in BCD format. DOS doesn't use this, but we need to return + ; something or the system thinks there is no RTC. + + push ds + push es + push ax + push bx + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 0 + mov cx, [tm_hour] + call hex_to_bcd + mov bh, al ; Hour in BCD is in BH + + mov ax, 0 + mov cx, [tm_min] + call hex_to_bcd + mov bl, al ; Minute in BCD is in BL + + mov ax, 0 + mov cx, [tm_sec] + call hex_to_bcd + mov dh, al ; Second in BCD is in DH + + mov dl, 0 ; Daylight saving flag = 0 always + + mov cx, bx ; Hour:minute now in CH:CL + + pop bx + pop ax + pop es + pop ds + + jmp reach_stack_clc + + int1a_getdate: + + ; Return the system date in BCD format. + + push ds + push es + push bx + push ax + + push cs + push cs + pop ds + pop es + + mov bx, timetable + + extended_get_rtc + + mov ax, 0x1900 + mov cx, [tm_year] + call hex_to_bcd + mov cx, ax + push cx + + mov ax, 1 + mov cx, [tm_mon] + call hex_to_bcd + mov dh, al + + mov ax, 0 + mov cx, [tm_mday] + call hex_to_bcd + mov dl, al + + pop cx + pop ax + pop bx + pop es + pop ds + + jmp reach_stack_clc + + int1a_init: + + jmp reach_stack_clc + +; ************************* INT 1Ch - the other timer interrupt + +int1c: + + iret + +; ************************* INT 1Eh - diskette parameter table + +int1e: + + db 0xdf ; Step rate 2ms, head unload time 240ms + db 0x02 ; Head load time 4 ms, non-DMA mode 0 + db 0x25 ; Byte delay until motor turned off + db 0x02 ; 512 bytes per sector +int1e_spt db 18 ; 18 sectors per track (1.44MB) + db 0x1B ; Gap between sectors for 3.5" floppy + db 0xFF ; Data length (ignored) + db 0x54 ; Gap length when formatting + db 0xF6 ; Format filler byte + db 0x0F ; Head settle time (1 ms) + db 0x08 ; Motor start time in 1/8 seconds + +; ************************* INT 41h - hard disk parameter table + +int41: + +int41_max_cyls dw 0 +int41_max_heads db 0 + dw 0 + dw 0 + db 0 + db 11000000b + db 0 + db 0 + db 0 + dw 0 +int41_max_sect db 0 + db 0 + +; ************************* ROM configuration table + +rom_config dw 16 ; 16 bytes following + db 0xfe ; Model + db 'A' ; Submodel + db 'C' ; BIOS revision + db 0b00100000 ; Feature 1 + db 0b00000000 ; Feature 2 + db 0b00000000 ; Feature 3 + db 0b00000000 ; Feature 4 + db 0b00000000 ; Feature 5 + db 0, 0, 0, 0, 0, 0 + +; Internal state variables + +num_disks dw 0 ; Number of disks present +hd_secs_hi dw 0 ; Total sectors on HD (high word) +hd_secs_lo dw 0 ; Total sectors on HD (low word) +hd_max_sector dw 0 ; Max sector number on HD +hd_max_track dw 0 ; Max track number on HD +hd_max_head dw 0 ; Max head number on HD +drive_tracks_temp dw 0 +drive_sectors_temp dw 0 +drive_heads_temp dw 0 +drive_num_temp dw 0 +boot_state db 0 +cga_refresh_reg db 0 + +; Default interrupt handlers + +int0: +int1: +int2: +int3: +int4: +int5: +int6: +intb: +intc: +intd: +inte: +intf: +int18: +int1b: +int1d: + +iret + +; ************ Function call library ************ + +; 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. + +hex_to_bcd: + + push bx + + jcxz h2bfin + + h2bloop: + + inc ax + + ; First process the low nibble of AL + mov bh, al + and bh, 0x0f + cmp bh, 0x0a + jne c1 + add ax, 0x0006 + + ; Then the high nibble of AL + c1: + mov bh, al + and bh, 0xf0 + cmp bh, 0xa0 + jne c2 + add ax, 0x0060 + + ; Then the low nibble of AH + c2: + mov bh, ah + and bh, 0x0f + cmp bh, 0x0a + jne c3 + add ax, 0x0600 + + c3: + loop h2bloop + h2bfin: + pop bx + ret + +; Takes a number in AL (from 0 to 99), and outputs the value in decimal using extended_putchar_al. + +puts_decimal_al: + + push ax + + aam + add ax, 0x3030 ; '00' + + cmp ah, 0x30 + je pda_2nd ; First digit is zero, so print only 2nd digit + + xchg ah, al ; First digit is now in AL + extended_putchar_al ; Print first digit + xchg ah, al ; Second digit is now in AL + + pda_2nd: + + extended_putchar_al ; Print second digit + + pop ax + ret + +; Keyboard adjust buffer head and tail. If either head or the tail are at the end of the buffer, reset them +; back to the start, since it is a circular buffer. + +kb_adjust_buf: + + push ax + push bx + + ; Check to see if the head is at the end of the buffer (or beyond). If so, bring it back + ; to the start + + mov ax, [es:kbbuf_end_ptr-bios_data] + cmp [es:kbbuf_head-bios_data], ax + jnge kb_adjust_tail + + mov bx, [es:kbbuf_start_ptr-bios_data] + mov [es:kbbuf_head-bios_data], bx + + kb_adjust_tail: + + ; Check to see if the tail is at the end of the buffer (or beyond). If so, bring it back + ; to the start + + mov ax, [es:kbbuf_end_ptr-bios_data] + cmp [es:kbbuf_tail-bios_data], ax + jnge kb_adjust_done + + mov bx, [es:kbbuf_start_ptr-bios_data] + mov [es:kbbuf_tail-bios_data], bx + + kb_adjust_done: + + pop bx + pop ax + ret + +; Convert CHS disk position (in CH, CL and DH) to absolute sector number in BP:SI +; 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 +; sector number, CL7..6 + CH7..0 is 10-bit cylinder/track number. Hard disks have 512 bytes per sector, but a variable +; number of tracks and heads. + +chs_to_abs: + + push ax + push bx + push cx + push dx + + mov [cs:drive_num_temp], dl + + ; First, we extract the track number from CH and CL. + + push cx + mov bh, cl + mov cl, 6 + shr bh, cl + mov bl, ch + + ; Multiply track number (now in BX) by the number of heads + + cmp byte [cs:drive_num_temp], 1 ; Floppy disk? + + push dx + + mov dx, 0 + xchg ax, bx + + jne chs_hd + + shl ax, 1 ; Multiply by 2 (number of heads on FD) + push ax + xor ax, ax + mov al, [cs:int1e_spt] + mov [cs:drive_sectors_temp], ax ; Retrieve sectors per track from INT 1E table + pop ax + + jmp chs_continue + +chs_hd: + + mov bp, [cs:hd_max_head] + inc bp + mov [cs:drive_heads_temp], bp + + mul word [cs:drive_heads_temp] ; HD, so multiply by computed head count + + mov bp, [cs:hd_max_sector] ; We previously calculated maximum HD track, so number of tracks is 1 more + mov [cs:drive_sectors_temp], bp + +chs_continue: + + xchg ax, bx + + pop dx + + xchg dh, dl + mov dh, 0 + add bx, dx + + mov ax, [cs:drive_sectors_temp] + mul bx + + ; Now we extract the sector number (from 1 to 63) - for some reason they start from 1 + + pop cx + mov ch, 0 + and cl, 0x3F + dec cl + + add ax, cx + adc dx, 0 + mov bp, ax + mov si, dx + + ; Now, SI:BP contains offset into disk image file (FD or HD) + + pop dx + pop cx + pop bx + pop ax + ret + +; Clear screen using ANSI codes. Also clear video memory with attribute in BH + +clear_screen: + + push ax + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, 'r' ; Set scrolling window + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' ; Reset attributes + extended_putchar_al + + push bx + push cx + push bp + push ax + push es + + mov bp, bx ; Convert from CGA to ANSI + mov cl, 12 + ror bp, cl + and bp, 7 + mov bl, byte [cs:bp+colour_table] + add bl, 10 + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bl ; Background colour + call puts_decimal_al + mov al, 'm' ; Set cursor position command + extended_putchar_al + + mov ax, 0x40 + mov es, ax + mov byte [es:curpos_x-bios_data], 0 + mov byte [es:crt_curpos_x-bios_data], 0 + mov byte [es:curpos_y-bios_data], 0 + mov byte [es:crt_curpos_y-bios_data], 0 + + pop es + pop ax + pop bp + pop cx + pop bx + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '2' ; Clear screen + extended_putchar_al + mov al, 'J' + extended_putchar_al + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '1' ; Cursor row 1 + extended_putchar_al + mov al, ';' + extended_putchar_al + mov al, '1' ; Cursor column 1 + extended_putchar_al + mov al, 'H' ; Set cursor + extended_putchar_al + + push es + push di + push cx + + cld + mov ax, 0xb800 + mov es, ax + mov di, 0 + mov al, 0 + mov ah, bh + mov cx, 80*25 + rep stosw + + cld + mov di, 0xc800 + mov es, di + mov di, 0 + mov cx, 80*25 + rep stosw + + cld + mov di, 0xc000 + mov es, di + mov di, 0 + mov cx, 80*25 + rep stosw + + pop cx + pop di + pop es + + pop ax + + mov byte [cs:vram_dirty], 1 + + ret + +; Pushes a key press, followed by a key release, event to I/O port 0x60 and calls +; INT 9. + +keypress_release: + + push ax + + cmp byte [es:key_now_down-bios_data], 0 + je kpr_no_prev_release + + mov al, [es:key_now_down-bios_data] + add al, 0x80 + call io_key_available + + pop ax + push ax + + kpr_no_prev_release: + + mov [es:key_now_down-bios_data], al + call io_key_available + + pop ax + + ret + +; Sets key available flag on I/O port 0x64, outputs key scan code in AL to I/O port 0x60, and calls INT 9 + +io_key_available: + + push ax + mov al, 1 + out 0x64, al + pop ax + + out 0x60, al + int 9 + ret + +; Reaches up into the stack before the end of an interrupt handler, and sets the carry flag + +reach_stack_stc: + + xchg bp, sp + or word [bp+4], 1 + xchg bp, sp + iret + +; Reaches up into the stack before the end of an interrupt handler, and clears the carry flag + +reach_stack_clc: + + xchg bp, sp + and word [bp+4], 0xfffe + xchg bp, sp + iret + +; Reaches up into the stack before the end of an interrupt handler, and returns with the current +; setting of the carry flag + +reach_stack_carry: + + jc reach_stack_stc + jmp reach_stack_clc + +; This is the VMEM driver, to support direct video memory access in 80x25 colour CGA mode. +; It scans through CGA video memory at address B800:0, and if there is anything there (i.e. +; applications are doing direct video memory writes), converts the buffer to a sequence of +; ANSI terminal codes to render the screen output. +; +; Note: this destroys all registers. It is the responsibility of the caller to save/restore +; them. + +vmem_driver_entry: + + cmp byte [cs:in_update], 1 + je just_finish ; If we are already in the middle of an update, skip. Needed for re-entrancy + + inc byte [cs:int8_ctr] + cmp byte [cs:int8_ctr], 8 ; Only do this once every 8 timer ticks + jne just_finish + +gmode_test: + + mov byte [cs:int8_ctr], 0 + mov dx, 0x3b8 ; Do not update if in Hercules graphics mode + in al, dx + test al, 2 + jz vram_zero_check + +just_finish: + + ret + +vram_zero_check: ; Check if video memory is blank - if so, do nothing + + mov byte [cs:in_update], 1 + + sti + + mov bx, 0x40 + mov ds, bx + + mov di, [vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + shl di, 1 + push di + + mov bx, 0xb800 + mov es, bx + mov cx, 0x7d0 + mov ax, 0x0700 + + cld + repz scasw + pop di + je vmem_done ; Nothing has been written to video RAM - no need to update + + cmp byte [cs:vram_dirty], 1 ; Cleared screen so always need to update + je vram_update + + mov bx, 0xc800 + mov ds, bx + mov si, 0 + mov cx, 0x7d0 + + cld + repz cmpsw + jne vram_update ; Video RAM is changed - need to update + + mov bx, 0x40 + mov ds, bx + mov bh, [crt_curpos_y-bios_data] + mov bl, [crt_curpos_x-bios_data] + + cmp bh, [cs:crt_curpos_y_last] + jne restore_cursor ; Cursor position changed (but nothing else) so update just that + cmp bl, [cs:crt_curpos_x_last] + jne restore_cursor + + jmp vmem_done + +vram_update: + + mov bx, 0x40 + mov es, bx + + push cs + pop ds + + mov byte [int_curpos_x], 0xff + mov byte [int_curpos_y], 0xff + + cmp byte [es:cursor_visible-bios_data], 0 + je dont_hide_cursor + + call ansi_hide_cursor + +dont_hide_cursor: + + mov byte [last_attrib], 0xff + + mov bx, 0x40 + mov es, bx + + mov di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + shl di, 1 + sub di, 2 ; Combined offset + + mov bx, 0xb800 + mov es, bx + + ; Set up the initial cursor coordinates. Since the first thing we do is increment the cursor + ; position, this initial position is actually off the screen + + mov bp, -1 ; Row number + mov si, 79 ; Column number + +disp_loop: + + ; Advance to next column + + add di, 2 + inc si + cmp si, 80 + jne cont + + ; Column is 80, so set to 0 and advance a line + +loop_next_line: + + mov si, 0 + inc bp + + ; Bottom of the screen reached already? If so, we're done + + cmp bp, 25 + je restore_attrib + + ; See if this line has changed in video RAM + + cmp byte [cs:vram_dirty], 1 + je cont + + push si + push di + + mov bx, 0xb800 + mov ds, bx + mov bx, 0xc800 + mov es, bx + mov si, di + + push es + mov bx, 0x40 + mov es, bx + sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + sub di, [es:vmem_offset-bios_data] + pop es + + mov cx, 80 ; One row's worth of characters + + cld + repz cmpsw + pop di + pop si + + je vmem_next_line ; This line is unchanged in video RAM, so do not update + +vmem_copy_buf: + + ; Copy the changed line to our double buffer at C800:0 + + push cx + push si + push di + + push es + mov bx, 0x40 + mov es, bx + mov si, di + sub di, [es:vmem_offset-bios_data] ; Adjust for CRTC video memory offset register + sub di, [es:vmem_offset-bios_data] + pop es + + mov cx, 80 ; One row's worth of characters + cld + rep movsw + + pop di + pop si + pop cx + + ; We want to start the update at the first character which differs - so calculate its position. + + mov bx, 79 + sub bx, cx + + add di, bx + add di, bx + add si, bx + + push ds + pop es ; Set ES back to B800 + + jmp cont + +vmem_next_line: + + add di, 160 + jmp loop_next_line ; Line is unchanged in video RAM + +cont: + push cs + pop ds + + cmp byte [es:di], 0 ; Ignore null characters in video memory + je disp_loop + + mov ax, bp + mov bx, si + mov dh, al + mov dl, bl + + cmp dh, [int_curpos_y] ; Same row as the last time? + jne ansi_set_cur_pos + push dx + dec dl + cmp dl, [int_curpos_x] ; One column to the right since the last time? + pop dx + je skip_set_cur_pos + +ansi_set_cur_pos: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, dh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, dl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + + mov [int_curpos_y], dh + +skip_set_cur_pos: + + mov [int_curpos_x], dl + + mov dl, [es:di+1] + cmp dl, [last_attrib] + je skip_attrib + + mov [last_attrib], dl + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + + mov al, dl + and al, 8 ; Bright attribute now in AL + cpu 186 + shr al, 3 + cpu 8086 + + call puts_decimal_al + mov al, ';' + extended_putchar_al + + push dx + + and dl, 7 ; Foreground colour now in DL + mov bx, colour_table + mov al, dl + xlat + + call puts_decimal_al + mov al, ';' + extended_putchar_al + + pop dx + + cpu 186 + shr dl, 4 + cpu 8086 + and dl, 7 ; Background colour now in DL + + mov al, dl + xlat + + add al, 10 + call puts_decimal_al + mov al, 'm' ; Set cursor attribute command + extended_putchar_al + +skip_attrib: + + mov al, [es:di] + + cmp al, 32 ; Non-printable ASCII? (< 32 decimal) + jae just_show_it + + mov bx, low_ascii_conv + cs xlat ; Convert to printable representation (mostly spaces) + +just_show_it: + + extended_putchar_al + + jmp disp_loop + +restore_attrib: + + mov al, 0x1B ; Escape + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, '0' ; Reset attributes + extended_putchar_al + mov al, 'm' + extended_putchar_al + +restore_cursor: + + ; On a real PC, the 6845 CRT cursor position registers take place over the BIOS + ; Data Area ones. So, if the cursor is not off the screen, set it to the CRT + ; position. + + mov bx, 0x40 + mov ds, bx + + mov bh, [crt_curpos_y-bios_data] + mov bl, [crt_curpos_x-bios_data] + mov [cs:crt_curpos_y_last], bh + mov [cs:crt_curpos_x_last], bl + + cmp bh, 24 + ja vmem_end_hidden_cursor + cmp bl, 79 + ja vmem_end_hidden_cursor + + mov al, 0x1B ; ANSI + extended_putchar_al + mov al, '[' ; ANSI + extended_putchar_al + mov al, bh ; Row number + inc al + call puts_decimal_al + mov al, ';' ; ANSI + extended_putchar_al + mov al, bl ; Column number + inc al + call puts_decimal_al + mov al, 'H' ; Set cursor position command + extended_putchar_al + +restore_cursor_visible: + + cmp byte [cursor_visible-bios_data], 1 + jne vmem_end_hidden_cursor + + call ansi_show_cursor + jmp vmem_done + +vmem_end_hidden_cursor: + + call ansi_hide_cursor + +vmem_done: + + mov byte [cs:vram_dirty], 0 + mov byte [cs:in_update], 0 + ret + +; Show cursor using ANSI codes + +ansi_show_cursor: + + mov al, 0x1B + extended_putchar_al + mov al, '[' + extended_putchar_al + mov al, '?' + extended_putchar_al + mov al, '2' + extended_putchar_al + mov al, '5' + extended_putchar_al + mov al, 'h' + extended_putchar_al + + ret + +; Hide cursor using ANSI codes + +ansi_hide_cursor: + + mov al, 0x1B + extended_putchar_al + mov al, '[' + extended_putchar_al + mov al, '?' + extended_putchar_al + mov al, '2' + extended_putchar_al + mov al, '5' + extended_putchar_al + mov al, 'l' + extended_putchar_al + + ret + +; **************************************************************************************** +; That's it for the code. Now, the data tables follow. +; **************************************************************************************** + +; Standard PC-compatible BIOS data area - to copy to 40:0 + +bios_data: + +com1addr dw 0 +com2addr dw 0 +com3addr dw 0 +com4addr dw 0 +lpt1addr dw 0 +lpt2addr dw 0 +lpt3addr dw 0 +lpt4addr dw 0 +equip dw 0b0000000000100001 +;equip dw 0b0000000100100001 + db 0 +memsize dw 0x280 + db 0 + db 0 +keyflags1 db 0 +keyflags2 db 0 + db 0 +kbbuf_head dw kbbuf-bios_data +kbbuf_tail dw kbbuf-bios_data +kbbuf: times 32 db 'X' +drivecal db 0 +diskmotor db 0 +motorshutoff db 0x07 +disk_laststatus db 0 +times 7 db 0 +vidmode db 0x03 +vid_cols dw 80 +page_size dw 0x1000 + dw 0 +curpos_x db 0 +curpos_y db 0 +times 7 dw 0 +cur_v_end db 7 +cur_v_start db 6 +disp_page db 0 +crtport dw 0x3d4 + db 10 + db 0 +times 5 db 0 +clk_dtimer dd 0 +clk_rollover db 0 +ctrl_break db 0 +soft_rst_flg dw 0x1234 + db 0 +num_hd db 0 + db 0 + db 0 + dd 0 + dd 0 +kbbuf_start_ptr dw 0x001e +kbbuf_end_ptr dw 0x003e +vid_rows db 25 ; at 40:84 + db 0 + db 0 +vidmode_opt db 0 ; 0x70 + db 0 ; 0x89 + db 0 ; 0x51 + db 0 ; 0x0c + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 + db 0 +kb_mode db 0 +kb_led db 0 + db 0 + db 0 + db 0 + db 0 +boot_device db 0 +crt_curpos_x db 0 +crt_curpos_y db 0 +key_now_down db 0 +next_key_fn db 0 +cursor_visible db 1 +escape_flag_last db 0 +next_key_alt db 0 +escape_flag db 0 +notranslate_flg db 0 +this_keystroke db 0 +this_keystroke_ext db 0 +timer0_freq dw 0xffff ; PIT channel 0 (55ms) +timer2_freq dw 0 ; PIT channel 2 +cga_vmode db 0 +vmem_offset dw 0 ; Video RAM offset +ending: times (0xff-($-com1addr)) db 0 + +; Keyboard scan code tables + +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 +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 + +; Interrupt vector table - to copy to 0:0 + +int_table dw int0 + dw 0xf000 + dw int1 + dw 0xf000 + dw int2 + dw 0xf000 + dw int3 + dw 0xf000 + dw int4 + dw 0xf000 + dw int5 + dw 0xf000 + dw int6 + dw 0xf000 + dw int7 + dw 0xf000 + dw int8 + dw 0xf000 + dw int9 + dw 0xf000 + dw inta + dw 0xf000 + dw intb + dw 0xf000 + dw intc + dw 0xf000 + dw intd + dw 0xf000 + dw inte + dw 0xf000 + dw intf + dw 0xf000 + dw int10 + dw 0xf000 + dw int11 + dw 0xf000 + dw int12 + dw 0xf000 + dw int13 + dw 0xf000 + dw int14 + dw 0xf000 + dw int15 + dw 0xf000 + dw int16 + dw 0xf000 + dw int17 + dw 0xf000 + dw int18 + dw 0xf000 + dw int19 + dw 0xf000 + dw int1a + dw 0xf000 + dw int1b + dw 0xf000 + dw int1c + dw 0xf000 + dw int1d + dw 0xf000 + dw int1e + +itbl_size dw $-int_table + +; Conversion from CGA video memory colours to ANSI colours + +colour_table db 30, 34, 32, 36, 31, 35, 33, 37 + +; Conversion from non-printable low ASCII to printable + +low_ascii_conv db ' ', 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, 250, '><|!|$', 250, '|^v><--^v' + +; Conversion from UNIX cursor keys/SDL keycodes to scancodes + +unix_cursor_xlt db 0x48, 0x50, 0x4d, 0x4b + +; Conversion from SDL keycodes to Home/End/PgUp/PgDn scancodes + +pgup_pgdn_xlt db 0x47, 0x4f, 0x49, 0x51 + +; Internal variables for VMEM driver + +int8_ctr db 0 +in_update db 0 +vram_dirty db 0 +last_attrib db 0 +int_curpos_x db 0 +int_curpos_y db 0 +crt_curpos_x_last db 0 +crt_curpos_y_last db 0 + +; INT 8 millisecond counter + +last_int8_msec dw 0 +last_key_sdl db 0 + +; Now follow the tables for instruction decode helping + +; R/M mode tables + +rm_mode0_reg1 db 3, 3, 5, 5, 6, 7, 12, 3 +rm_mode012_reg2 db 6, 7, 6, 7, 12, 12, 12, 12 +rm_mode0_disp db 0, 0, 0, 0, 0, 0, 1, 0 +rm_mode0_dfseg db 11, 11, 10, 10, 11, 11, 11, 11 + +rm_mode12_reg1 db 3, 3, 5, 5, 6, 7, 5, 3 +rm_mode12_disp db 1, 1, 1, 1, 1, 1, 1, 1 +rm_mode12_dfseg db 11, 11, 10, 10, 11, 11, 10, 11 + +; Opcode decode tables + +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 +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 +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 +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 +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 +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 + +flags_mult db 0, 2, 4, 6, 7, 8, 9, 10, 11 + +jxx_dec_a db 48, 40, 43, 40, 44, 41, 49, 49 +jxx_dec_b db 49, 49, 49, 43, 49, 49, 49, 43 +jxx_dec_c db 49, 49, 49, 49, 49, 49, 44, 44 +jxx_dec_d db 49, 49, 49, 49, 49, 49, 48, 48 + +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 + +; This is the format of the 36-byte tm structure, returned by the emulator's RTC query call + +timetable: + +tm_sec equ $ +tm_min equ $+4 +tm_hour equ $+8 +tm_mday equ $+12 +tm_mon equ $+16 +tm_year equ $+20 +tm_wday equ $+24 +tm_yday equ $+28 +tm_dst equ $+32 +tm_msec equ $+36 \ No newline at end of file diff --git a/2013/cable3/cable3.alt.c b/2013/cable3/cable3.alt.c new file mode 100644 index 000000000..152f89677 --- /dev/null +++ b/2013/cable3/cable3.alt.c @@ -0,0 +1,28 @@ +#include "SDL.h" +#define KB (kb=H(8),kbhit())&&(r[1190]=getch(),H(7)) +#define $ for(O=9 +#define CX M+=(T%3+2*!(!T*t-6)) +#define x ,A=4*!T,O=t,W=h=T<3?u(Q?p:D(A+3),D(A),D(A+1)[i]+D(A+2)*g+):K(t),U=V=K(a),o?U=h,W=V:V, +#define C 8*-~L +#define Z short +#define y a(Z)Y[++O] +#define B ),a--||( +#define _ ),e--||( + +#define V(I,D,E)(O=a(I)h[r])&&!(A=(D)(V=(1[E+L]<<16)+*i)/O,A-(I)A)?1[E+L]=V-O*(*E=A):H(0) +#define i(B,M)B(o){return M;} +#define R(O,M,_)(S=L?a(I Z)O:O,N=L?a(I Z)O M(f=a(I Z)_):(O M(f=a(I n)_))) +#define T(_)R(r[u(10,L=4,--)],=,_) +#define u(a,r,T)16*i[a]+(I Z)(T i[r]) +#define a(_)*(_*)& +#define L(_)M(W,_,U) + +#define M(S,F,T)R(r[S],F,r[T]) +#define A(_)(i[L=4]+=2,R(_,=,r[u(10,4,-2+)])) +#define c(R,T)(1[u=19,L+T]=(N=a(R)h[r]*(R)*T)>>16,*i=N,G(F(N-(R)N))) +#define h(_)(1&(L?a(Z)_:_)>>C-1) +#define I unsigned +#define n char +#define e(_)v(F(40[L(_##=40[E]+),E]&N==S|_ N<_(int)S)) + +I n t,e,l[80186],*E,m,u,L,a,T,o,r[1<<21],X,*Y,b,Q,R;I Z*i,M,p,q=3;I f,S,kb,h,W,U,c,g,d,V,A;N,O,P=983040,j[5];SDL_Surface*k;i(F,40[E]=!!o)i(z,42[E]=!!o)i(D,r[a(I)E[259+4*o]+O])i(w,i[o]+=~(-2*47[E])*~L)i(v,G(N-S&&1&(40[z((f^=S^N)&16),E]^f>>C-1)))J(){V=61442;$;O--;)V+=40[E+O]<9|42[E])),*E&=15))i(SP,(w(7),R&&--1[i]&&o?R++,Q&&Q++,M--:0))DX(){$,O*=27840;O--;)O[(I*)k->pixels]=-!!(1<<7-O%8&r[O/2880*90+O%720/8+(88+952[l]/128*4+O/720%4<<13)]);SDL_Flip(k);}main(BX,nE)n**nE;{9[i=E=r+P]=P>>4;$;q;)j[--q]=*++nE?open(*nE,32898):0;read(2[a(I)*i=*j?lseek(*j,0,2)>>9:0,j],E+(M=256),P);$;Y=r+16*9[i]+M,Y-r;Q|R||kb&46[E]&&KB)--64[T=1[O=32[L=(X=*Y&7)&1,o=X/2&1,l]=0,t=(c=y)&7,a=c/8&7,Y]>>6,g=~-T?y:(n)y,d=BX=y,l],!T*t-6&&T-2?T-1?d=g:0:(d=y),Q&&Q--,R&&R--x(O=*Y,O=u=D(51),e=D(8),m=D(14)_ O=*Y/2&7,M+=(n)c*(L^(D(m)[E]|D(22)[E]|D(23)[E]^D(24)[E]))_ L=*Y&8,R(K(X)[r],=,c)_ L=e+=3,o=0,a=X x a=m _ T(X[i])_ A(X[i])_ a<2?M(U,+=1-2*a+,P+24),v(f=1),G(S+1-a==1<S)B L?c(I Z,i):c(I n,E)B/**/L?c(Z,i):c(n,E)B L?V(I Z,I,i):V(I n,I Z,E)B L?V(Z,int,i):V(n,Z,E))_++e,h=P,d=c,T=3,a=m,M--_++e,13[W=h,i]=(o|=!L)?(n)d:d,U=P+26,M-=~!o,u=17+(m=a)_(a=m B L(+=),F(NS)B L(^=)B L(-),F(N>S)B L(=))_!L?L=a+=8 x L(=):!o?Q=1,R(r[p=m x V],=,h):A(h[r])_ T=a=0,t=6,g=c x M(U,=,W)_(A=h(h[r]),V=m?++M,(n)g:o?31&2[E]:1)&&(a<4?V%=a/2+C,R(A,=,h[r]):0,a&1?R(h[r],>>=,V):R(h[r],<<=,V),a>3?u=19:0,a<5?0:F(S>>V-1&1)B R(h[r],+=,A>>C-V),G(h(N)^F(N&1))B A&=(1<>1+C-V),G(h(N)^F(A&1<>V)))_(V=!!--1[a=X,i]B V&=!m[E]B V&=m[E]B 0 B V=!++1[i]),M+=V*(n)c _ M+=3-o,L?0:o?9[M=0,i]=BX:T(M),M+=o*L?(n)c:c _ M(U,&,W)_ L=e+=8,W=P,U=K(X)_!R||1[i]?M(m<2?u(8,7,):P,=,m&1?P:u(Q?p:11,6,)),m&1||w(6),m&2||SP(1):0 _!R||1[i]?M(m?P:u(Q?p:11,6,),-,u(8,7,)),43[u=92,E]=!N,F(N>S),m||w(6),SP(!N==b):0 _ o=L,A(M),m&&A(9[i]),m&2?s(A(V)):o||(4[i]+=c)_ R(U[r],=,d)_ 986[l]^=9,R(*E,=,l[m?2[i]:(n)c])_ R(l[m?2[i]:(n)c],=,*E)_ R=2,b=L,Q&&Q++_ W-U?L(^=),M(U,^=,W),L(^=):0 _ T(m[i])_ A(m[i])_ Q=2,p=m,R&&R++_ L=0,O=*E,F(D(m+=3*42[E]+6*40[E])),z(D(1+m)),N=*E=D(m-1)_ N=BP(m-1)_ 1[E]=-h(*E)_ 2[i]=-h(*i)_ 9[T(9[i]),T(M+5),i]=BX,M=c _ J(),T(V)_ s(A(V))_ J(),s((V&~m)+1[E])_ J(),1[E]=V _ L=o=1 x L(=),M(P+m,=,h+2)_++M,H(3)_ M+=2,H(c&m)_++M,m[E]&&H(4)_(c&=m)?1[E]=*E/c,N=*E%=c:H(0)_*i=N=m&E[L=0]+c*1[E]_*E=-m[E]_*E=r[u(Q?p:m,3,*E+)]_ m[E]^=1 _ E[m/2]=m&1 _ R(*E,&,c)_(a=c B write(1,E,1)B time(j+3),memcpy(r+u(8,3,),localtime(j+3),m)),a<2?*E=~lseek(O=4[E][j],a(I)5[i]<<9,0)?((I(*)())(a?write:read))(O,r+u(8,3,),*i):0:0),O=u,D(16)?v(0):D(17)&&G(F(0)),CX*D(20)+D(18)-D(19)*~!!L,D(15)?O=m=N,41[43[44[E]=h(N),E]=!N,E]=D(50):0,!++q?kb=1,*l?SDL_PumpEvents(),k=k?k:SDL_SetVideoMode(720,348,32,0),DX():k?SDL_Quit(),k=0:0:0;}i(G,48[E]=o)i(K,P+(L?2*o:2*o+o/4&7)) diff --git a/2013/cable3/cable3.c b/2013/cable3/cable3.c index ef2ac4125..15ab7ec02 100644 --- a/2013/cable3/cable3.c +++ b/2013/cable3/cable3.c @@ -1,5 +1,5 @@ #include "SDL.h" -#undef localtime +#include #define $ for(O=9 #define CX M+=(T%3+2*!(!T*t-6)) #define x ,A=4*!T,O=t,W=h=T<3?u(Q?p:D(A+3),D(A),D(A+1)[i]+D(A+2)*g+):K(t),U=V=K(a),o?U=h,W=V:V, diff --git a/2013/cable3/runme b/2013/cable3/runme index ab4787a58..af8db3ea2 100755 --- a/2013/cable3/runme +++ b/2013/cable3/runme @@ -2,7 +2,7 @@ make clobber all || exit 1 clear stty cbreak raw -echo min 0 -if [ -f hd.img ] +if [[ -f hd.img ]] then ./cable3 bios fd.img hd.img else diff --git a/bugs.md b/bugs.md index 8909091ba..e7e751a27 100644 --- a/bugs.md +++ b/bugs.md @@ -1829,6 +1829,16 @@ fire](https://en.wikipedia.org/wiki/Halt_and_Catch_Fire_(computing)) :-) # 2013 +## [2013/cable3](2013/cable3/cable3.c) ([README.md](2013/cable3/README.md)) +## STATUS: missing files - please provide them + +Many fixes and improvements were made by Cody but he observed that the author +referred to a file that is nowhere to be found: not in the directory or the +archive. + +The file is `hd.img`. + +Do you have it? Please provide it! # 2014 diff --git a/thanks-for-fixes.md b/thanks-for-fixes.md index 707b0feee..1790598d2 100644 --- a/thanks-for-fixes.md +++ b/thanks-for-fixes.md @@ -1592,12 +1592,23 @@ This meant that at one point to be strictly technical, the removal of `localtime()` wasn't necessary but this change was left in place anyway. Later on in an updated macOS (problem discovered in macOS Sonoma) the entry -failed to compile again also due to `localtime()` so the prototype was -completely removed to solve the problem. +failed to compile again also due to `localtime()` so he removed the prototype +entirely to solve the problem. -He also slightly improved the `runme` script to not assume that the program has -been compiled by running `make clobber all || exit 1`. +Cody also slightly improved the `runme` script to not assume that the program has +been compiled by running `make clobber all || exit 1` and he also made it pass +`shellcheck` (using `[[ .. ]]` over `[ .. ]`). +As well, based on the author's remarks, Cody added the [alternate +code](2013/cable3/cable3.alt.c) which should be compilable for Windows/MS Visual +Studio. This is done by in the compile line undefining `KB` (`-UKB`) and then in +the source code defining `KB` to what the author suggested, +`(kb=H(8),kbhit())&&(r[1190]=getch(),H(7))`. It need hardly be mentioned that +this will not link in Unix systems (including macOS). + +Finally Cody provided the [bios.asm](2013/cable3/bios.asm) that the author +referred to, found at the [GitHub repo for the +entry](https://github.com/adriancable/8086tiny/tree/master). ## [2013/hou](2013/hou/hou.c) ([README.md](2013/hou/README.md))