Batch File Menu Builder

Published: 2021/05/17

Last updated: 2021/05/17

While cleaning up some files, I was reminded of a program I wrote last year. I have often in the past, both for myself and for the sake of others, written up MS-DOS batch files for elegantly selecting and loading games. I found myself wanting to do this again and realised that while I appreciated the results, I didn’t like how tedious and repetitive actually building the batch files was. Thinking a bit, I realised that this was a perfect excuse to indulge in some meta-programming; I could write a program to write my batch file for me when given some structured input containing the relevant information.

I decided to make use of two tools readily available to me: my favourite scripting language (Perl) and a subset of the markup language YAML to handle the configuration file, since my usual fallback, plain INI files, didn’t seem quite up to the task.

The Batch File

I had to first start with defining what my output was going to be – how this batch file should look and function.

Some time in the distant past, I had learned about a common DOS utility file, CHOICE.COM. Thankfully, DOSBox, the environment I was generally targeting, comes with a built-in implementation already. This tiny program is the key to making the menu work. When invoked, it waits for one of a list of defined inputs, typically alphanumeric keys, and returns a particular exit code, called an errorlevel, in the order of the elements. For instance, given an input of “abc”, it would return errorlevel 1, 2, and 3 respectively, depending on which key was pressed. However, it is necessary to actually process these error codes in reverse order due to a quirk in how the batch language works.

Having done that, one can then use if-statements and labels to jump to the next area of the batch file, which can be a sub-menu constructed in the same way or else a chain of commands to run to actually launch the program. This operates pretty well as one might expect, but it gets tedious to write, hence the desire for this program.

So far as the main menu’s appearance goes, I wanted to have a nice alphabetical listing, with the applicable CHOICE.COM key clearly listed, and a genre tag for (informative) flair. I also wanted the menu to look reasonably attractive by using line-drawing characters to create a box around it precisely as wide as the longest string. I also decided that, for the sake of consistency, that every game’s sub-menu should be structured as similarly as possible.

The final layout would look something like this (which is precisely why I like this CSS line-drawing style):

Choose a game                                            
A) Alien Carnage [Action]                                
B) Commander Keen 1-3: Invasion of the Vorticons [Action]
C) One Must Fall 2097 [Fighting]
Z) Quit                                                  

When it came to to the sub-menus, I wanted essentially the overall look and feel as the main menu, with each game getting its own dedicated sub-menu with a little metadata, as well as a consistent layout for the user to choose whether to view information about the game, read the manual, change settings, play the game, or go back. It ends up looking something like this:

Alien Carnage (Action, Apogee, 1993)
A) Information                      
B) Manual                           
C) Configure                        
D) Play                             
Z) Back                             

In the case of a game not having a manual or a setup file, I wanted to either write one (in the case of the former) or at least put up a message stating that reading the game’s manual or configuring it could be done from within the game, as I expected that to be a fairly common scenario. I also decided that all commands, including commands that launch the game, should eventually cause the user to return to either the game entry page or the main menu (in the case of game commands), as re-running the batch file after every command or game can get annoying. It also makes it easier to write more self-contained DOSBox autorun scripts, as the entire program can simply exit after the batch file quits.

Now, in the case of episodic games, like Commander Keen, which often didn’t have their own launcher, I decided to also generate sub-menus, like so:

A) Marooned on Mars  
B) The Earth Explodes
C) Keen Must Die!    
Z) Back              

Fairly straightforward. I also decided that the informational blurb should be in the same kind of window, and automatically go back to its relevant menu area after pressing a key, which is accomplished with the built-in pause command, like so:

Commander Keen: Invasion of the Vorticons is a trilogy of
games starring Billy Blaze, alias "Commander Keen", 8-year
old boy genius and dispenser of galactic justice. In these
episodes, you guide Commander Keen on a mission of
exploration that quickly turns into a race to save the Earth
and defeat the mysterious Grand Intellect.

Press any key to continue.

The Configuration File

Having decided how everything should look and function, I then moved onto the configuration file. Trying to figure out how to neatly handle the episodic games was a bit tricky, but what I eventually settled on was that there would be two different ways to run the game-launching commands, depending on what was necessary. I also decided that the commands for the manual and configuration would be strictly optional, for the reasons discussed previously – the builder would substitute the appropriate text if needed.

As a full example, here’s an entry for One Must Fall 2097:

  title: One Must Fall 2097
  author: Epic MegaGames
  genre: Fighting
  year: 1991
  information: |
    One Must Fall 2097 is a robotic fighting game on the order
    of Mortal Kombat, but without the blood and gore. It has 5
    arenas, 10 Pilots in the One Player story mode, eleven
    robots (one of them is hidden), and four tournaments in the
    Tournament mode.
  manual: |
  configure: |
  play: |

