Enclosed is a summary and write up of code changes (patches against server rev fe1fdb5) needed to make Crossfire server compile on Windows.
NOTE: The code changes were generated with Claude Sonnet 4.6 - see the write up at the bottom of this ticket for details.
Six source files were modified to port the Crossfire server from Linux/POSIX to Windows (MSYS2/MinGW). The changes fall into four categories: Windows API compatibility, file system fixes, plugin loading, and socket/networking fixes.
common/object.cpp — Added a Windows-compatible ffs() (find-first-set-bit) function using x86 intrinsics, since MinGW doesn't provide one.
common/output_file.cpp — Replaced rename() with MoveFileExA() on Windows, because Windows rename() fails if the destination file already exists, unlike POSIX.
plugins/cfpython/cfpython.cpp — Added the missing CF_PLUGIN export macro to initPlugin(), which was preventing the Python plugin from being found and loaded by the server.
server/plugins.cpp — Replaced the POSIX opendir()/readdir() directory scan with the native Win32 FindFirstFileA()/FindNextFileA() API, and added absolute path construction from the executable location so plugins are found regardless of working directory.
server/win32.cpp — Fixed bRunning initialization from uninitialized to 1, which was causing the server's main loop to never execute when launched from the command line.
socket/init.cpp — Fixed Winsock-specific socket initialization: corrected u_long type for ioctlsocket(), added WSAStartup() error checking, and improved error reporting using WSAGetLastError() instead of strerror(errno).
socket/loop.cpp — Fixed select() error handling on Windows to use WSAGetLastError() instead of errno, and handle specific Winsock error codes (10004, 10038, 10022) that have different meanings than their POSIX equivalents.
common/object.cppMinGW does not provide a ffs() (find first set bit) implementation in its C runtime. On Linux this function is provided by glibc. The fix adds a MinGW-specific implementation inside #elif defined(__MINGW32__) || defined(__MINGW64__) guards that uses <intrin.h> and the _BitScanForward() x86 intrinsic, which maps directly to a BSF instruction. The function returns the 1-based index of the lowest set bit, or 0 if the input is 0, matching POSIX ffs() semantics. Note the diff also shows a duplicate #ifdef CF_MXE_CROSS_COMPILE block that was accidentally introduced — this is a minor artifact that doesn't affect compilation since the MinGW branch is what actually gets compiled on Windows.
common/output_file.cppThe server writes data files atomically by writing to a .tmp file first and then renaming it over the final destination. On POSIX systems rename() atomically replaces the destination if it exists. On Windows, rename() returns an error if the destination file already exists, causing the accounts, player, and highscore files to fail to save whenever a previous .tmp file was left behind (e.g. from a crash or improper shutdown). The fix wraps the rename in a #ifdef WIN32 block that uses MoveFileExA() with the MOVEFILE_REPLACE_EXISTING flag, which provides atomic replacement semantics on Windows. <windows.h> is included conditionally at the top of the file to provide the Win32 API declarations. Error reporting uses FormatMessageA() to convert the Win32 error code to a human-readable string.
plugins/cfpython/cfpython.cppThe CF_PLUGIN macro expands to __declspec(dllexport) on Windows, which marks a function as exported from the DLL and visible to GetProcAddress(). Every plugin entry point (getPluginProperty, postInitPlugin, closePlugin, eventListener, etc.) had this macro except initPlugin, which was declared as a plain int. As a result GetProcAddress(handle, "initPlugin") returned NULL and the server logged "The specified procedure could not be found", refusing to initialize the plugin. Adding CF_PLUGIN to the initPlugin declaration fixes the export and allows the server to call it successfully.
server/plugins.cppTwo problems were addressed. First, the POSIX opendir()/readdir() implementation in MSYS2 has a bug where readdir() returns NULL immediately on directories inside Program Files, even though opendir() succeeds. The fix replaces the entire directory scanning loop with native Win32 FindFirstFileA()/FindNextFileA() inside a #ifdef WIN32 block, keeping the original POSIX code in the #else branch for Linux. Second, the plugin paths were built from the relative LIBDIR string (lib/crossfire), which only works if the server's working directory is the install root. The fix uses GetModuleFileNameA() to get the absolute path of the running executable, strips the filename and \bin component to get the install root, then constructs absolute paths for both the DLL search directory (\bin) and the plugin directory (\lib\crossfire\plugins\). SetDllDirectoryA() is called with the absolute bin\ path so Windows can find libgcc_s_seh-1.dll, libstdc++-6.dll, libpython3.14.dll and the other runtime DLLs when loading plugin DLLs.
server/win32.cppThe global variable bRunning controls the server's main loop — the loop continues while bRunning is non-zero. On Linux it is initialized in the startup code, but on Windows the declaration int bRunning; left it uninitialized. In a release build with MSVC or MinGW, uninitialized globals in BSS are zero-initialized, meaning bRunning started as 0 and the main loop never ran when the server was launched directly from the command line (as opposed to as a Windows service, which sets it explicitly). The fix changes the declaration to int bRunning = 1; ensuring the main loop always starts running correctly.
socket/init.cppThree Winsock-specific fixes were made. First, ioctlsocket() on Windows requires a u_long* argument, not long* — the original code had a cross-compile guard (CF_MXE_CROSS_COMPILE) selecting between the two types, but since MSYS2 is not MXE, it was using the wrong long type, causing a type mismatch. The fix removes the guard and always uses u_long. Second, WSAStartup() previously had no error checking — if Winsock failed to initialize, the server would continue silently and crash later with confusing errors. The fix captures the return value and calls exit(1) with an error message if initialization fails. Third, error reporting for socket(), bind(), and listen() failures used strerror(errno), which doesn't work for Winsock errors since Winsock sets its own error code via WSAGetLastError() rather than errno. The fix adds #ifdef WIN32 branches that call WSAGetLastError() for accurate error messages.
socket/loop.cppThe select() call in the server's main network loop returns -1 on error. On Linux errno is set to indicate the reason. On Windows, Winsock does not set errno — it sets its own error code via WSAGetLastError(). The original code called strerror(errno) which always returned a meaningless or incorrect message on Windows. The fix adds a #ifdef WIN32 block that calls WSAGetLastError() and handles three specific Winsock error codes: 10004 (WSAEINTR — interrupted, normal on shutdown), 10038 (WSAENOTSOCK — invalid socket descriptor, handled by check_all_fds()), and 10022 (WSAEINVAL — invalid argument, also handled by check_all_fds()). Any other error code is logged with its numeric value and causes the server loop to return.
Yes, all changes are fully compatible with GPLv2. Every modification is either a bug fix, a platform portability shim, or a Windows API call replacing an equivalent POSIX call — none of which affect the license terms of the code.
GPLv2 governs distribution and licensing terms, not the technical content of changes. The relevant questions are:
The answer to all three is no.
common/object.cpp — ffs() implementation
The _BitScanForward() intrinsic is a compiler built-in provided by the MinGW/GCC toolchain, not a library function. Compiler intrinsics are not subject to library license terms — they compile directly to a single BSF machine instruction. The surrounding wrapper code was written from scratch as a trivial shim. No third-party code was copied. GPLv2 compatible.
common/output_file.cpp — MoveFileExA()
MoveFileExA() is part of the Windows API (kernel32.dll), which is a system library. GPLv2 contains an explicit system library exception: code that interfaces with the operating system's standard facilities is not considered a "derived work" subject to GPL restrictions. This is the same principle that allows GPLv2 software to call open(), read(), and write() on Linux without the kernel's license affecting the application. MoveFileExA() is the direct Windows equivalent of rename() — both are OS system calls. GPLv2 compatible.
plugins/cfpython/cfpython.cpp — CF_PLUGIN macro
This was a one-word addition of a macro that was already defined and used throughout the same file. No new code or external material was introduced. GPLv2 compatible.
server/plugins.cpp — Win32 directory scanning
FindFirstFileA(), FindNextFileA(), FindClose(), GetModuleFileNameA(), and SetDllDirectoryA() are all Windows system API calls covered by the same OS system library exception as above. The control flow logic wrapping them (the do/while loop, the path construction, the string manipulation) was written from scratch as a direct functional equivalent of the existing POSIX opendir()/readdir() code already in the file. GPLv2 compatible.
server/win32.cpp — bRunning = 1
A one-character initialization fix to an existing variable. No external material involved. GPLv2 compatible.
socket/init.cpp and socket/loop.cpp — Winsock fixes
WSAStartup(), WSAGetLastError(), ioctlsocket() are all Winsock system API calls covered by the OS system library exception. The error handling logic is original code. GPLv2 compatible.
One subtlety worth addressing: the server is compiled with MinGW-w64 (UCRT64), which links against the MinGW C runtime. The MinGW runtime itself is licensed under a mix of public domain, BSD, and MIT terms — all of which are compatible with GPLv2. Unlike MSVC's runtime (which has a proprietary license), MinGW's runtime poses no compatibility concern.
The bundled DLLs (libgcc_s_seh-1.dll, libstdc++-6.dll, libwinpthread-1.dll) are part of GCC and are distributed under the GCC Runtime Library Exception to GPLv3, which explicitly permits linking with software under any license including GPLv2. libpython3.14.dll is licensed under the Python Software Foundation License, which is GPLv2 compatible. The api-ms-win-crt-*.dll files and libsqlite3-0.dll are system/OS-level components covered by the system library exception.
All changes consist of:
The Crossfire server's GPLv2 license is fully preserved. The changes can be freely distributed as part of the GPLv2-licensed codebase, and the source code for all modifications must continue to be made available under GPLv2 terms, which is already satisfied by the git repository.
Server .exe is available for download at https://github.com/tannerrj/crossfire-windows/releases/tag/v1.75.0-git-fe1fdb5
I applied the patches to the windows branch in Git.
I had to re-do the
plugins.cppbecause AI had mysteriously clobbered the non-Windows build with something nonsensical.Visibility modifier to the plugin already pushed to master.
Everything is pushed and up to date. Here's a summary of where the crossfire-windows server project stands:
GitHub repo: https://github.com/tannerrj/crossfire-windows
Patches included in this follow up for convenience.
Current patch set (4 patches, down from 7):
output_file.cpp.diff — atomic rename via MoveFileExA()
plugins.cpp.diff — Win32 directory scan + absolute paths
init.cpp.diff — WSAStartup error checking + WSAGetLastError
loop.cpp.diff — Winsock select() error handling
Retired patches (now upstream):
object.cpp.diff — merged upstream 44c1e7f5a
cfpython.cpp.diff — merged upstream a28489bc1
win32.cpp.diff — merged upstream 7377b10f8