ebenoit.info

Running Legacy Worlds Beta 5 in 2025

Around 20 years ago, my friend Christopher Wicks wrote the first versions of Legacy, a web-based multiplayer strategy game set in space. Later on, I rewrote most of the game for its fifth version (it also changed name due to domain name availability), which started running some time between August and October 2006 and ended in 2009.

I was talking about that with a coworker recently and it got me curious. Would I be able to run it on a modern server? How bad would it be? Well, I took some time in the past three days to find that out.

The plan

I wanted to get the game to run as a bunch of containers controlled by Docker Compose, as that seemed easier to iterate over than writing an Ansible script to kick a VM into shape. In addition, I figured I'd run it using the next-to-latest PHP version (8.3 at the time of this writing - I hadn't paid attention to the fact that 8.4 had been released).

I didn't remember much of how the game ran and what its components were. Looking at the original source code release's README file, I noticed that the game included an IRC bot and a tool that checked for open proxies running on the IP addresses players connected from. These components did not make any sense in a modern context so I figured I'd just get rid of them. The instructions seemed pretty clear, so I was pretty optimistic I'd get the rest of it to run.

Running the website manually

In order to get the game to run, I started by trying to get it to run manually in a pair of containers.

The database

The first of these would contain a PostgreSQL RDBMS based on the official Docker image. I transferred the game's SQL files to the container and tried to feed it to psql after changing the passwords directly in the code. And it exploded, of course. Not because of incompatibilities between versions of PostgreSQL, but because of missing tables. Uh-oh.

As it turns out, the DB initialization files were never really re-used in this form after the game's initial launch. So, while the templates that are used in the admin interface to create new games (files named beta5-* in the sql/beta5 directory of the original source code) had been updated to include all the required SQL files, the main file hadn't. Worse, definitions for the game's common parts (accounts, etc..) had undergone some additions, which were not being loaded either.

Adding the appropriate \i lines fixed it and I was able to create a database for the game (there were some complains about PL/PgSQL being redefined, as well as a bad GRANT clause, but they weren't much of a problem).

Getting the site to run

The second container I created contained a minimal Debian 12 system on which I installed Apache and PHP. I wrote an initial Dockerfile that'd do that, using PHP from the Sury repos, and configuring Apache to listen on ports 80 and 81 for the game and admin pages, respectively. The LW code was exported to Docker through a bind mount, and I added a short script that would simply run Apache in the foreground. I updated the various config files, as indicated by the original README, and tried to load the game's main page.

Which, quite unsurprisingly, was blank.

And that's also when I remembered all logging in the game code was done through syslog.

Since I didn't want to install an actual syslog daemon in the container, I used socat to pipe the log entries to the container's stdout. This is hacky and would not be any good in production, but for what I wanted it was just fine. After some tweaking I came up with:

socat -u \
    UNIX-LISTEN:/dev/log,reuseaddr,mode=666,fork \
    SYSTEM:"(sed -z 's/$/\\\\n/'; /bin/echo)" \
    &

The sed part is used to replace \0 with \n as a line separator, and echo adds another line separator at the end of output.

Once that was done, I configured PHP to also log its errors to syslog. I relaunched the container, tried loading the page and cried a little.

PHP fixes

PHP has changed so much since LW was originally written. On the bright side, it got tremendously more verbose regarding various issues (incidentally, that would have helped a lot when I was working on that code).

One of the first issues I stumbled upon was the use of short opening tags (<? instead of <?php). I didn't know an option existed to toggle them on or off, so I just replaced them with the longer version, which is likely better anyway.

Another massive pain was caused by constructors. Most of the constructors used the old style of declaration, in which the constructor's name is the same as the class'. This no longer works since PHP 8.0, so I got to rename all of them to __construct.

An issue that was less widespread but prevented the game from loading anyway was the fact that back then, PHP didn't care too much if a method was static or not, allowing it to be called statically independently of the keyboard. Quite a few methods were missing the keyword, so static calls to them caused errors. Fortunately, I hadn't abused the "feature" at the time so there were no methods that could to be called in both modes.

Finally, the other bit of deprecated syntax that bit me was the use of {} for string indices (I used [] for arrays). There are probably still a few of these around in the code, although I fixed the ones I stumbled upon.

And then there were so many other minor issues: deprecation of split in favor of explode, count no longer returning 0 when called on something that isn't an array, manually disabling "magic quotes" in the admin scripts, less sensibility to types in some operations...

It took a while, but I finally got both the game site and the admin site pages to display. The log is still flooding with warnings about using undefined array keys and the like, but hey, it's working.

Actual containers and other fixes

At that point I was able to create proper containers for the game to run in, along with a compose.yml file. I kept on fixing PHP issues as they popped up. It was time to get the rest of the code to run though.

The control script

The game's code includes a Perl script, control.pl, that is responsible for starting and controlling the process that runs game ticks. It is also capable of updating the main configuration, which for some reason is stored as an XML file (with some bits in a PHP array on the side - I don't remember the rationale behind that but it seems a little weird).