Note that each entry must begin with a unique identifier, in this case “omf”. This will also be the label used in the batch file, conveniently enough. The five metadata fields (title, author, genre, year, and information) are not optional and are used for the informative flairs and description. Anything spanning multiple lines, whether it involves text or commands, must contain a pipe character after the parameter. When writing the informational blurb, note that blank lines can be inserted via a literal “\n”. This is necessary due to the fact that YAML::Tiny, the parsing library used, collapses consecutive newlines. The manual, configure, and play sections can contain any arbitrary series of commands.

For the episodic games, I had to allow for a slightly different syntax. Instead of the game-launching commands being under the “play” section, they use numbered “episode” commands. As an example, here’s what Commander Keen’s looks like:

  title: "Commander Keen 1-3: Invasion of the Vorticons"
  author: Id Software
  genre: Action
  year: 1991
  information: |
    Commander Keen: Invasion of the Vorticons is a trilogy of
    games starring Billy Blaze, alias "Commander Keen", 8-year
    old boy genius and dispenser of galactic justice. In these
    episodes, you guide Commander Keen on a mission of
    exploration that quickly turns into a race to save the Earth
    and defeat the mysterious Grand Intellect.
        title: Marooned on Mars
        play: |
        title: The Earth Explodes
        play: |
        title: Keen Must Die!
        play: |

Again, fairly straightforward. As I haven’t seen a need for it at this point, episodes cannot have their own individual manuals or configuration programs. Do note however that the series title, which contains a colon, needs to be quoted, as colons are reserved characters. Should both a “play” and an “episodes” section be present, this will count as a fatal error.

The Build Script

Seeing as how the script itself is currently 488 lines long, I don’t really want to give a play-by-play and you probably don’t want to read such anyway. Instead, I’ll give a high-level overview of how it operates, and conclude with a dump on a separate page.


The main subroutine is pretty straightforward. After the usual initialisations and option-handling, it reads in the YAML file via YAML::Tiny, does some basic sanity-checking, and grafts in a tag for the page number, which comes into play later. It then generates the menu via make_menu() and writes it to disk via write_file(), complaining if it fails.


After being passed in the list of games, this routine orchestrates building up the batch file. First, it sets up the title page by calling make_title_page(), telling it the games and how many there are. Then, for each game it knows about, it passes the game name and data to make_game_page(). Finally, it appends the ending screen via make_ending_page(), and returns the results back to the main routine.


After being passed the list of games and their amounts, it decides if everything can fit on one page (as defined by the $MAX_GAMES variable, which can be set by the user) or else how many pages it needs. If there’s more than one page, this is where the page number tag from the main subroutine comes into play. To explain that a little more fully, as it’s slightly confusing in practice, $MAX_GAMES tells the script that you want up to that many games on each page – if you provide more games than that, the script will split the listing into multiple sub-menus containing 2 less than $MAX_GAMES (to leave room for the necessary next/previous options). By default, it uses 18 games per page, as that number of games fits perfectly on-screen in DOSBox.

Having determined its course of action, it then builds up the page by putting in some boilerplate batch commands and inserting both the title and genre of each game (in alphabetical order). It then, if necessary, adds the listing for the next/previous commands and the exit command. A nice border is added via make_border(), and then the CHOICE.COM commands and resultant errorlevel checks are made. The title page(s) are then passed back to make_menu().


This routine, slightly less complex than make_title_page(), is given the name of each game as the game data. If it knows there’s only one page in the game listing, it blanks out the page tag so that the labels come out correctly (i.e. instead of start1, etc. it will simply use start. This is more for aesthetics than anything else), and then insert the title alongside its genre, author, and year of publication. Then, for each user-facing option, it will create the relevant entries, then wrap it up on a nice border, again using make_border().

From here, it runs through the CHOICE.COM and errorlevel dance, and then inserts the necessary commands for each of the options, with the informational blurb getting the fancy treatment with make_border(). This routine has a big split here depending on whether it needs to handle a simple play command or the more complex episode-handling. The latter results in another simple sub-menu being generated, and as usual it’s decorated via make_border(). One important thing to note is that each game command is followed up by a cd command that resets the current working directory to the root of the drive. Once everything is ready, it’s all handed off to make_menu().


This routine is actually very simple. It just makes the concluding screen used after the batch file is exited from, again decorating the instructional blurb with make_border() and returns the results to make_menu().


I mention this fairly obvious and simple routine only to point out that it ensures that the final batch file is written out DOS-style, using CRLF line-endings. This is very important if you want it to actually work in DOS.

Flow Recap

To recap the flow, it basically goes as follows:

Main -> make_menu() -> make_title_page() -> make_game_page() -> make_ending_page() -> write_file().


That’s basically the gist of it. The actual script can be found here. A manual is included via in-line POD after the __END__ marker. It can also be viewed by invoking the script with the --man argument.

I came here to awoo at you

Back to main page