Blog • 05.10.2020

Making a VT100 command interpreter with PHP

Making a VT100 command interpreter with PHP

I came across a ttyrec file that held inside a recorded NetHack-game. NetHack (for those who don’t know) is an open-source single-player roguelike video game, first released in 1987. The game is played in terminal screen and looks like this.


Image Source: Wikipedia

Some people record their games and share their games using this ttyrec-file format. The file contains the movements of a player, move by move. I wanted to make an animated gif from that; such a gif that it ends with the image of a grave and I can build it from a command line of Raspberry Pi to a specific length of time. First, I need to parse the ttyrec-file into an editable form. Re-playing the ttyrec-file with PHP is a simple piece of code, but parsing, so I can modify that, is another issue.

I wanted to find a screen that has “Killed by” text and drop the rest of the screens with a piece of code. So time to do some coding. First I need to extract the screens from the file and interpret the commands to a printable format.

Ttyrec-file is a text file that starts with the [seconds from 1970-01-01 00:00:00]/[microseconds]/[length of content] and then the content of a screen, followed by the same kind of block of the next screen. The content is filled with vt100-commands that are used to move cursor and print characters in a terminal. They are identified by an ESC-character and then a command which tells the terminal what to do. For example, ESC[30m tells the terminal to turn the foreground color to white and ESC[2;24H the command tells the terminal to move the cursor to row 2 column 24. Everything else is outputted to the terminal.

The parser structure

First I load the ttyrec-file into a class called Terminal. Then I separate the screens from the input and then interpret the strings into commands. The commands may or may not have a printable output. Output is a string that contains ascii characters that shape the dungeon, monsters and items shown in the first image. I also added commands for backspace, newline, and carriage return for easier interpreting later with a simple str_replace. Phases (1) and (2) are in the below picture.

Interpreting the commands to present the actual terminal

In ttyrec-format, the screens follow each other, and from the previous screen, there might be characters left to the next screen. if I want to know what is printed in screen 401, I need to go through all the screens from 1 to 400 in case they leave any output to be printed in screen 401. All the commands have an output, which is the actual printable string. For some commands, I added different variables to describe the command better. For example, MoveArrowCommand has booleans up, down, left, and right to determine which way to move the cursor. CursorMoveCommand has variables row and col to tell where to move the cursor before output. Interpreting commands to actual output is just looping the commands of a screen. In the end we “print out” the output with parseOutputToTerminal-function.

(The (3) path in the above picture) We get the screen, loop the commands of the screen, and output strings to build the actual console state. The functions which interpret the commands look like this.

After this looping, we have an array that is filled with rows that have an output. We set this ready array back to the screen object so we can access the state of the screen. Then we can just output them into anything, like to a text file.

or to a gif

Nice. After doing this for all of the screens, I have 6415 individual gif files. Now we come to the next problem. How do I combine them into a single animated gif?

Making an animated gif with PHP

Link to repository

ANSI Escape sequences – VT100

Duukkis

Duukkis has been building the Internet for 20 years in multiple various size projects. He is a coder and maker of things. He has done over 70 Twitter-bots, a noun- and verb- conjugator with PHP, and a portrait from 7366 pieces of Lego. He is a developer, architect and a nice guy.

Linkedin profileTwitter profile

Do you know a perfect match? Sharing is caring