Aug 262025
 
Raptor logo

It’s been a while since I’ve done this, but today I’d like to show you how to make the free and open-source version of the game “Raptor: Call of the Shadows” work on Windows XP and Windows XP Pro x64 Edition. Thanks fly out to Srandista, who let me know about this on the #guests channel of my [IRC chat server]. This is a funny one, given that the original game was developed for DOS in the early 90s. While the games’ developer Scott Host gave the source code to another developer going by the nick name [skynettx] to be released under the free GPLv2 license, this version is based on a reverse-engineered version of the source code produced by nukeykt. You can download The Windows release build [here]. It requires at least Windows Vista, however.

Attempting to launch the game as-is under Windows XP makes a screen appear that I have seen a couple of dozen times already. ;)

Raptor is not a valid Win32 application

An old friend stopping by for a visit!

Now before I start, let me say that there are two ways to handle this game when it comes to Windows XP compatibility: An easy way which includes modifying the games’ PE32 / PE64 header and a hard way, recompiling the whole thing from source code. Let’s start with the easy way!

No, wait… Let’s actually start with the downloads first:

0. The files

Here’s what you need to make your version of Raptor work on Windows XP:

The game download includes some assets, but only those that came with the shareware version of Raptor: Call of the Shadows. So you don’t get the full set of .GLB files. To get the full game, you still need to buy the game in its classic version (e.g. on Steam), as the assets are still distributed under a proprietary, commercial license[1]. The game asset files of the full version are called FILE0000.GLBFILE0000.GLBFILE0001.GLBFILE0002.GLBFILE0003.GLB and FILE0004.GLB. Those need to sit in the same folder as raptor.exe.

1. The easy way out

For those of you who are new to this, but want to fix the official release instead, please download the tool [CFF Explorer] by NTCore, and open raptor.exe in it. Click on “Optional Header” and look for the fields MajorSubsystemVersion and MinorSubsystemVersion:

CFF Explorer inspecting raptor.exe

CFF Explorer inspecting raptor.exe

These are set to 6 and 0, which corresponds to the Windows NT kernel version 6.0. So, Windows Vista. Change these to 5 and 1 instead (or 5 and 2 for the 64-bit version) and save the file. Do the same for raptorsetup.exe, should you need it:

Fixing the PE32 / PE64 header

Fixing the PE32 / PE64 header

This is really all, there are no actual Vista+ dependencies. The problem arises only due to the platform toolset of the Microsoft VisualStudio project not having been targeted at Windows XP. A very common (and by now understandable) issue with modern software projects that use Microsofts’ development environments.

The game should now work on Windows XP:

Raptor running on Windows XP

Open-source version of “Raptor: Call of the Shadows” running on Windows XP (click to enlarge)

The Raptor setup that comes with the official build is pretty funny actually, looks just like it did in DOS times:

Raptor setup.exe

raptorsetup.exe

2. Compiling from source, or: The hard way out

For this, you will need Microsoft VisualStudio 2017 and probably also 2015 and and its components for targeting Windows XP installed on some newer version of Windows. Newer versions of VisualStudio don’t have the platform toolsets for targeting Windows XP included anymore, so that’s why. Clone the source code from [here] using git. After that, open the solution file raptor\raptor.sln in VisualStudio 2017. Edit the project properties and change the platform toolset to “Visual Studio 2015 – Windows XP (v140_xp)”. Actually, v141_xp should also work, but the solution did not compile for me with that toolset, throwing some weird errors that I didn’t understand. That’s kind of strange considering skynettx seems to be using the much newer Visual Studio 2022 instead, but I didn’t care to inspect this more closely.

Edit the solution properties and pick your desired version (Debug or Release) and platform architecture (x86, which means 32-bit or x64, which means 64-bit) under Configuration Properties > Configuration. Now you can build the solution to get a raptor.exe that works on Windows XP.

Bundle raptor.exe with raptorsetup.exe (if desired), and at least with SDL2.dll, which is required, as SDL2 is the graphics library the open-source version of the game uses to draw stuff on screen.

Raptor Setup is actually a separate project, for which you can get the source code [over here]. The process to build it is similar, so you need to compile that as well to get the raptorsetup.exe I just mentioned.

And that’s it, have fun! Beer smilie I guess I will, given that I’m a huge fan of [Tyrian]! :)

[1] Raptor: Call of the Shadows is © 1993 Cygnus Studios and was licensed for exclusive distribution by Apogee Software, Ltd. The source code has since been re-licensed under the [GNU General Public License v2].

Nov 032022
 
ffmpeg logo

0. Introduction

This post is more documentation for myself rather than anything else. As I compiled ffmpeg 2.2.16 for my (mostly) successful backport of Audacium 1.0.0, which can be seen [in the comments here], I thought it’d be fun to once again attempt compilation of an XP-patched version of the latest ffmpeg release, which at the time of writing is [5.1.2 “Riemann”]. The idea was to build both a 32-bit version for regular Windows XP as well as a more powerful 64-bit version for Windows XP Pro x64 Edition and Server 2003 x64. As before, these are not as complete as the releases you might be used to; Most external libraries are missing here (Theora, Vorbis, WebP, lamemp3, FdK-AAC and many, many more), but the latest [libzimg release version 3.0.4] is included, as I need that one myself for high quality spatial and colorspace scaling, so for HDR → SDR tonemapping.

1. The downloads

For those who just want the release files, here they are, license is the [GNU General Public license version 3] or later, any modified source code is included:

Here’s a quick screenshot showing how ffmpeg was configured for this backport:

ffmpeg 5.1.2 x86_64 running on XP x64

ffmpeg 5.1.2 x86_64 running on XP x64

As you can see, support for cryptographic network connections is built-in using GnuTLS. I can’t vouch for much though, as it’s a relatively old version of the library, so likely no modern ciphers and surely no TLS v1.3. GDI grabbing for desktop screen recording had to be disabled, because I couldn’t get the stuff to compile. Modern GPU hardware acceleration has been disabled as well, because Windows XP doesn’t have Direct3D 11, DXVA or Vulkan anyway.

Hence I do not recommend using this backport on modern systems like Windows 7, 8.1, 10 or 11. Makes no sense, you’d be better off with other releases. For XP, it might be good enough, depending on what you need. I’m open to requests for additional, built-in libraries, but can’t promise anything. And please forget about AV1, no dice there, at least not with my skills. I tried libaom, libdav1d and librav1e, and couldn’t even get the build systems working. :(

2. How the backport was done

First of all, this is no MSYS/mingw build, but a dynamically linked CygWin one. For a platform, CygWin 1.7.32 was used. You can still get those older CygWin versions from the [CygWin time machine] project. Additionally, I compiled myself newer build tools and a C/C++ compiler to handle modern instruction set extensions and C/C++ standards. The following software was used for both the 32-bit and the 64-bit builds:

  • CygWin 1.7.32 (0.274/5/3)
  • GNU autoconf 2.69
  • GNU make 4.0
  • GNU binutils 2.33.1
  • nasm 2.14.02
  • GCC 9.2.0

2a. libzimg 3.0.4

For libzimg, I had problems with certain functions being called from the std namespace, where the compiler wouldn’t find them. So whenever it died, complaining about something like std::somefunction not being defined, I would just change the calls to somefunction in the corresponding source files, removing the std namespace identifiers. Just do so until the whole code compiles, then install it as usually done with autoconf+make based build systems.

2b. ffmpeg 5.1.2 “Riemann”

Before starting with ffmpeg, make sure that its build toolchain will be able to locate zimg’s pkg-config metadata file /usr/local/lib/pkgconfig/zimg.pc. Otherwise, it won’t be able to find libzimg at all and fail during the initial configuration step. Do so by exporting both the default and this additional path: $ export PKG_CONFIG_PATH="/usr/lib/pkgconfig:/usr/local/lib/pkgconfig". It may generally be a good idea to just add this to ~/.bash_profile, as it’s very useful to make build systems look for .pc files in both locations, since most autoconf+make build systems will by default install to /usr/local/.

Additionally to that, export some C/C++ environment variables for picking the desired, modern compiler and for including all required headers:

$ export CC="gcc-9.2.0"
$ export CXX="c++-9.2.0"
$ export CFLAGS="-I/usr/include -I/usr/local/include"
$ export CXXFLAGS="-I/usr/include -I/usr/local/include"

Then, start modifying build system and code! First, the configure script needs to be edited. Go to around line 2320, and look for the following block:

SYSTEM_LIBRARIES="
    bcrypt
    vaapi_drm
    vaapi_x11
    vdpau_x11
"

What we need to get rid of is the bcrypt (bcrypt.dll) library, as this modern, cryptographic library does not exist on any Windows XP version. Replace it with the following:

SYSTEM_LIBRARIES="
    vaapi_drm
    vaapi_x11
    vdpau_x11
"

Further down, more bcrypt stuff needs to be ripped out, jump to about line 3800 and look for this:

avutil_suggest="clock_gettime ffnvcodec libm libdrm libmfx opencl user32 vaapi vulkan videotoolbox
corefoundation corevideo coremedia bcrypt stdatomic"

Replace it with the following:

avutil_suggest="clock_gettime ffnvcodec libm libdrm libmfx opencl user32 vaapi vulkan videotoolbox
corefoundation corevideo coremedia stdatomic"

We’re not done removing it yet, jump to line 6330 or so and look for the following code:

check_lib bcrypt   "windows.h bcrypt.h"   BCryptGenRandom     -lbcrypt &&
    check_cpp_condition bcrypt bcrypt.h "defined BCRYPT_RNG_ALGORITHM"

Just comment it out:

#check_lib bcrypt   "windows.h bcrypt.h"   BCryptGenRandom     -lbcrypt &&
#    check_cpp_condition bcrypt bcrypt.h "defined BCRYPT_RNG_ALGORITHM"

That’s it for the configuration script. Unfortunately, even with that, the code will still try to include the Windows bcrypt header file for some random number generation code. Open the C file libavutil/random_seed.c and hop to about line 30. Look for the following preprocessor conditional block:

#if HAVE_BCRYPT
#include <windows.h>
#include <bcrypt.h>
#endif

Actually, we should just make sure we don’t have HAVE_BCRYPT set, but the configuration step somehow doesn’t take care of that. While not a clean solution, let’s just comment it out to be done with Microsoft’s bcrypt library:

/*
 * #if HAVE_BCRYPT
 * #include <windows.h>
 * #include <bcrypt.h>
 * #endif
 */

And that’s it, as long as your build tools are new enough. As for configuring the code, it depends on what external libraries you have and wish to include and what your licensing restrictions might be like, but as shown in the screenshot in the beginning, I configured ffmpeg as follows:

$ ./configure --cc="gcc-9.2.0.exe" --cxx="c++-9.2.0.exe" --enable-shared --enable-pic --enable-gpl \
--enable-version3 --enable-bsf=hevc_metadata --enable-libzimg --disable-w32threads \
--disable-schannel --enable-gnutls --disable-vulkan --disable-dxva2 --disable-d3d11va \
--disable-indev=gdigrab --disable-htmlpages --disable-manpages --disable-podpages --disable-debug

After that, just compile it by running $ make, as with libzimg. As ffmpeg is rather large, you may wish to speed up the process by utilizing multiple CPUs. To see how many CPUs are usable by CygWin, just run $ nproc. Let’s assume that number would be 16, then just run $ make -j 16, and it will compile much faster.

3. Deployment

To know which .dll files I need to include, I’d usually just copy the resulting ffmpeg.exe and ffprobe.exe files to some temporary, empty path like X:\release\ffmpeg512\, outside of the CygWin environment. Then, just launch a CMD terminal and clear the local search paths to ensure you’re not pulling in any libraries from anywhere at load time by accident:

SET PATH=

Alright, now switch to the folder with the two .exe files, e.g. CD /D X:\release\ffmpeg512\ and run .\ffmpeg.exe -version. It will surely complain about some missing DLL. Locate that DLL inside of your CygWin bin\ folder or the usr\local\bin\ one. Then, rinse and repeat for as many times as necessary – about 30 times in my case – and copy over each missing DLL to that X:\release\ffmpeg512\ folder. Once done, you can zip it up and redeploy it on any Windows XP machine, as all required libraries are included without any ballast.

Naturally, 64-bit builds will only run on Windows XP Professional x64 Edition and Windows Server 2003 x64 or newer, whereas the 32-bit one would work on any XP machine or newer.

So much for libzimg 3.0.4 and ffmpeg 5.1.2 on some very old Microsoft operating systems. Beer Smilie

Jun 152022
 
S.T.A.L.K.E.R. Anomaly logo

0. Introduction

A short while ago, user Ergo asked about a possible backport for S.T.A.L.K.E.R. Anomaly to make it run on Windows XP in a discussion about a similar Ion Fury backport, [see here]. Having looked through the code and the MSVC build system of the latest version 1.5.1 of that game made me think I couldn’t probably fix all this… but i still gave it a shot. The required changes were quite extensive actually, and mostly revolved around ripping out the game’s [Discord] integration. That being the major part, some DXGI, Direct3D 10.x & 11.x error reporting code was also failing on top of some other, rather minor things related to the project configuration for the compiler and linker.

Way I understand it, the game’s Discord integration is mainly for reporting your player status (faction, level, current task, etc.) on the game’s Discord channel, so let’s just call this feature “non-essential”. ;)

In the end it took me multiple days and many failures to get there, but against all my expectations I actually managed to compile and link the code to a Windows XP x64 platform target. That – naturally – was only the first step. Code that compiles and links is one thing, code that runs and works is an entirely different story however. ;)

Before I start with showing you my modification documentation, let’s look at the results first though:

1. The results

There are some components that I couldn’t compile from source code in one go, as only libraries and headers are coming with the [S.T.A.L.K.E.R. Anomaly sources] for those. The Intel oneAPI (formerly “Thread Building Blocks”) library was one such thing, as were some unicode libraries, but also the launcher itself, which seems to be its own little software project carrying its own version number. The Intel TBB library and the launcher don’t appear to have any Windows Vista/7/8/10 dependencies though, they’ll work without any mod whatsoever:

S.T.A.L.K.E.R. Anomaly launcher on XP x64

Anybody can get this far though (click to enlarge)

But this is where things usually break apart with S.T.A.L.K.E.R. Anomaly 1.5.1 on XP x64. No version will actually launch (e.g. bin\AnomalyDX9.exe, bin\AnomalyDX8.exe, etc.). Actually attempting to do so will just produce a crash in the launcher. Trying to start e.g. bin\AnomalyDX9.exe manually gives us a first hint as to what’s wrong:

