VOGONS


First post, by songoffall

User metadata
Rank Member
Rank
Member

Back in 90s, when I was a kid with a 486, some of my early programming experiences came from making DIY tools to customize my MSDOS experience. The code wasn't the nicest, but it got the job done.

Now that my MS-DOS programming skills are a bit rusty, and I'm in the process of restoring a DOS PC, I knew beforehand I was going to run either MS DOS command prompt, Norton Commander or Windows 3.11, and I'd rather have a way to choose one on boot.

Pretty sure there are proper loaders out there, but I'm in this for the adventure, and some of you might find the techniques used useful. It's in Borland C, and uses Borland-specific libraries, like conio.h, and I used Borland C++ 4.5 - I think I used an older version back in the day, because this one doesn't seem to have a DOS editor.

I'm not using Borland's GUI tools, because for something as simple as this, that's too much hassle. This is very much work in progress done in a couple of hours, so pretty sure it will change later, like using a cfg file to generate the menu to be customizable, etc.

First, here's the full code, it's public domain.

//Simple MS DOS Startup Manager
//Allows you to choose between three options:
//MS DOS Prompt, Norton Commander and Windows
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <process.h>
#include <dos.h>
#include <string.h>

typedef int Boolean; //Support for booleans in Borland C++
const Boolean TRUE = 1;
const Boolean FALSE = 0;

int SCREENMODE;
//Screen modes:
//1 - BW40 - Black and white, 40 columns
//2 - C40 - Color, 40 columns
//3 - BW80 - Black and white, 80 columns
//4 - C80 - Color, 80 columns
//5 - MONO - Monochrome, 80 columns
//Only C80 is fully implemented and tested at this point in time

//Color pallette and the number of screen columns, the number of lines is assumed to be 25
int ScreenBG;
int ScreenFiller;
int WindowBG;
int WindowShadow;
int WindowBorder;
int WindowText;
int ButtonBG;
int ButtonText;
int Columns;

//Global cursor location
int CursorX;
int CursorY;

//Cursor routine written by Gary Chambers
static void cursor (int state);

void screeninit (void); //Initializes the screen
void screenreset (void); //Resets the screen for use in DOS
void drawwindow (void); //Draws the window
void drawbuttons (int selection); //Draws and updates the selection buttons

int main(int argc, char* argv[])
{
int selection=0;
char input=0;
Boolean proceed=TRUE;

SCREENMODE=4; //Default screen mode: 80x25 color
if (!strcmp (argv[1], "help")){
printf ("MS-DOS Loading Screen 1.0 by Bayandur Poghosyan.\n\n");
printf ("Usage: dolor [SCREENMODE]\n");
printf ("Supported screen modes:\n");
printf ("BW40 - Black and white, 40 columns\n");
printf ("C40 - Color, 40 columns\n");
printf ("BW80 - Black and white, 80 columns\n");
Show last 236 lines
		printf ("C80  - Color, 80 columns	*DEFAULT SETTING*\n");
printf ("MONO - Monochrome, 80 columns");
printf ("\n");
printf ("Example: dolor BW80 - Launches the program in black and white, 80 columns mode.");
proceed=FALSE;
} else if (!strcmp(argv[1], "BW40")){
SCREENMODE=1;
} else if (!strcmp(argv[1], "C40")){
SCREENMODE=2;
} else if (!strcmp(argv[1], "BW80")){
SCREENMODE=3;
} else if (!strcmp(argv[1], "C80")){
SCREENMODE=4;
} else if (!strcmp(argv[1], "MONO")){
SCREENMODE=5;
}
if (proceed){
screeninit();
drawwindow();
while (input!=13){
drawbuttons (selection);
switch (getch()){
case 13:
input=13;
break;
case 'H':
if (selection>0){
selection--;
} else if (selection==0){
selection=2;
}
break;
case 'P':
if (selection<2){
selection++;
} else {
selection=0;
}
break;
}
}
screenreset ();
switch (selection){
case 1: system("nc");
case 2: system("win");
}
}
return 0;
}