At any rate, the control process runs as root, and the rest of the game can order it around through a FIFO.

Turns out, this mostly still worked out of the box. The only thing I had to fix was the way the ticks manager got started - it relied on some trick were the manager was started as a system user's shell. The system user is still created in the container, but the ticks manager is now started in a slightly more sane way (using runuser).

The planet generator

The planet generator is another Perl script which checks a directory for "requests" (empty files whose names follow the req-<game instance>-<first planet ID>-<count> pattern) every minute, and generates semi-random planet pictures using POV-Ray based on the requests.

I had already run into a few issues that pointed to that, but trying to get this thing to work made it pretty obvious - the version of the code that was originally released is either incomplete or based on a future, work-in-progress version. The planet generator's pictures were not being written to the right directories for the client-side JS to find them.

I ended up having to fix that, in addition to getting the generator to work in a separate container. The "requests" directory and the output directory are both shared as Docker volumes between the generator and game containers.

Configuration through environment variables

The original code relied on multiple configuration files (the game's PHP and XML config file, as well as the admin interface's PHP config array). In addition, quite a few things were hardcoded. This had to be changed if I wanted the game to be configured through a single .env file to be read by Docker Compose.

For the database, it was easy enough: PostgreSQL's psql tool can read environment variables directly since version 15 using the \getenv command. These variables can then be injected into SQL queries using the :'my_var' syntax. A short shell script was added in order to find the names of all required variables, read values from files if a <var>_FILE variable exists instead, and then use psql to run the SQL initialization.

The administration interface's configuration file didn't require too many changes - it was plain PHP, so it was a matter of adding a function to read secrets from files, and reading the rest from the appropriate env vars.

It was a little trickier for the game, as the DB configuration lives in the XML part of the config. I had to hack in a from-env attribute which can be used to read an attribute from the environment. Since the XML config is cached as PHP serialized data, that hack would cause problems if the cache lingered on, but fortunately it's on a tmpfs so it goes away on container restarts.

Sending mail

The last remaining problem was the fact that the code relies on PHP's mail() function to send mail. That function requires a local mailer, which will then relay the mail somewhere else - and that requires yet another daemon which I didn't want to run in the container.

It didn't take much research to read about msmtp, a program that provides a sendmail-compatible interface and forwards the mail directly to some arbitrary SMTP server.

I also removed the hardcoded From address in the LW code and replaced it with a configuration variable.

Conclusion

It was fun to get this old code to run again. It's very likely still full of PHP compatibility issues, and no doubt comes along with a bunch of bugs that could be fixed by reading the logs, but that wasn't the point. In addition, it would be absolutely critical to replace the authentication code with something that doesn't store user's passwords in clear text in the DB if one wanted to run it even for a private game.

It surprised me to see that the client-side part of the game still works without issues so many years after having been written. I mean, most of the code in the game was written before the first version of JQuery! Speaking of which, version 1.2.3 of it is included in the game's assets, but it must have been a later addition.

The SQL code still works as well, but it is mostly trivial so that's no surprise.

I got a nice chuckle out of the game's home screen:

This game works best with modern browsers such as Firefox 2 or Internet Explorer 7.

"Modern", heh.

The Dockerized, "working" version can be found on my Forgejo instance.