The most common error when running new programs on Windows XP

The most common error when running newer programs on Windows XP

Out of curiosity, I inspected the PE32+ / PE64 optional header of the program with [CFF Explorer] to check out the values of MajorSubsystemVersion and MinorSubsystemVersion as well. They were set to 0006 and 0000, so NT 6.0, Windows Vista. As a quick test I set them to 0005 and 0002, reflecting NT 5.2, so Windows XP x64 or Windows Server 2003. But of course, just patching the program’s header wasn’t enough, there are a lot more dependencies on modern API functionality to be found here, many more than any binary fix known to me can deal with. I’ll spare you the full list:

A call to SetThreadErrorMode(), which does not exist on any XP

A call to the Kernel32 API function SetThreadErrorMode(), which [requires] NT 6.1 / Windows 7 as a minimum

So I had to try and actually backport the source code. A simple recompile/relink with just a few changes in the build configuration is not enough in case of S.T.A.L.K.E.R. Anomaly 1.5.1. My backport only covers the DirectX 9 version of the game, so you’ll have to pick that one in the launcher, then configure the rest of the settings and hit that “Play S.T.A.L.K.E.R. Anomaly” button. While inside of my test VM, this is the last thing I saw before a crash due to the lack of any hardware Direct3D renderers:

S.T.A.L.K.E.R. Anomaly splash screen

The splash screen – a first sign that at least something works

The stock version crashes before even showing a splash screen, so I was looking forward to a first real test on my physical XP x64 machine, which happened the evening before yesterday. And the result was a big surprise for me as I really didn’t expect even limited success given my code mutilations:

S.T.A.L.K.E.R. Anomaly 1.5.1 on XP x64: Main menu

S.T.A.L.K.E.R. Anomaly 1.5.1 running on XP x64: Main menu (click to enlarge)

The main menu is already based on modified code, so this was pretty nice already! On top of that, the OpenAL sound subsystem and OGG media decoder were obviously also working as I could hear some background music at this point. Now, I still fully expected it to crash and burn when launching an actual game, but nope:

S.T.A.L.K.E.R. Anomaly 1.5.1 on XP x64: Ingame

S.T.A.L.K.E.R. Anomaly 1.5.1 on XP x64: Ingame (click to enlarge)

This is at a low resolution of 1024×768 with 8xMSAA and 8xSSTAA enabled through the nVidia driver, running on a GeForce GTX Titan Black (think: GeForce GTX 780 Ti). Performance was okay even if not stellar. I guess much higher resolutions like 1920×1200, 2560×1600 or even my current 3840×1600 will likely require some reduction of subsample counts, especially for the transparency supersampling. I have not played around with that yet.

2. Patch download

And here’s the patch to make the game run on Windows XP Professional x64 Edition:

First, unpack [S.T.A.L.K.E.R. Anomaly 1.5.1] using [7-Zip], then do the same with the patch. You’ll see that the patch files are an exact subset of the game. Just copy the AnomalyLauncher.exe file and bin\ subdirectory including all its files from the patch over the stock version of the game. The launcher and .dll files are probably not required, but I still decided to include them.

As described above, start the launcher and make sure to pick the DirectX 9 version of the game, then configure the rest and launch it!

Note: A version optimized for the use of the AVX instruction set extension is not included, as AVX cannot work on any Windows XP system anyway. The kernel thread scheduler lacks the necessary XSAVE and XRSTOR instruction support to make [context switching work for AVX code], so no AVX support on XP or XP x64.

Now if all you want to do is play the game on XP x64, this is all you’ll need. No guarantees for stability or anything though. ;) If you want to know how the backport was made, read on!

3. Build system modifications

Note: The modified source code is meant to be compiled with MS Visual Studio 2017 with the Visual Studio 2015 platform toolset and Windows XP support for it installed to provide the correct compiler and platform SDK. The below steps should hopefully show you how to reach the XP-compatible, modified state starting from the [original version 1.5.1 source code].

But before looking at any source code modifications, let’s look at the MSVC++ build system first!

Having opened the solution src\engine.sln in MS VisualStudio 2017, we’ll need to handle the solution & projects import first. This is easy: Leave the “Windows SDK Version” field alone. The “Platform toolset” field will suggest an upgrade to v141. Click on the dropdown and select “No Upgrade”, then click “OK”:

Solution import into VisualStudio 2017

Solution import into VisualStudio 2017

These steps should not be necessary using Visual Studio 2015 directly, but this is what I have around here, so yeah.

Once all projects have been loaded, click on “Solution ‘engine’ (29 projects)” on top, and in the engine Solution Properties on the bottom right, switch from DX10|x64 to DX9|x64. Be sure not to pick DX9-AVX|64, as the resulting code would terminate due to causing an illegal instruction exception on XP x64:

Changing the solution configuration

Changing the solution configuration to DX9|x64

Alright, now we need to change the toolset for all eligible projects from v140 to v140_xp. This is only possible if you have Windows XP support for Microsoft Visual Studio 2015 installed, so if there is no v140_xp to select for you, that would be the reason. You can install it using the original VS2015 installer. Mark all projects other than “LuaJIT-2”, which is not MSVC project file based, but Makefile-based:

S.T.A.L.K.E.R. Anomaly project list

S.T.A.L.K.E.R. Anomaly project list

Right click them, and select “Properties”. Now a dialog with the General project configuration options should show up. Change the “Platform toolset” from “Visual Studio 2015 (v140)” to “Visual Studio 2015 – Windows XP (v140_xp)”:

Retargeting Anomaly projects from v140 to v140_xp

Retargeting Anomaly projects from v140 to v140_xp

Click “Apply” and “OK”. The project list will now reflect the change:

Projects retargeted

Projects retargeted

Having changed the toolset will likely mess up detection of the header Windows.h, as parts of the SDK cannot be found, so we now need to reconfigure some header include and library linking options. Right-click the project “3rd Party\lua_extensions” and select “Properties”. There’s no screenshot for this, but switch to “VC++ Directories” in the tree on the left and for the two drop-down fields “Include Directories” and “Library Directories”, choose “<Inherit from parent or project defaults>”. Click “Apply” and “OK”.

Another issue is that having changed the platform toolset to v140_xp will likely have added a “Minimum Required Version” field to projects, set to 5.02 for Windows NT 5.2 (XP x64, Server 2003). When present, this field needs to be passed to the linker together with a “SubSystem” specification, or the linker will die. We may now be missing that for the main xrEngine project, though this doesn’t happen always. I’m not sure what exactly this depends on.

Right-click the “xrEngine” project and select “Properties” again. Navigate to Linker \ System. Click the dropdown field next to “SubSystem”, and select “Windows (/SUBSYSTEM:WINDOWS)”. The “Minimum Required Version” field should contain 5.02. If not, enter it manually! It should look like this:

xrEngine project linker properties

xrEngine project linker properties

Now, time for Discord to be murderized! First, we’ll violently break the Interface of xrEngine to the Discord library. Expand the “xrEngine\Interfaces” part in solution explorer to the right, mark “Discord” and delete it:

Remove xrEngine's Discord interface

Remove xrEngine’s Discord interface

Now, on top of that, fire up a file browser like Windows Explorer and completely delete the subdirectory src\3rd party\discord. This completes all steps for build system reconfiguration. Time to mess up some source code!

4. Source code modifications

If only changing the build system would’ve been enough, but no! We need to subject the source code to quite a bit of abuse as well! Practically, pretty much all we’re gonna do here is remove stuff.

4a. DXGI, Direct3D 10.x & Direct3D 11.x error reporting code

Before looking at the larger scale modifications needed for complete removal of the Discord integration, let’s show you one other part, which is about error reporting related to the DirectX Graphics Infrastructure (DXGI), Direct3D 10.x and Direct3D 11.x. That stuff wouldn’t compile when targeting XP and Direct3D 9.0 using a v140_xp platform toolset.

The following code blocks need to be removed or commented out. In the Windows XP case, they wouldn’t serve any purpose anyway. The paths shown in the code block headers are not actual file system paths, but reflect the structure as shown in Microsoft VisualStudio’s Solution Explorer. The line numbers shown here are not always exact due to my edits, so it may happen that code is found on ±10 or so lines, but it should be close enough. Click to expand/collapse individual blocks:

src\3rd party\DXERR\Source Files\dxerr.cpp
  1. include <d3d10_1.h>
  2. include <d3d11_1.h>

 

