You ought to be able to get this working on any Windows-7/8/10 PC,
any Linux box or any recent Apple computer. I picked a Raspberry Pi 4
($35 computer.)
Google and install DOSBox-x (or just plain DOSBox might work, I
didn't try it.) Install it as per the instruction with it . . .
probably easier on a Windows-10 system, but I honestly had it going in
less than an hour on a Linux based Pi.
This will give you a window with good old MS-DOS running in it.
Yes, DIR, EDIT, FORMAT, FDISK, XCOPY, ERASE, MKDIR, etc., all work just
like they did on an original 1980s Intel 8088 4.77 MHz based PC with
640K RAM. So did a dozen DOS-based games I managed to test. Finally,
you need to dedicate a small folder on your hard drive and map it C:\
for DOSBox-x . . . maybe 1 GB or less.
To see if DOSBox-x is REALLY as good an emulator as they claim, I
just wrote something in Turbo Pascal that alters the interrupt table.
It counts the clock ticks from the emulated "4.77 MHz" CPU (which kicks
the BIOS on an original PC 18.3 times a second.) Somehow this is all
duplicated on entirely different hardware running 314.4 times faster!
And it works. Given this, I have to assume one can run any MS-DOS-based
legacy software they can find.
If you are inclined, get DOSBox-x and put it on something. Then
run some old DOS programs. They work.
My acid test was to install Turbo Pascal. That worked fine with
the ubiquitous "Hello world" test. Then I put this on it, a mixture of
Pascal and assembly language. It just asks you to type your name (or
anything else you want), and it counts the 18.3/second "ticks" while you
are doing it. This allows it to calculate how long it took you to type
it. Have fun . . . it's stuck on the bottom of this post.
Also WordPerfect, Turbo C, Wordstar, Qbasic, Lotus 1-2-3, Harvard
Graphics, etc. Here's a good place to find DOS software:
<
https://winworldpc.com/library/operating-systems#>
Look under the Application and System links on that page.
--
HRM Resident
{$R-,U-,C-}
Program Timer(input,output);
{****************************************************************************}
{*
*}
{* Program name: Timer ( Demonstrates the use of the system timer )
*}
{*
*}
{* Programmer: HRM Resident
*}
{*
*}
{* Date: 2021-11-13
*}
{*
*}
{* Language: TURBO Pascal Version 3.0
*}
{*
*}
{* Implementation: MS-DOS 5.0 Running on a Raspberry Pi in a
"Dosbox-x" *}
{* DOS installation for a Quad core Cortex-A72 (ARM
v8) *}
{* 64-bit CPU @ @ 1.5GHz emulating a 12 MHz to 40 MHz
*}
{* 80386 32-bit CPU.
*}
{*
*}
{*
*}
{* Function: This program modifies the interrupt vector table so that
*}
{* when the system timer generates an interrupt, a local
*}
{* interrupt service routine ( ISR ) is invoked rather than
*}
{* the standard MS-DOS timer ISR. This program then counts
*}
{* the number of interrupts ( which occur 18.3 times per
*}
{* second ) and uses this count to time events.
*}
{*
*}
{****************************************************************************}
const
{ DOS uses IRQ0 for the clock, IRQ1 for the keyboard, and IRQ6
for the }
{ NEC PD765 floppy disc controller. In addition, if a Winchester
disc }
{ is installed, IRQ5 is reserved for this purpose. Therefore,
IRQ2, }
{ IRQ3, IRQ4, and IRQ7 are available for user applications.
}
IRQ_CLK = 0;
{ When the 8259 PIC generates an interrupt vector address, it
produces }
{ a number equal to the IRQ line triggered plus a 5 bit offset
that is }
{ set by the host computer during initialization. On the IBM PC
this }
{ number is: 0 0 0 0 1 + x x x; where x x x is the IRQ
requesting }
{ service. Therefore, the vector table entry is 8 + IRQ_line.
}
IBM_PC_int_offset = 8; { 8259 offset used by IBM PC }
overhead = 7;
var
{ Vector table entries to be modified }
timer_vector_table_entry : integer;
{ Original vector table entries }
system_timer_offset : integer;
system_timer_segment : integer;
clock_ticks : integer;
real_clock_ticks : real;
elapsed_time : real;
name : string[80];
dsave : integer absolute cseg:$0006;
Procedure Get_Vector_Address(var offset, segment : integer;
vector : integer);
var
first_word, second_word : integer;
begin
first_word := vector * 4;
second_word := first_word + 2;
offset := memW[0000:first_word];
segment := memW[0000:second_word];
end;
Procedure Set_Vector_Address(offset, segment, vector : integer);
var
first_word, second_word : integer;
begin
first_word := vector * 4;
second_word := first_word + 2;
{ Disable interrupts while the vector table is modified }
inline($FA); { CLI }
memW[0000:first_word] := offset;
memW[0000:second_word] := segment;
inline($FB); { STI }
end;
Procedure Enable_IRQx(IRQx : byte);
{ The BIOS masks out all un-used interrupts upon initialization.
This }
{ mask must be cleared before hardware interrupts from IRQx will
be }
{ passed on to the CPU.
}
var
imr, mask : integer;
begin
mask := not ( 1 shl IRQx );
imr := port[$21]; { Get Interrupt Mask Register
from 8259 }
imr := imr and mask; { clear mask for IRQx ( bit x )
}
port[$21] := imr; { and return to interrupt
controller }
end;
Procedure Disable_IRQx(IRQx : byte);
{ Set IRQx mask bit so interrupts from this source will not be
passed }
{ on to the 8086.
}
var
imr, mask : integer;
begin
mask := 1 shl IRQx;
imr := port[$21];
imr := imr or mask;
port[$21] := imr;
end;
Procedure Timer_ISR;
{ Handles interrupts generated by the system timer }
begin
{ Save system state }
inline($FB { STI }
/$1E { PUSH DS }
/$50 { PUSH AX }
/$53 { PUSH BX }
/$51 { PUSH CX }
/$52 { PUSH DX }
/$57 { PUSH DI }
/$56 { PUSH SI }
/$06); { PUSH ES }
inline($8C/$C8/ { MOV AX,CS }
$8E/$D8/ { MOV DS,AX }
$A1/dsave/ { MOV AX,dsave }
$8E/$D8); { MOV DS,AX }
clock_ticks := clock_ticks + 1;
{ Non-specific EOI to 8259 }
inline($FA/ { CLI }
$B0/$20/ { MOV AL,020H }
$E6/$20); { OUT 020H,AL }
{ Restore system state before returning from interrupt. }
inline( $07 { POP ES }
/$5E { POP SI }
/$5F { POP DI }
/$5A { POP DX }
/$59 { POP CX }
/$5B { POP BX }
/$58 { POP AX }
/$1F { POP DS }
/$CF); { IRET }
{ IRET re-enables further interrrupts }
end;
Procedure Integrity_Check( off, seg : integer);
{ Because of the variable overhead generated by compiler directives }
{ an integrity check is performed to ensure that the vector address }
{ actually contains the ISR. }
const
ISR_preamble : array[0..8] of byte =
( $FB, { STI }
$1E, { PUSH DS }
$50, { PUSH AX }
$53, { PUSH BX }
$51, { PUSH CX }
$52, { PUSH DX }
$57, { PUSH DI }
$56, { PUSH SI }
$06 ); { PUSH ES }
var
index : integer;
Procedure ISR_Error;
begin
writeln('ISR vector is pointing to the wrong address.');
writeln;
writeln('Check compiler directives');
writeln;
writeln('Aborted ');
halt;
end;
begin
for index := 0 to 8 do
if not ( mem[seg:off + index] = ISR_preamble[index] ) then
ISR_Error;
end;
Procedure Set_Timer_Vector_Table;
{ This routine modifies the IBM PC interrupt vector table in low }
{ memory to point to the local service routine for the interrupt }
{ generated by the system timer chip ( IRQ0 ). }
var
local_offset, local_segment : integer;
begin
local_offset := ofs(Timer_ISR) + overhead;
local_segment := cseg;
Set_Vector_Address(local_offset,local_segment,
timer_vector_table_entry);
Integrity_Check(local_offset,local_segment);
end;
begin
dsave := Dseg;
timer_vector_table_entry := IRQ_CLK + IBM_PC_int_offset;
clock_ticks := 0;
Get_Vector_Address(system_timer_offset,
system_timer_segment,
timer_vector_table_entry);
Disable_IRQx(IRQ_CLK);
Set_Timer_Vector_Table;
clrscr;
Enable_IRQx(IRQ_CLK);
write('Please type your name: ');
readln(name);
Disable_IRQx(IRQ_CLK);
Set_Vector_Address(system_timer_offset,system_timer_segment,
timer_vector_table_entry);
Enable_IRQx(IRQ_CLK);
real_clock_ticks := clock_ticks;
elapsed_time := real_clock_ticks * 0.05493;
writeln;
writeln('It took you ',elapsed_time:4:2,' seconds.');
writeln;
end.