-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix building with wine wrapped MSVC #24
Conversation
msvc and gcc/clang have different ABI, a function returning a trivial class instance for example (class containing only numerics for example) is flattened directly into RAX register on x64 with gcc/clang for Windows, while msvc will always change the signature of the function to be void and the 1st arg will be an out param with same type as the desired (old) return type, and the caller will get the result via an allocated local.
check this API via an assembly debugger as an example Line 58 in f8c18b5
Valve did this manually in some of the old interfaces versions and later ditched the idea. gbe_fork/sdk/steam/isteamfriends004.h Line 42 in f8c18b5
gbe_fork/sdk/steam/steamtypes.h Line 204 in f8c18b5
and the implementation gbe_fork/dll/steam_friends.cpp Line 279 in f8c18b5
this won't work, the original API library and the client library on Windows are both using msvc's ABI. |
some similar cases if you search the web remember all apps/games out there are linked against the original API library (steam_api.dll), you can't change that |
How does DXVK, vkd3d, wine libs and proton steam api bridge work then? They all use mingw gcc as far as I know and they work nicely with existing programs. I would like to build gbe fork with FOSS tools and libs. |
are they returning class instances or trivial types ? I don't know what is proton steam api bridge, but in case it does the magic I'm interested as well, didn't find any workflow though so I can't tell what's the purpose or the output, it could be just an implementation/hooking for Win32 APIs which are all flat C functions. ping me if you found more about this. |
I don't understand how they do it. There is not that much information from my search other that it's not possible but wine or mingw must have something to make it work. Another possibility is to build using either MSVC in wine (like goldberg originally did in his CI) or use clang-cl with MSVC but that still make the build reliant on proprietary stuff. |
I looked now at the gitlab workflow of the original repo, it seems to have 2 build types. The first one is - python generate_build_win_bat.py Looking into the .py script, it generates a out = ""
out += cl_line_obj(normal_build_args + release_build_args + include_arch + all_deps, deps_folder)
out += cl_line_link(normal_build_args + release_build_args + include_arch + steam_deps + sc_different_deps + ["deps/net.pb.obj"] + linker_arch + normal_linker_libs, ["/debug:none", "/OUT:release\\{}".format(steam_api_name)]) And the function def cl_line_link(arguments, linker_arguments):
return "cl /LD {} /link {}\n".format(' '.join(arguments), ' '.join(linker_arguments)) The function above it creates the object files def cl_line_obj(arguments, out_dir):
return "rmdir /S /Q {0}\nmkdir {0}\ncl /Fo:{0}/ /c {1}\n".format(out_dir, ' '.join(arguments)) This is the string formatted (replacing "rmdir /S /Q {0}"
"mkdir {0}"
"cl /Fo:{0}/ /c {1}" The last line is also another call to It eventually dumps the output to with open("build_win_release_test.bat", "w") as f:
f.write(out) And wine will run this batch script here - python generate_build_win_bat.py
- export WINEDEBUG=-all
- wine cmd /c build_win_release_test.bat This way the CI run/build time is as minimum as possible (all repos providers charge/consume from your minutes way less on Linux runners), and the build is done through a Microsoft compiler ensuring correct ABI in the output binaries. This is a clever idea indeed but github (and I assume also gitlab) gives you unlimited build times for non-private repos, still the build time is always way less on Linux. Looking at the pipeline of the last commit: SDK 1.56 and 1.57 you can see it runs the above workflow job Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24245 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
steam_apps.cpp
steam_gameserver.cpp
steam_parental.cpp
steam_screenshots.cpp
local_storage.cpp
settings_parser.cpp
steam_applist.cpp
...
...
...
Microsoft (R) Incremental Linker Version 14.00.24245.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:steam_apps.dll
/dll
/implib:steam_apps.lib
...
...
...
rtlgenrandom.lib
Shell32.lib
Creating library steam_apps.lib and object steam_apps.exp
Success. The second one is ...
...
- 7za x sdk_standalone.7z -osdk_standalone
...
...
- wine cmd /c
- mkdir cmake-builds && cd cmake-builds
- mkdir x64-release && cd x64-release
- echo call .\\..\\..\\sdk_standalone\\set_vars64.bat >> cmake-build.bat
- echo .\\..\\..\\cmake-3.15.0-rc1-win64-x64\\bin\\cmake.exe ..\\.. -G \"NMake Makefiles\" -DCMAKE_BUILD_TYPE:STRING="RelWithDebInfo" -DCMAKE_PREFIX_PATH="protobuf_x64-windows-static" -DProtobuf_PROTOC_EXECUTABLE:STRING="./../../protobuf_x64-windows-static/tools/protobuf/protoc.exe" >> cmake-build.bat
- echo nmake.exe >> cmake-build.bat
- wine cmd /c cmake-build.bat You can download the batch scriptSET VSINSTALLDIR=%~dp0Microsoft Visual Studio 14.0\
SET VCINSTALLDIR=%VSINSTALLDIR%VC\
SET VS140COMNTOOLS=%VSINSTALLDIR%Common7\Tools\
SET UCRTVersion=10.0.17763.0
SET WindowsSdkDir=%~dp0Windows Kits\10\
SET UniversalCRTSdkDir=%WindowsSdkDir%
SET WindowsSDKVersion=10.0.17763.0\
SET WindowsSDKLibVersion=10.0.17763.0\
SET WindowsSDK_ExecutablePath_x64=%~dp0Windows Kits\10\bin\%WindowsSDKVersion%x64\
SET LIB=
SET INCLUDE=
SET LIBPATH=
@if exist "%VSINSTALLDIR%Common7\Tools" set PATH=%VSINSTALLDIR%Common7\Tools;%PATH%
@if exist "%VSINSTALLDIR%Common7\IDE" set PATH=%VSINSTALLDIR%Common7\IDE;%PATH%
@if exist "%VCINSTALLDIR%VCPackages" set PATH=%VCINSTALLDIR%VCPackages;%PATH%
@if exist "%VCINSTALLDIR%BIN\amd64" set PATH=%VCINSTALLDIR%BIN\amd64;%PATH%
@if not "%UCRTVersion%" == "" @set INCLUDE=%UniversalCRTSdkDir%include\%UCRTVersion%\ucrt;%INCLUDE%
@if not "%UCRTVersion%" == "" @set LIB=%UniversalCRTSdkDir%lib\%UCRTVersion%\ucrt\x64;%LIB%
@if not "%WindowsSdkDir%" == "" @set PATH=%WindowsSdkDir%bin\x64;%WindowsSdkDir%bin\x86;%PATH%
@if not "%WindowsSdkDir%" == "" @set INCLUDE=%WindowsSdkDir%include\%WindowsSDKVersion%shared;%WindowsSdkDir%include\%WindowsSDKVersion%um;%WindowsSdkDir%include\%WindowsSDKVersion%winrt;%INCLUDE%
@if not "%WindowsSdkDir%" == "" @set LIB=%WindowsSdkDir%lib\%WindowsSDKLibVersion%um\x64;%LIB%
@if not "%WindowsSdkDir%" == "" @set LIBPATH=%WindowsLibPath%;%ExtensionSDKDir%\Microsoft.VCLibs\14.0\References\CommonConfiguration\neutral;%LIBPATH%
@if not "%WindowsSDK_ExecutablePath_x64%" == "" @set PATH=%WindowsSDK_ExecutablePath_x64%;%PATH%
@if exist "%VCINSTALLDIR%LIB\store\amd64" set LIB=%VCINSTALLDIR%LIB\store\amd64;%LIB%
@if exist "%VCINSTALLDIR%ATLMFC\INCLUDE" set INCLUDE=%VCINSTALLDIR%ATLMFC\INCLUDE;%INCLUDE%
@if exist "%VCINSTALLDIR%INCLUDE" set INCLUDE=%VCINSTALLDIR%INCLUDE;%INCLUDE%
@if exist "%VCINSTALLDIR%ATLMFC\LIB\amd64" set LIB=%VCINSTALLDIR%ATLMFC\LIB\amd64;%LIB%
@if exist "%VCINSTALLDIR%LIB\amd64" set LIB=%VCINSTALLDIR%LIB\amd64;%LIB%
@if exist "%VCINSTALLDIR%ATLMFC\LIB\amd64" set LIBPATH=%VCINSTALLDIR%ATLMFC\LIB\amd64;%LIBPATH%
@if exist "%VCINSTALLDIR%LIB\amd64" set LIBPATH=%VCINSTALLDIR%LIB\amd64;%LIBPATH%
@if exist "%VCINSTALLDIR%LIB\store\amd64" set LIBPATH=%VCINSTALLDIR%LIB\store\amd64;%VCINSTALLDIR%LIB\store\references;%LIBPATH% It is setting every env var used by Microsoft's build tools, and accordingly Visual Studio, manually. And finally calls cmake as seen above. It looks more or less the same as the first job, except the build files are generated and ran by cmake. The summary of it is that the entire project was always being built with a Microsoft compiler for Windows OS, except it was done through wine, not MinGW. Some things you can also see in the workflow is that the original author did the same and cached the deps as public archives in the repo, they are the equivalent to the branches I still remember the 1st issue discussing how crap and hacky the 2 build scripts I made for myself back then, but it seems no one bothered to look into the original project's CI workflow (I'm still salty about it 😄 ). Anyway, this is my deep dive into the gitlab workflows, I'm looking forward to the future of how this develops, compiler/linker challenges are always intriguing to me. Edit: Looking into the archive more, it turned to be the entire Microsoft Build tools compressed, when extracted it is 1.52GB. |
I always used the same way to build as Goldberg, always build it locally with wine and MSVC, but with the whole rewrite thought we could go leaner. I currently managed to build it with wine and MSVC cl, the resulting dll worked correctly. I'm now trying with clang-cl (and x86_64-windows-msvc target), but I'm struggling. The deps part worked correctly, but the problem is with gbe fork. premake with the deps is just using cmake so it generate correct makefile, but premake with gbe fork will either generate a visual studio solution which I can't use or makefile in normal gcc/ld format. |
According to their docs it can generate GNU make files including Cygwin & MinGW using the
I don't have any VM but did the above line not work on wine? |
I just don't think that it is possible to currently use clang-cl with premake. Would have to rewrite it to use CMake or another build system that support clang-cl. I'm too lazy for that, and it will still use most of the proprietary MSVC stuff anyway.
That not the problem, the problem is those makefiles use GNU-like command options instead of MSVC-like command options. CMake auto detect which compiler options should those makefile use, premake have hardcoded GNU-like options.
Those command are not being run in wine, I never run premake under wine in either wine msvc build or clang-cl, wine is only used to call the compiler (and msbuild in msvc case). Also the whole goal of using clang-cl is to not use wine at all. I added a commit to support building with msvc on wine, I'm using msvc-wine to simplify the task. It is running on a single thread because it errors otherwise (at least on my machine), you can run it on more if you want to build faster but would have to relaunch it a few times. Here basically how I build it: premake5 --file=premake5-deps.lua --32-build --all-ext --all-build --custom-cmake=cmake --cmake-toolchain=/opt/msvc/cmake/toolchain-x86.cmake --custom-extractor=7z --j=$(nproc) --os=windows vs2022
premake5 --file=premake5-deps.lua --64-build --all-ext --all-build --custom-cmake=cmake --cmake-toolchain=/opt/msvc/cmake/toolchain-x64.cmake --custom-extractor=7z --j=$(nproc) --os=windows vs2022
premake5 --file=premake5.lua --genproto --os=windows vs2022
cd build/project/vs2022/win
/opt/msvc/bin/x86/msbuild '/nologo' '/v:n' '/p:Configuration=release,Platform=Win32' gbe.sln
/opt/msvc/bin/x64/msbuild '/nologo' '/v:n' '/p:Configuration=release,Platform=x64' gbe.sln |
So, what should be the future of this PR? I propose to merge those two commits into a single "Allow building with wine wrapped MSVC", keeping only the relevant change to make it able to be built with MSVC wrapped in wine. Keeping custom-extractor, cmake-toolchain options and calling executable with wine. We could do without cmake-toolchain by using a wrapper like this for cmake: if echo -- "$@" | grep -Ewq "(--build|--install|-E|--system-information)" ; then
cmake "$@"
else
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake "$@"
fi But that is a bit annoying, same for extractor, could just wrap it with wine or use linux extractor binaries. But overall, I'm really not a fan of using binaries, I understand that on windows it might be annoying to fetch those, but on linux it's just another simple dependencies to install (premake, cmake, 7z), at least giving user an option to not use those bundled binaries is great, but would be even better if they weren't shipped and used at all. |
I did what I talked about in my previous post. Here a PKGBUILD that I use to build a package on Arch Linux that build windows .dll, linux .so and tools: pkgbase=gbe-fork-git
pkgname=('gbe-fork-common-git' 'gbe-fork-linux-git' 'gbe-fork-windows-git' 'gbe-fork-tools-git')
pkgver=r1785.be2a8eeb
pkgrel=1
pkgdesc="GBE Fork (Git version)"
arch=('x86_64')
license=('GPL-3.0')
makedepends=('git' 'premake' 'wine' 'cmake' 'p7zip' 'python' 'msvc-wine-git')
source=("git+https://github.com/Detanup01/gbe_fork.git")
sha256sums=('SKIP')
pkgver() {
cd "${srcdir}/gbe_fork"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
prepare() {
cd "${srcdir}/gbe_fork"
git -c protocol.file.allow=always submodule update --init --recursive
cd "${srcdir}/gbe_fork/tools/generate_emu_config"
python -m venv .env-linux
chmod +x rebuild_linux.sh
cd "${srcdir}/gbe_fork/tools/migrate_gse"
python -m venv .env-linux
chmod +x rebuild_linux.sh
}
build() {
cd "${srcdir}/gbe_fork"
# Build for Linux
premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --custom-cmake=cmake --custom-extractor=7z --j=8 --os=linux gmake2
premake5 --file=premake5.lua --genproto --os=linux gmake2
cd build/project/gmake2/linux
make config=release_x32 -j8 all
make config=release_x64 -j8 all
cd "${srcdir}/gbe_fork"
# Build for Windows
wine_env() {
(
export WINEESYNC=1
export WINEFSYNC=1
unset CFLAGS CXXFLAGS LDFLAGS LTOFLAGS
"$@"
)
}
wine_env premake5 --file=premake5-deps.lua --32-build --all-ext --all-build --custom-cmake=cmake --cmake-toolchain=/opt/msvc/cmake/toolchain-x86.cmake --custom-extractor=7z --j=8 --os=windows vs2022
wine_env premake5 --file=premake5-deps.lua --64-build --all-ext --all-build --custom-cmake=cmake --cmake-toolchain=/opt/msvc/cmake/toolchain-x64.cmake --custom-extractor=7z --j=8 --os=windows vs2022
wine_env premake5 --file=premake5.lua --genproto --os=windows vs2022
cd build/project/vs2022/win
wine_env /opt/msvc/bin/x86/msbuild '/nologo' '/v:n' '/p:Configuration=release,Platform=Win32' gbe.sln
wine_env /opt/msvc/bin/x64/msbuild '/nologo' '/v:n' '/p:Configuration=release,Platform=x64' gbe.sln
# Build tools
cd "${srcdir}/gbe_fork/tools/generate_emu_config"
source .env-linux/bin/activate
pip install -r requirements.txt
./rebuild_linux.sh
deactivate
cd "${srcdir}/gbe_fork/tools/migrate_gse"
source .env-linux/bin/activate
pip install -r requirements.txt
./rebuild_linux.sh
deactivate
}
package_gbe-fork-common-git() {
pkgdesc="GBE Fork - Common files (Git version)"
conflicts=('gbe-fork-common')
cd "${srcdir}/gbe_fork"
package_dir="${pkgdir}/opt/gbe-fork"
mkdir -p "$package_dir"
cp -r "post_build/steam_settings.EXAMPLE/" "$package_dir/"
cp "post_build/README.release.md" "$package_dir/"
cp "CHANGELOG.md" "$package_dir/"
cp "CREDITS.md" "$package_dir/"
mkdir "$package_dir/tools"
mkdir "$package_dir/tools/generate_interfaces"
mkdir "$package_dir/tools/lobby_connect"
cp "post_build/README.generate_interfaces.md" "$package_dir/tools/generate_interfaces/"
cp "post_build/README.lobby_connect.md" "$package_dir/tools/lobby_connect/"
}
package_gbe-fork-linux-git() {
pkgdesc="GBE Fork - Linux files (Git version)"
depends=('gbe-fork-common-git')
conflicts=('gbe-fork-linux')
cd "${srcdir}/gbe_fork"
package_dir="${pkgdir}/opt/gbe-fork"
mkdir -p "$package_dir"
cp -r build/linux/gmake2/release/experimental "$package_dir"
cp -r build/linux/gmake2/release/regular "$package_dir"
cp -r build/linux/gmake2/release/tools "$package_dir"
cp "post_build/README.experimental_linux.md" "$package_dir/experimental/"
}
package_gbe-fork-windows-git() {
pkgdesc="GBE Fork - Windows files (Git version)"
depends=('gbe-fork-common-git')
conflicts=('gbe-fork-windows')
cd "${srcdir}/gbe_fork"
package_dir="${pkgdir}/opt/gbe-fork"
mkdir -p "$package_dir"
cp -r build/win/vs2022/release/experimental "$package_dir"
cp -r build/win/vs2022/release/regular "$package_dir"
cp -r build/win/vs2022/release/steamclient_experimental "$package_dir"
cp -r build/win/vs2022/release/tools "$package_dir"
cp "post_build/README.experimental.md" "$package_dir/experimental/README.experimental_windows.md"
cp "post_build/README.experimental_steamclient.md" "$package_dir/steamclient_experimental"
}
package_gbe-fork-tools-git() {
pkgdesc="GBE Fork - Tools (Git version)"
depends=('gbe-fork-common-git')
conflicts=('gbe-fork-tools')
cd "${srcdir}/gbe_fork"
package_dir="${pkgdir}/opt/gbe-fork"
mkdir -p "$package_dir/tools"
cp -r tools/steamclient_loader/linux "$package_dir/tools/steamclient_loader"
cp -r tools/generate_emu_config/bin/linux/* "$package_dir/tools/"
cp -r tools/migrate_gse/bin/linux/migrate_gse "$package_dir/tools/"
} |
I don't think I want to change ingame_overlay. |
Please read the PR history. The scope was changed from building with mingw which resulted in an ABI incompatible dll to building with wine wrapped msvc. The change to ingame_overlay was for building with mingw. I should have fixed the case where |
Those changes allow to build gbe_fork for Windows on Linux with MinGW. There is also some change needed ingame_overlay:
I only tested building on x64. The resulting .dll fail make my game crash for some reason. With a debug build, I don't see anything wrong in the STEAM_LOG.txt generated by the emu. The crash is a simple
Unhandled exception: page fault on read access to 0xffffffffffffffff in 64-bit code (0x00000140e8d11f).
while the game is loading. The backtrace doesn't tell anything useful, only game/kernel/ntdll in it, nothing about steam.