src\3rd party\DXERR\Source Files\dxerr.cpp
  1. // -------------------------------------------------------------
  2. // dxgi.h error codes
  3. // -------------------------------------------------------------
  4.         CHK_ERRA(DXGI_STATUS_OCCLUDED)
  5.         CHK_ERRA(DXGI_STATUS_CLIPPED)
  6.         CHK_ERRA(DXGI_STATUS_NO_REDIRECTION)
  7.         CHK_ERRA(DXGI_STATUS_NO_DESKTOP_ACCESS)
  8.         CHK_ERRA(DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE)
  9.         CHK_ERRA(DXGI_STATUS_MODE_CHANGED)
  10.         CHK_ERRA(DXGI_STATUS_MODE_CHANGE_IN_PROGRESS)
  11.         CHK_ERRA(DXGI_ERROR_INVALID_CALL)
  12.         CHK_ERRA(DXGI_ERROR_NOT_FOUND)
  13.         CHK_ERRA(DXGI_ERROR_MORE_DATA)
  14.         CHK_ERRA(DXGI_ERROR_UNSUPPORTED)
  15.         CHK_ERRA(DXGI_ERROR_DEVICE_REMOVED)
  16.         CHK_ERRA(DXGI_ERROR_DEVICE_HUNG)
  17.         CHK_ERRA(DXGI_ERROR_DEVICE_RESET)
  18.         CHK_ERRA(DXGI_ERROR_WAS_STILL_DRAWING)
  19.         CHK_ERRA(DXGI_ERROR_FRAME_STATISTICS_DISJOINT)
  20.         CHK_ERRA(DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE)
  21.         CHK_ERRA(DXGI_ERROR_DRIVER_INTERNAL_ERROR)
  22.         CHK_ERRA(DXGI_ERROR_NONEXCLUSIVE)
  23.         CHK_ERRA(DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
  24.         CHK_ERRA(DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED)
  25.         CHK_ERRA(DXGI_ERROR_REMOTE_OUTOFMEMORY)

 

src\3rd party\DXERR\Source Files\dxerr.cpp
  1. // -------------------------------------------------------------
  2. // d3d11.h error codes
  3. // -------------------------------------------------------------
  4.         CHK_ERRA(D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS)
  5.         CHK_ERRA(D3D11_ERROR_FILE_NOT_FOUND)
  6.         CHK_ERRA(D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS)
  7.         CHK_ERRA(D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD)

 

src\3rd party\DXERR\Source Files\dxerr.cpp
  1. // -------------------------------------------------------------
  2. // d3d10.h error codes
  3. // -------------------------------------------------------------
  4.         CHK_ERR(D3D10_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS, "There are too many unique state objects.")
  5.         CHK_ERR(D3D10_ERROR_FILE_NOT_FOUND, "File not found")
  6.  
  7. // -------------------------------------------------------------
  8. // dxgi.h error codes
  9. // -------------------------------------------------------------
  10.         CHK_ERR(DXGI_STATUS_OCCLUDED, "The target window or output has been occluded. The application should suspend rendering operations if possible.")
  11.         CHK_ERR(DXGI_STATUS_CLIPPED, "Target window is clipped.")
  12.         CHK_ERR(DXGI_STATUS_NO_REDIRECTION, "")
  13.         CHK_ERR(DXGI_STATUS_NO_DESKTOP_ACCESS, "No access to desktop.")
  14.         CHK_ERR(DXGI_STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE, "")
  15.         CHK_ERR(DXGI_STATUS_MODE_CHANGED, "Display mode has changed")
  16.         CHK_ERR(DXGI_STATUS_MODE_CHANGE_IN_PROGRESS, "Display mode is changing")
  17.         CHK_ERR(DXGI_ERROR_INVALID_CALL, "The application has made an erroneous API call that it had enough information to avoid. This error is intended to denote that the application should be altered to avoid the error. Use of the debug version of the DXGI.DLL will provide run-time debug output with further information.")
  18.         CHK_ERR(DXGI_ERROR_NOT_FOUND, "The item requested was not found. For GetPrivateData calls, this means that the specified GUID had not been previously associated with the object.")
  19.         CHK_ERR(DXGI_ERROR_MORE_DATA, "The specified size of the destination buffer is too small to hold the requested data.")
  20.         CHK_ERR(DXGI_ERROR_UNSUPPORTED, "Unsupported.")
  21.         CHK_ERR(DXGI_ERROR_DEVICE_REMOVED, "Hardware device removed.")
  22.         CHK_ERR(DXGI_ERROR_DEVICE_HUNG, "Device hung due to badly formed commands.")
  23.         CHK_ERR(DXGI_ERROR_DEVICE_RESET, "Device reset due to a badly formed commant.")
  24.         CHK_ERR(DXGI_ERROR_WAS_STILL_DRAWING, "Was still drawing.")
  25.         CHK_ERR(DXGI_ERROR_FRAME_STATISTICS_DISJOINT, "The requested functionality is not supported by the device or the driver.")
  26.         CHK_ERR(DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE, "The requested functionality is not supported by the device or the driver.")
  27.         CHK_ERR(DXGI_ERROR_DRIVER_INTERNAL_ERROR, "An internal driver error occurred.")
  28.         CHK_ERR(DXGI_ERROR_NONEXCLUSIVE, "The application attempted to perform an operation on an DXGI output that is only legal after the output has been claimed for exclusive owenership.")
  29.         CHK_ERR(DXGI_ERROR_NOT_CURRENTLY_AVAILABLE, "The requested functionality is not supported by the device or the driver.")
  30.         CHK_ERR(DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED, "Remote desktop client disconnected.")
  31.         CHK_ERR(DXGI_ERROR_REMOTE_OUTOFMEMORY, "Remote desktop client is out of memory.")
  32.  
  33. // -------------------------------------------------------------
  34. // d3d11.h error codes
  35. // -------------------------------------------------------------
  36.         CHK_ERR(D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS, "There are too many unique state objects.")
  37.         CHK_ERR(D3D11_ERROR_FILE_NOT_FOUND, "File not found")
  38.         CHK_ERR(D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS, "Therea are too many unique view objects.")
  39.         CHK_ERR(D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD, "Deferred context requires Map-Discard usage pattern")

 

4b. Discord integration removal

Now this part is the biggest one. All the following code blocks need to be removed or commented out:

src\xrEngine\defines.h
  1. 	rsDiscord = (1 << 5),

 

src\xrEngine\RenderRef\Execution & 3D\Device\device.cpp
  1. extern discord::Core* discord_core;
  2. extern bool use_discord;

 

src\xrEngine\RenderRef\Execution & 3D\Device\device.cpp
  1. 	//Discord
  2. 	if (use_discord && psDeviceFlags2.test(rsDiscord))
  3. 	{
  4. 		discord_core->RunCallbacks();
  5.  
  6. 		static float last_update;
  7. 		if (!last_update)
  8. 		{
  9. 			updateDiscordPresence();
  10. 			last_update = Device.fTimeGlobal;
  11. 		}
  12. 		else if ((Device.fTimeGlobal - last_update) > discord_update_rate)
  13. 		{
  14. 			updateDiscordPresence();
  15. 			last_update = Device.fTimeGlobal;
  16. 		}
  17. 	}

 

src\xrEngine\General\x_ray.cpp
  1. #import discord\discord.h

 

src\xrEngine\General\x_ray.cpp
  1. //Discord
  2. discord::Core* discord_core{};
  3. discord::Activity discordPresence{};
  4. static int64_t StartTime;
  5. bool use_discord = true;
  6. #pragma comment(lib, "discord_game_sdk.lib")
  7. rpc_info discord_gameinfo;
  8. rpc_strings discord_strings;
  9. float discord_update_rate = .5f;

 

src\xrEngine\General\x_ray.cpp
  1. //Discord Rich Presence - Rezy ------------------------------------------------
  2.  
  3. void DiscordLog(discord::LogLevel level, std::string message)
  4. {
  5. 	Msg("[Discord RPC]: %s", message.c_str());
  6. }
  7.  
  8. void updateDiscordPresence()
  9. {
  10. 	if (!use_discord)
  11. 		return;
  12.  
  13. 	static char details_buffer[128];
  14. 	static char state_buffer[128];
  15.  
  16. 	// Main Menu
  17. 	if (discord_gameinfo.mainmenu)
  18. 	{
  19. 		snprintf(state_buffer, 128, discord_strings.mainmenu);
  20. 		discordPresence.GetAssets().SetLargeImage("gamelogo");
  21. 		discordPresence.GetAssets().SetLargeText("");
  22. 		discordPresence.GetAssets().SetSmallImage("");
  23. 		discordPresence.GetAssets().SetSmallText("");
  24.  
  25. 		// Pause Menu
  26. 		if (discord_gameinfo.ingame)
  27. 			snprintf(state_buffer, 128, discord_strings.paused);
  28. 		else
  29. 			discordPresence.SetDetails("");
  30. 	}	
  31.  
  32. 	// Loading
  33. 	else if (discord_gameinfo.loadscreen)
  34. 	{
  35. 		snprintf(state_buffer, 128, discord_strings.loading);
  36. 		discordPresence.SetDetails("");
  37. 		discordPresence.GetAssets().SetLargeImage("gamelogo");
  38. 		discordPresence.GetAssets().SetLargeText("");
  39. 		discordPresence.GetAssets().SetSmallImage("");
  40. 		discordPresence.GetAssets().SetSmallText("");
  41. 		discord_gameinfo.ex_update = true;
  42. 	}
  43.  
  44. 	// In Game
  45. 	else if (discord_gameinfo.ingame)
  46. 	{
  47. 		// Time + Level Name
  48. 		char levelname_time[128];
  49. 		if (discord_gameinfo.level_name && discord_gameinfo.currenttime)
  50. 		{
  51. 			snprintf(levelname_time, 128, "%s | %s", discord_gameinfo.level_name, discord_gameinfo.currenttime);
  52. 			discordPresence.GetAssets().SetLargeText(levelname_time);
  53. 		}
  54. 		else if (discord_gameinfo.level_name)
  55. 		{
  56. 			snprintf(levelname_time, 128, discord_gameinfo.level_name);
  57. 			discordPresence.GetAssets().SetLargeText(levelname_time);
  58. 		}
  59. 		else
  60. 			discord_gameinfo.ex_update = true;
  61.  
  62. 		//Faction, Rank, Rep
  63. 		if (discord_gameinfo.faction && discord_gameinfo.faction_name)
  64. 		{
  65. 			discordPresence.GetAssets().SetSmallImage(discord_gameinfo.faction);
  66. 			char rank_faction_rep[128];
  67. 			if (discord_gameinfo.rank_name && discord_gameinfo.reputation)
  68. 				snprintf(rank_faction_rep, 128, "%s | %s", discord_gameinfo.rank_name, discord_gameinfo.reputation);
  69. 			else
  70. 				snprintf(rank_faction_rep, 128, discord_gameinfo.faction_name);
  71. 			discordPresence.GetAssets().SetSmallText(rank_faction_rep);
  72. 		}
  73.  
  74. 		// GameMode + Active Task
  75. 		if (discord_gameinfo.gamemode)
  76. 		{
  77. 			if (discord_gameinfo.task_name && 0 != xr_strcmp(discord_gameinfo.task_name, ""))
  78. 				snprintf(details_buffer, 128, "%s | %s", discord_gameinfo.gamemode, discord_gameinfo.task_name);
  79. 			else
  80. 				snprintf(details_buffer, 128, discord_gameinfo.gamemode);
  81. 			discordPresence.SetDetails(details_buffer);
  82. 		}
  83.  
  84. 		// God Mode
  85. 		if (discord_gameinfo.godmode)
  86. 			snprintf(state_buffer, 128, discord_strings.godmode);
  87.  
  88. 		// Health
  89. 		else if (discord_gameinfo.health)
  90. 		{
  91. 			// Iron Man
  92. 			if (discord_gameinfo.ironman && discord_gameinfo.lives_left)
  93. 			{
  94. 				if (discord_gameinfo.lives_left == 0 || discord_gameinfo.lives_left > 1)
  95. 					snprintf(state_buffer, 128, "%s: %i | %i %s", discord_strings.health, discord_gameinfo.health,
  96. 					        discord_gameinfo.lives_left, discord_strings.livesleft);
  97. 				else
  98. 					snprintf(state_buffer, 128, "%s: %i | %i %s", discord_strings.health, discord_gameinfo.health,
  99. 					        discord_gameinfo.lives_left, discord_strings.livesleftsingle);
  100. 			}
  101.  
  102. 			// Azazel
  103. 			else if (discord_gameinfo.possessed_lives)
  104. 			{
  105. 				if (discord_gameinfo.possessed_lives == 0 || discord_gameinfo.possessed_lives > 1)
  106. 					snprintf(state_buffer, 128, "%s: %i | %i %s", discord_strings.health, discord_gameinfo.health,
  107. 					        discord_gameinfo.possessed_lives, discord_strings.livespossessed);
  108. 				else
  109. 					snprintf(state_buffer, 128, "%s: %i | %i %s", discord_strings.health, discord_gameinfo.health,
  110. 					        discord_gameinfo.possessed_lives, discord_strings.livespossessedsingle);
  111. 			}
  112.  
  113. 			// No Iron Man or Azazel
  114. 			else
  115. 				snprintf(state_buffer, 128, "%s: %i", discord_strings.health, discord_gameinfo.health);
  116.  
  117. 			discordPresence.SetState(state_buffer);
  118. 		}
  119. 		else
  120. 		{
  121. 			// Iron Man
  122. 			if (discord_gameinfo.ironman && discord_gameinfo.lives_left)
  123. 			{
  124. 				int real_lives = discord_gameinfo.lives_left - 1;
  125. 				if (real_lives == 0 || real_lives > 1)
  126. 					snprintf(state_buffer, 128, "%s | %i %s", discord_strings.dead, real_lives, discord_strings.livesleft);
  127. 				else
  128. 					snprintf(state_buffer, 128, "%s | %i %s", discord_strings.dead, real_lives,
  129. 						discord_strings.livesleftsingle);
  130. 			}
  131.  
  132.  
  133. 			// Azazel
  134. 			else if (discord_gameinfo.possessed_lives)
  135. 			{
  136. 				if (discord_gameinfo.possessed_lives == 0 || discord_gameinfo.possessed_lives > 1)
  137. 					snprintf(state_buffer, 128, "%s | %i %s", discord_strings.dead, discord_gameinfo.possessed_lives,
  138. 						discord_strings.livespossessed);
  139. 				else
  140. 					snprintf(state_buffer, 128, "%s | %i %s", discord_strings.dead, discord_gameinfo.possessed_lives,
  141. 						discord_strings.livespossessedsingle);
  142. 			}
  143.  
  144. 			// No Iron Man or Azazel
  145. 			else
  146. 				snprintf(state_buffer, 128, "%s", discord_strings.dead);
  147.  
  148. 			discordPresence.SetState(state_buffer);
  149. 		}
  150.  
  151. 		// Level Icon
  152. 		if (discord_gameinfo.level && discord_gameinfo.level_icon_index)
  153. 		{
  154. 			char icon_buffer[32];
  155. 			snprintf(icon_buffer, 32, "%s_%i", discord_gameinfo.level, discord_gameinfo.level_icon_index);
  156. 			discordPresence.GetAssets().SetLargeImage(icon_buffer);
  157. 		}
  158. 	}
  159.  
  160. 	discordPresence.SetState(state_buffer);
  161. 	discord_core->ActivityManager().UpdateActivity(discordPresence, [](discord::Result result) {});
  162. }
  163.  
  164. void Init_Discord()
  165. {
  166. 	auto result = discord::Core::Create(477910171964801060, DiscordCreateFlags_NoRequireDiscord, &discord_core);
  167.  
  168. 	if (result != discord::Result::Ok)
  169. 	{
  170. 		Msg("[Discord RPC] Failed to create Discord RPC");
  171. 		use_discord = false;
  172. 		return;
  173. 	}
  174.  
  175. 	discord_core->SetLogHook(discord::LogLevel::Error, DiscordLog);
  176. 	Msg("[Discord RPC] Created successfully!");
  177.  
  178. 	//Set up basic RPC
  179. 	StartTime = time(0);
  180. 	discordPresence.SetType(discord::ActivityType::Playing);
  181. 	discordPresence.GetTimestamps().SetStart(StartTime);
  182. 	discordPresence.GetAssets().SetLargeImage("gamelogo");
  183. 	discord_core->ActivityManager().UpdateActivity(discordPresence, [](discord::Result result) {});
  184. }
  185.  
  186. void clearDiscordPresence()
  187. {
  188. 	if (discord_core)
  189. 		discord_core->ActivityManager().ClearActivity([](discord::Result result) {});
  190. }

 

src\xrEngine\General\x_ray.cpp
  1. 	//Discord Rich Presence - Rezy
  2. 	Init_Discord();

 

src\xrEngine\General\x_ray.cpp
  1. 	// Discord
  2. 	clearDiscordPresence();

 

src\xrGame\Core\Common\console_commands.cpp
  1. class CCC_DiscordStatus : public CCC_Mask
  2. {
  3. public:
  4. 	CCC_DiscordStatus(LPCSTR N, Flags32* V, u32 M) :
  5. 		CCC_Mask(N, V, M){};
  6.  
  7. 	virtual void Execute(LPCSTR args)
  8. 	{
  9. 		if (EQ(args, "on") || EQ(args, "1"))
  10. 		{
  11. 			value->set(mask, TRUE);
  12. 			discord_gameinfo.ex_update = true;
  13. 		}
  14. 		else if (EQ(args, "off") || EQ(args, "0"))
  15. 		{
  16. 			value->set(mask, FALSE);
  17. 			clearDiscordPresence();
  18. 		}
  19. 		else InvalidSyntax();
  20. 	}
  21. };

 

src\xrGame\Core\Common\console_commands.cpp
  1. 	//Discord
  2. 	psDeviceFlags2.set(rsDiscord, TRUE);
  3. 	CMD3(CCC_DiscordStatus, "discord_status", &psDeviceFlags2, rsDiscord);
  4. 	CMD4(CCC_Float, "discord_update_rate", &discord_update_rate, .5f, 5.f);

 

src\xrGame\Core\Common\StringTable\string_table.cpp
  1. 	//Discord
  2. 	snprintf(discord_strings.mainmenu, 128, xr_ToUTF8(*CStringTable().translate("st_main_menu")));
  3. 	snprintf(discord_strings.paused, 128, xr_ToUTF8(*CStringTable().translate("st_pause_menu")));
  4. 	snprintf(discord_strings.loading, 128, xr_ToUTF8(*CStringTable().translate("st_loading")));
  5. 	snprintf(discord_strings.health, 128, xr_ToUTF8(*CStringTable().translate("st_ui_health_sensor")));
  6. 	snprintf(discord_strings.dead, 128, xr_ToUTF8(*CStringTable().translate("st_player_dead")));
  7. 	snprintf(discord_strings.livesleft, 128, xr_ToUTF8(*CStringTable().translate("st_hardcore_lives_left")));
  8. 	snprintf(discord_strings.livesleftsingle, 128, xr_ToUTF8(*CStringTable().translate("st_hardcore_lives_left_single")));
  9. 	snprintf(discord_strings.livespossessed, 128, xr_ToUTF8(*CStringTable().translate("st_azazel_lives_possessed")));
  10. 	snprintf(discord_strings.livespossessedsingle, 128, xr_ToUTF8(*CStringTable().translate("st_azazel_lives_possessed_single")));
  11. 	snprintf(discord_strings.godmode, 128, xr_ToUTF8(*CStringTable().translate("st_godmode")));
  12.  
  13. 	discord_gameinfo.ex_update = true;

 

src\xrGame\UI\Common\MainMenu\MainMenu.cpp
  1. 	//Discord
  2. 	discord_gameinfo.mainmenu = bActivate;
  3. 	if (bActivate && psDeviceFlags2.test(rsDiscord))
  4. 		updateDiscordPresence();

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 	//Discord
  2. 	discord_gameinfo.ingame = true;

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 	//Discord
  2. 	discord_gameinfo.ingame = false;

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. //Discord
  2. 	if (psDeviceFlags2.test(rsDiscord))
  3. 	{
  4. 		//God
  5. 		bool isGodmode = psActorFlags.test(AF_GODMODE);
  6. 		discord_gameinfo.godmode = isGodmode;
  7.  
  8. 		if (!isGodmode)
  9. 		{
  10. 			int disc_cur_health = roundf(GetfHealth() * 100);
  11. 			if (disc_cur_health <= 0)
  12. 				discord_gameinfo.health = NULL;
  13. 			else
  14. 				discord_gameinfo.health = disc_cur_health;
  15. 		}
  16.  
  17. 		//Current Time
  18. 		str_c current_time = InventoryUtilities::GetGameTimeAsString(InventoryUtilities::etpTimeToMinutes).c_str();
  19. 		discord_gameinfo.currenttime = current_time;
  20.  
  21. 		// Update once after a loadscreen
  22. 		if (!discord_gameinfo.loadscreen && discord_gameinfo.ex_update)
  23. 		{
  24. 			//Update Iron Man state and lives
  25. 			luabind::functor ironman_enabled;
  26. 			if (ai().script_engine().functor("_g.IsHardcoreMode", ironman_enabled))
  27. 			{
  28. 				if (ironman_enabled && ironman_enabled())
  29. 					discord_gameinfo.ironman = true;
  30. 				else
  31. 					discord_gameinfo.ironman = false;
  32. 			}
  33.  
  34. 			if (discord_gameinfo.ironman)
  35. 			{
  36. 				//Lives left
  37. 				luabind::functor ironman_lives;
  38. 				if (ai().script_engine().functor("ironman_manager.get_lives_left", ironman_lives))
  39. 				{
  40. 					if (ironman_lives)
  41. 					{
  42. 						int lives_left = ironman_lives();
  43. 						discord_gameinfo.lives_left = lives_left;
  44. 					}
  45. 				}
  46. 			}
  47.  
  48. 			//Level
  49. 			if (g_pGameLevel && g_pGameLevel->name() != NULL)
  50. 			{
  51. 				snprintf(discord_gameinfo.level_name, 128, xr_ToUTF8(*CStringTable().translate(g_pGameLevel->name())));
  52. 				srand(time(0));
  53. 				int level_icon_id = rand() % 3 + 1;
  54. 				discord_gameinfo.level_icon_index = level_icon_id;
  55. 				discord_gameinfo.level = g_pGameLevel->name().c_str();
  56. 			}
  57.  
  58. 			//Story Mode
  59. 			luabind::functor game_mode;
  60. 			if (ai().script_engine().functor("_g.IsStoryMode", game_mode) && game_mode())
  61. 				snprintf(discord_gameinfo.gamemode, 128, xr_ToUTF8(*CStringTable().translate("st_cap_check_story")));
  62.  
  63. 			//Warfare
  64. 			else if (ai().script_engine().functor("_g.IsWarfare", game_mode) && game_mode())
  65. 				snprintf(discord_gameinfo.gamemode, 128, xr_ToUTF8(*CStringTable().translate("st_cap_check_warfare")));
  66.  
  67. 			//Azazel Mode
  68. 			else if (ai().script_engine().functor("_g.IsAzazelMode", game_mode) && game_mode())
  69. 			{
  70. 				snprintf(discord_gameinfo.gamemode, 128, xr_ToUTF8(*CStringTable().translate("st_cap_check_azazel_mode")));
  71.  
  72. 				luabind::functor possessed_lives;
  73. 				if (ai().script_engine().functor("azazel_mode.get_possessed_lives", possessed_lives))
  74. 				{
  75. 					int lives_possessed = possessed_lives();
  76. 					discord_gameinfo.possessed_lives = lives_possessed;
  77. 				}
  78. 			}
  79.  
  80. 			//Survival Mode
  81. 			else if (ai().script_engine().functor("_g.IsSurvivalMode", game_mode) && game_mode())
  82. 				snprintf(discord_gameinfo.gamemode, 128, xr_ToUTF8(*CStringTable().translate("st_cap_check_survival")));
  83.  
  84. 			//Freeplay Mode
  85. 			else
  86. 				snprintf(discord_gameinfo.gamemode, 128, xr_ToUTF8(*CStringTable().translate("st_cap_check_freeplay")));
  87.  
  88. 			//Update Active Task
  89. 			Level().GameTaskManager().RPC_UpdateTaskName();
  90.  
  91. 			//Update Faction, Rank and Reputation
  92. 			RPC_UpdateFaction();
  93. 			RPC_UpdateRank();
  94. 			RPC_UpdateReputation();
  95.  
  96. 			discord_gameinfo.ex_update = false;
  97. 		}
  98. 	}

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 			discord_gameinfo.faction = faction_name;

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 			snprintf(discord_gameinfo.faction_name, 128, xr_ToUTF8(*CStringTable().translate(buffer)));

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 			snprintf(discord_gameinfo.rank_name, 128, xr_ToUTF8(*CStringTable().translate(rank_name)));

 

src\xrGame\Core\Client\Objects\actor\base\Actor.cpp
  1. 					if (reputation_name)
  2. 						snprintf(discord_gameinfo.reputation, 128, xr_ToUTF8(*CStringTable().translate(reputation_name)));

 

src\xrGame\Core\Client\Objects\tasks_info_dialogs\game tasks\GametaskManager.cpp
  1. 	//Discord
  2. 	if (psDeviceFlags2.test(rsDiscord))
  3. 		RPC_UpdateTaskName();

 

src\xrGame\Core\Client\Objects\tasks_info_dialogs\game tasks\GametaskManager.cpp
  1. void CGameTaskManager::RPC_UpdateTaskName()
  2. {
  3. 	CGameTask* tr = ActiveTask();
  4. 	if (tr)
  5. 		snprintf(discord_gameinfo.task_name, 128, xr_ToUTF8(*CStringTable().translate(tr->m_Title)));
  6. }

 

src\xrGame\Core\Client\Level\level_script.cpp
  1. 	if (psDeviceFlags2.test(rsDiscord))
  2. 	{
  3. 		Actor()->RPC_UpdateFaction();
  4. 		Actor()->RPC_UpdateRank();
  5. 		Actor()->RPC_UpdateReputation();
  6.  
  7. 		Level().GameTaskManager().RPC_UpdateTaskName();
  8. 	}

 

The next part handles editing a file of the xrRender_R2 layer. Technically it doesn’t matter whether you edit it for xrRender_R1, xrRender_R2, xrRender_R3 or xrRender_R4, as removing the following code for one of them will affect all four. I just picked xrRender_R2 arbitrarily:

src\Layers\xrRender_R2\Interfase Implementations\ApplicationRender\dxApplicationRender.cpp
  1. 	//Discord
  2. 	discord_gameinfo.loadscreen = true;
  3.  
  4. 	if (psDeviceFlags2.test(rsDiscord))
  5. 		updateDiscordPresence();

 

src\Layers\xrRender_R2\Interfase Implementations\ApplicationRender\dxApplicationRender.cpp
  1. 	//Discord
  2. 	discord_gameinfo.loadscreen = false;
  3.  
  4. 	if (psDeviceFlags2.test(rsDiscord))
  5. 	{
  6. 		discord_gameinfo.ex_update = true;
  7. 		updateDiscordPresence();
  8. 	}

 

Aaand that’s it. Minus all the stuff I forgot to document of course. :roll: But even if this doesn’t work a 100% out of the box, it should at least give you about 90% of the idea.

If you your compiler does die with a heap memory exhaustion error caused by the precompiled header memory allocation, you can just open the affected projects’ “Configuration Properties \ C/C++ \ All Options” and add the option /Zm256 to the “Additional Options” field. Or if one is already set there, e.g. /Zm112, just increase the value to 256, then rebuild.

Once the build completes successfully, you will find the game executable here: _build\_game\bin_dbg\AnomalyDX9.exe. Additionally to that you’ll need to copy the companion libraries tbb.dll, soft_oal.dll, icuuc65.dll and icudt65.dll from the sdk\binaries\ subfolder. The discord_game_sdk.dll also sitting there can be ignored, as all Discord support is now gone with this patched version of S.T.A.L.K.E.R. Anomaly.

Edit: Actually, only almost, as I just found out. There is one Discord artifact left in the game’s main menu: The switch for enabling Discord status reporting on the game’s channel. It’s a dud though, so while you can ostensibly enable it and save that setting, the next time to open that submenu, it’ll be deactivated again. No crashes or anything. See here:

S.T.A.L.K.E.R. Anomaly "Discord status" option in the main menu

“Discord status” option found in the main menu under “Settings \ Others” (click to enlarge)

This concludes all necessary build system & source code modifications to build the game for a Windows XP Professional x64 Edition target.

And with that: Cheers! Beer Smilie

Sep 302021
 
Audacium logo

A short while ago there’s been quite a fuzz about the free and open source audio editor [Audacity] going all spyware on users, quickly spawning a set of forks to run away to after the [Muse group] acquired the project. I haven’t studied the issues in detail, but most of the outrage seems to center around a new privacy policy which included all kinds of telemetry data collection plus some Google Analytics integration. Another part was the idea to dual-license the project, which currently stands under the GNU General Public License version 2. Dual-licensing it as the copyright holder (this is perfectly legal by the way) under an additional, unspecified license may open quite the can of worms. In theory, they could integrate proprietary improvements or questionable code into their official binary releases without the source code for them ever being published. In essence, a build from the free and open-source code might no longer be the same program as the one officially released by the developers. Of course, this is scaring off a lot of people who actually prefer free software for exactly what it is. And while the privacy policy seems to have been [fixed] by now, the dual-licensing issue [still remains].

Now, aside from all that I found that Audacity has dropped Windows XP support somewhere after version 2, now standing at version 3.0.5. The forks all spawn from 3.0.4, which I guess was the last version before the Muse takeover. Out of curiosity and also because I could use a simple and powerful audio editor sometimes, I picked up the source code of the [Audacium] fork by [Sartox Software] and attempted to compile it to a WinXP target using Visual Studio 2017 with a v141_xp platform toolset on my Windows 7 build VM. The actual developers are using cmake ≥3.16.x and VS2019 for the Windows builds by now, likely on Windows 10.

I found that there are some parts that need to be changed in the source code to make it work however, just a very few things that aren’t compatible with the older Win32 kernel API of XP anymore. One problem is an error message like this in Windows header objbase.h: error C2760: syntax error: unexpected token 'identifier', expected 'type specifier'. This originates from a missing struct IUnknown type definition. So in some places that include objbase.h, I had to add this before including any headers (as other headers might include objbase.h just as well): typedef struct IUnknown IUnknown;.

With that out of the way, there’s a few more things in AudioIO.cpp related to timing functions. The program calls either GetTickCount64() or timeGetTime() to get a system time at millisecond precision, which is then interestingly divided by 1000 before returning the value. I thought that strange, but ok. Additionally, it attempts to obtain the time source frequency via QueryPerformanceFrequency(), another function not present on XP. In essence I removed the includes for the modern headers profileapi.h, sysinfoapi.h and timeapi.h, instead including the full Win32 kernel API via Windows.h. Then I removed all modern function calls declared in those headers from AudioIO.cpp and replaced the time acquisition part with good old GetTickCount(). I also removed the division by 1000, although I am not sure as to whether that was a smart idea… it was probably not.

Here’s the altered code:

expand/collapse source code
  1. // #include "profileapi.h"
  2. // #include "sysinfoapi.h"
  3. #include "windows.h"
  4. // #include "timeapi.h"
  5.  
  6. static int usePerformanceCounter_;
  7. static double secondsPerTick_;
  8.  
  9. static struct InitializeTime {
  10.     InitializeTime() {
  11.         LARGE_INTEGER ticksPerSecond;
  12.  
  13. 		/*
  14.         if (QueryPerformanceFrequency(&ticksPerSecond) != 0)
  15.         {
  16.             usePerformanceCounter_ = 1;
  17.             secondsPerTick_ = 1.0 / (double)ticksPerSecond.QuadPart;
  18.         }
  19.         else
  20.         {
  21. 		*/
  22.             usePerformanceCounter_ = 0;
  23.         // }
  24.     }
  25. } initializeTime;

 

Part 2:

expand/collapse source code
  1. static double util_GetTime(void)
  2. {
  3.     LARGE_INTEGER time;
  4.  
  5.     if (usePerformanceCounter_)
  6.     {
  7.         /*
  8.             Note: QueryPerformanceCounter has a known issue where it can skip forward
  9.             by a few seconds (!) due to a hardware bug on some PCI-ISA bridge hardware.
  10.             This is documented here:
  11.             http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q274323&amp;
  12.             The work-arounds are not very paletable and involve querying GetTickCount
  13.             at every time step.
  14.             Using rdtsc is not a good option on multi-core systems.
  15.             For now we just use QueryPerformanceCounter(). It's good, most of the time.
  16.         */
  17.         QueryPerformanceCounter(&time);
  18.         return time.QuadPart * secondsPerTick_;
  19.     }
  20.     else
  21.     {
  22. #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
  23.         // return GetTickCount64() * .001;
  24. 		return GetTickCount();
  25. #else
  26.         // return timeGetTime() * .001;
  27. 		return GetTickCount();
  28. #endif
  29.     }
  30. }

 

Maybe the division is actually important? I am not sure. I guess something in the program might show up a thousandfold wrong now, but a quick rebuild can be done at any time if so.

I didn’t care to build the installer (what for?), so I just attempted to compile the project including its bundled libraries. And et voilà:

Audacium 3.0.4 backported to Windows XP

Audacium 3.0.4 backported to Windows XP (click to enlarge)

It actually worked! And time information seems to be correct as well, although… who knows, I only tried it for a little bit. With that the program isn’t complete yet though, as it also requires a pretty old ffmpeg 2.2.2 library set from 2014 for full transcoding functionality. Seems they never moved to 3.x, let alone 4.x. I will provide a working build below (fetched from [here]), as my own are all too modern to work with Audacium. At least version 2.2.2 just works on XP without any modifications.

You’ll have to manually specify the location where ffmpeg resides by selecting the ffmpeg library avformat-55.dll in the Audacium preferences:

Specifying the location of ffmpeg 2.2.2 in Audacium

Specifying the location of ffmpeg 2.2.2 in Audacium

Note that the “Download” button here does not function, as the network code seems to have been removed from Audacium. You have to click “Locate” and pick the local library.

And that’s it, now the current version of the program at the time of writing works on XP, and in a version without potentially questionable things attached. ;)

