#!/usr/bin/env bash ### make sure to cd to the emu src dir required_files=( "dll/dll.cpp" "dll/steam_client.cpp" "controller/gamepad.c" "sdk/steam/isteamclient.h" ) for emu_file in "${required_files[@]}"; do if [ ! -f "$emu_file" ]; then echo "[X] Invalid emu directory, change directory to emu's src dir (missing file '$emu_file')" >&2 exit 1 fi done BUILD_LIB32=1 BUILD_LIB64=1 BUILD_CLIENT32=1 BUILD_CLIENT64=1 BUILD_TOOL_CLIENT_LDR=1 BUILD_TOOL_FIND_ITFS32=1 BUILD_TOOL_FIND_ITFS64=1 BUILD_TOOL_LOBBY32=1 BUILD_TOOL_LOBBY64=1 # < 0: deduce, > 1: force PARALLEL_THREADS_OVERRIDE=-1 # 0 = release, 1 = debug, otherwise error BUILD_TYPE=-1 CLEAN_BUILD=0 VERBOSE=0 for (( i=1; i<=$#; i++ )); do var="${!i}" if [[ "$var" = "-j" ]]; then i=$((i+1)) PARALLEL_THREADS_OVERRIDE="${!i}" [[ "$PARALLEL_THREADS_OVERRIDE" =~ ^[0-9]+$ ]] || { echo "[X] Invalid arg after -j, expected a number" >&2; exit 1; } #echo "[?] Overriding parralel build jobs count with $PARALLEL_THREADS_OVERRIDE" elif [[ "$var" = "-lib-32" ]]; then BUILD_LIB32=0 elif [[ "$var" = "-lib-64" ]]; then BUILD_LIB64=0 elif [[ "$var" = "-client-32" ]]; then BUILD_CLIENT32=0 elif [[ "$var" = "-client-64" ]]; then BUILD_CLIENT64=0 elif [[ "$var" = "-tool-clientldr" ]]; then BUILD_TOOL_CLIENT_LDR=0 elif [[ "$var" = "-tool-itf-32" ]]; then BUILD_TOOL_FIND_ITFS32=0 elif [[ "$var" = "-tool-itf-64" ]]; then BUILD_TOOL_FIND_ITFS64=0 elif [[ "$var" = "-tool-lobby-32" ]]; then BUILD_TOOL_LOBBY32=0 elif [[ "$var" = "-tool-lobby-64" ]]; then BUILD_TOOL_LOBBY64=0 elif [[ "$var" = "-verbose" ]]; then VERBOSE=1 elif [[ "$var" = "clean" ]]; then CLEAN_BUILD=1 elif [[ "$var" = "release" ]]; then BUILD_TYPE=0 elif [[ "$var" = "debug" ]]; then BUILD_TYPE=1 else echo "[X] Invalid arg: $var" >&2 exit 1 fi done # use 70% build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" [[ $PARALLEL_THREADS_OVERRIDE -gt 0 ]] && build_threads="$PARALLEL_THREADS_OVERRIDE" [[ $build_threads -lt 2 ]] && build_threads=2 # build type optimize_level="" dbg_level="" dbg_defs="" linker_strip_dbg_symbols='' build_folder="" if [[ "$BUILD_TYPE" = "0" ]]; then optimize_level="-O2" dbg_level="-g0" dbg_defs="-DEMU_RELEASE_BUILD -DNDEBUG" linker_strip_dbg_symbols='-Wl,-S' build_folder="release" elif [[ "$BUILD_TYPE" = "1" ]]; then optimize_level="-Og" dbg_level="-g3" dbg_defs="" linker_strip_dbg_symbols="" build_folder="debug" else echo "[X] You must specify any of: [release debug]" >&2 exit 1 fi build_root_dir="build/linux/$build_folder" build_root_32="$build_root_dir/x32" build_root_64="$build_root_dir/x64" build_root_tools="$build_root_dir/tools" # common stuff deps_dir="build/deps/linux" libs_dir="libs" tools_dir='tools' build_temp_dir="build/tmp/linux" protoc_out_dir="dll/proto_gen/linux" third_party_dir="third-party" third_party_build_dir="$third_party_dir/build/linux" protoc_exe_32="$deps_dir/protobuf/install32/bin/protoc" protoc_exe_64="$deps_dir/protobuf/install64/bin/protoc" parallel_exe="$third_party_build_dir/rush/rush" # https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html common_compiler_args="-std=c++17 -fvisibility=hidden -fexceptions -fno-jump-tables" # third party dependencies (include folder + folder containing .a file) ssq_inc="$deps_dir/libssq/include" ssq_lib32="$deps_dir/libssq/build32" ssq_lib64="$deps_dir/libssq/build64" curl_inc32="$deps_dir/curl/install32/include" curl_inc64="$deps_dir/curl/install64/include" curl_lib32="$deps_dir/curl/install32/lib" curl_lib64="$deps_dir/curl/install64/lib" protob_inc32="$deps_dir/protobuf/install32/include" protob_inc64="$deps_dir/protobuf/install64/include" protob_lib32="$deps_dir/protobuf/install32/lib" protob_lib64="$deps_dir/protobuf/install64/lib" zlib_inc32="$deps_dir/zlib/install32/include" zlib_inc64="$deps_dir/zlib/install64/include" zlib_lib32="$deps_dir/zlib/install32/lib" zlib_lib64="$deps_dir/zlib/install64/lib" mbedtls_inc32="$deps_dir/mbedtls/install32/include" mbedtls_inc64="$deps_dir/mbedtls/install64/include" mbedtls_lib32="$deps_dir/mbedtls/install32/lib" mbedtls_lib64="$deps_dir/mbedtls/install64/lib" # directories to use for #include release_incs_both=( "$ssq_inc" "$libs_dir" "$protoc_out_dir" "$libs_dir/utfcpp" "controller" "dll" "sdk" "overlay_experimental" ) release_incs32=( "${release_incs_both[@]}" "$curl_inc32" "$protob_inc32" "$zlib_inc32" "$mbedtls_inc32" ) release_incs64=( "${release_incs_both[@]}" "$curl_inc64" "$protob_inc64" "$zlib_inc64" "$mbedtls_inc64" ) # directories where libraries (.a or .so) will be looked up release_libs_dir32=( "$ssq_lib32" "$curl_lib32" "$protob_lib32" "$zlib_lib32" "$mbedtls_lib32" ) release_libs_dir64=( "$ssq_lib64" "$curl_lib64" "$protob_lib64" "$zlib_lib64" "$mbedtls_lib64" ) # libraries to link with, either static ".a" or dynamic ".so" (name only) # if it's called libXYZ.a, then only write "XYZ" # for static libs make sure to build a PIC lib # each will be prefixed with -l, ex: -lpthread release_libs=( "pthread" "dl" "ssq" "z" # libz library "curl" "protobuf-lite" "mbedcrypto" ) # common source files used everywhere, just for convinience, you still have to provide a complete list later release_src=( "dll/*.cpp" "$protoc_out_dir/*.cc" ) # additional #defines common_defs="-DGNUC -DUTF_CPP_CPLUSPLUS=201703L -DCURL_STATICLIB" release_defs="$dbg_defs $common_defs" # errors to ignore during build release_ignore_warn="-Wno-switch" [ ! -d "$deps_dir" ] && { echo "[X] Dependencies dir \"$deps_dir\" was not found" >&2; exit 1; } [ ! -f "$protoc_exe_32" ] && { echo "[X] protobuff compiler wasn't found - 32" >&2; exit 1; } [ ! -f "$protoc_exe_64" ] && { echo "[X] protobuff compiler wasn't found - 64" >&2; exit 1; } [ ! -f "$parallel_exe" ] && { echo "[X] tool to run parallel compilation jobs '$parallel_exe' wasn't found" >&2; exit 1; } chmod 777 "$parallel_exe" echo "[?] All build operations will use $build_threads parallel jobs" last_code=0 function build_for () { local is_32_build=$( [[ "$1" != 0 ]] && { echo 1; } || { echo 0; } ) local is_exe=$( [[ "$2" != 0 ]] && { echo 1; } || { echo 0; } ) local out_filepath="${3:?'missing filepath'}" local extra_defs="$4" local -n all_src=$5 [[ "${#all_src[@]}" = "0" ]] && { echo [X] "No source files specified" >&2; return 1; } # https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html local cpiler_m32='' [[ $is_32_build = 1 ]] && cpiler_m32='-m32' # https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html local cpiler_pic_pie='-fPIC' [[ $is_exe = 1 ]] && cpiler_pic_pie='-fPIE' # https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html # https://www.baeldung.com/linux/library-convert-static-to-shared local linker_pic_pie='-shared' [[ $is_exe = 1 ]] && linker_pic_pie='-pie' local tmp_work_dir="${out_filepath##*/}_$is_32_build" tmp_work_dir="$build_temp_dir/${tmp_work_dir%.*}" echo " -- Cleaning compilation directory '$tmp_work_dir/'" rm -f -r "$tmp_work_dir" mkdir -p "$tmp_work_dir" || { echo [X] "Failed to create compilation directory" >&2; return 1; } local build_cmds=() local -A objs_dirs=() for src_file in $( echo "${all_src[@]}" ); do [[ -f "$src_file" ]] || { echo "[X] source file "$src_file" wasn't found" >&2; return 1; } # https://stackoverflow.com/a/9559024 local obj_dir=$( [[ -d "${src_file%/*}" ]] && echo "${src_file%/*}" || echo '.' ) obj_dir="$tmp_work_dir/$obj_dir" local obj_name="${src_file##*/}" obj_name="${obj_name%.*}.o" build_cmds+=("$src_file<>$obj_dir/$obj_name") objs_dirs["$obj_dir"]="$obj_dir" mkdir -p "$obj_dir" done [[ "${#build_cmds[@]}" = "0" ]] && { echo [X] "No valid source files were found" >&2; return 1; } local target_incs=() local target_libs_dirs=() if [[ $is_32_build = 1 ]]; then target_incs=("${release_incs32[@]}") target_libs_dirs=("${release_libs_dir32[@]}") else target_incs=("${release_incs64[@]}") target_libs_dirs=("${release_libs_dir64[@]}") fi # wrap each -ImyIncDir with single quotes -> '-ImyIncDir' target_incs=("${target_incs[@]/#/\'-I}") target_incs=("${target_incs[@]/%/\'}") echo " -- Compiling object files with $build_threads parallel jobs inside directory '$tmp_work_dir/'" local build_cmd="clang++ -c -x c++ $common_compiler_args $cpiler_pic_pie $cpiler_m32 $optimize_level $dbg_level $release_ignore_warn $release_defs $extra_defs ${target_incs[@]} '{1}' '-o{2}'" if [[ $VERBOSE = 1 ]]; then printf '%s\n' "${build_cmds[@]}" | "$parallel_exe" --dry-run -j$build_threads -d '<>' -k "$build_cmd" echo; fi printf '%s\n' "${build_cmds[@]}" | "$parallel_exe" -j$build_threads -d '<>' -k "$build_cmd" exit_code=$? echo " -- Exit code = $exit_code" echo; echo; sleep 1 [[ $exit_code = 0 ]] || { rm -f -r "$tmp_work_dir"; sleep 1 return $exit_code; } echo " -- Linking all object files from '$tmp_work_dir/' to produce '$out_filepath'" local obj_files=() for obj_file in $(echo "${objs_dirs[@]/%//*.o}" ); do [[ -f "$obj_file" ]] && obj_files+=("$obj_file") done # if no files were added [[ "${#obj_files[@]}" -gt 0 ]] || { echo "[X] No files to link" >&2; return 1; } # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/developer_guide/gcc-using-libraries#gcc-using-libraries_using-both-static-dynamic-library-gcc # https://linux.die.net/man/1/ld if [[ $VERBOSE = 1 ]]; then echo "clang++ $common_compiler_args $cpiler_pic_pie $cpiler_m32 $optimize_level $dbg_level $linker_pic_pie $linker_strip_dbg_symbols -o" "$out_filepath" "${obj_files[@]}" "${target_libs_dirs[@]/#/-L}" "-Wl,--whole-archive -Wl,-Bstatic" "${release_libs[@]/#/-l}" "-Wl,-Bdynamic -Wl,--no-whole-archive -Wl,--exclude-libs,ALL" echo; fi clang++ $common_compiler_args $cpiler_pic_pie $cpiler_m32 $optimize_level $dbg_level $linker_pic_pie $linker_strip_dbg_symbols -o "$out_filepath" "${obj_files[@]}" "${target_libs_dirs[@]/#/-L}" -Wl,--whole-archive -Wl,-Bstatic "${release_libs[@]/#/-l}" -Wl,-Bdynamic -Wl,--no-whole-archive -Wl,--exclude-libs,ALL exit_code=$? echo " -- Exit code = $exit_code" echo; echo; rm -f -r "$tmp_work_dir" sleep 1 return $exit_code } function cleanup () { rm -f -r "$build_temp_dir" rm -f -r "$protoc_out_dir" } if [[ "$CLEAN_BUILD" = "1" ]]; then echo // cleaning previous build [[ -d "$build_root_dir" ]] && rm -f -r "$build_root_dir" echo; echo; fi ## tools if [[ "$BUILD_TOOL_CLIENT_LDR" = "1" ]]; then [[ -d "$build_root_tools/steamclient_loader/" ]] || mkdir -p "$build_root_tools/steamclient_loader/" cp -f "$tools_dir"/steamclient_loader/linux/* "$build_root_tools/steamclient_loader/" echo; echo; fi ### x32 build cleanup mkdir -p "$protoc_out_dir" echo // invoking protobuf compiler - 32 "$protoc_exe_32" ./dll/*.proto -I./dll/ --cpp_out="$protoc_out_dir/" last_code=$((last_code + $?)) echo; echo; if [[ "$BUILD_LIB32" = "1" ]]; then echo // building shared lib libsteam_api.so - 32 [[ -d "$build_root_32" ]] || mkdir -p "$build_root_32" all_src_files=( "${release_src[@]}" "controller/*.c" ) build_for 1 0 "$build_root_32/libsteam_api.so" '-DCONTROLLER_SUPPORT' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_CLIENT32" = "1" ]]; then echo // building shared lib steamclient.so - 32 [[ -d "$build_root_32" ]] || mkdir -p "$build_root_32" all_src_files=( "${release_src[@]}" "controller/*.c" ) build_for 1 0 "$build_root_32/steamclient.so" '-DCONTROLLER_SUPPORT -DSTEAMCLIENT_DLL' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_TOOL_LOBBY32" = "1" ]]; then echo // building executable lobby_connect_x32 - 32 [[ -d "$build_root_tools/lobby_connect" ]] || mkdir -p "$build_root_tools/lobby_connect" all_src_files=( "${release_src[@]}" "$tools_dir/lobby_connect/lobby_connect.cpp" ) build_for 1 1 "$build_root_tools/lobby_connect/lobby_connect_x32" '-DNO_DISK_WRITES -DLOBBY_CONNECT' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_TOOL_FIND_ITFS32" = "1" ]]; then echo // building executable generate_interfaces_file_x32 - 32 [[ -d "$build_root_tools/find_interfaces" ]] || mkdir -p "$build_root_tools/find_interfaces" all_src_files=( "$tools_dir/generate_interfaces/generate_interfaces.cpp" ) build_for 1 1 "$build_root_tools/find_interfaces/generate_interfaces_file_x32" '' all_src_files last_code=$((last_code + $?)) fi ### x64 build cleanup mkdir -p "$protoc_out_dir" echo // invoking protobuf compiler - 64 "$protoc_exe_64" ./dll/*.proto -I./dll/ --cpp_out="$protoc_out_dir/" last_code=$((last_code + $?)) echo; echo; if [[ "$BUILD_LIB64" = "1" ]]; then echo // building shared lib libsteam_api.so - 64 [[ -d "$build_root_64" ]] || mkdir -p "$build_root_64" all_src_files=( "${release_src[@]}" "controller/*.c" ) build_for 0 0 "$build_root_64/libsteam_api.so" '-DCONTROLLER_SUPPORT' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_CLIENT64" = "1" ]]; then echo // building shared lib steamclient.so - 64 [[ -d "$build_root_64" ]] || mkdir -p "$build_root_64" all_src_files=( "${release_src[@]}" "controller/*.c" ) build_for 0 0 "$build_root_64/steamclient.so" '-DCONTROLLER_SUPPORT -DSTEAMCLIENT_DLL' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_TOOL_LOBBY64" = "1" ]]; then echo // building executable lobby_connect_x64 - 64 [[ -d "$build_root_tools/lobby_connect" ]] || mkdir -p "$build_root_tools/lobby_connect" all_src_files=( "${release_src[@]}" "$tools_dir/lobby_connect/lobby_connect.cpp" ) build_for 0 1 "$build_root_tools/lobby_connect/lobby_connect_x64" '-DNO_DISK_WRITES -DLOBBY_CONNECT' all_src_files last_code=$((last_code + $?)) fi if [[ "$BUILD_TOOL_FIND_ITFS64" = "1" ]]; then echo // building executable generate_interfaces_file_x64 - 64 [[ -d "$build_root_tools/find_interfaces" ]] || mkdir -p "$build_root_tools/find_interfaces" all_src_files=( "$tools_dir/generate_interfaces/generate_interfaces.cpp" ) build_for 0 1 "$build_root_tools/find_interfaces/generate_interfaces_file_x64" '' all_src_files last_code=$((last_code + $?)) fi # cleanup cleanup # copy configs + examples if [[ $last_code = 0 ]]; then echo "// copying readmes + files examples" cp -f -r "post_build/steam_settings.EXAMPLE/" "$build_root_dir/" cp -f "post_build/README.release.md" "$build_root_dir/" cp -f "CHANGELOG.md" "$build_root_dir/" if [[ $BUILD_TYPE = 1 ]]; then cp -f "post_build/README.debug.md" "$build_root_dir/" fi if [[ -d "$build_root_tools/find_interfaces" ]]; then cp -f "post_build/README.find_interfaces.md" "$build_root_tools/find_interfaces/" fi if [[ -d "$build_root_tools/lobby_connect" ]]; then cp -f "post_build/README.lobby_connect.md" "$build_root_tools/lobby_connect/" fi else echo "[X] Not copying readmes or files examples due to previous errors" >&2 fi echo; echo; echo; if [[ $last_code = 0 ]]; then echo "[GG] no failures" else echo "[XX] general failure" >&2 fi exit $last_code