//Hide or unhide cursor
static void cursor (int state)
{
union REGS regs;
static int oldstart=0, oldstop=0, hidden=0;

if (state && hidden){ //Restores cursor
regs.h.ah=0x01; //BIOS set cursor
regs.h.ch=oldstart; //Retrieve starting scan line
regs.h.cl=oldstop; //Retrieve ending scan line
int86(0x10, &regs, &regs); //Call BIOS
hidden=0; //Not hidden
} else if (!hidden){ //Removes cursor
regs.h.ah=0x03; //BIOS Get Cursor Position
int86(0x10, &regs, &regs); //Call BIOS
oldstart=regs.h.ch; //Save starting scan line
oldstop=regs.h.cl; //Save ending scan line
regs.h.ah=0x01; //BIOS Set Cursor Type
regs.h.ch=0x20; //Set bit 5 in CH
int86(0x10, &regs, &regs); //Call BIOS
hidden=1; //Cursor is hidden now
}
}

void screeninit(void)
{
int i,j;
if(SCREENMODE==1){
textmode(BW40);
Columns=40;
ScreenBG=BLACK;
ScreenFiller=WHITE;
WindowBG=WHITE;
WindowShadow=BLACK;
WindowBorder=BLACK;
WindowText=BLACK;
ButtonBG=BLACK;
ButtonText=WHITE;
}else if(SCREENMODE==2){
textmode(C40);
Columns=40;
ScreenBG=LIGHTGRAY;
ScreenFiller=BLUE;
WindowBG=LIGHTGRAY;
WindowShadow=BLACK;
WindowBorder=WHITE;
WindowText=WHITE;
ButtonBG=CYAN;
ButtonText=WHITE;
}else if(SCREENMODE==3){
textmode(BW80);
Columns=80;
ScreenBG=BLACK;
ScreenFiller=WHITE;
WindowBG=WHITE;
WindowShadow=BLACK;
WindowBorder=BLACK;
WindowText=BLACK;
ButtonBG=BLACK;
ButtonText=WHITE;
}else if(SCREENMODE==4){
textmode(C80);
Columns=80;
ScreenBG=LIGHTGRAY;
ScreenFiller=BLUE;
WindowBG=LIGHTGRAY;
WindowShadow=BLACK;
WindowBorder=WHITE;
WindowText=WHITE;
ButtonBG=CYAN;
ButtonText=WHITE;
}else if(SCREENMODE==5){
textmode(MONO);
Columns=80;
ScreenBG=LIGHTGRAY;
ScreenFiller=BLACK;
WindowBG=LIGHTGRAY;
WindowShadow=BLACK;
WindowBorder=WHITE;
WindowText=WHITE;
ButtonBG=BLACK;
ButtonText=WHITE;
}
cursor(0);
textbackground(ScreenBG);
clrscr();
gotoxy(1,2);
textcolor(ScreenFiller);
for(i=1; i<24; i++){
for(j=0; j<Columns; j++){
cprintf("%c", 177);
}
}
}

void screenreset(void)
{
cursor(1);
textbackground(BLACK);
textcolor(WHITE);
clrscr();
}

void drawwindow(void)
{
int x, y;
int i, j;
x=(Columns-32)/2;
y=10;
gotoxy(x, y);
textcolor(WindowBorder);
textbackground(WindowBG);
cprintf(" %c", 201);
for(j=0; j<28; j++){
cprintf("%c", 205);
}
cprintf("%c ", 187);
gotoxy(x+6, y);
cprintf(" MS-DOS Startup Menu ");
y++;
for(i=0; i<3; i++){
gotoxy(x, y);
textbackground(WindowBG);
cprintf(" %c", 186);
for(j=0; j<28; j++){
cprintf(" ", 205);
}
cprintf("%c ", 186);
textbackground(WindowShadow);
cprintf(" ");
y++;
}
gotoxy(x, y);
textbackground(WindowBG);
cprintf(" %c", 200);
for(j=0; j<28; j++){
cprintf("%c", 205);
}
cprintf("%c ", 188);
textbackground(WindowShadow);
cprintf(" ");
y++;
x++;
gotoxy(x, y);
for(j=0; j<33; j++){
cprintf(" ");
}
}