Here’s the files:

As said, there is no installer for my build, so just install the redistributable, then unpack Audacium, unpack ffmpeg 2.2.2, then launch Audacium, pick avformat-55.dll in the preferences like shown above, and you’re ready to go! This has been tested on Windows XP Pro SP3 and Windows XP Pro x64 Edition SP2.

You may of course use this build on newer versions of Windows as well, though that would make little sense. Better pick an [official release] in such cases. Also, should my XP version break in any way or cause damage to your files, do not hassle the original developers, as they’re quite likely not responsible for it! Just post a reply here if anything bad happens.

Edit 2022-10-25: At the [request] of commenter Thomas Wöbbekind, I have now backported Audacium 1.0.0 (yes, that’s newer than 3.0.4…) as well, and accompanied the 64-bit XP x64 version with a newer ffmpeg 2.2.16 suite that includes some important codecs and libraries. Specifically, the ffmpeg libs for this version include zlib/bzip2 for compressed streams, iconv internationalization support, Speex, Opus, Theora, OGG/Vorbis and WavPack. For the 32-bit version (which is a bit shaky with this 1.0.0 release, stability-wise), you can still use the above, even more full-featured ffmpeg 2.2.2 libraries. Here’s the new files, including locally hosted Microsoft VC++ 2017 redistributables for 32- & 64-bit x86 to run Audacium 1.0.0:

