Closed Edremon closed 2 months ago
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.
MyClass get()
// becomes
void get(MyClass *ret)
check this API via an assembly debugger as an example https://github.com/Detanup01/gbe_fork/blob/f8c18b52e2e6c82fbd1ae85ea8ab41ba0c788696/dll/steam_user.cpp#L58
Valve did this manually in some of the old interfaces versions and later ditched the idea. https://github.com/Detanup01/gbe_fork/blob/f8c18b52e2e6c82fbd1ae85ea8ab41ba0c788696/sdk/steam/isteamfriends004.h#L42 https://github.com/Detanup01/gbe_fork/blob/f8c18b52e2e6c82fbd1ae85ea8ab41ba0c788696/sdk/steam/steamtypes.h#L204
and the implementation https://github.com/Detanup01/gbe_fork/blob/f8c18b52e2e6c82fbd1ae85ea8ab41ba0c788696/dll/steam_friends.cpp#L279
this won't work, the original API library and the client library on Windows are both using msvc's ABI. this is why you're getting weird crashes, attach a debugger to see it in action. the original SDK doesn't even support anything other than Visual Studio. forget about this whole idea, trust me it's an absolute waste of time with 0 gain, if you hate Visual Studio that much use Build Tools from Microsoft or use Gihub's runner, it takes less than 10 min to build everything.
some similar cases https://stackoverflow.com/questions/71711846/segfault-on-some-steam-api-dll-calls-from-executable-compiled-with-mingw-gcc-an/76582160#76582160
if you search the web can i use mingw for steam sdk
you'll get plenty more from people trying that with the original SDK.
this is my genuine search query, not being sarcastic.
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 ? also note that Dx11 and Dx12 even have manual vftable which you can hook, making it compatible with C code
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. if you can check the API I mentioned earlier with an assembly debugger see how it returns the user id, in RAX or in a local stack var.
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 build_windows
, it indeed runs on fedora, it uses Wine and eventually calls python generate_build_win_bat.py
at line 75.
- python generate_build_win_bat.py
Looking into the .py script, it generates a .bat
file, but it uses cl.exe
as a compiler (the compiler used by Visual Studio & Microsoft's build tools)
These are the relevant lines:
Line 97 will write the line that links all objects in the generated .bat
file
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 cl_line_link
is defined above as
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 \n
with actual lines for readability)
"rmdir /S /Q {0}"
"mkdir {0}"
"cl /Fo:{0}/ /c {1}"
The last line is also another call to cl.exe
It eventually dumps the output to build_win_release_test.bat
here
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 build_windows
In the job log, starting from this line the Microsoft compiler is invoked and the usual output is displayed
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 build_cmake_windows
which you can see is always skipped in the CI pipeline, it creates a batch script which calls set_vars64.bat
to set the path for cl.exe
...
...
- 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 sdk_standalone
archive from its link mentioned here: https://gitlab.com/Mr_Goldberg/goldberg_emulator/-/blob/master/.gitlab-ci.yml?ref_type=heads#L114
It's a 112MB .7z archive.
Here's the entire set_vars64.bat
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.
I'm not sure how or why nmake
is used there, but you can completely ignore this job since the CI never runs it.
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 third-party/****
here on Github, the idea is to avoid depending on the original links since they may get taken down for one reason or another.
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 gmake2
action
premake5 --file=premake5.lua --genproto --os=windows gmake2
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.
According to their docs it can generate GNU make files including Cygwin & MinGW using the
gmake2
action
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.
I don't have any VM but did the above line not work on wine?
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. Head over to source and make a pr there.
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 CMAKE_GENERATOR
was not explicitly set on windows (like CI).
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.