void drawbuttons (int selection)
{
int x, y;
x=(Columns-26)/2;
y=11;
gotoxy(x, y);
if(selection==0){
textbackground(ButtonBG);
textcolor(ButtonText);
}else{
textbackground(WindowBG);
textcolor(WindowText);
}
cprintf(" MS-DOS Command Prompt ");
y++;
gotoxy(x, y);
if(selection==1){
textbackground(ButtonBG);
textcolor(ButtonText);
}else{
textbackground(WindowBG);
textcolor(WindowText);
}
cprintf(" Norton Commander ");
y++;
gotoxy(x, y);
if(selection==2){
textbackground(ButtonBG);
textcolor(ButtonText);
}else{
textbackground(WindowBG);
textcolor(WindowText);
}
cprintf(" Windows 3.11 ");
}

This is the most dodgy part that will definitely change, because I'm not even sure it will work on other computers:

			switch (getch()){
case 13:
input=13;
break;
case 'H':
if (selection>0){
selection--;
} else if (selection==0){
selection=2;
}
break;
case 'P':
if (selection<2){
selection++;
} else {
selection=0;
}
break;
}
}

As you can see, I'm getting the H and P keys for up and down, because the up and down keys work differently and don't return normal ASCII characters. But they do return H and P on my computer, so this solution works. I just don't like it. I could capture the keys, using the console input cache, but that would create problems with other keys, like the enter key.

So I'll have to sleep on it for a while.

If you need a specific part of the code explained, I'd be happy to oblige. I choose not to share the executable itself, for security reasons.

P2 300MHz/Matrox Mystique/Sound Blaster AWE 32 Value
Pentium 3 733MHz/3dfx Voodoo 3 3000/Aureal Vortex 2 (Diamond Monster Sound)
Pentium 4 HT 3.0GHz/GeForce FX 5500/Creative Audigy 2
Core2 Quad Q9400/GeForce 8800GT/Creative X-Fi Titanium Fatal1ty

Reply 1 of 8, by HanSolo

User metadata
Rank Member
Rank
Member
songoffall wrote on 2023-04-30, 08:46:

As you can see, I'm getting the H and P keys for up and down, because the up and down keys work differently and don't return normal ASCII characters

getch() returns more than one byte on cursor keys. I just don't remember what the first returned value is (in Pascal it was 0). If you get that one you have to call getch() again to get the second value.

Reply 2 of 8, by debs3759

User metadata
Rank Oldbie
Rank
Oldbie

Wouldn't it be easier to just use the built in menu options in config.sys and autoexec.bat? I'd have to look up how to do it if nobody else steps in, as my DOS coding is pretty rusty 😀 Not even sure where my DOS reference books are since moving home, everything is boxed up until I get carpets fitted.

See my graphics card database at www.gpuzoo.com
Constantly being worked on. Feel free to message me with any corrections or details of cards you would like me to research and add.

Reply 3 of 8, by amadeus777999

User metadata
Rank Oldbie
Rank
Oldbie

Maybe
'typedef enum { false, true } Boolean;'
would be a bit more elegant.

Is the 'getch()' in BC++ similar to the usual 'getchar()' or similar to the one which could also be found in old Visual C?

Reply 4 of 8, by songoffall

User metadata
Rank Member
Rank
Member
amadeus777999 wrote on 2023-04-30, 17:07:

Maybe
'typedef enum { false, true } Boolean;'
would be a bit more elegant.

Yeah, but I don't think it would work in Borland C++. I vaguely remember trying a few more elegant options and then settling on whatever works.

amadeus777999 wrote on 2023-04-30, 17:07:

Is the 'getch()' in BC++ similar to the usual 'getchar()' or similar to the one which could also be found in old Visual C?

getch() reads keypresses, getchar() reads from stdin. It's an old method I knew would work with DOS. If I were writing C++ for a more modern system, I'd go with std::cin.get().

P2 300MHz/Matrox Mystique/Sound Blaster AWE 32 Value
Pentium 3 733MHz/3dfx Voodoo 3 3000/Aureal Vortex 2 (Diamond Monster Sound)
Pentium 4 HT 3.0GHz/GeForce FX 5500/Creative Audigy 2
Core2 Quad Q9400/GeForce 8800GT/Creative X-Fi Titanium Fatal1ty