Dec 172020
 
puTTY logo

0. Introduction

First of all: Big thanks fly out to Simon Tatham, the original developer of [puTTY], for the significant time he has invested to help me fix and refine this hack, and to teach me about how and how not to debug a program when things go wrong. If it weren’t for his help, this modification wouldn’t have worked out in a fashion that would make the program redistributable in working order. So, again: Thank you, Simon! :)

In recent years, I’ve come to like and make use of [desktop tiling], which some people mostly in the UNIX and Linux worlds like to use for productive work.

And what is this for? Simple: To build a (virtual) desktop tuned to suit a specific workflow without distractions. Put a few connected programs in properly sized, pre-defined tiles, side-by-side. No moving or resizing windows getting in your way, no waste of screen real estate due to title bars*, scroll bars* or window borders*, just focus on content and the work to be done. Optionally, run it in hybrid mode, e.g. to stack several windows on one tile if they’re not needed simultaneously.

*Note: This is much harder on MS Windows than it is with your typical window manager on common UNIX or Linux systems.

On my aging XP x64 workstation, I’m solving this with several 3rd party tools. For reference: Window snapping is done by ntwind [WindowSpace] (commercial software), and desktop restore to the tiled setup is done with Stefan Didak’s [WiLMa] – the Windows Layout Manager, whereas virtual desktops are being done by [VirtualDimension]. All of this software still works all the way up to Windows 10 20H2, the most current version at the time of writing; So it’s not limited to good old XP x64.

There’s just one thing that bugged me though: I use multiple [puTTY] SSH terminals to connect to my UNIX box, but quite a few pixels were being wasted by GUI decorations I simply didn’t need: Title bars and scroll bars. Now there are fancy little tools like [NirSoft WinExplorer] that can rip them out of running programs’ windows with Win32 API calls, but this doesn’t always work. Some programs like to insist on keeping their title & scroll bars, because they’re programmed that way.

puTTY is one of them. Luckily though, puTTY is free and open source software, and while I can’t dare call myself a programmer, I still took a look at the code, and managed to modify it accordingly, at least after asking Simon for help and after his advising me thoroughly. :)

1. The result

First, before giving you any files or showing you the modifications in the code, let’s show you what it looks like when in use so you can see whether this is for you. First, a screenshot of puTTY in use in its unmodified form, see the eight terminals on the left side:

Terminal desktop to the "BEAST" from XP x64 before having modified puTTY

Terminal desktop to my Threadripper UNIX machine (the “BEAST”) from XP x64 before having modified puTTY (click to enlarge)

As you can see, it’s pretty tiled alright. The largest tile is a stacked one here, shared by Notepad++, the browser and the FileZilla FTP/SFTP client, with the web browser currently sitting on top. So I’m running this in “hybrid” mode. I thought about removing the task bar, but decided to keep it after all. It’s still needed for a few things.

Now, here’s what it looks like after hacking up puTTY and modifying a few more windows using WinExplorer:

Terminal desktop to the "BEAST" from XP x64 after having modified puTTY

The same desktop after having modified puTTY and having tweaked a few more windows (click to enlarge)

As you can see, the puTTY terminals are specifically clean and save as many pixels as possible. The window border cannot be removed (WS_THICKFRAME window style), because if you do that, the window loses resizing capabilities. Also, X-Chat still has its bars, because that’s another tool that would need its code altered to permanently do away with them. X-Chat or its successor HexChat is notoriously hard to compile on MS Windows though, due to its dependency tree. So it’s the only window left unchanged.

This also works pretty well together with the new MS [FancyZones PowerToy] for Windows 10, and on top of that, WS_THICKFRAME is far more slim on Win10, making things look even more clean, see here:

Windows 10 "FancyZones" with the pixel-saving puTTY

Windows 10 “FancyZones” with the pixel-saving puTTY (Click to enlarge)

You can say a lot of things about Windows 10 – and I’m usually quite loud when it comes to complaining about it – but it’s not all bad. WS_THICKFRAME has been thinned out to a width of just 1 pixel, and the new FancyZones thingy means serious competition for what we’re used to on UNIX and Linux, at least in my opinion. And that’s especially true since Microsoft has released it as free and open source software under the [MIT license]. Here we see this modern, more clean look working together with a trimmed puTTY.

Only thing is: In WS_POPUP style, Windows 10 draws a small white border on top of every window for seemingly no good reason. We would need to extend the client area into the non-client area of the window to fix this. That’s not so easy, so I left it that way.

When I began using tiling, I think I originally did so on Linux, and shortly thereafter tried to turn my old XP x64 into something more UNIXish as well. Windows 10 was added to the mix just now, after the release of FancyZones, just for playing around. Just for reference, this is what my old Linux desktop looks like, working as a similar terminal desktop for the UNIX box:

My old Linux desktop was the role model

My old Linux desktop was the role model (click to enlarge)

It’s not a true tiled desktop either, just an old Gnome 2 with Metacity, tuned by [devilspie(1)], which can remove window decorations and is configured by [symbolic expressions] in config files. Scary stuff, but it works. Maybe I’ll describe the setup in another post.

In case you’re wondering what the empty area on the Zabbix website is for: More S.M.A.R.T. graphs will be added here as soon as my NVMe SSD RAID-0 will be added to the machine early next year. Host read/writes, SSD temperatures, that kind of thing. That’s for another post though.

2. The files

2a. Downloads

Before showing you how to modify puTTY to save more space, I’ll give you the files to try it out for yourself, should you be interested:

Please note that the mod also affects the puTTY telnet client puttytel.exe, because it shares the same frontend code with the SSH client.

I’ve used  Microsoft VisualStudio 2017 to compile the code, because when using the VS versions for the project files the code comes with (2010, 2012), the build fails. I didn’t really look into this, but I assume it’s due to some C99 code being in there now.

2b. Details about how to actually use this

The puTTY configuration window that you’ll see after launch is unaffected by my changes and features normal window decorations, see here:

puTTY configuration window

puTTY configuration window

That’s just a sample. The real one has one saved profile for each terminal, so I can tile them by window titles. Anyway, it’s really only the terminal window that’s modified to save pixels.

3. How to modify puTTY and remove title & scroll bars

What follows now is a list of source code modifications necessary to reliably remove title & scroll bars from puTTY. Also, we’ll re-introduce Windows XP compatibility while we’re at it. All paths shown from here on out are relative to the root of the unpacked puTTY source code, so they’re subfolders of the putty-src\ folder you get after unpacking the source.

3a. A simple issue

Open windows\version.rc2 in your favorite text editor or within VS2017, and look for two include lines near the top:

#include "version.h"
#include "licence.h"

At least in my case, the compiler failed trying to locate those headers, as they’re one level above version.rc2. I guess some include directories are missing, but we’ll do it the dirty way. Simply change those two lines so they look like this:

#include "..\version.h"
#include "..\licence.h"

Save the file.

3b. Windows XP & XP x64 platform compatibility

Open windows\winmisc.c and find this code block:

    if (!kernel32_module) {
        kernel32_module = load_system32_dll("kernel32.dll");
#if (defined _MSC_VER && _MSC_VER < 1900) || defined COVERITY
        /* For older Visual Studio, and also for the system I
         * currently use for Coveritying the Windows code, this
         * function isn't available in the header files to
         * type-check */
        GET_WINDOWS_FUNCTION_NO_TYPECHECK(
            kernel32_module, SetDefaultDllDirectories);
#else
        GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
#endif
    }

Unfortunately, this preprocessor directive checks only for the Visual Studio / compiler version. I have “1911” or something, so the legacy function GET_WINDOWS_FUNCTION_NO_TYPECHECK() is omitted and instead, GET_WINDOWS_FUNCTION() is being called. This does not work when targeting XP or XP x64!

I could probably do this intelligently by checking the actual platform toolset in use (e.g. vs141_xp instead of vs141), but instead I decided to just hard-code things, always calling GET_WINDOWS_FUNCTION_NO_TYPECHECK(). Change the above code to this:

    if (!kernel32_module) {
        kernel32_module = load_system32_dll("kernel32.dll");
        /*
         * Modified by M. Lackner / Thrawn to disable the call to 
         * GET_WINDOWS_FUNCTION(), instead of making it dependent on
         * the compiler version. This isn't the smart way of doing
         * it I guess, but it ensures WinXP / XP x64 compatibility,
         * calling GET_WINDOWS_FUNCTION_NO_TYPECHECK() instead and
         * removing the preprocessor conditional.
         */
//#if (defined _MSC_VER && _MSC_VER < 1900) || defined COVERITY
        /* For older Visual Studio, and also for the system I
         * currently use for Coveritying the Windows code, this
         * function isn't available in the header files to
         * type-check */
        GET_WINDOWS_FUNCTION_NO_TYPECHECK(
            kernel32_module, SetDefaultDllDirectories);
        /*
#else
        GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories);
#endif
        */
    }

With that, the code can be compiled for XP or XP x64 platform targets. Save the file.

3b. The actual pixel saver modification: terminal.c

This was the part where Simon Tatham really helped me out, because I was just stumbling through the code blindly, knocking tableware off the shelves left and right. I will not describe all the mistakes I’ve made here though. While the learning process and the mistakes being made can be interesting at times, it’d make the article too big. It’s big enough already.

So, let’s get to it. Open terminal.c, and hop to about line number 1800. Look for void term_size(Terminal *term, int newrows, int newcols, int newsavelines). Right below, add a bit of code so that it looks like this:

/*
 * Set up the terminal for a given size.
 */
void term_size(Terminal *term, int newrows, int newcols, int newsavelines)
{
    /*
     * Modified by M. Lackner / Thrawn to handle cases where we would need term
     * before it being defined. This happens when creating a window with the
     * WS_POPUP style, were we get calls back from the Win32 API to determine
     * the size of term, when term doesn't even exist yet. We deal with that
     * using a null pointer check. If term isn't there yet, just drop
     * everything and run away!
     */
    if (term == NULL)
        return;
 
    tree234 *newalt;
    termline **newdisp, *line;
    int i, j, oldrows = term->rows;

Because without that added term == NULL check, you’ll see a null pointer exception!

puTTY running into a null pointer exception / read access violation

Nooooooo!

Before Simon taught me things about how to use a debugger properly and how to not ignore his remarks in the code ( :roll: ), I was at a loss here. Let’s visualize what’s happening:

puTTY stack trace for that nullptr exception

puTTY stack trace for that nullptr exception

Read from bottom to top: The main code in WinMain() creates a window first by calling CreateWindowExW() in the Win32 API, and then creates the terminal inside. But in this case we never get to the terminal! As we change the window style to something rather unusual (see below), we get an unexpected call back from the Win32 API because the window size isn’t as expected at window creation time. This causes a call from the API to puTTY’s WndProc() function, and here’s where things start going very wrong, because that causes a call to term_size() in terminal.c. And term_size() needs a terminal term to be defined. But term doesn’t exist yet, because it’s defined only after window creation.

So we check for term being NULL right at the beginning of term_size(), and if term is indeed not there yet, we simply leave the function without doing anything, whistling a lighthearted melody while doing so.

That’s all you need to do for terminal.c. Save the file.

3c. The actual pixel saver modification: windows\window.c

This is the last file to change, and most of the work needs to be done here. Open windows\window.c. Look for the following code block:

        int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
        int exwinmode = 0;
        wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
        if (!conf_get_bool(conf, CONF_scrollbar))
            winmode &= ~(WS_VSCROLL);
        if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
            winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
        if (conf_get_bool(conf, CONF_alwaysontop))
            exwinmode |= WS_EX_TOPMOST;
        if (conf_get_bool(conf, CONF_sunken_edge))
            exwinmode |= WS_EX_CLIENTEDGE;
        hwnd = CreateWindowExW(exwinmode, uappname, uappname,
                               winmode, CW_USEDEFAULT, CW_USEDEFAULT,
                               guess_width, guess_height,
                               NULL, NULL, inst, NULL);
        sfree(uappname);
    }

Now this is the most important change, even if not the only one required, change it into this:

        /* 
         * Modified by M. Lackner / Thrawn to spawn terminal windows in pixel
         * saver mode. This means no title bar, no scroll bar, but a resizable
         * window border.
         */
        // int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
        int winmode = (WS_POPUP | WS_SYSMENU | WS_THICKFRAME) & ~WS_VSCROLL;
        int exwinmode = 0;
        wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
        if (!conf_get_bool(conf, CONF_scrollbar))
            winmode &= ~(WS_VSCROLL);
        if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
            winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
        if (conf_get_bool(conf, CONF_alwaysontop))
            exwinmode |= WS_EX_TOPMOST;
        if (conf_get_bool(conf, CONF_sunken_edge))
            exwinmode |= WS_EX_CLIENTEDGE;
        hwnd = CreateWindowExW(exwinmode, uappname, uappname,
                               winmode, CW_USEDEFAULT, CW_USEDEFAULT,
                               guess_width, guess_height,
                               NULL, NULL, inst, NULL);
        sfree(uappname);
    }

