Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,736 words · 5 segments analyzed
Back in the day when Principia originally released for Windows in 2014, the game would run on versions as far back as Windows XP. Given that Principia 1.4 was released while Windows XP still had mainstream support, this of course made sense at the time, and there was no real reason not to support it given that the tooling and dependencies at the time were still compatible with it.
Fast forward to today with Principia as an open source project. While the Windows version should still run as far back as Windows 7, our usage of modern toolchains, dependencies and system libraries such as UCRT means that hard to truly guarantee far into the future as the ecosystem moves forward. However, this has always been something I wanted to change one day, bringing Principia back onto Windows XP and producing a fully open source build of the game that can run on it.
The technical details of Principia I suppose it would be useful to first describe the technical details of Principia that are relevant to this endeavour. Principia is a game that was originally designed to run on phones from around 2012, so it should have no problem running on very old hardware as long as it supports at least OpenGL 2.0. Principia has very few dependencies, and makes use of SDL for cross-platform support. SDL (both SDL2 and the newer SDL3) still supports Windows XP, and as previously mentioned Principia also used to run on Windows XP. So unless there have been changes I have made during my work on the open source version that broke XP compatibility, it should be possible to get it running again on XP without too many code changes.
The main issue is just the toolchain and some other dependencies which have moved on to newer Windows versions.
The Windows version of Principia only officially supports being compiled with a mingw-w64 Windows toolchain. While historically this was due to MSVC lacking support for C99 features used in Principia’s backing engine TMS, I’m not sure how the situation for this is like nowadays. However mingw-w64 also contains various polyfills for simple C functions that do not actually exist natively on Windows, and unless someone shows up with a burning interest to build Principia with MSVC this is likely not going to change anytime soon.
Regardless, FOSS toolchains tend to be the easiest to get a modern version up that can target older versions of Windows. However, the current LLVM-based mingw-w64 toolchain from MSYS2 we use for official Windows builds are unsuitable for targeting Windows XP, both due to linking against the UCRT, LLVM’s libc++ is 7+, and any other support libraries are built expecting symbols in e.g. Vista to always exist. So we will need to step out of this and find something else… Or build something from source.
Let’s build our own toolchain! When going further back than around Vista/7, the compatibility for existing mingw-w64 toolchains you can find start to drop. Any LLVM-based toolchain has a minimum of around Windows 7 for C++ projects due to libc++ not supporting anything older, while other GCC-based toolchains are built expecting Vista+ APIs to always exist in their pre-built support libraries. While there are toolchains whose primary purpose is to target versions as old as 95, I ended up just rolling with my own. This was likely going to be the simplest, as I specifically also wanted a cross-compiling toolchain I can run on a Linux host while targeting Windows.
Okay, so we will be compiling GCC, binutils, mingw-w64… What else? Oh jeez…
Thankfully I had a bit of an ace up my sleeve in the form of a Dockerfile written by Martin Storsjö (which you may know as the llvm-mingw guy) that he linked sometime in the #mingw-w64 IRC channel. Being eager and curious, I had already taken this Dockerfile, rewrote it into a simple shell script and split it up into distinct steps for each component.
I did some adjustments to the version I had lying around, changing the triplet to i686-w64-mingw32 for 32-bit Windows, switching to MSVCRT as the system runtime it will target, and also set the default WIN32_WINNT to 0x0501 (Windows NT 5.1, corresponding to Windows XP). Then ran it to see if it would still build…
It already failed at the first step of building libgmp (the GNU MP Bignum library) with a weird error about not having a functioning compiler.
Perplexed, I went into the config.log that autotools had generated and saw that it gave an error about trying to compile some real funky code:
for(i=0;i<1;i++){if(e(got,got,9,d[i].n)==0)h();g(i,d[i].src,d[i].n,got,d[i].want,9);if(d[i].n)h();}
I was building this with the latest GCC 16, and it appears that the latest stable version of GMP as of writing (6.3.0) does not compile with GCC 15+ on default settings. The build system has a compile-time check that includes code with a function with empty parameters. In older versions of C this meant any number of arguments could be passed (and to prevent this, you would put foo(void)), while in C23 this was changed where an empty parameter list means no arguments can be passed. GCC 15 defaults to C23, making it fail to compile this code and the build system then treats it as a broken compiler.
I found a patch for the first issue in Arch’s buildscripts for GMP, but in the end I just configured GMP to build with -std=gnu99 to get it to shut up and move on with the build process, without needing to regenerate the autotools files.
After that, it was mostly smooth sailing! I would build MPFR, then MPC, then binutils, then GCC. Then build mingw-w64’s CRT and libraries using the newly built GCC, and build GCC’s libraries using the toolchain.
While letting it build, I wanted to check to make sure there hasn’t been any recent changes in GCC that would break Windows XP compatibility, specifically in the C++ standard library. That’s when I discovered that the libstdc++ that ships with GCC 16 has a newly added hard dependency on a Vista+ Win32 API function. Specifically GetDynamicTimeZoneInformation, used in std::chrono for timezone handling.
While I am mostly on my own building my own toolchain like this, I am not beyond borrowing things from other people’s toolchains. w64devkit’s 32-bit version uses GCC 16, still supports Windows XP, and was where I found out about this.
They also carry a patch which turns the dependency on the abovementioned API function into a dynamic one, attempting to grab the symbol and falling back to nothing if it does not exist. std::chrono is a C++20 feature and Principia barely even makes use of C++11 standards, so just stubbing it out would have worked fine too. But a ready-made clean patch is always best!
With that patch and some time spent compiling GCC, I now had a toolchain I knew almost with certainly could produce binaries that will work on Windows XP. Every part was built with NT 5.1 as the target, and as long as the program does not use newer functions, it should work! Just as a smoke test I compiled and ran a simple hello world program in Wine, and the compiler can in fact create runnable Windows executables.
Building dependencies Before we can build Principia, we need to build its dependencies with the newly built toolchain. Principia has the following dependencies that we need to provide externally:
curl Freetype libjpeg-turbo libpng SDL zlib
These are some of the most portable and universal software libraries in the world! However, out of these, curl ended up being a bit of a hassle. They very recently dropped support for Windows XP in curl 8.19.0, which is honestly understandable for a networking library, but somehow even curl 8.18.0 did not work as it tries to locate a freopen_s function in msvcrt.dll, which does not exist in XP. I ended up going with curl 8.17.0, which is still very recent considering where we will be running it on.
Luanti core developer sfan5 has a repository with buildscripts for cross-compiling various common libraries for Windows (link), which is used for building dependencies needed by Luanti and which I’ve found to be a useful resource for other things. All the dependencies that Principia needs are covered, and just like that all the dependencies are built!
…There is however a rather big elephant in the room in the form of GTK3, which Principia uses for dialogs on desktops. This is a huge dependency with a tree of dependencies that I don’t even want to go into.
I could resurrect the old GTK2 dialogs from the commit history, or there may be an old version of GTK3 that still supports Windows XP, but I basically did not want to deal with this at least initially. Instead I decided that I will just build the game with the experimental new dialogs that use Dear Imgui instead, which is a much lighter dependency (in addition to being much better suited for our use cases and being integrated into the game window), but a lot of dialogs in the game still remain unimplemented.
Hopefully one day that will improve, but for now that will make these builds a bit limited in terms of usability.
Building Principia With all the prerequisites up to this point done, all that was left was to actually build Principia. This would involve a long CMake incantation to point to the cross-compiler toolchain, along with all the dependencies… Some fighting with CMake option names and paths later, and the configuration was successful.
And then build. It almost got to the end and then… compiler error. Something something, incompatible pointer type, in pipeline.c in TMS, relating to these typedefs.
typedef void (*TMS_UNIFORM_FN)(GLint, GLsizei, const GLfloat*); typedef void (*TMS_UNIFORM_MAT_FN)(GLint, GLsizei, GLboolean, const GLfloat*);
pipeline.c is probably one of the hairiest source files in the codebase. I’ve improved it over the years but it’s still one of the files you’d rather not touch. However turns out the issue was quite simple - you ever heard of __stdcall? It defines the calling convention for Win32 API functions… or something along those lines. One definition expected __stdcall and another one did not, causing the compiler error.
I’m not sure why this is the case, but I’ve seen more cases of this across the codebase so I assume it is just a legacy thing from when the game used less complete GL headers.