Welcome to my blog! This is my first post in which I would like to recap some of the work I’ve done on S.T.A.L.K.E.R. 2: Old Concept project. This is the first challenge that I had to deal with right after I joined the development team in September 2022. I decided to split this topic into parts, primarily because I don’t think I can write and redact the whole thing in one sitting.
State of affairs after joining the team
This was a jump into unknown. I had little information about the state of the project back then, and the only thing I knew for sure was that it was supposed to utilize X-Ray 2.0 engine from the cancelled S.T.A.L.K.E.R. 2 (2011) game. After going through programmer screening process, which, to keep it short, was just a skill check to ensure that I understand C++ good enough to work on the engine (there were also headache-inducing C macros; a lot of them), I jumped straight to porting the codebase.
Understanding the caliber of the challenge ahead took a while. As I dug through the existing code of the engine, its dependencies, and SDK, I was getting more and more aware of the fact that moving all of this is going to take time. Additionally, thoughts of possibly outdated and/or discontinued dependencies of the project kept crawling at the back of my head.
The ultimate morale crusher was the fact that I wasn’t the first one to attempt this – several other programmers had attempted to port the codebase in the past and they either burned out, got overwhelmed, or both. Nonetheless, job had to be done and I had to figure out how to carry it out.
Reasoning behind the port
The main reason that pushed us to port the codebase was the fact that nobody wanted to work on the project using Visual Studio 2008. It was very hard to find people who would like to work on a large project from S.T.A.L.K.E.R. franchise to begin with and outdated tooling only made things worse. On top of that, X-Ray 2.0 was (and still is) frowned upon by vast majority of modders due to leaked build of the engine that revealed how unfinished it really is. Moving the codebase to more modern IDE was the only thing that could possibly give this engine chances of survival. Potential benefits didn’t end there – newer MSVC compiler was faster and supported multi-threading. At the same time, this was a huge risk as no one knew what kind of bugs and rewrites newer versions of libraries could bring to the table.
Implicit complications along the road
Moving the codebase from Visual Studio 2008 to newer versions had some implications. First things first, the C++ standard – the codebase used primarily C++03 with a small subset of C++11 features. I decided to move to C++14 to ensure backwards compatibility, as some of the third party libraries went through a lot of changes since 2011. Some of the C code also was very likely to stop working (and so it did) due to more things getting formalized over the years and thus, while completely legal in the past, were invalid as per newer standard. Fortunately, those were easy to cleanup.
The elephant in the room – size of the project
The Visual Studio 2008 solution that I received access to was 90+ projects strong. I didn’t want to (and still don’t want to) know the exact amount of source files were part of the engine. I knew very well that doing all the work manually is a no-go and decided to look for potential tools to aid me. I consulted the team to find out what had already been used in previous codebase port attempts so that I don’t end up getting stuck mid-process later.
A few days later I came to conclusion that CMake is going to be the right tool for the job. It had been previously field-tested by me in my hobby gamedev projects and I felt comfortable enough to use it for something larger. This decision backfired later, but I will cover that in the continuation of this series of blog posts.
Another thing that CMake addressed is something that has troubled projects like this since I can remember – inconsistent solution configuration across machines of team members. A situation in which changes made by team member A wouldn’t work on team member B’s machine were very frequent. That could happen because of various reasons, such as duct-tape solutions that weren’t fully committed to the repository. Oftentimes, the owner of changes could also forget what exactly he did after pulling an all-nighter trying squash one of the bugs that previous owners of the codebase left us with.
Where to start?
X-Ray 2.0 turned out to be a twisted cobweb of inter-dependent modules consisting of engine projects, third party libraries and inconsistent means of defining said dependencies. At first glance, everything was connected using Visual Studio 2008, but I soon realized that it wasn’t the case and individual project configuration was (probably) a leftover from the distant past, after which GSC Game World programmers switched to more complicated, verbose definition of dependencies using #pragma comment(lib, „xxx.lib„) directive. A cherry on top of that was the fact that the name of the library passed to the directive was built using… macros. This is one of the little gems that I found.
#ifdef _MSC_VER
# define XRAY_LIBRARY_NAME(library, extension) \
XRAY_MAKE_STRING(\
XRAY_STRING_CONCAT(\
XRAY_LIBRARY_PREFIX,\
XRAY_STRING_CONCAT(\
library,\
XRAY_STRING_CONCAT(\
XRAY_STATIC_ID,\
XRAY_STRING_CONCAT(\
XRAY_CONFIGURATION_ID,\
XRAY_STRING_CONCAT(\
.,\
extension\
)\
)\
)\
)\
)\
)
#else // #ifdef _MSC_VER
# if defined(__SNC__)
# define XRAY_COMPILER_ID -snc
# elif defined(__GCC__) // #if defined(__SNC__)
# define XRAY_COMPILER_ID -gcc
# else // #elif defined(__GCC__)
# error define your XRAY_COMPILER_ID here
# endif // #if defined(__SNC__)
# define XRAY_LIBRARY_NAME(library, extension) \
XRAY_MAKE_STRING(\
XRAY_STRING_CONCAT(\
XRAY_LIBRARY_PREFIX,\
XRAY_STRING_CONCAT(\
library,\
XRAY_STRING_CONCAT(\
XRAY_COMPILER_ID,\
XRAY_STRING_CONCAT(\
XRAY_STATIC_ID,\
XRAY_CONFIGURATION_ID\
)\
)\
)\
)\
)
#endif // #ifdef _MSC_VER
Yes, it consists of other macros. Is it spectacular? Probably. Does it work? No. Why? Because all built binaries were output to the same directory. What was the culprit? While all the in-house engine components used this convoluted way of defining names of binaries, the third parties that we had to compile on our own didn’t. One would assume that Visual Studio was smart enough to re-compile them when changing configuration from Debug to Release. Well, that wasn’t always the case and there weren’t any custom post-build events neither…
…but I digress. After some tinkering I managed to identify the, let’s call them, core modules that the rest of the engine depended on. Approximately one hour later I was able to sketch some CMakeLists and hook them up to generate Visual Studio 2019 solution. But wait, you said that you ported the codebase to Visual Studio 2022? Yes, that’s correct. Initially, I wanted to port it over to Visual Studio 2019, but the port took so much time that a relatively stable version of Visual Studio 2022 came around. Nonetheless, the code compiled after ironing some super long C++ template errors out. Hooray.
The road ahead
I got the two little projects to compile… now what? That’s just the tip of the iceberg. There must be some way to potentially get all of this to work in a reasonable amount of time. I will describe the process in the next entry of my blog. I hope that you have been enjoying reading my story and you would like to hear the rest of it. Until we see each other again!