The only window styles that I found that could really get rid of the title bar while sticking to the Win32 API were WS_POPUP and the slightly more feature-rich WS_POPUPWINDOW. Going for a minimalistic approach, I started with just WS_POPUP, adding only what I wanted using logical OR operators:

  1. WS_SYSMENU adds a right-click menu for the program on the task bar. We need this so we can access the menu options “Move” and “Resize”.
  2. WS_THICKFRAME ensures that the window actually is resizable. This will cause visible borders being drawn around windows, looks worst on Windows 7, okay on XP, best on Windows 10.
  3. WS_VSCROLL is connected via an AND NOT operator, since we don’t need a vertical scroll bar here. I consider it useless, like the title bar, because we can just use the mouse wheel or the PgUp/PgDn keys.

As for that window style, I got the [right hint] from user [knn9], so thanks, knn9!

Let’s continue to somewhere near line number 800 or so, look for this code:

    /*
     * Initialise the scroll bar.
     */
    {
        SCROLLINFO si;
 
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
        si.nMin = 0;
        si.nMax = term->rows - 1;
        si.nPage = term->rows;
        si.nPos = 0;
        SetScrollInfo(hwnd, SB_VERT, &si, false);
    }

There’s zero need to do this anymore, so disable it:

    /*
     * Initialise the scroll bar.
     */
    /*
     * Modified by M. Lackner / Thrawn. We disable vertical scrollbar
     * setup entirely for pixel saver mode.
     */
    /*
    {
        SCROLLINFO si;
 
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
        si.nMin = 0;
        si.nMax = term->rows - 1;
        si.nPage = term->rows;
        si.nPos = 0;
        SetScrollInfo(hwnd, SB_VERT, &si, false);
    }
    */

Then, quite a bit farther down, somewhere around line 2490, look for this:

                    if (conf_get_bool(conf, is_full_screen() ?
                                      CONF_scrollbar_in_fullscreen :
                                      CONF_scrollbar))
                        nflg |= WS_VSCROLL;
                    else
                        nflg &= ~WS_VSCROLL;

To ensure we don’t get any scroll bar shoved into our window if the configuration says so or if fullscreen mode is activated, change it into this:

                    if (conf_get_bool(conf, is_full_screen() ?
                                      CONF_scrollbar_in_fullscreen :
                                      CONF_scrollbar))
                        /* 
                         * Modified by M. Lackner: No vertical scrollbar
                         * even if activated or in fullscreen.
                         */
                        nflg &= ~WS_VSCROLL;
                    else
                        nflg &= ~WS_VSCROLL;

About a thousand lines below this part, around line 3480 or so, look for the function static void sys_cursor_update(void). This, too, gets called when the Win32 API reacts to CreateWindowExW(). Like term_size() in terminal.c, it’s trying to do things with term at a time when it doesn’t exist yet. This would cause a similar null pointer exception, which we can deal with in the exact same way as before. Add another term == NULL check as shown here:

static void sys_cursor_update(void)
{
    /*
     * Modified by M. Lackner to handle cases where we would need term before
     * it being defined. This happens when creating a window with the WS_POPUP
     * style, were we get calls back from the Win32 API, causing this code to
     * run. We deal with that using a null pointer check, returning immediately
     * if term isn't there yet.
     */
    if (term == NULL)
        return;
 
    COMPOSITIONFORM cf;
    HIMC hIMC;
 
    if (!term->has_focus) return;

The next thing to look for sits roughly around line number 4800, look for this code block:

static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page)
{
    SCROLLINFO si;
 
    if (!conf_get_bool(conf, is_full_screen() ?
                       CONF_scrollbar_in_fullscreen : CONF_scrollbar))
        return;
 
    si.cbSize = sizeof(si);
    si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
    si.nMin = 0;
    si.nMax = total - 1;
    si.nPage = page;
    si.nPos = start;
    if (hwnd)
        SetScrollInfo(hwnd, SB_VERT, &si, true);
}

Scroll bar stuff! Let’s disable it:

static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page)
{
    /*
     * Modified by M. Lackner: We don't want puTTY to mess with its
     * vertical scrollbar, so this code is disabled entirely, we
     * return immediately!
     */
     /*
    SCROLLINFO si;
 
    if (!conf_get_bool(conf, is_full_screen() ?
                       CONF_scrollbar_in_fullscreen : CONF_scrollbar))
    */
        return;
    /*
 
    si.cbSize = sizeof(si);
    si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
    si.nMin = 0;
    si.nMax = total - 1;
    si.nPage = page;
    si.nPos = start;
    if (hwnd)
        SetScrollInfo(hwnd, SB_VERT, &si, true);
    */
}

Strangely enough, when spawning a window using WS_POPUP, we somehow branch into fullscreen code, which also tends to mess with the scroll bars, especially on returning from fullscreen to windowed mode. Look for the corresponding code near line number 5770:

/*
 * Go full-screen. This should only be called when we are already
 * maximised.
 */
static void make_full_screen()
{
    DWORD style;
    RECT ss;
 
    assert(IsZoomed(hwnd));
 
        if (is_full_screen())
                return;
 
    /* Remove the window furniture. */
    style = GetWindowLongPtr(hwnd, GWL_STYLE);
    style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
    if (conf_get_bool(conf, CONF_scrollbar_in_fullscreen))
        style |= WS_VSCROLL;
    else
        style &= ~WS_VSCROLL;
    SetWindowLongPtr(hwnd, GWL_STYLE, style);
    /* Resize ourselves to exactly cover the nearest monitor. */
        get_fullscreen_rect(&ss);
    SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
                        ss.right - ss.left,
                        ss.bottom - ss.top,
                        SWP_FRAMECHANGED);
    /* We may have changed size as a result */
 
    reset_window(0);
 
    /* Tick the menu item in the System and context menus. */
    {
        int i;
        for (i = 0; i < lenof(popup_menus); i++)
            CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED);
    }
}

Actually, you know what? Let’s just get rid of fullscreen mode! Some people might not like this, but me? I never use puTTY in fullscreen mode.

/*
 * Go full-screen. This should only be called when we are already
 * maximised.
 */
static void make_full_screen()
{
    /*
     * Disabled by M. Lackner to make sure we'll keep our no-title-bar, no-scroll-bar
     * kind of window when returning from fullscreen.
     */
 
    /*
    DWORD style;
    RECT ss;
 
    assert(IsZoomed(hwnd));
 
        if (is_full_screen()) */
                return;
 
    /* Remove the window furniture. */
    /*
    style = GetWindowLongPtr(hwnd, GWL_STYLE);
    style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME);
    if (conf_get_bool(conf, CONF_scrollbar_in_fullscreen))
        style |= WS_VSCROLL;
    else
        style &= ~WS_VSCROLL;
    SetWindowLongPtr(hwnd, GWL_STYLE, style);
    */
    /* Resize ourselves to exactly cover the nearest monitor. */
    /*
        get_fullscreen_rect(&ss);
    SetWindowPos(hwnd, HWND_TOP, ss.left, ss.top,
                        ss.right - ss.left,
                        ss.bottom - ss.top,
                        SWP_FRAMECHANGED);
    */
    /* We may have changed size as a result */
 
    // reset_window(0);
 
    /* Tick the menu item in the System and context menus. */
    /*
    {
        int i;
        for (i = 0; i < lenof(popup_menus); i++)
            CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED);
    }
    */
}

Now we do the same for the code that switches back from fullscreen into windowed mode, look just below:

/*
 * Clear the full-screen attributes.
 */
static void clear_full_screen()
{
    DWORD oldstyle, style;
 
    /* Reinstate the window furniture. */
    style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE);
    style |= WS_CAPTION | WS_BORDER;
    if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
        style &= ~WS_THICKFRAME;
    else
        style |= WS_THICKFRAME;
    if (conf_get_bool(conf, CONF_scrollbar))
        style |= WS_VSCROLL;
    else
        style &= ~WS_VSCROLL;
    if (style != oldstyle) {
        SetWindowLongPtr(hwnd, GWL_STYLE, style);
        SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                     SWP_FRAMECHANGED);
    }
    /* Untick the menu item in the System and context menus. */
    {
        int i;
        for (i = 0; i < lenof(popup_menus); i++)
            CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED);
    }
    return;
}

Get rid of its functionality as well:

/*
 * Clear the full-screen attributes.
 */
static void clear_full_screen()
{
    /*
     * Disabled by M. Lackner / Thrawn to make sure we'll keep our no-title-bar,
     * no-scroll-bar kind of window when returning from fullscreen.
     */
    // DWORD oldstyle, style;
 
    /* Reinstate the window furniture. */
    /*
    style = oldstyle = GetWindowLongPtr(hwnd, GWL_STYLE);
    style |= WS_CAPTION | WS_BORDER;
    if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
        style &= ~WS_THICKFRAME;
    else
        style |= WS_THICKFRAME;
    if (conf_get_bool(conf, CONF_scrollbar))
        style |= WS_VSCROLL;
    else
        style &= ~WS_VSCROLL;
    if (style != oldstyle) {
        SetWindowLongPtr(hwnd, GWL_STYLE, style);
        SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
                     SWP_FRAMECHANGED);
    }
    */
    /* Untick the menu item in the System and context menus. */
    /*
    {
        int i;
        for (i = 0; i < lenof(popup_menus); i++)
            CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED);
    }
    */
    return;
}

Naturally I should look into why fullscreen code is even being touched when using the WS_POPUP style instead of just diabling stuff like this. Generally, I should be doing a few things a little bit more smartly, but I’m too much of a lazy bum I guess. ;)

And that concludes the modification, you can now compile the code to build yourself a pixel-saving puTTY terminal specifically altered for use with tiled desktops.

If you think I did something really stupid there somewhere, or if you would like a current version of puTTY for Windows XP / XP x64 without the GUI modifications, let me know in the comments!

And now, let’s tile some windows! Beer Smilie

PS.: I just hope this hacked up puTTY doesn’t crash anywhere else… But if it does, let me know in the comments as well!

Nov 062020
 
FreeBSD logo

[1] 1. Introduction

For the longest time, my FreeBSD UNIX notebook, running FreeBSD 12.2-RELEASE-p0 at the time of writing was being haunted by an ACPI bug that would cause the kernel to throw messages on my terminals every few minutes or so. The messages would read as follows:

acpi_tz0: _CRT value is absurd, ignored (256.1C)

The bad thing about this is that the message appears at your current cursor location on any currently active tty. This means that it overwrites parts of the text you’re currently reading, forcing you to refresh the terminal. It may even overwrite the text you are currently writing, which is even worse, especially when it happens at a shell prompt, where you can’t just “reload” what you’re written. So usually, I would CTRL+C and redo it. That sucks.

Now some people have suggested to set the sysctl hw.acpi.thermal.user_override from 0 to 1, and then just write your own value to hw.acpi.thermal.tz0._CRT. This works only temporarily however, as the kernel seems to just overwrite the value on its own after a short period of time, making the problem re-appear.

HP/Compaq nc6320 running FreeBSD 11.0 UNIX

HP/Compaq nc6320 running FreeBSD 11.1 UNIX (older photo, click to enlarge)

2. Analysis

After a lot of searching for this on the web, I came to the conclusion that this problem is being caused by a faulty ACPI DSDT. Typically this would happen when original equipment manufacturers such as Lenovo, Dell or as in my case, Hewlett Packard would write buggy ACPI code and/or compile it with Microsoft’s buggy ASL compiler, producing horrid AML output that probably works with Microsoft Windows, but causes trouble everywhere else.

Operating systems that conform to ACPI standards more strictly – such as Linux or FreeBSD – would then complain about this, attempt to fix it or just misbehave due to the buggy ACPI byte code just doing “whatever”.

3. Fixing the issue

One of the things you might try to fix it is to fake your ACPI operating system identifier, lying to the BIOS. This can be done by changing hw.acpi.osname at boot time. To do such an ugly thing, you could e.g. add hw.acpi.osname="Windows 2009" to /boot/loader.conf.

I decided against that.

What I did instead was to fix (well, to some degree) the ACPI source code, recompile it using the far better Intel ASL compiler, and let the kernel load it at boot time, overriding the DSDT provided by the HP system BIOS.

First, you need to dump the code. For that you will need [acpidump(8)] installed on your FreeBSD system. Install it, and run the following as superuser: # acpidump -dt > ./acpi-stock.asl. This will dump and de-compile the code to the ACPI source language, a human-readable format. In my case that code comes from the final HP nc6320 [ROM BIOS version F.0E]. That’s important, because the resulting code will likely be different if using a different BIOS version. Create a copy to work on, I called this acpi-modded.asl: # cp ./acpi-stock.asl ./acpi-modded.asl.

First, we’ll have to fix a few errors that would otherwise prohibit us from re-compiling the code with the now-current Intel ASL compiler version 20200430. Open acpi-modded.asl in your favorite text editor. Now look for the buggy method C02D(), which in its stock form is not returning any values, thus breaking other methods calling it. The method should sit at or around line number 514:

Method (C02D, 0, Serialized)
{
	Local0 = (C02A + 0x00027EC0)
}

It’s fetching something from C02A and adds a value (from an address?) to it, but doesn’t do anything with the result?! Can’t be right. Following my gut feeling, which is usually wrong (but hey, whatever, right?), change the method as follows:

Method (C02D, 0, Serialized)
{
	Local0 = (C02A + 0x00027EC0)
	Return (Local0)
}

Alrighty, now hop to roughly line number 826, where you should find this:

expand/collapse source code
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
    0x00000000,		// Granularity
    0x00000000,		// Range Minimum
    0xFEDFFFFF,		// Range Maximum
    0x00000000,		// Translation Offset
    0x00000000,		// Length
    ,, _Y02, AddressRangeMemory, TypeStatic)
DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
    0x00000000,		// Granularity
    0xFEE01000,		// Range Minimum
    0xFFFFFFFF,		// Range Maximum
    0x00000000,		// Translation Offset
    0x011FEFFF,		// Length
    ,, , AddressRangeMemory, TypeStatic)
})

 

Using lengths together with fixed minima and maxima causes the Intel compiler to trip or so it seems, so I decided to make them flexible instead. This might not be the correct way to deal with this, but it worked for me without any further issues, so uhm… yeah. To do it like me, change MinFixed to MinNotFixed and MaxFixed to MaxNotFixed so that it looks like this:

