Privateer's Hidden Text Adventure
Or: How to Make a Text Adventure Game Out of DOS Batch Files
Background
Wing Commander: Privateer (often just called Privateer) is a space simulator game for PC, released by Origin Systems in 1993. It’s largely of the Elite lineage, but set in the Wing Commander universe and with a bit more focus on fast action and dogfighting than on simulation detail, a design concept also inherited from Wing Commander. I highly recommend trying the game if that sort of thing sounds interesting to you. If you don’t have a copy, you can buy one from GOG.com; what you’ll get from there the CD-ROM re-release which includes the expansion pack Righteous Fire, the speech pack, and a few other added features.
That’s all I’m going to say about Privateer. We’re here to talk about something else.
Poking Around
If you want to follow along with this article, you'll find all the files I'm talking about in the install directory of either the original or the GOG version. If you don't have or want a copy of Privateer, you can find just the relevant files nicely packaged and ready to run on the Internet Archive.
I had a copy of Privateer as a kid, and I was also constantly curious about what was going on with my computer, so obviously one of the first things I did was have a look in the game’s installation directory.
What I found there was all the usual DOS game stuff: a setup utility where you can tell the game about your sound card(s), some configuration files, lots of data files, and the game executables. But there’s another file too that didn’t look like it fit any of those categories. It’s about 10KB and it’s called TABTNE.VDA
. So naturally I needed to have a look in there.
Since I didn’t have any actual tools at the time, I opened the file in EDIT.COM
. And it was… text? I didn’t expect readable text. Okay, maybe readable is a stretch, but it is ASCII text. In fact it looked like a batch file, the old DOS version of what today we would think of as a shell script. The first line is @echo off
, which is a telltale sign; echo off
is a special form of the normal echo
command that disables the DOS shell’s normal behavior of automatically printing every command in a batch file to the console, and the @
at the front disables echoing just that one command. So tons of DOS/Windows batch files start with @echo off
, and nothing else really does. I knew I was looking at a batch file.
Let’s Run a Questionable Script
At the time I had no concept of computer security (in my defense, hardly anybody did), so my obvious next move is to run this thing and see what happens. In hindsight, I think there must have been instructions somewhere on how to get it to work, but I didn’t have any, so let’s go through it the hard way.
I’ve got Privateer installed into an MS-DOS 6 virtual machine, so I’ll be showing you some transcripts from its command line as we go through this. The obvious first step is to rename the file with a .BAT
extension and run it:
C:\PRIVATER>ren tabtne.vba tabtne.bat
C:\PRIVATER>tabtne.bat
First you have to give this file its rightful name.
C:\PRIVATER>
Yes, the name of the directory is missing an "e". That's because this is DOS, and file names have to follow the old 8.3 format, so that's how the game's install program handles the title being too long.
… Okay. Well. Let’s look at the file again. At the top there’s a bunch of setup and command line handling and such. Line 21 says if not exist advent.bat goto c0
1. Scrolling a few lines later, we find the label called :c0
, which is what prints that message. So advent.bat
must be the file’s “rightful” name. We’ll rename it to that and run it again:
C:\PRIVATER>ren tabtne.bat advent.bat
C:\PRIVATER>advent
Please place this file in an empty directory.
Then type 'advent setup'.
C:\PRIVATER>
So, it needs to be in an empty directory, huh. That might be a clue to how this thing works. But for now we’ll just do as it asks and give it a directory and run the setup.
C:\PRIVATER>mkdir C:\advent
C:\PRIVATER>copy advent.bat c:\advent
1 file(s) copied
C:\PRIVATER>cd ..\advent
C:\ADVENT>advent setup
Type 'advent' to start.
C:\ADVENT>
Okay! Progress! We must finally be done settings things up.
But wait a sec. What did that setup
command actually do? What setup could a batch file possibly need?
C:\ADVENT>dir
Volume in drive C is MS-DOS_6
Volume Serial Number is 4F1F-8F60
Directory of C:\ADVENT
. <DIR> 05-03-20 7:38p
.. <DIR> 05-03-20 7:38p
ADVENT BAT 9,843 08-10-92 8:04p
D BAT 15 05-03-20 7:39p
DROP BAT 18 05-03-20 7:39p
E BAT 15 05-03-20 7:39p
GET BAT 18 05-03-20 7:39p
GO BAT 16 05-03-20 7:39p
INV BAT 17 05-03-20 7:39p
N BAT 15 05-03-20 7:39p
QUIT BAT 15 05-03-20 7:39p
S BAT 15 05-03-20 7:39p
L BAT 18 05-03-20 7:39p
TAKE BAT 18 05-03-20 7:39p
U BAT 15 05-03-20 7:39p
W BAT 15 05-03-20 7:39p
LOOK BAT 18 05-03-20 7:39p
SAY BAT 17 05-03-20 7:39p
USE BAT 17 05-03-20 7:39p
I BAT 17 05-03-20 7:39p
EXAMINE BAT 18 05-03-20 7:39p
21 file(s) 10,140 bytes
… it made a bunch more tiny batch files. Which all have names that look suspiciously like verbs you would type in to a text adventure game parser (N
, S
, E
, and W
to move to the north, south, east, or west, for example). And it wanted us to call it “advent.” Surely this thing is not an entire text adventure game, hidden away inside of a batch file. Surely.
Let’s see what’s in those tiny verb batch files:
C:\ADVENT>type N.BAT
@advent go n
So these all just call the “main” batch file to pass along commands (again we see the @
to disable echo).
If you’re curious about how creating batch files from within a batch file works, here’s a few snippets of the setup code, with comments added (REM
for “remark” starts a comment line in batch files):
REM There's one of these blocks for each verb batch file.
REM Check if the file we're trying to create already exists.
if exist d.bat goto e1
REM If it doesn't, echo the correct command string into it.
echo @advent go d > d.bat
REM Here's another example that writes a different verb.
e1:
if exist drop.bat goto e2
REM This time the verb has a subject, so this verb's batch file
REM needs to pass its first argument through to advent.bat.
echo @advent drop %%1% > drop.bat
REM Skipping over more of those blocks here.
REM Print the instruction message.
echo Type 'advent' to start.
Actually Running the Script
Now, finally, for real, let’s start it up:
C:\ADVENT>advent
[ ... the screen clears ... ]
--- Art D's First Batch Adventure ---
A few notes before you begin:
1. Do not let your commands stray beyond two words.
2. Type 'quit' to quit.
3. Don't type DOS commands while playing (except 'dir' for a verb list).
You are walking along a sunny north/south path near a small stream.
A very recent landslide prevents your return to the south.
There is a sharp knife here.
>
Yeah, this certainly looks like the first screen of a normal text adventure game. We’re playing a text adventure game implemented entirely as a batch file, and hidden as an easter egg within another, commercially released game.
"Art D", by the way, I think must be Arthur DiBianca, who is credited as a programmer on the original Privateer and as project lead and sole programmer (among other things) for both its expansion pack Righteous Fire and the CD-ROM re-release that combined both (the original versions were sold on floppy disks).
But what does it mean about not typing any DOS commands? Aren’t we inside a game? It took our DOS prompt away, how can any DOS commands even work? Let’s try the one it does suggest and see what happens.
>dir
Volume in drive C is MS-DOS_6
Volume Serial Number is 4F1F-8F60
Directory of C:\ADVENT
. <DIR> 05-03-20 7:38p
.. <DIR> 05-03-20 7:38p
ADVENT BAT 9,843 08-10-92 8:04p
D BAT 15 05-03-20 7:39p
DROP BAT 18 05-03-20 7:39p
E BAT 15 05-03-20 7:39p
GET BAT 18 05-03-20 7:39p
GO BAT 16 05-03-20 7:39p
INV BAT 17 05-03-20 7:39p
N BAT 15 05-03-20 7:39p
QUIT BAT 15 05-03-20 7:39p
S BAT 15 05-03-20 7:39p
L BAT 18 05-03-20 7:39p
TAKE BAT 18 05-03-20 7:39p
U BAT 15 05-03-20 7:39p
W BAT 15 05-03-20 7:39p
LOOK BAT 18 05-03-20 7:39p
SAY BAT 17 05-03-20 7:39p
USE BAT 17 05-03-20 7:39p
I BAT 17 05-03-20 7:39p
EXAMINE BAT 18 05-03-20 7:39p
21 file(s) 10,140 bytes
Wait, that’s… that’s DOS’s dir
output. From the same directory we were already in. It’s identical to what we saw before. This thing has definitely not reimplemented dir
. It must have just dropped us right back into the DOS shell. Plus it’s confirmed our guess from before: the batch files that the setup command created really are our verbs for playing the game. But it’s lying to us a bit; if we try other DOS commands, they do still work:
>more < n.bat
@advent go n
>
So yeah. All it’s done is mess with our prompt to make it look like a custom thing and not a normal DOS prompt, which is totally what it still is. We’re going to play a text adventure game inside of COMMAND.COM
.
We’re Playing a Game Now, Apparently
Since we’re in a text adventure, let’s do the natural thing and pick up the inventory item it told us is here:
>take knife
Taken.
>i
You are carrying a knife.
>
But wait! Wait, I say! We know now that all we just did was run a couple of files called TAKE.BAT
and I.BAT
, which each just exited and left us back at the shell. So how does the game keep track of an inventory? The location the player is currently in is another kind of important thing for the game to know, so it must be storing that somewhere too.
The possibilities for something like this in DOS are rather limited, especially when you’re running entirely out of batch files; all the really sneaky things you could do would require running your own native code, which isn’t an option here.
We know the game can create files because that’s what its setup routine does. So let’s check for any new files it might have just made.
>dir
[ ... everything is the same as before ... ]
>
There’s no new files, and none of the sizes or times are different either. It’s not using any aspect of the file system to store its state.
That pretty much leaves environment variables as our only other option. Since normal DOS commands work fine while we’re in the game, we have the tools we need to check and see if it’s made any changes to the environment. What that actually means here in unmodified MS-DOS 6 is really only that we can dump the entire environment, but let’s try that.
>set
COMSPEC=C:\DOS\COMMAND.COM
PATH=C:\WINDOWS;C:\DOS
TEMP=C:\DOS
_R=$p$g
_L=path
_BOX=road
_LAMP=nowh
_SHOES=gymn
PROMPT=$g
_KNIFE=poss
_U=echo You are carrying
_T=X
>
It sure has made some environment variables; everything that starts with an _
was created by the game. It’s also edited the PROMPT
to remove the current directory; the original value of PROMPT
is being kept around in _R
so that it can be restored when we quit the game. In fact, I bet all that the quit
command does is restore the prompt and delete all the _
variables. Let’s look at its code:
:cquit
prompt %_R%
set _R=
set _L=
set _T=
set _D=
set _BOX=
set _KNIFE=
set _LAMP=
set _SHOES=
REM "echo." prints a blank line.
echo.
echo Your environment is clear. You may proceed.
echo.
REM Exit the script.
goto e
Yep.
So let’s take stock. We can see four variables with names that look like inventory items, _BOX
, _LAMP
, _SHOES
, and _KNIFE
. Doesn’t seem like very many, but the whole game is one 10 KB batch file, what do you want. _KNIFE
is set to poss
, and since we picked up the knife I’m assuming that’s short for “possessed”. The other three have values that look like names of locations, so that seems like it’s storing where those items currently are at. _L
also looks like the name of a place, so that must be where the game stores the player’s current location. We don’t know yet what _U
and _T
are for.
Unfortunately the way this system of keeping track of game state works means that our progress isn’t saved at all, unless you manually copy out all of those environment variables somewhere yourself. But this game doesn’t seem long enough for that to become a problem. Again: 10 KB batch file. The Markdown file I am writing this blog post in is almost twice that long. There’s also the risk of name collisions with existing environment variables, the game doesn’t do anything to handle that, but the leading underscores and the particular names chosen make that seem unlikely.
From here, the rest of how things work is not complicated. Each of the tiny verb batch files calls into ADVENT.BAT
with certain arguments for the verb it wants to execute, and there’s a simple parser in there that just GOTO
’s the right label for whatever command you ran. Those labels then have those own if/goto chains that do different things based on the other parameters and on those environment variables. And that’s it, that’s how it works, there really isn’t any more to it. I’ll show you one example. One of the available commands is called drop
; it takes an item out of your inventory and leaves it in whatever location you’re currently at. Here’s is the entire implementation of drop
:
REM Near the beginning of the file:
if '%1'=='drop' goto cdrop
REM Later on:
:cdrop
if '%2'=='' goto dwhat
if '%2'=='box' set _T=%_BOX%
if '%2'=='knife' set _T=%_KNIFE%
if '%2'=='lamp' set _T=%_LAMP%
if '%2'=='shoes' set _T=%_SHOES%
if '%_T%'=='poss' goto dg
if not '%_T%'=='' goto db
echo Drop the %2? I don't think I quite understand you.
goto e
:dwhat
echo 'Take' ain't the only transitive verb, either.
goto e
:db
echo You aren't carrying the %2.
goto e
:dg
set _%2=%_L%
echo Dropped the %2.
goto e
Even though this isn’t complicated, there are a few things to explain.
The e
label is at the end of the file and just exits, so going there leaves you back at the modified DOS prompt.
%1
and %2
and so on are special variables containing the command-line arguments. If we don’t pass the name of an object to drop, the game prints a message complaining at us, if we pass a name that it doesn’t know, we get a different message, and there’s a third message for if we try to drop an item we don’t have.
set
is the DOS command that sets environment variables, so we can see now that, indeed, those are where all the game state is. We even see how that _T
one that we didn’t understand earlier is used; it’s just temporary storage. And if the parameter validation passes, all the game does to implement the command is set the environment variable with the same name as the object (but with an underscore prepended) to the value of the current location, which is stored in the variable _L
.
In DOS (and still today in some places in Windows), surrounding the name of an environment variable in %
is how you retrieve its value, so the set _T
commands are setting _T
to the value of one of the four inventory object variables (which we’ve already seen hold the name of the location where that object currently is).
And that’s how the whole game works. The other commands are all the same, with different checks and different messages based on whatever the relevant environment variables are. That’s all you need to have for a complete text adventure game.
But Wait
The game is short, and I don’t want to spoil anything here, so I won’t show any more of it. But if you do play through to the end, you’ll receive this message:
As you emerge from the cellar, its entrance collapses behind you! You find
yourself in a sunny clearing with a statue of a smiling crowned man.
On its base is an inscription: 'You have passed the first test. Your
reward is a single word: <redacted>. Remember it well, for it will aid you
on the dark road that lies ahead. Play Art D's Next Batch Adventure and
fulfill your destiny!'
I even redacted the secret world, that’s how serious I am about not spoiling anything.
But yes! There is an Art D’s Next Batch Adventure! Privateer was successful enough to get an expansion pack, called Righteous Fire, and so was Art D’s First Batch Adventure deemed worthy of a sequel to include with it. You’ll find the batch file for this one in the same place, but the file name you’re looking for now is TABTXE.NDA
. There’s even a third Batch Adventure, included in a World War I flight simulator called Wings of Glory, which Art D isn’t actually credited on (it appears he had left Origin to do his own thing by then).
And that’s it! I’m out of things to talk about here. I don’t have any conclusion to draw or point I was trying to make, I just hope you enjoyed going along on this nostalgia trip with me to see this crazy, awesome, ridiculous thing.
-
Code snippets that I quote here will not be identical to what’s in the original file, because I’ve undone a bit of what looks like intentional obfuscation. ↩