Reply 5 of 8, by songoffall

User metadata
Rank Member
Rank
Member
debs3759 wrote on 2023-04-30, 11:35:

Wouldn't it be easier to just use the built in menu options in config.sys and autoexec.bat? I'd have to look up how to do it if nobody else steps in, as my DOS coding is pretty rusty 😀 Not even sure where my DOS reference books are since moving home, everything is boxed up until I get carpets fitted.

Maybe. But it wouldn't be as much fun, would it.

Btw, in config.sys, it would be

[MENU]
MENUITEM=COMMAND, MS DOS Command Líne
MENUITEM=NC, Norton Commander
MENUITEM=WIN, Windows

I was more interested in, say, talking to the bios and hiding the cursor, doing a good old ASCII UI which won me some awards at the university back in the day, or getting the UI to work with TFEL displays properly. It's not a practical thing, just something a kid might do, and I'm pretty sure I did a couple of these back then 😀) kinda reliving the glory days.

P2 300MHz/Matrox Mystique/Sound Blaster AWE 32 Value
Pentium 3 733MHz/3dfx Voodoo 3 3000/Aureal Vortex 2 (Diamond Monster Sound)
Pentium 4 HT 3.0GHz/GeForce FX 5500/Creative Audigy 2
Core2 Quad Q9400/GeForce 8800GT/Creative X-Fi Titanium Fatal1ty

Reply 6 of 8, by songoffall

User metadata
Rank Member
Rank
Member
HanSolo wrote on 2023-04-30, 10:41:
songoffall wrote on 2023-04-30, 08:46:

As you can see, I'm getting the H and P keys for up and down, because the up and down keys work differently and don't return normal ASCII characters

getch() returns more than one byte on cursor keys. I just don't remember what the first returned value is (in Pascal it was 0). If you get that one you have to call getch() again to get the second value.

That makes sense. I'll give it a try.

P2 300MHz/Matrox Mystique/Sound Blaster AWE 32 Value
Pentium 3 733MHz/3dfx Voodoo 3 3000/Aureal Vortex 2 (Diamond Monster Sound)
Pentium 4 HT 3.0GHz/GeForce FX 5500/Creative Audigy 2
Core2 Quad Q9400/GeForce 8800GT/Creative X-Fi Titanium Fatal1ty

Reply 7 of 8, by HanSolo

User metadata
Rank Member
Rank
Member
songoffall wrote on 2023-04-30, 18:37:
debs3759 wrote on 2023-04-30, 11:35:

Wouldn't it be easier to just use the built in menu options in config.sys and autoexec.bat? I'd have to look up how to do it if nobody else steps in, as my DOS coding is pretty rusty 😀 Not even sure where my DOS reference books are since moving home, everything is boxed up until I get carpets fitted.

Maybe. But it wouldn't be as much fun, would it.

That I can relate to 😀 I wrote a similar boot-menu before DOS had it built-in (which came with 6.0 if I'm not mistaken)

Reply 8 of 8, by songoffall

User metadata
Rank Member
Rank
Member
HanSolo wrote on 2023-04-30, 19:37:
songoffall wrote on 2023-04-30, 18:37:
debs3759 wrote on 2023-04-30, 11:35:

Wouldn't it be easier to just use the built in menu options in config.sys and autoexec.bat? I'd have to look up how to do it if nobody else steps in, as my DOS coding is pretty rusty 😀 Not even sure where my DOS reference books are since moving home, everything is boxed up until I get carpets fitted.

Maybe. But it wouldn't be as much fun, would it.

That I can relate to 😀 I wrote a similar boot-menu before DOS had it built-in (which came with 6.0 if I'm not mistaken)

Cheers, mate 😀) and one of the reasons I would make one of these back in the day was to write my name on the boot screen in ASCII art 😁

P2 300MHz/Matrox Mystique/Sound Blaster AWE 32 Value
Pentium 3 733MHz/3dfx Voodoo 3 3000/Aureal Vortex 2 (Diamond Monster Sound)
Pentium 4 HT 3.0GHz/GeForce FX 5500/Creative Audigy 2
Core2 Quad Q9400/GeForce 8800GT/Creative X-Fi Titanium Fatal1ty