expand/collapse source code
DWordMemory (ResourceProducer, PosDecode, MinNotFixed, MaxNotFixed, Cacheable, ReadWrite,
    0x00000000,		// Granularity
    0x00000000,		// Range Minimum
    0xFEDFFFFF,		// Range Maximum
    0x00000000,		// Translation Offset
    0x00000000,		// Length
    ,, _Y02, AddressRangeMemory, TypeStatic)
DWordMemory (ResourceProducer, PosDecode, MinNotFixed, MaxNotFixed, Cacheable, ReadWrite,
    0x00000000,		// Granularity
    0xFEE01000,		// Range Minimum
    0xFFFFFFFF,		// Range Maximum
    0x00000000,		// Translation Offset
    0x011FEFFF,		// Length
    ,, , AddressRangeMemory, TypeStatic)
})

 

Now, as for the actual fix that I had originally set out to apply, jump to around line number 12818, where you’ll find the method _CRT(), which stands for “CRitical Temperature”. It should return a value specific to your actual CPU! For my Intel Core 2 Duo T7600, that value – also known as Tj(max) – should be 100°C, [as specified by the Intel ARK]. So go look up your chip’s maximum temperature before continuing!

In its stock form, the code looks like this:

Method (_CRT, 0, Serialized)	// _CRT: Critical Temperature
{
	Return (C316 (0x04, 0x00))
}

I don’t understand what C316() really does, even after looking at it, and I’m not fully sure what the addresses 0x04hex and 0x00hex point at, but since it returns an interpreted value of 256.1°C, it can’t be right. Now some people are suggesting to fetch input values from different addresses and feed them to C316(), like e.g. [described here]. Instead though, I decided to follow [this highly informative post]Japanese flag, and hard-code the value specific to my CPU into the DSDT. For me this is ok, since I’m not going to change the processor anymore, as it’s the fastest chip the notebook supports officially already.

According to [清水 文行]Japanese flag, who wrote that post, the value is being set in 0.1K increments, so tenths of a Kelvin. The offset of Kelvin to degrees Celsius is -273.15, so to specify 101°C (I decided to set it just a notch over the Tj(max)), the value would be 374.15K. Since it’s specified in tenths of Kelvin, the number should be 3741.5. We have to return integers here, which made me decide to round that up to 3742. That should probably result in a new trip temperature and upper limit of 101.1°C.

Change the method to look like this, omitting the call to C316() entirely, and hard-coding _CRT()‘s return value to 101.1°C:

Method (_CRT, 0, Serialized)	// _CRT: Critical Temperature
{
	Return (3742)
}

Save acpi-modded.asl to disk and quit your text editor, you’re done modifying the code. It’s time to recompile it. For that you need [iasl(8)], the Intel ASL compiler installed on your FreeBSD system.

Run: # iasl ./acpi-modded.asl 2>./stderr.log. There should be 0 errors, with all warnings and remarks logged to stderr.log. If everything works as it should, the compiler should’ve created a file acpi-modded.aml. If there are unexpected errors after all, maybe because you’re running a newer, stricter compiler or maybe because you’ve got a different BIOS version, please report that in the comments, we may still get it working. In such cases, post all lines from stderr.log which contain actual errors, but none of the warnings or remarks. Also, please report your BIOS, operating system and iasl version numbers.

Now, to load your modified ACPI code and override the one provided by your system BIOS, copy acpi-modded.aml to /boot/, and edit /boot/loader.conf. Add the following lines at the end of that configuration file:

# Load fixed ACPI tables to remove erroneous trip temperature reports
acpi_dsdt_load="YES"
acpi_dsdt_name="/boot/acpi-modded.aml"

Save the file, make sure the file /boot/acpi-modded.aml really exists, and if so, reboot.

You can now verify the value is being provided correctly by your new ACPI machine code:

$ sysctl hw.acpi.thermal.tz0._CRT
hw.acpi.thermal..tz0._CRT: 101.1C

101.1°C indeed! So the math was correct. To check your actual core temperature, you’ll need the kernel module coretemp.ko loaded, like so: # kldload coretemp. Now you can check every individual CPU core’s actual temperature, here shown for core #1:

$ sysctl dev.cpu.1.temperature
dev.cpu.1.temperature: 76.0C

In this specific case, this was done while compiling the ffmpeg libraries on both cores simultaneously. If the CPU would ever reach or exceed a core temperature of 101.1°C, the FreeBSD kernel would initiate an emergency shutdown automatically, so don’t set the _CRT() return value too low in your ASL/AML code! You don’t wanna enter a reboot loop! ;)

Naturally, there would be many more things to fix, as iasl is still reporting tons of warnings when compiling the code, but I’m too lazy (and incompetent) to tackle all of those. I’m happy enough that the kernel isn’t spamming me with nonsense anymore. One little ACPI bug fixed. Beer Smilie

If you want to know more about ACPI debugging, there’s a [section about it] in the FreeBSD handbook as well, so that’s probably the place to start. Happy bugfixing! ;)

Thanks fly out to 清水 文行 for teaching me a few things about how the _CRT() return value is represented in HP’s ASL, and to Chris Whitehouse and Ian Smith for [providing further interesting information] about this issue in the FreeBSD mailing lists.

[1] Original drawing is © by ASK (Pixiv profile). 【PFFK】琉璃, Pixiv Fantasia: Fallen Kings series. Altered and used with express permission.

Sep 082020
 
Comment moderation notice logo

On this (still super-slow) weblog, people often sent their comments multiple times, because they thought the first one didn’t go through. I had always assumed this was just performance-related, because people run into white pages of death or timeout errors. But it appears that that alone is not what was responsible for some users’ behavior. It was that the commenting system simply left them in a confused state, which I only noticed today! After a certain WordPress update implementing the original changeset [43436], operators had the option to active a cookie opt-in checkbox below the comment form. This was supposed to make the WordPress commenting system GDPR-compliant, giving the user the choice as to whether they wish to accept cookies. My site isn’t compliant anyway, due to me making use of the Akismet anti-spam system, but yeah [1]. In any case, this update had an unexpected and very much undesired side effect!

If the checkbox remains unchecked, either because the user chose not to opt-in to receive cookies from this page, or because they never even could, because the box wasn’t enabled, comments would go through without the user receiving a “Your comment is awaiting moderation.” notice and without the user seeing their own comments! The page would reload after 10-20 seconds, and there was no change whatsoever, just as if the user had pressed F5 / reload.

Essentially it was as if the user had just sent their text into /dev/null (data oblivion). This understandably left people in a confused state, which is really bad. So some of you chose to try and re-send your comments, further increasing server load in the process (which is significant because the server is from the stone age). So my blog confused users and put my server under needless load – both of which are really bad!

As mentioned before, I had not noticed this until today, and hence never activated that checkbox for users to see. I have switched it on just a few hours ago. From now on, if you reach this page as a new user or an existing user who’s deleted their cookies, you will see this new checkbox when commenting:

Cookie opt-in checkbox

Cookie opt-in checkbox

Now if you check it, everything’s fine, cookie’s being set, and you’ll see your moderation notice. However, if you do not opt-in, the default behavior of older WordPress installations is to not display anything at all. So this was better, but still not the way I wanted it for people to be.

Why should users who choose not to accept cookies have a significantly worse user experience? That’s no good. By now, this issue has been fixed upstream (in some WP 5.x release), so I chose to back-port the patches into this weblog. The corresponding changeset is [44659]. On top of that, I decided to back-port [44681] as well, to further enhance the software’s behavior.

No matter whether you choose to accept cookies for recurring commenting or not, you will now always receive proper feedback from this site, so that you’re not left hanging, wondering what state your comment’s in:

Moderation notice without any cookie being set

Moderation notice without any cookies having been set, giving the commenting user clear feedback

This should improve people’s experience with this site if they choose to write a comment, both with and without cookies.

Please note that the cookies set by this site serve only a single purpose: To allow users to bypass the moderation queue and my approvement of their comments. With the cookie set, only your first comment will need manual approval by me. For the rest of the lifetime of the cookie – currently set to 1 year – you can keep commenting directly, with no approval process standing in your way. Once the cookie expires or you choose to delete it, you will have to have another single comment approved by me.

If you choose not to opt-in to use cookies containing potentially personally identifiable information on this site, every single one of your comments will enter the moderation queue and will thus have to be manually approved by me. With this improvement, this is now the only remaining downside of not accepting cookies here.

My apologies go to you people for having confused you over the last 2 or so years! :oops:

[1] Upon further research, I found that the way this site uses Akismet is actually supposed to be GDPR compliant.

May 162018
 
VMware logo

1. Introduction

I’m not sure whether this applies to other versions of VMware Player as well, as I haven’t tried any othery, but starting with either some version of the software or some Linux kernel version, the recompilation of the VMware Player kernel modules would fail after a kernel update and a reboot with the new kernel. In this article I’ll got into great detail about the issue (far more that what you need to just fix it), so if you don’t care about “reasons” and just wish to apply the fix, please go ahead and read only 2., then hop to the end of the article at 6.! Those who wish to understand the whole thing can keep reading after 2..

My exact software versions look like this at the moment, so for those, the problem definitely exists:

$ uname -or
2.6.32-696.23.1.el6.x86_64 GNU/Linux

$ vmware-installer -l | grep vmware-player
vmware-player        7.0.0.2305329

2. The issue

After updating your kernel and rebooting like normal, once you restart VMware Player, it’ll ask for a recompilation of its kernel modules for network, block device and other kinds of support to be usable with your updated kernel:

VMware Player 7 asking to recompile the kernel modules

VMware Player 7 asking to recompile the kernel modules

That’d be fine of course, if it wouldn’t suddenly fall on its face like this:

The vmnet kernel module compilation failing on CentOS 6.9

The network kernel module compilation failing on CentOS 6.x

Of course, without network support, the services won’t start, and VMware Player will refuse to even work at all. Wouldn’t make much sense anyway, as pretty much every virtual machine will require network support. So, let’s look into what’s going wrong!

3. A quick, superficial analysis

The VMware Kernel Module Updater is telling us to look at some log file, and that’s what I did (requires root privileges to just look at it by the way). However, it was far less revealing than I’d hoped. The only relevant line to be found was this one:

W110: Failed to build vmnet.  Failed to execute the build command.

Yeah alright, great. That didn’t help much, but at least it gave us the name of the failing module: “vmnet”. Let’s get more detailed output by retrying the module compilation on a terminal instead of with the GUI:

# vmware-modconfig --console --install-all 2>&1 | grep error

That command will launch the same process of compiling the kernel modules, but it’ll show us compiler errors as well. As we’re filtering the output to show errors only, we’ll get this:

/tmp/modconfig-5SHpfd/vmnet-only/netif.c:153:79: error: macro "alloc_netdev" passed 4 arguments, but takes just 3
/tmp/modconfig-5SHpfd/vmnet-only/netif.c:153: error: ‘alloc_netdev’ undeclared (first use in this function)
/tmp/modconfig-5SHpfd/vmnet-only/netif.c:153: error: (Each undeclared identifier is reported only once
/tmp/modconfig-5SHpfd/vmnet-only/netif.c:153: error: for each function it appears in.)

Now that’s actually helpful! We now know that the call of function alloc_netdev() from netif.c, which is a part of vmnet fails. The corresponding VMware kernel module source tarballs can be found in /usr/lib/vmware/modules/source/, which is where we’ll start with the real work.

4. The subsequent in-depth analysis

Enter that directory in a terminal (as root), and you’ll find several source tarballs, one of them being vmnet.tar. Create a backup copy of the tarball for added safety and then unpack it. Something like this:

# cp ./vmnet.tar ./vmnet-stock-backup.tar
# tar -xvf ./vmnet.tar

With that, you’ll get a new subfolder /usr/lib/vmware/modules/source/vmnet-only/.

The errors above showed us that the problem starts in line 153 of netif.c, so open vmnet-only/netif.c in your favorite text editor and jump to said line 153. There you’ll find the following C preprocessor conditional block:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(NET_NAME_USER)
   dev = alloc_netdev(sizeof *netIf, deviceName, NET_NAME_USER, VNetNetIfSetup);
#else
   dev = alloc_netdev(sizeof *netIf, deviceName, VNetNetIfSetup);
#endif

What this does is to compare your currently running Linux kernel version to 3.18.0 and check for the presence of some NET_NAME_USER macro. Depending on that, it calls the alloc_netdev() function with either four or only three parameters, omitting the macro mentioned. Clearly, on CentOS 6.x it’s being called with all four parameters, even though the function expects three on that system, thus breaking the code.

Initially I assumed that the first part of the conditional (the #if #else #endif block) wouldn’t work correctly. So I had a look at the LINUX_VERSION_CODE and KERNEL_VERSION macros. Later I found that to be the wrong place to start looking, but I’ll document my slightly pointless excursion nonetheless, as it proved to be somewhat informative:

4a. The Linux kernel version checking code

Those two macros are defined in /usr/src/kernels/`uname -r`/include/linux/version.h, and look like this on a typical CentOS 6.x system:

#define LINUX_VERSION_CODE 132640
#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

The LINUX_VERSION_CODE number is rather interesting. It’s calculated from the Linux version number triplet in a curious way. In my case it’s kernel version 2.6.32, so let a = 2, b = 6 and c = 32. The way to calculate the number would then be 216 × a + 28 × b + c. So it’s 216 × 2 + 28 × 6 + 32 = 132640. Knowing that, and having looked at the macros, it didn’t make any sense that the code in vmnet-only/netif.c would fail. After a quick search on the web it seemed like that was the usual way to do it too. So time to look at the second part that the code connected to the first via a logical OR (||).

4b. The NET_NAME_USER macro

The check for whether NET_NAME_USER was defined could also trigger the 4-parameter call to alloc_netdev(), so I needed to look at that next. That macro has something to do with network interface naming by user space applications, and it should be defined in the Linux kernel header netdevice.h. On CentOS 6.x that file can be found in /usr/src/kernels/`uname -r`/include/uapi/linux/, and we’ll look at line 170 of it in a text editor, where you can find this:

#define NET_NAME_USER           3       /* provided by user-space */

So it seems to be there alright? But I wanted to really make sure!

4c. Writing a small C program to verify the working of VMwares’ preprocessor conditional

To make sure I wasn’t just seeing things that weren’t really there, I wrote myself a small C program check-nnu+kernelversion.c to check for those macros. It looks like this:

#include <stdio.h>
#include <sys/socket.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/netdevice.h>
 
int main()
{
#if defined(NET_NAME_USER)
  printf("NET_NAME_USER is \e[1mDEFINED\e[0m in linux/netdevice.h.\n");
#else
  printf("NET_NAME_USER is \e[1mNOT DEFINED\e[0m in linux/netdevice.h.\n");
#endif
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0)
  printf("Kernel version \e[1mGREATER THAN OR EQUAL TO\e[0m 3.18.0.\n");
#else
  printf("Kernel version \e[1mLOWER THAN\e[0m 3.18.0.\n");
#endif
  return 0;
}

