vs2008_wine_pluginmax_build - ryzom/ryzomcore GitHub Wiki


title: Building ryzomcore's 3ds Max plugin (PluginMax) under Wine description: Verified end-to-end โ€” driving the real ryzomcore CMake build (WITH_NEL_MAXPLUGIN) through a wine-hosted VS2008 x86 toolchain, matching tool/quick_start's real PluginMax spec published: true date: 2026-08-06T00:00:00.000Z tags: editor: markdown dateCreated: 2026-07-07T00:00:00.000Z

Builds on Visual C++ 2008 build environment on Linux using Wine โ€” read that first for the base Wine prefix, the toolchain archive, and the raw cl.exe/CMake+Ninja traps. This page covers the additional setup needed to build the real ryzomcore repo's 3ds Max plugin (nel/tools/3d/plugin_max and friends), not just standalone probes. {.is-info}

Verified 2026-07-07 (plugin-only config): 1008/1008 objects, all plugin binaries built. Re-verified and extended 2026-08-05 against a fresh WinXP-sourced toolchain: three full configurations build green under this setup โ€” asset build tools + MFC tools + Max 2010 plugins (D3D/DSound/XAudio2/OpenAL drivers all ON, mapext198m3.dlm included), snowballs client + server + NeL samples, and the ryzom client โ€” with SxS manifests embedded through the traditional mt flow. The MFC GUI tools (world_editor, georges, branch_patcher, tile_edit, ...) link for the first time under Wine (unicode MFC apps need an explicit /ENTRY:wWinMainCRTStartup; NL_DEFAULT_PROPS adds it via the NL_MFC_UNICODE_APP target property). {.is-info}

Most of this page's manual setup is now automated: tool/wine_vs2008/setup.sh in the ryzomcore repo creates the dosdevices links, the VC_DIR shadow directory, the lowercase-alias symlink farms, and the Max SDK header patch in one idempotent pass, and tool/wine_vs2008/toolchain.cmake is the maintained toolchain file. The sections below document what those scripts do and why. Set NL_WINE_VS2008_PREFIX to your prefix path before running any of it. {.is-info}

Archives needed

