Kjetil's Information Center: A Blog About My Projects

DOS Keyboard Fix TSR

I have an old AST Premium Exec 386SX/20 laptop running DOS. After the mechanical harddisk failed I replaced it with a flashdisk, but after re-assembly some of the keys on the keyboard would no longer work. I traced this to the '.', 'ø', 'å', '+' and '\' keys, all of which happens to be on one row/column on the keyboard matrix. After further troubleshooting I could find no fault with the keyboard or associated cables, so unfortunately the problem seems to be within the keyboard controller which is embedded into a custom chipset.

Custom AST Actel Chipset


The lack of '.' and '\' makes it hard to navigate around in DOS, so I started looking into a solution. Since it is nearly impossible to find spare parts like that custom chipset I ended up with a software solution, specifically in the form of a TSR.

This TSR hooks into and intercepts the int 16h calls that are used for keyboard services. By pressing Alt+F1 a scancode representing '.' is returned instead. Also Alt+F2 returns '\' and Alt+F3 returns ':'. This will only work for DOS programs like COMMAND.COM or EDIT that actually use the BIOS services. For most games it probably won't work, but luckily the affected keys are seldom used in games.

The code:

org 0x100
bits 16
cpu 8086

section .text
start:
  jmp main

int16_interrupt:
  sti ; Allow other interrupts!
  mov word [int16_incoming_ax], ax ; Store incoming parameter for later use.

  ; Call original interrupt handler:
  pushf
  cli
original_int16:
  call original_int16:original_int16 ; Will be overwritten runtime!
  sti

  ; Check if result should be intercepted:
  pushf
  push bx
  push ax
  mov word bx, [int16_incoming_ax]
  cmp bh, 0x00 ; Check if AH was "Wait for Keypress".
  je int16_check_f1
  cmp bh, 0x10 ; Check if AH was "Extended Wait for Keypress".
  je int16_check_f1
  jmp int16_end_pop_ax

int16_check_f1:
  cmp ax, 0x6800 ; Alt + F1
  jne int16_check_f2
  pop ax
  mov ax, 0x342E ; '.'
  jmp int16_end

int16_check_f2:
  cmp ax, 0x6900 ; Alt + F2
  jne int16_check_f3
  pop ax
  mov ax, 0x2B5C ; '\'
  jmp int16_end

int16_check_f3:
  cmp ax, 0x6A00 ; Alt + F3
  jne int16_end_pop_ax
  pop ax
  mov ax, 0x273A ; ':'
  jmp int16_end

int16_end_pop_ax:
  pop ax
int16_end:
  pop bx
  popf
  retf 2

int16_incoming_ax:
  dw 0

tsr_end: ; TSR end marker.

main:
  ; NOTE: No protection to prevent TSR from being loaded twice or more!

  ; Call DOS to get original interrupt handler:
  mov al, 0x16
  mov ah, 0x35
  int 0x21
  mov word [original_int16 + 3], es
  mov word [original_int16 + 1], bx

  ; Call DOS to set new interrupt handler:
  mov al, 0x16
  mov ah, 0x25
  ; DS is already same as CS, no need to change.
  mov dx, int16_interrupt
  int 0x21

  ; Terminate and Stay Resident:
  mov dx, tsr_end
  shr dx, 1
  shr dx, 1
  shr dx, 1
  shr dx, 1
  add dx, 0x11 ; Add 0x1 for remainder and 0x10 for PSP.
  mov ax, 0x3100
  int 0x21
          


Assemble it with NASM: nasm fixkeys.asm -fbin -o fixkeys.com

Topic: Scripts and Code, by Kjetil @ 25/02-2022, Article Link