You can compile it very easily: $ cc ./check-nnu+kernelversion.c -o check-nnu+kernelversion. And when you run it on CentOS 6.x by executing $ ./check-nnu+kernelversion.o, you’ll likely get exactly this:

Displaying the status of the kernel version and the NET_NAME_USER macro with "check-nnu+kernelversion"

Displaying the status of the kernel version and the NET_NAME_USER macro with my “check-nnu+kernelversion” program

Su huh? What? Now wait, let’s make 100% sure, and look at alloc_netdev() as well!

4d.) alloc_netdev()

alloc_netdev() is defined in /usr/src/kernels/`uname -r`/include/linux/netdevice.h, so open that file up, and have a look at lines 2627:2628:

#define alloc_netdev(sizeof_priv, name, setup) \
        alloc_netdev_mq(sizeof_priv, name, setup, 1)

As you can see, it’s defined with three parameters only. The second function is the multiqueue version, that we won’t be using anyway, so that one doesn’t matter to us. So, it’s three parameters in any case, no matter what. The presence of NET_NAME_USER isn’t being taken into account there at all!

So what does that all mean?

5. Conclusion or where I think VMware went wrong

It seems that the VMware developers had assumed that alloc_netdev() would always take that fourth interface naming parameter as soon as we have a Linux kernel newer than or equal to 3.18.0 or if we have NET_NAME_USER defined. But that’s simply not the case!

Even if that macro is defined, the system may still have a version of alloc_netdev() that doesn’t make use of it, maybe because some parts of the kernel code just weren’t backported to CentOS from upstream. That “fourth parameter patch” for alloc_netdev() seems to have been [submitted in 2014], quite some time after the initial release of CentOS 6.x, but long before its current 2.6.32.x kernel versions were rolled out.

Still, CentOS kernels rarely get feature backports, but mostly just security and compatibility fixes, so maybe that’s a reason somehow.

Anyhow, VMwares’ assumption that the presence of NET_NAME_USER would also mean the presence of a past-2014 version of alloc_netdev() in the kernel was flawed, at least on all Red Hat Enterprise Linux 6 systems and its derivatives.

We can either change the OR to an AND in VMwares’ vmnet-only/netif.c, or simply remove the check for NET_NAME_USER in that file altogether. I went for the logical AND variety, although I’m not perfectly sure if this is correct. But I assume it should be, given the circumstantial evidence.

6. The solution

I’ll describe this in a way so that both people who’ve read the in-depth stuff (there’ll be some redundancies for you guys) as well as others who just want to solve this quickly can understand it. First, open up a terminal window, become superuser root and prepare your favorite text editor.

Navigate to /usr/lib/vmware/modules/source/ and backup the file vmnet.tar sitting there, if you haven’t already, for safety and also for reference. Then, unpack it while in /usr/lib/vmware/modules/source/: # tar -xvf ./vmnet.tar. This yields a subfolder vmnet-only/. Open the file vmnet-only/netif.c in your text editor, and look for the following line at line number 153:

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(NET_NAME_USER)

Change the || to && (replacing OR with AND) so that the line looks like this (Here I’ve also added a comment on top of it describing the change):

/* Modded version by XIN.at changing || to &&, because some systems may have
 * the NET_NAME_USER macro in linux/netdevice.h while still not having a
 * modern, 4-parameter version of alloc_netdev() in the same header.
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) && defined(NET_NAME_USER)

Ok, now repack the tarball while still sitting in /usr/lib/vmware/modules/source/ (this will overwrite the existing vmnet.tar):

# tar -cvf vmnet.tar ./vmnet-only

After that, relaunch VMware Player 7 and confirm the recompilation of the kernel modules. If you’ve tried this before, then only the network part should be missing, and building that remaining part should work now:

VMware Player 7, successful compilation of the remaining vmnet kernel module

Successful compilation of the remaining vmnet kernel module

Alright, up and running again! You may now delete /usr/lib/vmware/modules/source/vmnet-only/, but first please make sure you still have a backup copy of the original vmnet.tar first, ’cause you never know!

After that, subsequent updates of the Linux kernel and VMware Player 7 kernel modules will keep working, as VMware Player will keep using the updated /usr/lib/vmware/modules/source/vmnet.tar from here on out.

Mar 292018
 
TLS v1.3 logo

1.) Introduction

Now that the Internet Engineering Task Force has approved the new TLS v1.3 cryptographic protocol, we’ll surely see implementation based on the final specification rather soon, given how far work has progressed in OpenSSL and probably other libraries. In the second to last article I have [shown] how to upgrade servers running on Windows 2000 with modern cryptography and Let’s Encrypt certificates. That software was based on OpenSSL 1.0.2 and stunnel 5.44, which both fully support Windows 2000. However, when looking at the [current state] of the stunnel project in its latest 5.45 beta 6 version, it seems there isn’t even a 32-bit Windows version of it anymore.

Since compiling OpenSSL with some old Microsoft compiler is especially tricky, I tried to cross-compile the latest beta version with mingw 4.9.2 on CentOS 6.9 Linux instead, to get my 32-bit version. The same goes for stunnel. When tried that, I ran into several parts of code that simply can’t work on Windows 2000 any longer as-is.

For my ancient server, TLS v1.3 would be helpful though, as it would lower the CPU load due to its more efficient way of doing TLS handshakes. So there is a pretty good reason for trying this for real.

2.) Required software

Naturally, you need a Linux machine with the 32-bit x86 version of mingw installed for this. It might very well be easy to do on BSDs and other UNIX systems as well. You’ll also need the usual essentials for building programs under Linux like GNU autoconf, make, etc. Then, fetch the latest versions of OpenSSL and stunnel. At the time of writing, that’d be OpenSSL 1.1.1-pre3 and stunnel 5.45b6.

Unpack those, and get your favorite text editor ready, as we’ll need to modify parts of the source code before attempting a build.

3.) OpenSSL with TLS v1.3

First we need OpenSSL, as stunnel will be linking against it. Enter its source directory, then open the header file crypto/bio/bio_lcl.h. After the initial include lines, you’ll need to switch off the AI_PASSIVE macro. This results in OpenSSL using it’s own implementation for name resolutions. If you don’t do this, it would call the Winsock API functions getnameinfo(), getaddrinfo(), freeaddrinfo(), as well as use the struct addrinfo, none of which exist on Windows 2000, but only on Windows XP and newer. So add the following as described, just after the first #include block:

/* XIN.at mod: Disable AI_PASSIVE so we don't call freeaddrinfo()
* and getaddrinfo() etc. on Windows 2000 */
# undef AI_PASSIVE

Save the file, then compile (here, for 16 CPU threads in parallel with make -j16) and install with the following options set:

$ ./Configure enable-tls1_3 no-asm no-async no-dso no-engine --cross-compile-prefix=i686-w64-mingw32- --prefix=/opt/openssl-mingw mingw shared
$ make -j16
# make install

Just a little explanation for the ./Configure options, why they need to be there, and what the implications are:

  1. enable-tls1_3:

    Like the name says, this enables TLS v1.3. Currently, as it’s still only a draft and not the final version, TLS v1.3 won’t be enabled by default, hence this option.

  2. no-asm:

    This is optional depending on your hardware. If you remove it, OpenSSL will be built including SSE2 code, so you’d need to run Windows on a machine with a new enough processor, meaning either Intels’ Pentium 4 or AMDs’ Athlon 64 CPU. With that option gone, it won’t work on older chips. As my Pentium Pro CPUs don’t have any SSE, I have to fall back to pure C code without assembly optimizations by setting this parameter.

  3. no-async:

    This disables asynchronous sockets in WinSock. This is required as those need light-weight co-operatively multitasking threads called “Fibres”, which don’t exist on Windows 2000. This disables the otherwise failing calls ConvertFiberToThread() and ConvertThreadToFiber().

  4. no-dso:

    Disables the shared object abstraction layer, and with it the call to GetModuleHandeEx(), which is used to communicate with kernel drivers for cryptographic hardware acceleration engines. This also means that we have to switch off support for things like VIA Padlock unfortunately. So no more hardware acceleration, as no-dso implies no-engine.

  5. no-engine:

    Required by no-dso. This disables all cryptographic hardware acceleration engines. As a side-effect, this also disables the native Windows CryptAPI/schannel support of stunnel, so you can no longer store certificates in Windows’ own certificate store either. For us, the CryptAPI is useless anyway, as it’s far too old on Windows 2000, no matter how you look at it (SSL v3, TLS v1.0, SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA).

  6. --cross-compile-prefix:

    Here: A mingw running on a 64-bit host operating system targeting a 32-bit Windows system. You can determine this string by the names of your mingw programs. Usually that string triplet should be correct as-is for what we’re doing.

  7. --prefix:

    Don’t change it! This is the installation directory for our cross-compiled Windows version of OpenSSL, and this location is exactly where the stunnel build system will look for it!

Allright, that settles it with OpenSSL. Next: stunnel!

4.) stunnel 5.45

Enter stunnels’ source directory, and open src/str.c in a text editor. Then, look for the following code block:

expand/collapse source code
/* reportedly, malloc does not always return 16-byte aligned addresses
* for 64-bit targets as specified by
* https://msdn.microsoft.com/en-us/library/6ewkz86d.aspx */
#ifdef USE_WIN32
#define system_malloc(n) _aligned_malloc((n),16)
#define system_realloc(p,n) _aligned_realloc((p),(n),16)
#define system_free(p) _aligned_free(p)
#else
#define system_malloc(n) malloc(n)
#define system_realloc(p,n) realloc((p),(n))
#define system_free(p) free(p)
#endif

 

This shows that a fix of some memory allocation functions has been implemented to correct the old functions’ behavior for 64-bit code. However, this also replaces the functions for 32-bit versions of Windows with aligning ones. Problem: Those don’t exist on Windows 2000 either. We’re talking about _aligned_malloc(), _aligned_realloc() and _aligned_free() replacing the good old malloc(), realloc() and free() functions.

Mind you, the x86 architecture isn’t as strict as not to allow for unaligned memory access, but the CPU fixing things transparently in the background does reduce performance when misaligning your accesses to RAM (You can compare this to filesystem misalignments on solid state drives). So forcing the use of memory alignment is a good thing. But it’s not something Windows 2000 needs, as for 32-bit, the old functions should align properly anyway. Replace the above code with the following:

expand/collapse source code
/* XIN.at mod: Removed aligning memory functions required for x86_64
* to restore Windows 2000 compatibility. Will be using the regular calls
* instead, as they should work fine for x86_32 */
#define system_malloc(n) malloc(n)
#define system_realloc(p,n) realloc((p),(n))
#define system_free(p) free(p)

 

Now run the following commands for a cross-compiled build of stunnel linked against the modern OpenSSL you’ve built before. In my case, I’m doing a parallel build across 16 CPU threads again. What ./configure does might look wrong to you in terms of cross-compiling, but just ignore that. It’s going to work just fine (Hopefully at least, heh)!

$ ./configure
$ cd src/
$ make -j16 mingw
$ cd ../doc/
$ make
$ cd ..

And we’re set! All you have to do now is copy off the required files to replace your original stunnel installation. The following files are required; On the left side: The location of the file on a typical Windows installation of stunnel. On the right side: Where to find the corresponding new file on your Linux machine. You might need to adjust the paths a little depending on where you have extracted the source code and where exactly mingw is installed. Also, your current installation might not yet have libssp-0.dll, but you’ll need that one too:

  • %PROGRAMFILES%\stunnel\bin\libssp-0.dll <- /usr/i686-w64-mingw32/sys-root/mingw/bin/libssp-0.dll
  • %PROGRAMFILES%\stunnel\bin\openssl.exe <- /opt/openssl-mingw/bin/openssl.exe
  • %PROGRAMFILES%\stunnel\bin\libssl-1_1.dll <- /opt/openssl-mingw/bin/libssl-1_1.dll
  • %PROGRAMFILES%\stunnel\bin\libcrypto-1_1.dll <- /opt/openssl-mingw/bin/libcrypto-1_1.dll
  • %PROGRAMFILES%\stunnel\config\openssl.cnf <- /opt/openssl-mingw/ssl/openssl.cnf
  • %PROGRAMFILES%\stunnel\bin\stunnel.exe <- ~/yourbuilddir/stunnel/bin/mingw/stunnel.exe
  • %PROGRAMFILES%\stunnel\bin\tstunnel.exe <- ~/yourbuilddir/stunnel/bin/mingw/tstunnel.exe
  • %PROGRAMFILES%\stunnel\doc\stunnel.html <- ~/yourbuilddir/stunnel/doc/stunnel.html

5.) Using it

stunnel with the modern OpenSSL can be used as-is. Just copy the files over your existing installation of stunnel 5.44 or older, then restart the program or service.

Other applications which do not work in tandem with stunnel might need to be recompiled though. There is some software where OpenSSL DLLs can just be swapped out all the way from version 0.9.6 to 1.0.2, but for those, it failed with 1.1.1 for me. The DLLs just wouldn’t load even if a full [dependency walk] checks out ok. Could also be because they’ve been built with mingw instead of Microsoft Visual C though, who knows. For closed source software or open source software that is too hard to recompile, using stunnel is usually a good alternative, as has been shown [here].

6.) The truly final TLS v1.3

As soon as OpenSSL 1.1.1 gets released with official TLS v1.3 support, I will update this article in case the process shown here will require any changes to work with the final versions.

Anyway, I’m looking forward to how much speed can be gained using TLS v1.3 over TLS v1.2 on CPUs from 1997! ;)

7.) Hey, I just want the binary programs to use on Windows 2000!

Oh my… Well, here you go:

As for the source code, you can find the original sources of OpenSSL [here] and the stunnel sources [here]. The patches shown by me here shall be released under the [OpenSSL license] for the code added to OpenSSL and under the [GPL v3] for the code added to stunnel.