Beyond the base VS2008 toolchain archive from the companion page, extracted into the same prefix's drive_c:

  • Max 2010 SDK โ†’ Program Files/Autodesk/3ds Max 2010 SDK/ (top-level archive folder name matches). Find3dsMaxSDK.cmake looks for maxsdk/include/maxversion.h under $ENV{PROGRAMFILES}/Autodesk/3ds Max <year> SDK/maxsdk.
  • Externals (2019q4_external_v90_x86, matching nel_plugin_max_9_x86.bat/_2010_x86.bat/_2012_x86.bat's redist paths) โ†’ C:\2019q4_external_v90_x86 directly (not under Program Files) โ€” one subfolder per library (zlib/include, zlib/lib, boost/include, ...), not a single unified tree.
  • DirectX SDK (June 2010) โ†’ Program Files/Microsoft DirectX SDK (June 2010)/ โ€” required unconditionally for any MSVC configure of this repo (see "Genuine upstream bugs" below), regardless of whether a Direct3D driver is actually being built.

With everything extracted, one script prepares the prefix (idempotent โ€” safe to rerun after adding archives):

export NL_WINE_VS2008_PREFIX=~/.local/share/wineprefixes/vs2008
/path/to/ryzomcore/tool/wine_vs2008/setup.sh

(NL_WINE_VS2008_EXTERNAL overrides the externals location if they live outside the prefix's drive_c.)

Case-insensitivity: the symlink farm

Vendor SDK headers #include each other with inconsistent casing (e.g. one Max SDK header does #include "assetmanagement/AssetUser.h", another does #include "assetmanagement/assetUser.h") โ€” harmless on a case-insensitive Windows filesystem, but on a case-sensitive Linux one this defeats #pragma once's file-identity tracking: the two casings resolve to two different paths, so the header gets parsed twice, and the second pass hits error C2011: class type redefinition. Same root cause as an ordinary missing-file case mismatch (e.g. ComCtl32.lib vs. requested ComCtl32.lib โ€” actually stored as ComCtl32.Lib; BIPEXP.H vs. requested bipexp.h), just surfacing as a redefinition instead of a not-found.

ciopfs (a case-insensitive FUSE overlay) looked like the systematic fix, but turned out broken on a stock Ubuntu 24.04 box โ€” an isolated test showed it doesn't list any subdirectory at all, even space-free ones:

mkdir -p /tmp/t/{src/sub,mnt} && ciopfs /tmp/t/src /tmp/t/mnt && ls /tmp/t/mnt   # empty - real bug, not a config issue

Instead, generate lowercase-alias symlinks across every relevant tree in one pass โ€” the script is tool/wine_vs2008/lowercase_alias.py in the repo (reproduced here for reference), and setup.sh runs it over all the trees listed below:

#!/usr/bin/env python3
# For every file/dir under ROOT whose name isn't already all-lowercase,
# add a lowercase-named symlink alongside it (skip if a same-named entry
# already exists - real file or prior symlink).
import os, sys

root = sys.argv[1]
created = 0
skipped_collision = 0

for dirpath, dirnames, filenames in os.walk(root):
	for name in list(dirnames) + filenames:
		lower = name.lower()
		if name == lower:
			continue
		full = os.path.join(dirpath, name)
		lower_full = os.path.join(dirpath, lower)
		if os.path.lexists(lower_full):
			if not os.path.islink(lower_full):
				skipped_collision += 1
			continue
		os.symlink(name, lower_full)
		created += 1

print(f"created={created} skipped_collision={skipped_collision}")

Run against every include/lib tree that vendor code touches (roughly 2000 symlinks total on a fresh prefix, zero collisions observed):

PFX=~/.local/share/wineprefixes/vs2008/drive_c
for d in \
  "$PFX/Program Files/Autodesk/3ds Max 2010 SDK/maxsdk/include" \
  "$PFX/Program Files/Autodesk/3ds Max 2010 SDK/maxsdk/lib" \
  "$PFX/Program Files/Microsoft Visual Studio 9.0/VC/include" \
  "$PFX/Program Files/Microsoft Visual Studio 9.0/VC/lib" \
  "$PFX/Program Files/Microsoft Visual Studio 9.0/VC/atlmfc" \
  "$PFX/Program Files/Microsoft SDKs/Windows/v6.0A/Include" \
  "$PFX/Program Files/Microsoft SDKs/Windows/v6.0A/Lib" \
  "$PFX/Program Files/Microsoft DirectX SDK (June 2010)/Include" \
  "$PFX/Program Files/Microsoft DirectX SDK (June 2010)/Lib" \
  "$PFX/2019q4_external_v90_x86"
do
  python3 lowercase_alias.py "$d"
done

This is a superset of (and replaces) individually-discovered symlinks like ComCtl32.lib/bipexp.h โ€” run it once up front on a fresh prefix rather than reactively chasing each mismatch as it surfaces during a build.

VC_DIR shadow directory

CMakeModules/FindMSVC.cmake derives VC_DIR by regexing /bin/.+ off CMAKE_C_COMPILER's path, assuming it points directly at a real .../VC/bin/cl.exe โ€” breaks since our compiler is a wrapper script, not literally inside a VC/bin/ tree. setup.sh builds a fake shadow directory (default <prefix>-msvc-shadow, override with NL_WINE_VS2008_SHADOW) ending in the right shape, with the repo wrapper scripts standing in for the real binaries and everything else symlinked through to the real toolchain install; for reference:

SHADOW=~/.local/share/wineprefixes/vs2008-msvc-shadow/VC
PFX="$HOME/.local/share/wineprefixes/vs2008/drive_c/Program Files/Microsoft Visual Studio 9.0/VC"
mkdir -p "$SHADOW/bin"
ln -sfn "$PFX/include" "$SHADOW/include"
ln -sfn "$PFX/lib" "$SHADOW/lib"
ln -sfn "$PFX/atlmfc" "$SHADOW/atlmfc"
ln -sfn "$PFX/redist" "$SHADOW/redist"
cp tool/wine_vs2008/winecl-env "$SHADOW/bin/winecl-env"    # winecl-{cc,link,lib} source this by dirname($0), needs a local copy
cp tool/wine_vs2008/winecl-cc "$SHADOW/bin/cl.exe"
cp tool/wine_vs2008/winecl-link "$SHADOW/bin/link.exe"
cp tool/wine_vs2008/winecl-lib "$SHADOW/bin/lib.exe"
chmod +x "$SHADOW/bin/cl.exe" "$SHADOW/bin/link.exe" "$SHADOW/bin/lib.exe"

CMAKE_MT/CMAKE_RC_COMPILER stay pointed at the plain tool/wine_vs2008/winecl-{mt,rc} wrappers โ€” only CMAKE_C_COMPILER/CMAKE_CXX_COMPILER/CMAKE_LINKER/CMAKE_AR need to live under the shadow, since only FindMSVC.cmake's regex cares.

Max SDK header patch

Any TU that reaches both maxscript's maxsdk/include/maxscrpt/value.h and VC's ivec.h dies with C2365: 'empty' : redefinition โ€” the SDK declares a global extern ScripterExport Empty empty; that collides with the intrinsics header's global empty(). The historically-working SDK copy had that extern commented out (an invisible local vendor patch, rediscovered the hard way against a pristine SDK). setup.sh applies the same one-line comment-out idempotently; plugin code never references the global.

Environment variables

Set for every cmake configure invocation (not needed at build time โ€” these feed CMake's own find_path/find_library calls on the Linux host, not the compiler):

export NL_WINE_VS2008_PREFIX=~/.local/share/wineprefixes/vs2008   # or wherever the prefix lives
export PROGRAMFILES="$NL_WINE_VS2008_PREFIX/drive_c/Program Files"
export WINSDK_DIR="$PROGRAMFILES/Microsoft SDKs/Windows/v6.0A"
export DXSDK_DIR="$PROGRAMFILES/Microsoft DirectX SDK (June 2010)"
  • NL_WINE_VS2008_PREFIX โ€” read by the repo wrapper scripts and toolchain file (default $HOME/toolchain_v90_prefix). NL_WINE_VS2008_SHADOW and NL_WINE_VS2008_EXTERNAL similarly override the shadow-dir and externals locations.
  • PROGRAMFILES โ€” Find3dsMaxSDK.cmake's hint path.
  • WINSDK_DIR โ€” read by FindWindowsSDK.cmake's USE_CURRENT_WINSDK() macro (see bugs below). Must be a Linux path (not C:\...) since these find_path calls run natively on the Linux host, not through Wine. Needed again on every reconfigure โ€” the cached value doesn't survive cmake --regenerate-during-build.
  • EXTERNAL_PATH is no longer needed: the legacy unified-externals probe (FindExternal.cmake) is no longer hard-required when WITH_EXTERNAL=OFF (it used to be, unconditionally on WIN32), and per-library resolution comes from CMAKE_PREFIX_PATH in the toolchain file. Beware what that probe does when it does run against an empty prefix path: it matches any directory containing include/zlib.h โ€” including the Linux host's own /usr โ€” and prepends it to the global include/library paths, poisoning every later find with host headers that will never have a matching Windows .lib (the toolchain file fails fast when the externals glob is empty for exactly this reason).

Toolchain file

tool/wine_vs2008/toolchain.cmake in the repo โ€” extends the base setup from the companion page with everything this build needs. What it sets and why (read the file itself for the full commentary):

  • TARGET_CPU x86 (forced): CMakeModules/nel.cmake uses its own TARGET_CPU convention, not CMAKE_SYSTEM_PROCESSOR โ€” unset, it falls back to the build machine's x86_64 and points every Find module (MAXSDK, DXSDK, MFC) at 64-bit library directories.
  • Compiler/linker/ar paths under the VC_DIR shadow; CMAKE_MT/CMAKE_RC_COMPILER at the plain wrappers.
  • All three CMAKE_FIND_ROOT_PATH_MODE_* = NEVER (the ONLY-with-empty-root trap โ€” see the companion page).
  • Per-config compiler flags forced to /Z7 + /MD (the /Zi-breaks-under-Wine and silent-/MDd-failure traps).
  • CMAKE_PREFIX_PATH from a glob over the per-library externals tree (<prefix>/drive_c/2019q4_external_v90_x86/* by default), with a fail-fast error when the glob is empty.
  • NL_EMBED_SXS_MANIFEST_MT ON โ€” the traditional manifest flow (see the companion page's Manifest embedding section; on CMake 3.30+ CMake's own vs_link runs the mt pass, on older CMake a POST_BUILD step does).
  • CMAKE_DISABLE_FIND_PACKAGE_MySQL โ€” the externals carry no mariadb client for VC9, and with find-root NEVER the stock FindMySQL happily finds the Linux host's own mariadb, putting a nonexistent mariadb.lib on MSVC link lines.

The CMake configure command

Matching tool/quick_start/configure_targets.py's real PluginMax spec branch (NeLSpecPluginMax), for an MSVC/x86/non-Hunter target โ€” read the script itself (GenerateCMakeOptions) rather than trust a summary; it's the single source of truth for what a real build actually passes, and doesn't match what the option names suggest at a glance (e.g. WITH_TOOLS, the root-level "Build Tools" option, is not part of this spec at all โ€” WITH_NEL_MAXPLUGIN=ON alone already satisfies nel/CMakeLists.txt's IF(WITH_NEL_TOOLS OR WITH_NEL_MAXPLUGIN) gate):

cmake -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=/path/to/ryzomcore/tool/wine_vs2008/toolchain.cmake \
  -DWITH_SSE2=OFF -DWITH_SSE3=OFF \
  -DWITH_EXTERNAL=OFF -DWITH_STLPORT=OFF -DWITH_NEL_TESTS=OFF -DWITH_TESTING=OFF -DWITH_STATIC=ON \
  -DWITH_FFMPEG=OFF -DWITH_MSQUIC=OFF -DWITH_QT=OFF -DWITH_QT5=OFF -DWITH_QT6=OFF \
  -DWITH_STATIC_LIBXML2=OFF -DWITH_STATIC_CURL=OFF -DCURL_NO_CURL_CMAKE=ON \
  -DFINAL_VERSION=ON \
  -DWITH_LUA51=OFF -DWITH_LUA53=ON \
  -DWITH_MFC=ON \
  -DWITH_DRIVER_DIRECT3D=OFF -DWITH_DRIVER_DSOUND=OFF -DWITH_DRIVER_XAUDIO2=OFF \
  -DWITH_DRIVER_OPENGL=ON -DWITH_DRIVER_OPENGL3=ON -DWITH_DRIVER_OPENAL=OFF \
  -DWITH_RYZOM_CLIENT=OFF -DWITH_RYZOM_SERVER=OFF \
  -DWITH_NEL_TOOLS=OFF -DWITH_RYZOM_TOOLS=OFF -DWITH_ASSIMP=OFF \
  -DWITH_NEL_SAMPLES=OFF -DWITH_SNOWBALLS=OFF -DWITH_NELNS=OFF \
  -DWITH_RYZOM=OFF -DWITH_NEL_MAXPLUGIN=ON \
  -DMAXSDK_DIR="$HOME/.local/share/wineprefixes/vs2008/drive_c/Program Files/Autodesk/3ds Max 2010 SDK/maxsdk" \
  /path/to/ryzomcore

Notes:

  • As of 2026-08-05 the full driver set builds green โ€” WITH_DRIVER_DIRECT3D/WITH_DRIVER_DSOUND/WITH_DRIVER_XAUDIO2/WITH_DRIVER_OPENAL can all be ON (matching the real spec; the OpenAL target_link_libraries signature bug that used to force it off is fixed). The DirectX SDK June 2010 in the prefix covers D3D/DSound/XAudio2.
  • The same toolchain also builds two broader verified configurations: add -DWITH_RYZOM=ON -DWITH_RYZOM_TOOLS=ON -DWITH_NEL_TOOLS=ON -DWITH_MFC=ON for the full asset-tools + MFC-tools + plugins build, or -DWITH_NEL_SAMPLES=ON -DWITH_SNOWBALLS=ON / -DWITH_RYZOM=ON -DWITH_RYZOM_CLIENT=ON -DWITH_RYZOM_PATCH=ON -DWITH_LOGIC=OFF for the snowballs and ryzom-client configurations. WITH_NELNS stays OFF โ€” nelns genuinely requires C++14 (cxx_return_type_deduction) and is out of VC9 scope. zone_painter and the GUI sample's atlas step self-exclude on pre-VS2010 toolchains.
  • WITH_STATIC_CURL isn't an actual option anywhere in this codebase's CMake โ€” CMake warns it's unused. Harmless; kept for fidelity with the real script.
  • Packaging: tool/wine_vs2008/package_max_plugin.sh <build-bin-dir> <externals-root> <out-dir> assembles the plugin set in the historical install arrangement (root support DLLs, renamed plugins/, scripts/ + scripts/startup/, macroscripts/) โ€” the script mirror of tool/quick_start/win32/install/redist/nel_plugin_max_2010_x86.bat, minus code signing.

Genuine upstream bugs found (independent of Wine)

Two real, pre-existing defects in CMakeModules/FindWindowsSDK.cmake's USE_CURRENT_WINSDK() macro, surfaced only because this is (as far as anyone can tell) the first time this exact code path has run without a Windows registry to short-circuit past it โ€” on real Windows, VS2008's SDK registers in the registry, so DETECT_WINSDK_VERSION_HELPER succeeds and this macro's fallback logic never runs. Both fixed and committed to the repo:

Bug 1 โ€” SET(WINSDK_DIR "") defeats the subsequent find_path(). A plain (non-cache) set() creates a normal variable that shadows the cache entry of the same name; find_path() then treats the mere existence of that normal variable (even empty) as "already resolved" and silently skips its own search โ€” confirmed in complete isolation, independent of any cross-compiling or root-path setting:

# Reproduces with or without a pre-existing valid CACHE value for MYVAR:
set(MYVAR "")
find_path(MYVAR Windows.h HINTS /real/path/with/Include)
# MYVAR stays empty

Fix: UNSET(WINSDK_DIR CACHE) instead of SET(WINSDK_DIR "").

Bug 2 โ€” the find_path target was wrong, once bug 1 stopped masking it. FIND_PATH(WINSDK_DIR Windows.h HINTS ${WINSDKENV_DIR}/Include/um ${WINSDKENV_DIR}/Include) searches for a bare filename with the hints already pointing inside Include/ โ€” find_path returns the directory containing the found file, so WINSDK_DIR ends up as .../Include itself, not the SDK root the rest of the macro (and everything downstream) expects (which then appends /Include again, doubling the path). Fix: search for the nested path Include/Windows.h from the candidate root instead โ€” the same pattern Find3dsMaxSDK.cmake (include/maxversion.h) and FindDirectXSDK.cmake (Include/dxsdkver.h) already use correctly:

FIND_PATH(WINSDK_DIR Include/Windows.h HINTS ${WINSDKENV_DIR})
IF(NOT WINSDK_DIR)
  FIND_PATH(WINSDK_DIR Include/um/Windows.h HINTS ${WINSDKENV_DIR})  # newer Windows Kits layout
ENDIF()

A third apparent blocker โ€” error C2719: formal parameter with __declspec(align('16')) won't be aligned on std::vector<CBone> (VS2008's STL takes resize()'s fill value by value, which 32-bit MSVC can't do for an SSE-aligned type; confirmed even a bare default-constructed std::vector<AlignedType> member triggers it, no specific call site needed) โ€” turned out not to be a bug at all: tool/quick_start/configure_targets.py already forces -DWITH_SSE2=OFF -DWITH_SSE3=OFF for any x86 target, which makes NL_ALIGN_SSE2 a no-op and sidesteps the whole class of issue. The fix was matching that existing convention, not patching NeL's alignment macros or the vendor STL.

The pipeline_max tools (no plugin) โ€” x87 reference build

The same toolchain (all of the setup above: archives, symlink farm, VC_DIR shadow, environment variables, toolchain file) also builds the standalone pipeline_max export tools โ€” the .max-processing reimplementation that runs the Max export pipeline without 3ds Max. This became possible once those tools stopped depending on libgsf (a Linux-only OLE reader) and got a native cross-platform OLE backend. Building them under VS2008 gives an x87 / no-SSE2 reference matching the Max 2010 that produced the corpus's reference exports, which is the instrument for chasing the remaining float-precision residual (see the design doc's ยง2b VS2008 x87 reference build for the porting details, the two latent bugs it surfaced โ€” an empty-std::list dereference caught by MSVC's checked iterators, and a SuperClassId static-initialization-order fiasco โ€” and the measured precision improvement).

Use a separate build dir next to the plugin one (both are full NeL builds under Wine โ€” keep them; a clean rebuild is ~500 objects). The configure differs from the plugin spec by turning the plugin off and the tools + 3D + native OLE on; no Max SDK is needed:

cmake -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=/path/to/ryzomcore/tool/wine_vs2008/toolchain.cmake \
  -DWITH_SSE2=OFF -DWITH_SSE3=OFF \
  -DWITH_EXTERNAL=OFF -DWITH_STLPORT=OFF -DWITH_NEL_TESTS=OFF -DWITH_TESTING=OFF -DWITH_STATIC=ON \
  -DWITH_FFMPEG=OFF -DWITH_MSQUIC=OFF -DWITH_QT=OFF -DWITH_QT5=OFF -DWITH_QT6=OFF \
  -DWITH_STATIC_LIBXML2=OFF -DWITH_STATIC_CURL=OFF -DCURL_NO_CURL_CMAKE=ON \
  -DFINAL_VERSION=ON -DWITH_LUA51=OFF -DWITH_LUA53=ON -DWITH_MFC=ON \
  -DWITH_DRIVER_DIRECT3D=OFF -DWITH_DRIVER_DSOUND=OFF -DWITH_DRIVER_XAUDIO2=OFF \
  -DWITH_DRIVER_OPENGL=ON -DWITH_DRIVER_OPENGL3=ON -DWITH_DRIVER_OPENAL=OFF \
  -DWITH_RYZOM_CLIENT=OFF -DWITH_RYZOM_SERVER=OFF -DWITH_RYZOM_TOOLS=OFF -DWITH_ASSIMP=OFF \
  -DWITH_NEL_SAMPLES=OFF -DWITH_SNOWBALLS=OFF -DWITH_NELNS=OFF -DWITH_RYZOM=OFF \
  -DWITH_NEL_MAXPLUGIN=OFF -DWITH_NEL_TOOLS=ON -DWITH_3D=ON \
  -DWITH_PIPELINE_NATIVE_OLE=ON -DWITH_LIBGSF=OFF \
  /path/to/ryzomcore

Targets are .exe (ninja pipeline_max_export_zone.exe, โ€ฆ_export_shape.exe, pipeline_max_corpus_test.exe, โ€ฆ). Running an exe needs the release external DLLs (libxml2/jpeg62/libpng16/zlib/freetype/โ€ฆ) from 2019q4_external_v90_x86/*/bin/ on its search path (copy them next to the exe) and Windows-form path arguments (winepath -w); a thin per-tool wrapper that does both lets the x64 corpus drivers (zone_corpus.py, etc.) drive the VS2008 binaries via --bin.

See also

โš ๏ธ **GitHub.com Fallback** โš ๏ธ