Build a WinXP compatible version of Spice (#611)

This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
This commit is contained in:
Will
2026-04-06 18:14:29 +10:00
committed by GitHub
parent d1779b93fa
commit e452734bc1
15 changed files with 240 additions and 124 deletions
+82 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# set to EN-US for consistency # set to EN-US for consistency
LANG=en_us_8859_1 LANG=en_us_8859_1
@@ -43,10 +43,16 @@ GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none") GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}" TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}" TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32" BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32" BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64" BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64" BUILDDIR_64_DEBUG="./cmake-build-debug-64"
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
DEBUG=0 DEBUG=0
OUTDIR="./bin/spice2x" OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras" OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -69,14 +75,29 @@ TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEnc
BUILD_TYPE="Release" BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE} BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE} BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0)) if ((DEBUG > 0))
then then
BUILD_TYPE="Debug" BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG} BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG} BUILDDIR_64=${BUILDDIR_64_DEBUG}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/') DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi fi
# is the XP-compatible toolchain installed?
XP_MUST_BUILD=0
BUILD_XP=0
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
BUILD_XP=1;
elif ((XP_MUST_BUILD > 0))
then
echo "WinXP toolchain not available, aborting"
exit 1
fi
# determine number of cores # determine number of cores
CORES=$(nproc) CORES=$(nproc)
@@ -90,6 +111,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD" echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32" echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64" echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
if ((BUILD_XP > 0))
then
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
else
echo "WinXP toolchain not available, skipping WinXP builds"
fi
echo "Distribution Name: $DIST_NAME" echo "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE" echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES" echo "Cores: $CORES"
@@ -130,11 +158,58 @@ time (
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null popd > /dev/null
if ((BUILD_XP > 0))
then
# 32 bit Windows XP
echo "Building 32bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_32}
fi
mkdir -p ${BUILDDIR_WINXP_32}
pushd ${BUILDDIR_WINXP_32} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
popd > /dev/null
# 64 bit Windows XP
echo ""
echo "Building 64bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_64}
fi
mkdir -p ${BUILDDIR_WINXP_64}
pushd ${BUILDDIR_WINXP_64} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null
else
echo "Skipping WinXP builds, toolchain not specified"
fi
echo "" echo ""
echo "Compilation process done :)" echo "Compilation process done :)"
echo "===========================" echo "==========================="
) )
if ((BUILD_XP > 0))
then
echo ""
echo "Checking XP compatibility..."
echo "============================="
if ! command -v windows_dll_compat_checker &> /dev/null; then
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
else
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice_laa.exe
fi
fi
# generate PDBs # generate PDBs
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -226,6 +301,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
fi
fi fi
# pack source files to output directory # pack source files to output directory
+10 -2
View File
@@ -1,4 +1,12 @@
#!/bin/bash #!/usr/bin/env bash
set -eu
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64 docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
docker run --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
# Interactive TTY if available, so docker build can be Ctrl+C'd
DOCKER_FLAGS=""
[ -t 0 ] && DOCKER_FLAGS="-it"
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake" RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN mkdir -p /opt/llvm-mingw-xp \
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin
+1 -1
View File
@@ -1,6 +1,6 @@
#include "camera.h" #include "camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include "mf_wrappers.h" #include "mf_wrappers.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <vector> #include <vector>
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#include "games/iidx/local_camera.h" #include "games/iidx/local_camera.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <d3d9.h> #include <d3d9.h>
#include <dxva2api.h> #include <dxva2api.h>
+3
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include <initguid.h> #include <initguid.h>
#include "graphics.h" #include "graphics.h"
+15 -9
View File
@@ -1294,7 +1294,19 @@ int main_implementation(int argc, char *argv[]) {
} }
// log // log
#ifndef SPICE_LINUX #if SPICE_LINUX
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#elif SPICE_XP
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for WinXP");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for WinXP");
#endif
#else
#ifdef SPICE64 #ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#elif SPICE32_LARGE_ADDRESS_AWARE #elif SPICE32_LARGE_ADDRESS_AWARE
@@ -1302,12 +1314,6 @@ int main_implementation(int argc, char *argv[]) {
#else #else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)"); log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif #endif
#else
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x) for Linux");
#else
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x) for Linux");
#endif
#endif #endif
log_info("launcher", "{}", VERSION_STRING); log_info("launcher", "{}", VERSION_STRING);
@@ -2226,7 +2232,7 @@ int main_implementation(int argc, char *argv[]) {
game->attach(); game->attach();
} }
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (!cfg::CONFIGURATOR_STANDALONE) { if (!cfg::CONFIGURATOR_STANDALONE) {
if (games::iidx::TDJ_CAMERA) { if (games::iidx::TDJ_CAMERA) {
games::iidx::init_camera_hooks(); games::iidx::init_camera_hooks();
@@ -2507,7 +2513,7 @@ int main_implementation(int argc, char *argv[]) {
// disable poke // disable poke
games::iidx::poke::disable(); games::iidx::poke::disable();
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
games::iidx::camera_release(); games::iidx::camera_release();
#endif #endif
+1 -1
View File
@@ -350,7 +350,7 @@ void overlay::SpiceOverlay::init() {
this->window_add(window_config = new overlay::windows::Config(this)); this->window_add(window_config = new overlay::windows::Config(this));
this->window_add(window_control = new overlay::windows::Control(this)); this->window_add(window_control = new overlay::windows::Control(this));
this->window_add(window_log = new overlay::windows::Log(this)); this->window_add(window_log = new overlay::windows::Log(this));
#ifdef SPICE64 #if SPICE64 && !SPICE_XP
if (avs::game::is_model("LDJ")) { if (avs::game::is_model("LDJ")) {
this->window_add(window_camera = new overlay::windows::CameraControl(this)); this->window_add(window_camera = new overlay::windows::CameraControl(this));
} }
@@ -1,6 +1,6 @@
#include "camera_control.h" #include "camera_control.h"
#if SPICE64 #if SPICE64 && !SPICE_XP
#include <games/io.h> #include <games/io.h>
#include <strmif.h> #include <strmif.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#if SPICE64 #if SPICE64 && !SPICE_XP
#include "overlay/window.h" #include "overlay/window.h"
#include <strmif.h> #include <strmif.h>
+2 -2
View File
@@ -152,8 +152,8 @@ namespace overlay::windows {
void CardManager::open_card_editor() { void CardManager::open_card_editor() {
if (this->current_card) { if (this->current_card) {
const auto card = this->current_card; const auto card = this->current_card;
strcpy_s(this->name_buffer, std::size(this->name_buffer), card->name.c_str()); snprintf(this->name_buffer, std::size(this->name_buffer), "%s", card->name.c_str());
strcpy_s(this->card_buffer, std::size(this->card_buffer), card->id.c_str()); snprintf(this->card_buffer, std::size(this->card_buffer), "%s", card->id.c_str());
this->color_buffer[0] = card->color[0]; this->color_buffer[0] = card->color[0];
this->color_buffer[1] = card->color[1]; this->color_buffer[1] = card->color[1];
this->color_buffer[2] = card->color[2]; this->color_buffer[2] = card->color[2];
+12 -5
View File
@@ -4289,7 +4289,9 @@ namespace overlay::windows {
ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0."); ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0.");
ImGui::TextUnformatted(""); ImGui::TextUnformatted("");
ImGui::TextUnformatted("Pick CPU cores to use:"); ImGui::TextUnformatted("Pick CPU cores to use:");
const uint64_t cpu_count = GetActiveProcessorCount(0); SYSTEM_INFO info;
GetSystemInfo(&info);
const uint64_t cpu_count = info.dwNumberOfProcessors;
uint64_t affinity = 0; uint64_t affinity = 0;
if (!option.value.empty()) { if (!option.value.empty()) {
try { try {
@@ -4580,7 +4582,7 @@ namespace overlay::windows {
if (definition.category.empty()) { if (definition.category.empty()) {
ImGui::TextDisabled("-"); ImGui::TextDisabled("-");
} else { } else {
ImGui::TextDisabled(definition.category.c_str()); ImGui::TextDisabled("%s", definition.category.c_str());
} }
} }
@@ -4900,14 +4902,19 @@ namespace overlay::windows {
} }
void Config::build_about() { void Config::build_about() {
#ifndef SPICE_LINUX #if SPICE_LINUX
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools)\r\n" "spice2x (a fork of SpiceTools) for Linux\r\n"
"=========================\r\n" +
to_string(VERSION_STRING)).c_str());
#elif SPICE_XP
ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for WinXP\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#else #else
ImGui::TextUnformatted(std::string( ImGui::TextUnformatted(std::string(
"spice2x (a fork of SpiceTools) for Linux\r\n" "spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" + "=========================\r\n" +
to_string(VERSION_STRING)).c_str()); to_string(VERSION_STRING)).c_str());
#endif #endif
+3
View File
@@ -1,3 +1,6 @@
// GetDisplayConfigBufferSizes etc is Vista+
#define _WIN32_WINNT 0x0601
#include "sysutils.h" #include "sysutils.h"
#include <cstdlib> #include <cstdlib>