diff --git a/.github/workflows/emu-build-all-win-vs22.yml b/.github/workflows/emu-build-all-win-vs22.yml new file mode 100644 index 000000000..c08d8a559 --- /dev/null +++ b/.github/workflows/emu-build-all-win-vs22.yml @@ -0,0 +1,123 @@ +name: "Build emu (Windows)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + PREMAKE_ACTION: "vs2022" + DEPS_CACHE_KEY: "emu-deps-win" + DEPS_CACHE_DIR: "build/deps/win" + + THIRD_PARTY_BASE_DIR: "third-party" + + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + +jobs: + deps: + name: "Restore or build deps" + if: ${{ !cancelled() }} + uses: "./.github/workflows/emu-deps-win-vs22.yml" + + builds-matrix-win: + name: "build" + needs: ["deps"] + runs-on: "windows-2022" + if: ${{ !cancelled() }} + continue-on-error: true + + strategy: + fail-fast: false + matrix: + prj: [ + # regular api + "api_regular", + # (experimental) api + client + "api_experimental", + "steamclient_experimental_stub", + # client (experimental) + loader + extra dll + gameoverlaylib + "steamclient_experimental", + "steamclient_experimental_loader", + "steamclient_experimental_extra", + "lib_game_overlay_renderer", + # steam old lib + "lib_steam_old", + # tools + "tool_lobby_connect", + "tool_generate_interfaces", + ] + arch: ["x64", "Win32"] + cfg: ["debug", "release"] + + exclude: + - prj: lib_steam_old + arch: x64 + + steps: + # clone branch + - name: "Checkout branch" + uses: actions/checkout@v6 + + # ensure we have msbuild + - name: "Add MSBuild to PATH" + uses: microsoft/setup-msbuild@v3 + + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + # deps + - name: "Restore deps" + id: "emu-deps-cache-step" + uses: actions/cache@v5 + with: + key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" + path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" + + # extra helpers/tools, these are not built inside the deps build dir + - name: "Clone third-party build helpers (common/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/common/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" + + - name: "Clone third-party deps (build/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/build/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" + + # generate project files + - name: "Generate project files" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5.lua --genproto --emubuild=${{ github.sha }} --dosstub --winrsrc --winsign --os=windows vs2022 + + # build target + - name: "Build target" + shell: "cmd" + working-directory: "${{ github.workspace }}/build/project/vs2022/win" + run: | + msbuild /nologo /target:${{ matrix.prj }} /m:1 -p:CL_MPCount=${{ env.MAX_PARALLEL_JOBS }} /v:n /p:Configuration=${{ matrix.cfg }},Platform=${{ matrix.arch }} gbe.sln + + # upload artifact/package to github Actions + - name: "Upload target package" + uses: actions/upload-artifact@v7 + with: + name: "emu-win-vs22-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}" + path: "build/win" + if-no-files-found: "error" + compression-level: 9 + retention-days: 1 diff --git a/.github/workflows/emu-deps-win-vs22.yml b/.github/workflows/emu-deps-win-vs22.yml new file mode 100644 index 000000000..174374fe1 --- /dev/null +++ b/.github/workflows/emu-deps-win-vs22.yml @@ -0,0 +1,78 @@ +name: "Emu third-party dependencies (Windows vs22)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + PREMAKE_ACTION: "vs2022" + DEPS_CACHE_KEY: "emu-deps-win" + DEPS_CACHE_DIR: "build/deps/win" + + PACKAGE_BASE_DIR: "build/package/win" + THIRD_PARTY_BASE_DIR: "third-party" + + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories + MAX_PARALLEL_JOBS: 4 + +jobs: + deps-build: + runs-on: "windows-2022" + if: ${{ !cancelled() }} + + steps: + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: ${{ github.workspace }} + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + - name: "Lookup cache for deps" + id: "emu-deps-cache-step" + uses: actions/cache@v5 + with: + key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}" + path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}" + lookup-only: true # don't restore cache if found + + # we need branch because it has build scripts + - name: "Checkout branch" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + + - name: "Clone third-party deps (common/win)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/common/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win" + + - name: "Clone third-party deps (deps/win)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/deps/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win" + + - name: "Clone third-party deps (deps/common)" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + uses: actions/checkout@v6 + with: + ref: "third-party/deps/common" + path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common" + + - name: "Build deps" + if: steps.emu-deps-cache-step.outputs.cache-hit != 'true' + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + set "CMAKE_GENERATOR=Visual Studio 17 2022" + "${{env.THIRD_PARTY_BASE_DIR}}\common\win\premake\premake5.exe" --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=${{ env.MAX_PARALLEL_JOBS }} --verbose --clean --os=windows vs2022 diff --git a/.github/workflows/emu-pull-request.yml b/.github/workflows/emu-pull-request.yml index eb25a2c59..8652b90f5 100644 --- a/.github/workflows/emu-pull-request.yml +++ b/.github/workflows/emu-pull-request.yml @@ -24,6 +24,11 @@ jobs: if: ${{ !cancelled() }} uses: "./.github/workflows/emu-build-all-win.yml" + emu-win-all-vs22: + name: "win vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/emu-build-all-win-vs22.yml" + emu-linux-all: name: "linux" if: ${{ !cancelled() }} diff --git a/.github/workflows/migrate_gse-build-win-vs22.yml b/.github/workflows/migrate_gse-build-win-vs22.yml new file mode 100644 index 000000000..b54fa7e6a --- /dev/null +++ b/.github/workflows/migrate_gse-build-win-vs22.yml @@ -0,0 +1,68 @@ +name: "Build migrate_gse script (Windows)" + +on: + workflow_call: + # needed since it allows this to become a reusable workflow + workflow_dispatch: + # allows manual trigger + +permissions: + contents: "write" + +env: + ARTIFACT_NAME: "migrate_gse-win-vs22-${{ github.sha }}" + SCRIPT_BASE_DIR: "tools/migrate_gse" + PACKAGE_BASE_DIR: "tools/migrate_gse/bin/win" + + THIRD_PARTY_BASE_DIR: "third-party" + +jobs: + build: + runs-on: "windows-2022" + + steps: + - name: "Set up Python 3.12" + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + # on Windows Git will auto change line ending to CRLF, not preferable + - name: "Ensure LF line ending" + shell: "cmd" + working-directory: "${{ github.workspace }}" + run: | + git config --local core.autocrlf false + git config --system core.autocrlf false + git config --global core.autocrlf false + + - name: "Checkout branch" + uses: actions/checkout@v6 + + # extra helpers/tools, these are not built inside the deps build dir + - name: "Clone third-party deps (build/win)" + uses: actions/checkout@v6 + with: + ref: "third-party/build/win" + path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win" + + # env + - name: "Install env" + shell: cmd + working-directory: "${{ env.SCRIPT_BASE_DIR }}" + run: recreate_venv_win.bat + + # build + - name: "Rebuild" + shell: cmd + working-directory: "${{ env.SCRIPT_BASE_DIR }}" + run: rebuild_win.bat + + # upload artifact + - name: "Upload build package" + uses: actions/upload-artifact@v7 + with: + name: "${{ env.ARTIFACT_NAME }}" + path: "${{ env.PACKAGE_BASE_DIR }}/" + if-no-files-found: "error" + compression-level: 9 + retention-days: 1 diff --git a/.github/workflows/migrate_gse-pull-request.yml b/.github/workflows/migrate_gse-pull-request.yml index f4f113e74..68f555d80 100644 --- a/.github/workflows/migrate_gse-pull-request.yml +++ b/.github/workflows/migrate_gse-pull-request.yml @@ -16,6 +16,12 @@ jobs: if: ${{ !cancelled() }} uses: "./.github/workflows/migrate_gse-build-win.yml" + script-win-vs22: + name: "Migrate GSE win vs22" + if: ${{ !cancelled() }} + uses: "./.github/workflows/migrate_gse-build-win-vs22.yml" + + script-linux: name: "Migrate GSE linux" if: ${{ !cancelled() }} diff --git a/.gitmodules b/.gitmodules index 101d0a063..bc66e2d26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,12 +11,12 @@ [submodule "third-party/common/win"] path = third-party/common/win url = ./ - branch = third-party/common/win + branch = third-party/common/win [submodule "third-party/common/linux"] path = third-party/common/linux url = ./ - branch = third-party/common/linux + branch = third-party/common/linux [submodule "third-party/deps/win"] path = third-party/deps/win @@ -31,4 +31,4 @@ [submodule "third-party/deps/common"] path = third-party/deps/common url = ./ - branch = third-party/deps/common + branch = third-party/deps/common diff --git a/README.md b/README.md index bd940ebb5..4a3a050dc 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ You can find helper guides, scripts, and tools here: **(These guides, scripts, and tools are maintained by their authors.)** -* **[GBE Fork Tools](https://github.com/Detanup01/gbe_fork_tools)** +* **[gbe_fork_tools](https://github.com/Detanup01/gbe_fork_tools)** +* **[gen.emu.sharp](https://github.com/otavepto/gen.emu.sharp)** +* **[gse_fork_tools](https://github.com/alex47exe/gse_fork_tools)** * **[Semuexec](https://gitlab.com/detiam/Semuexec)** * **[Steam Emu Utility](https://github.com/turusudiro/SteamEmuUtility)** * **[How to use Goldberg Emulator](https://rentry.co/goldberg_emulator)** @@ -142,8 +144,8 @@ The only times you'll need to rebuild them is either when their separete build f Open CMD in the repo folder, then run the following * To build using `Visual Studio` ```batch - set "CMAKE_GENERATOR=Visual Studio 17 2022" - third-party\common\win\premake\premake5.exe --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=windows vs2022 + set "CMAKE_GENERATOR=Visual Studio 18 2026" + third-party\common\win\premake\premake5.exe --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=windows vs2026 ``` * To build using `MSYS2` **this is currently experimental and will not work due to ABI differences**
@@ -194,14 +196,14 @@ This will: Open CMD in the repo folder, then run the following * For `Visual Studio 2022` ```batch - third-party\common\win\premake\premake5.exe --file=premake5.lua --genproto --os=windows vs2022 + third-party\common\win\premake\premake5.exe --file=premake5.lua --genproto --os=windows vs2026 ``` - You can then go to the folder `build\project\vs2022\win` and open the produced `.sln` file in Visual Studio. - Or, if you prefer to do it from command line, open the `Developer Command Prompt for VS 2022` inside the above folder, then: + You can then go to the folder `build\project\vs2026\win` and open the produced `.sln` file in Visual Studio. + Or, if you prefer to do it from command line, open the `Developer Command Prompt for VS 2026` inside the above folder, then: ```batch - msbuild /nologo /v:n /p:Configuration=release,Platform=Win32 gbe.sln + msbuild /nologo /v:n /p:Configuration=release,Platform=Win32 gbe.slnx - msbuild /nologo /v:n /p:Configuration=release,Platform=x64 gbe.sln + msbuild /nologo /v:n /p:Configuration=release,Platform=x64 gbe.slnx ``` * For `MSYS2` **this is currently experimental and will not work due to ABI differences** @@ -310,7 +312,7 @@ Open CMD in the repos's directory, then run this script ```batch package_win.bat ``` -`build_folder` is any folder inside `build\win`, for example: `vs2022\release` +`build_folder` is any folder inside `build\win`, for example: `vs2026\release` The above example will create a `.7z` archive inside `build\package\win\` ### On Linux: diff --git a/build_linux_premake.sh b/build_linux_premake.sh index 4a22bc3af..15c6c9273 100644 --- a/build_linux_premake.sh +++ b/build_linux_premake.sh @@ -5,21 +5,22 @@ function help_page () { echo "switches:" echo " --deps: rebuild third-party dependencies" echo " --nogen: don't regenerate build files" + echo " --j: parallel build jobs" echo " --help: show this page" } -# use 70% -build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" -[[ $build_threads -lt 2 ]] && build_threads=2 - BUILD_DEPS=0 GEN_PROJECT=1 +BUILD_JOBS=-1 for (( i=1; i<=$#; ++i )); do arg="${!i}" if [[ "$arg" = "--deps" ]]; then BUILD_DEPS=1 elif [[ "$arg" = "--nogen" ]]; then GEN_PROJECT=0 + elif [[ "$arg" = "--j" ]]; then + BUILD_JOBS="$2" + shift 1 elif [[ "$arg" = "--help" ]]; then help_page exit 0 @@ -29,6 +30,11 @@ for (( i=1; i<=$#; ++i )); do fi done +# use 70% +build_threads="$(( $(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 0) * 70 / 100 ))" +[[ $build_threads -lt 2 ]] && build_threads=2 +[[ "$BUILD_JOBS" -ge 1 ]] && build_threads="$BUILD_JOBS" + premake_exe=./"third-party/common/linux/premake/premake5" if [[ ! -f "$premake_exe" ]]; then echo "preamke wasn't found" 1>&2 diff --git a/build_win_premake.bat b/build_win_premake.bat index 9968af8b9..85f49dbfb 100644 --- a/build_win_premake.bat +++ b/build_win_premake.bat @@ -2,17 +2,9 @@ setlocal EnableDelayedExpansion cd /d "%~dp0" -set /a "MAX_THREADS=2" -if defined NUMBER_OF_PROCESSORS ( - :: use 70% - set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" - if %MAX_THREADS% lss 1 ( - set /a "MAX_THREADS=1" - ) -) - set /a "BUILD_DEPS=0" set /a "GEN_PROJECT=1" +set /a "BUILD_JOBS=-1" :args_loop if "%~1" equ "" ( @@ -21,6 +13,9 @@ set /a "GEN_PROJECT=1" set /a "BUILD_DEPS=1" ) else if "%~1" equ "--nogen" ( set /a "GEN_PROJECT=0" + ) else if "%~1" equ "--j" ( + set /a "BUILD_JOBS=%~2" + shift /1 ) else if "%~1" equ "--help" ( goto :help_page ) else ( @@ -32,6 +27,18 @@ set /a "GEN_PROJECT=1" goto :args_loop :args_loop_end + set /a "MAX_THREADS=2" + if defined NUMBER_OF_PROCESSORS ( + :: use 70% + set /a "MAX_THREADS=%NUMBER_OF_PROCESSORS% * 70 / 100" + if %MAX_THREADS% lss 1 ( + set /a "MAX_THREADS=1" + ) + ) + if %BUILD_JOBS% geq 1 ( + set /a MAX_THREADS=BUILD_JOBS + ) + :: check premake set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe" if not exist "%PREMAKE_EXE%" ( @@ -41,8 +48,8 @@ set /a "GEN_PROJECT=1" :: build deps if %BUILD_DEPS% equ 1 ( - set "CMAKE_GENERATOR=Visual Studio 17 2022" - call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2022 || ( + set "CMAKE_GENERATOR=Visual Studio 18 2026" + call "%PREMAKE_EXE%" --file="premake5-deps.lua" --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=windows vs2026 || ( goto :end_script_with_err ) goto :end_script @@ -60,6 +67,14 @@ set /a "GEN_PROJECT=1" for /f "tokens=* delims=" %%A in ('"%VSWHERE_EXE%" -prerelease -latest -nocolor -nologo -property installationPath 2^>nul') do ( set "MSBUILD_EXE=%%~A\MSBuild\Current\Bin\MSBuild.exe" ) + :: in case we are running inside a vcvars cmd from a portable buildtools installation without Visual Studio + if not exist "%MSBUILD_EXE%" ( + for /f "tokens=* delims=" %%A in ('where MSBuild 2^>nul') do ( + set "MSBUILD_EXE=%%~A" + goto :end_find_custom_msbuild + ) + ) +:end_find_custom_msbuild if not exist "%MSBUILD_EXE%" ( 1>&2 echo:MSBuild wasn't found goto :end_script_with_err @@ -69,13 +84,13 @@ set /a "GEN_PROJECT=1" if %GEN_PROJECT% equ 0 ( goto :gen_project_end ) - call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2022 || ( + call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2026 || ( goto :end_script_with_err ) :gen_project_end :: check .sln - set "SLN_FILE=build\project\vs2022\win\gbe.sln" + set "SLN_FILE=build\project\vs2026\win\gbe.slnx" if not exist "%SLN_FILE%" ( 1>&2 echo:.sln file wasn't found goto :end_script_with_err @@ -93,7 +108,7 @@ set /a "GEN_PROJECT=1" for %%C in (%BUILD_TARGETS%) do ( set "BUILD_TARGET=%%C" echo. & echo:building !BUILD_TARGET! !BUILD_TYPE! !BUILD_PLATFORM! - call "%MSBUILD_EXE%" /nologo -m:%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( + call "%MSBUILD_EXE%" /nologo -m:1 -p:CL_MPCount=%MAX_THREADS% -v:n /p:Configuration=!BUILD_TYPE!,Platform=!BUILD_PLATFORM! /target:!BUILD_TARGET! "%SLN_FILE%" || ( goto :end_script_with_err ) ) @@ -116,5 +131,6 @@ set /a "GEN_PROJECT=1" echo:switches: echo: --deps: rebuild third-party dependencies echo: --nogen: don't regenerate build files + echo: --j: parallel build jobs echo: --help: show this page goto :end_script diff --git a/dll/appticket.cpp b/dll/appticket.cpp index cccbbbb5b..4f8a760ce 100644 --- a/dll/appticket.cpp +++ b/dll/appticket.cpp @@ -259,8 +259,32 @@ Steam_AppTicket::Steam_AppTicket(class Settings *settings) : uint32 Steam_AppTicket::GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature ) { - PRINT_DEBUG("TODO %u, %p, %u, %p, %p, %p, %p", nAppID, pvBuffer, cbBufferLength, piAppId, piSteamId, piSignature, pcbSignature); + PRINT_DEBUG("%u, %p, %u, %p, %p, %p, %p", nAppID, pvBuffer, cbBufferLength, piAppId, piSteamId, piSignature, pcbSignature); std::lock_guard lock(global_mutex); + if (pvBuffer != nullptr && cbBufferLength >= 24) { + *reinterpret_cast(pvBuffer) = nAppID; + *reinterpret_cast(reinterpret_cast(pvBuffer) + 8) = settings->get_current_steam_id().ConvertToUint64(); + *reinterpret_cast(reinterpret_cast(pvBuffer) + 16) = 0; + + if (piAppId != nullptr) { + *piAppId = 0; + } + + if (piSteamId != nullptr) { + *piSteamId = 8; + } + + if (piSignature != nullptr) { + *piSignature = 16; + } + + if (pcbSignature != nullptr) { + *pcbSignature = 8; + } + + return 24; + } + return 0; } diff --git a/dll/auth.cpp b/dll/auth.cpp index b6b27cbd7..efcaeb85d 100644 --- a/dll/auth.cpp +++ b/dll/auth.cpp @@ -1,6 +1,24 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #include "dll/auth.h" -#define STEAM_TICKET_PROCESS_TIME 0.03 +// this should be at least as long as SteamGameServer logon time +#define STEAM_TICKET_PROCESS_TIME 0.15 static inline int generate_random_int() { @@ -691,18 +709,18 @@ void Auth_Manager::launch_callback_gs_steam2(CSteamID steam_id, uint32_t user_id data3.m_SteamID = data3.m_OwnerSteamID = steam_id; callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME * 2.0); } else { - /* + // Fire Steam2 callback. Only one of these is actually needed to kick the client. GSClientSteam2Deny_t data2{}; data2.m_UserID = user_id; - data2.m_eSteamError = k_EDenyNotLoggedOn; + data2.m_eSteamError = k_EDenyNotLoggedOn; //TODO: other reasons? callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2), STEAM_TICKET_PROCESS_TIME); - */ - // Fire Steam3 callback. Only one of these is actually needed to kick the client. + /* GSClientDeny_t data3{}; data3.m_SteamID = steam_id; - data3.m_eDenyReason = k_EDenyNotLoggedOn; //TODO: other reasons? - callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME); + data3.m_eDenyReason = k_EDenyNotLoggedOn; + callbacks->addCBResult(data3.k_iCallback, &data3, sizeof(data3), STEAM_TICKET_PROCESS_TIME * 2.0); + */ } } @@ -863,7 +881,7 @@ HAuthTicket Auth_Manager::getWebApiTicket( const char* pchIdentity ) CSteamID Auth_Manager::fakeUser() { Auth_Data data = {}; - data.id = generate_steam_anon_user(); + data.id = generate_steam_id_anonserver(); // not k_EAccountTypeAnonUser inbound.push_back(data); return data.id; } diff --git a/dll/callsystem.cpp b/dll/callsystem.cpp index 78478259e..1b7d50ea8 100644 --- a/dll/callsystem.cpp +++ b/dll/callsystem.cpp @@ -220,6 +220,11 @@ void SteamCallResults::setCbAll(void (*cb_all)(std::vector result, int cal this->cb_all = cb_all; } +void SteamCallResults::clear() +{ + callresults.clear(); +} + void SteamCallResults::runCallResults() { unsigned long current_size = static_cast(callresults.size()); diff --git a/dll/client_known_interfaces.cpp b/dll/client_known_interfaces.cpp index f1d92b303..a67790f46 100644 --- a/dll/client_known_interfaces.cpp +++ b/dll/client_known_interfaces.cpp @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #include "dll/client_known_interfaces.h" /* @@ -25,6 +42,7 @@ extern const std::unordered_set client_known_interfaces = { "STEAMAPPS_INTERFACE_VERSION006", "STEAMAPPS_INTERFACE_VERSION007", "STEAMAPPS_INTERFACE_VERSION008", + "STEAMAPPS_INTERFACE_VERSION009", "STEAMAPPTICKET_INTERFACE_VERSION001", "SteamBilling002", "STEAMCHAT_INTERFACE_VERSION003", @@ -135,6 +153,7 @@ extern const std::unordered_set client_known_interfaces = { "STEAMREMOTEPLAY_INTERFACE_VERSION001", "STEAMREMOTEPLAY_INTERFACE_VERSION002", "STEAMREMOTEPLAY_INTERFACE_VERSION003", + "STEAMREMOTEPLAY_INTERFACE_VERSION004", "STEAMREMOTESTORAGE_INTERFACE_VERSION001", "STEAMREMOTESTORAGE_INTERFACE_VERSION002", "STEAMREMOTESTORAGE_INTERFACE_VERSION003", diff --git a/dll/dll.cpp b/dll/dll.cpp index 9ac937e01..daa6ccce5 100644 --- a/dll/dll.cpp +++ b/dll/dll.cpp @@ -211,14 +211,14 @@ static void load_old_steam_interfaces() reset_LastError(); } -//steam_api_internal.h -STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser() +inline int get_old_interface_ver(const char *interface_name) { - PRINT_DEBUG_ENTRY(); - if (!get_steam_client()->IsUserLogIn()) return 0; - return CLIENT_HSTEAMUSER; -} + size_t len = strlen(interface_name); + if (len < 3) + return 0; + return atoi(interface_name + (len - 3)); +} // declare "g_pSteamClientGameServer" as an export for API library, then actually define it #if !defined(STEAMCLIENT_DLL) // api @@ -235,6 +235,11 @@ Steam_Client *get_steam_client() if (!steamclient_instance) { load_old_steam_interfaces(); steamclient_instance = new Steam_Client(); + + int friends_ver = get_old_interface_ver(old_friends); + if (friends_ver != 0 && friends_ver <= 4) { + steamclient_instance->using_old_callbacks = true; + } } } @@ -319,7 +324,7 @@ static void *create_client_interface(const char *ver) return static_cast(client_ptr); } else if (strcmp(ver, "SteamClient021") == 0) { return static_cast(client_ptr); - } else if (strcmp(ver, "SteamClient022") == 0) { + } else if (strcmp(ver, "SteamClient022") == 0) { // not public, based on reversing return static_cast(client_ptr); } else if (strcmp(ver, STEAMCLIENT_INTERFACE_VERSION) == 0) { return static_cast(client_ptr); @@ -456,7 +461,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_Shutdown() return; } - get_steam_client()->clientShutdown(); + get_steam_client()->ReleaseUser(user_steam_pipe, CLIENT_HSTEAMUSER); get_steam_client()->BReleaseSteamPipe(user_steam_pipe); get_steam_client()->BShutdownIfAllPipesClosed(); @@ -681,6 +686,8 @@ STEAMAPI_API void Steam_RegisterInterfaceFuncs( void *hModule ) PRINT_DEBUG_TODO(); } +STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser(); + // returns the HSteamUser of the last user to dispatch a callback STEAMAPI_API HSteamUser Steam_GetHSteamUserCurrent() { @@ -707,6 +714,14 @@ STEAMAPI_API const char *SteamAPI_GetSteamInstallPath() return steam_folder; } +//steam_api_internal.h +STEAMAPI_API HSteamUser SteamAPI_GetHSteamUser() +{ + PRINT_DEBUG_ENTRY(); + if (!steamclient_instance || !get_steam_client()->IsUserLogIn()) return 0; + return CLIENT_HSTEAMUSER; +} + // returns the pipe we are communicating to Steam with STEAMAPI_API HSteamPipe SteamAPI_GetHSteamPipe() { @@ -750,7 +765,7 @@ STEAMAPI_API ISteamClient *SteamClient() return (ISteamClient *)SteamInternal_CreateInterface(old_client); } -#define CACHE_OLDSTEAM_INSTANCE(variable, get_func) { if (variable) return variable; else return variable = (get_func); } +#define CACHE_OLDSTEAM_INSTANCE(variable, get_func) { if (!steamclient_instance) return nullptr; if (variable) return variable; else return variable = (get_func); } STEAMAPI_API ISteamUser *SteamUser() { @@ -953,42 +968,200 @@ STEAMAPI_API HSteamPipe S_CALLTYPE SteamGameServer_GetHSteamPipe() STEAMAPI_API HSteamUser S_CALLTYPE SteamGameServer_GetHSteamUser() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return 0; if (!get_steam_client()->IsServerInit()) return 0; return SERVER_HSTEAMUSER; } -//See: SteamGameServer_Init -//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe(uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) -STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 unknown, EServerMode eServerMode, void *unknown1, void *unknown2, void *unknown3 ) +static bool is_nosteam_server{}; + +STEAMAPI_API ISteamClient *SteamGameServerClient(); + +// 0.99u: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); +// 0.99v-1.00: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); +// 1.01-1.16: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ); +// 1.16x-1.50: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); +// 1.51 and later: +//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); +STEAMAPI_API steam_bool SteamGameServer_Init( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8, void *a9 ) { PRINT_DEBUG_ENTRY(); - const char *pchVersionString{}; - EServerMode serverMode{}; - uint16 usQueryPort{}; + // call this first since it loads old interfaces - Steam_Client* client = get_steam_client(); - bool logon_anon = false; - if (strcmp(old_gameserver, "SteamGameServer010") == 0 || strstr(old_gameserver, "SteamGameServer00") == old_gameserver) { - PRINT_DEBUG("Old game server init safe"); - pchVersionString = (char *)unknown3; - memcpy(&serverMode, &unknown1, sizeof(serverMode)); - memcpy(&usQueryPort, (char *)&eServerMode, sizeof(usQueryPort)); - logon_anon = true; - } else { - pchVersionString = (char *)unknown1; - serverMode = eServerMode; - usQueryPort = unknown; + Steam_Client *client = get_steam_client(); + if (!server_steam_pipe) { + client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); + sdk_lifetime_counters.update(true); + //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) + g_pSteamClientGameServer = SteamGameServerClient(); } - bool ret = SteamInternal_GameServer_Init( unIP, usSteamPort, usGamePort, usQueryPort, serverMode, pchVersionString ); - if (logon_anon) { - client->steam_gameserver->LogOnAnonymous(); + is_nosteam_server = false; + + int gameserver_version = get_old_interface_ver(old_gameserver); + int user_version = get_old_interface_ver(old_user); + bool has_ms_updater = (settings_old_interfaces().count(SettingsItf::MASTERSERVER_UPDATER) != 0); + + if (gameserver_version < 2 || user_version < 4) + return false; + + bool ret = false; + + if (gameserver_version == 2 && !has_ms_updater) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + EServerMode eServerMode; + int nGameAppId; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&eServerMode, &a4, sizeof(eServerMode)); + memcpy(&nGameAppId, &a5, sizeof(nGameAppId)); + memcpy(&pchGameDir, &a6, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a7, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + + if (!bLANMode) { + client->steam_gameserver->LogOnAnonymous(); + ret = client->steam_gameserver->GSSetServerType(nGameAppId, unFlags, unIP, usGamePort, pchGameDir, pchVersionString); + } else { + is_nosteam_server = true; + ret = true; + } + } else if (gameserver_version <= 4) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + uint16 usSpectatorPort; + uint16 usQueryPort; + EServerMode eServerMode; + int nGameAppId; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usSpectatorPort, &a4, sizeof(usSpectatorPort)); + memcpy(&usQueryPort, &a5, sizeof(usQueryPort)); + memcpy(&eServerMode, &a6, sizeof(eServerMode)); + memcpy(&nGameAppId, &a7, sizeof(nGameAppId)); + memcpy(&pchGameDir, &a8, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a9, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + + if (user_version >= 6 || !bLANMode) { + client->steam_gameserver->LogOnAnonymous(); + } else { + is_nosteam_server = true; + } + + ret = client->steam_gameserver->BSetServerType(nGameAppId, unFlags, unIP, usGamePort, usSpectatorPort, usQueryPort, pchGameDir, pchVersionString, bLANMode); + } else if (gameserver_version <= 10) { + uint32 unIP; + uint16 usPort; + uint16 usGamePort; + uint16 usSpectatorPort; + uint16 usQueryPort; + EServerMode eServerMode; + const char *pchGameDir; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usPort, &a2, sizeof(usPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usSpectatorPort, &a4, sizeof(usSpectatorPort)); + memcpy(&usQueryPort, &a5, sizeof(usQueryPort)); + memcpy(&eServerMode, &a6, sizeof(eServerMode)); + memcpy(&pchGameDir, &a7, sizeof(pchGameDir)); + memcpy(&pchVersionString, &a8, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + bool bLANMode = (eServerMode == eServerModeNoAuthentication); + + ret = client->steam_gameserver->BSetServerType(unFlags, unIP, usGamePort, usSpectatorPort, usQueryPort, pchGameDir, pchVersionString, bLANMode); + + if (ret) { + client->steam_gameserver->LogOnAnonymous(); + } + } else if (gameserver_version <= 12) { // export was removed in 1.37 + uint32 unIP; + uint16 usSteamPort; + uint16 usGamePort; + uint16 usQueryPort; + EServerMode eServerMode; + const char *pchVersionString; + + memcpy(&unIP, &a1, sizeof(unIP)); + memcpy(&usSteamPort, &a2, sizeof(usSteamPort)); + memcpy(&usGamePort, &a3, sizeof(usGamePort)); + memcpy(&usQueryPort, &a4, sizeof(usQueryPort)); + memcpy(&eServerMode, &a5, sizeof(eServerMode)); + memcpy(&pchVersionString, &a6, sizeof(pchVersionString)); + + uint32 unFlags = 0; + if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + + int nGameAppId = client->settings_server->get_local_game_id().AppID(); + + if (nGameAppId != 0) { + ret = client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); + } else { + PRINT_DEBUG("error appid is not set"); + } } return ret; } -STEAMAPI_API ISteamClient *SteamGameServerClient(); +// See SteamGameServer_Init +//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ) +//STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) +STEAMAPI_API steam_bool S_CALLTYPE SteamGameServer_InitSafe( void *a1, void *a2, void *a3, void *a4, void *a5, void *a6, void *a7, void *a8 ) +{ + PRINT_DEBUG_ENTRY(); + + // call this first since it loads old interfaces + Steam_Client *client = get_steam_client(); + if (!server_steam_pipe) { + client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); + sdk_lifetime_counters.update(true); + //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) + g_pSteamClientGameServer = SteamGameServerClient(); + } + + int gameserver_version = get_old_interface_ver(old_gameserver); + if (gameserver_version < 5) // export didn't exist before 1.01 + return false; + + bool ret = false; + + if (gameserver_version <= 10) { + ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, a7, a8, 0); + } else if (gameserver_version <= 12) { // export was removed in 1.37 + ret = SteamGameServer_Init(a1, a2, a3, a4, a5, a6, 0, 0, 0); + } + + return ret; +} STEAMAPI_API steam_bool S_CALLTYPE SteamInternal_GameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) { @@ -998,13 +1171,20 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamInternal_GameServer_Init( uint32 unIP, u if (!server_steam_pipe) { client->CreateLocalUser(&server_steam_pipe, k_EAccountTypeGameServer); sdk_lifetime_counters.update(true); - //g_pSteamClientGameServer is only used in pre 1.37 (where the interface versions are not provided by the game) g_pSteamClientGameServer = SteamGameServerClient(); } uint32 unFlags = 0; if (eServerMode == eServerModeAuthenticationAndSecure) unFlags = k_unServerFlagSecure; - return client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, 0, pchVersionString); + else if (eServerMode == eServerModeNoAuthentication) unFlags = k_unServerFlagPrivate; + + int nGameAppId = client->settings_server->get_local_game_id().AppID(); + if (nGameAppId == 0) { + PRINT_DEBUG("error appid is not set"); + return false; + } + + return client->steam_gameserver->InitGameServer(unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); } STEAMAPI_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, const char *pszInternalCheckInterfaceVersions, SteamErrMsg *pOutErrMsg ) @@ -1021,43 +1201,6 @@ STEAMAPI_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( ui return ESteamAPIInitResult::k_ESteamAPIInitResult_FailedGeneric; } -//SteamGameServer004 and before: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, int nGameAppId, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer010 and before: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usSpectatorPort, uint16 usQueryPort, EServerMode eServerMode, const char *pchGameDir, const char *pchVersionString ); -//SteamGameServer011 and later: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -//SteamGameServer015 and later: -//STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -STEAMAPI_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 unknown, EServerMode eServerMode, void *unknown1, void *unknown2, void *unknown3 ) -{ - PRINT_DEBUG_ENTRY(); - const char *pchVersionString{}; - EServerMode serverMode{}; - uint16 usQueryPort{}; - // call this first since it loads old interfaces - Steam_Client* client = get_steam_client(); - bool logon_anon = false; - if (strcmp(old_gameserver, "SteamGameServer010") == 0 || strstr(old_gameserver, "SteamGameServer00") == old_gameserver) { - PRINT_DEBUG("Old game server init"); - pchVersionString = (char *)unknown3; - memcpy(&serverMode, &unknown1, sizeof(serverMode)); - memcpy(&usQueryPort, (char *)&eServerMode, sizeof(usQueryPort)); - logon_anon = true; - } else { - pchVersionString = (char *)unknown1; - serverMode = eServerMode; - usQueryPort = unknown; - } - - bool ret = SteamInternal_GameServer_Init( unIP, usSteamPort, usGamePort, usQueryPort, serverMode, pchVersionString ); - if (logon_anon) { - client->steam_gameserver->LogOnAnonymous(); - } - - return ret; -} - STEAMAPI_API void SteamGameServer_Shutdown() { PRINT_DEBUG_ENTRY(); @@ -1075,7 +1218,7 @@ STEAMAPI_API void SteamGameServer_Shutdown() return; } - get_steam_client()->serverShutdown(); + get_steam_client()->ReleaseUser(server_steam_pipe, SERVER_HSTEAMUSER); get_steam_client()->BReleaseSteamPipe(server_steam_pipe); get_steam_client()->BShutdownIfAllPipesClosed(); @@ -1100,18 +1243,20 @@ STEAMAPI_API void SteamGameServer_Shutdown() STEAMAPI_API void SteamGameServer_RunCallbacks() { // PRINT_DEBUG_ENTRY(); - get_steam_client()->RunCallbacks(false, true); + get_steam_client()->RunCallbacks(false, !is_nosteam_server); } STEAMAPI_API steam_bool SteamGameServer_BSecure() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return false; return get_steam_client()->steam_gameserver->BSecure(); } STEAMAPI_API uint64 SteamGameServer_GetSteamID() { PRINT_DEBUG_ENTRY(); + if (!steamclient_instance) return 0; return get_steam_client()->steam_gameserver->GetSteamID().ConvertToUint64(); } @@ -1124,6 +1269,7 @@ STEAMAPI_API ISteamClient *SteamGameServerClient() STEAMAPI_API uint32 SteamGameServer_GetIPCCallCount() { + if (!steamclient_instance) return 0; return get_steam_client()->GetIPCCallCount(); } @@ -1187,9 +1333,9 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteam return; } - if (it->second == Steam_Pipe::SERVER) { + if (it->second.type == Steam_Pipe_Type::SERVER) { steam_client->RunCallbacks(false, true); - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { steam_client->RunCallbacks(true, false); } } @@ -1201,7 +1347,8 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte { PRINT_DEBUG("%i %p", hSteamPipe, pCallbackMsg); Steam_Client *steam_client = get_steam_client(); - if (!steam_client->steamclient_server_inited) { + steam_client->RunCallbacks(true, true); + if (!steam_client->IsServerInit()) { while(!server_cb.empty()) server_cb.pop(); } @@ -1213,10 +1360,16 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte std::queue *q = NULL; HSteamUser m_hSteamUser = 0; - if (it->second == Steam_Pipe::SERVER) { + + if (it->second.got_last_cb) { + PRINT_DEBUG("last cb not freed on pipe %d!", hSteamPipe); + return false; + } + + if (it->second.type == Steam_Pipe_Type::SERVER) { q = &server_cb; m_hSteamUser = SERVER_HSTEAMUSER; - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { q = &client_cb; m_hSteamUser = CLIENT_HSTEAMUSER; } else { @@ -1235,6 +1388,7 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSte pCallbackMsg->m_pubParam = (uint8 *)&(q->front().result[0]); pCallbackMsg->m_cubParam = static_cast(q->front().result.size()); PRINT_DEBUG("cb number %i", q->front().cb_id); + it->second.got_last_cb = true; return true; } @@ -1253,15 +1407,17 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPip return; } - if (it->second == Steam_Pipe::SERVER) { + if (it->second.type == Steam_Pipe_Type::SERVER) { q = &server_cb; - } else if (it->second == Steam_Pipe::CLIENT) { + } else if (it->second.type == Steam_Pipe_Type::CLIENT) { q = &client_cb; } else { return; } if (!q->empty()) q->pop(); + + it->second.got_last_cb = false; } /// Return the call result for the specified call on the specified pipe. You really should @@ -1274,9 +1430,9 @@ STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSt return false; } - if (steam_client->steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_client->steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { return steam_client->steam_gameserver_utils->GetAPICallResult(hSteamAPICall, pCallback, cubCallback, iCallbackExpected, pbFailed); - } else if (steam_client->steam_pipes[hSteamPipe] == Steam_Pipe::CLIENT) { + } else if (steam_client->steam_pipes[hSteamPipe].type == Steam_Pipe_Type::CLIENT) { return steam_client->steam_utils->GetAPICallResult(hSteamAPICall, pCallback, cubCallback, iCallbackExpected, pbFailed); } else { return false; @@ -1402,29 +1558,27 @@ SteamMasterServerUpdater */ -STEAMCLIENT_API steam_bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) +bool steamclient_get_callback(HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg) { - PRINT_DEBUG("%i", hSteamPipe); SteamAPI_ManualDispatch_Init(); - Steam_Client *steam_client = get_steam_client(); - steam_client->RunCallbacks(true, true); - return SteamAPI_ManualDispatch_GetNextCallback( hSteamPipe, pCallbackMsg ); + return SteamAPI_ManualDispatch_GetNextCallback(hSteamPipe, pCallbackMsg); } -STEAMCLIENT_API void Steam_FreeLastCallback( HSteamPipe hSteamPipe ) +void steamclient_free_callback(HSteamPipe hSteamPipe) { - //PRINT_DEBUG("%i", hSteamPipe); - SteamAPI_ManualDispatch_FreeLastCallback( hSteamPipe ); + SteamAPI_ManualDispatch_FreeLastCallback(hSteamPipe); } -bool steamclient_get_callback(HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg) +STEAMCLIENT_API steam_bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg ) { - return Steam_BGetCallback(hSteamPipe, pCallbackMsg); + PRINT_DEBUG("%i", hSteamPipe); + return steamclient_get_callback(hSteamPipe, pCallbackMsg); } -void steamclient_free_callback(HSteamPipe hSteamPipe) +STEAMCLIENT_API void Steam_FreeLastCallback( HSteamPipe hSteamPipe ) { - return Steam_FreeLastCallback(hSteamPipe); + //PRINT_DEBUG("%i", hSteamPipe); + steamclient_free_callback(hSteamPipe); } STEAMCLIENT_API steam_bool Steam_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void* pCallback, int cubCallback, int iCallbackExpected, bool* pbFailed ) @@ -1755,13 +1909,7 @@ STEAMCLIENT_API void Steam_TerminateGameConnection( HSteamUser hUser, HSteamPipe } // older sdk ---------------------------------------------------------- -STEAMCLIENT_API HSteamUser Steam3_ConnectToGlobalInstance() -{ - PRINT_DEBUG_ENTRY(); - get_steam_client()->steamclient_version = 4; - return get_steam_client()->ConnectToGlobalInstance(); -} - +// SteamClient002 ----------------------------------------------------- STEAMCLIENT_API HSteamUser Steam3_CreateLocalInstance() { PRINT_DEBUG_ENTRY(); @@ -1790,7 +1938,7 @@ STEAMCLIENT_API void Steam3_LogOn( HSteamUser hUser, uint64 steamID ) ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - su->LogOn(&steam_id_struct); + su->LogOn_old(steam_id_struct); } STEAMCLIENT_API void Steam3_LogOff( HSteamUser hUser ) @@ -1831,7 +1979,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendLogonRequest( HSteamUser hUser, uint64 s ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendLogonRequest(&steam_id_struct); + return su->GSSendLogonRequest(steam_id_struct); } STEAMCLIENT_API steam_bool Steam3_GSSendDisconnect( HSteamUser hUser, uint64 steamID ) @@ -1842,7 +1990,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendDisconnect( HSteamUser hUser, uint64 ste ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendDisconnect(&steam_id_struct); + return su->GSSendDisconnect(steam_id_struct); } STEAMCLIENT_API steam_bool Steam3_GSSendStatusResponse( HSteamUser hUser, uint64 steamID, int nSecondsConnected, int nSecondsSinceLast ) @@ -1853,7 +2001,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendStatusResponse( HSteamUser hUser, uint64 ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); CSteamID steam_id_struct(steamID); - return su->GSSendStatusResponse(&steam_id_struct, nSecondsConnected, nSecondsSinceLast); + return su->GSSendStatusResponse(steam_id_struct, nSecondsConnected, nSecondsSinceLast); } STEAMCLIENT_API steam_bool Steam3_GSSetStatus( HSteamUser hUser, int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) @@ -1871,24 +2019,33 @@ STEAMCLIENT_API steam_bool Steam3_GSSetStatus( HSteamUser hUser, int32 nAppIdSer return gs->Obsolete_GSSetStatus(nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, pchServerName, pchGameDir, pchMapName, pchVersion); } -static std::map capi_cmcallback_map; +static std::map capi_cmcallback_map; STEAMCLIENT_API void Steam3_Init( HSteamUser hUser, OnLogonSuccessFunc func1, OnLogonFailureFunc func2, OnLoggedOffFunc func3, OnBeginLogonRetryFunc func4, - HandleVACChallengeFunc func5, - GSHandleClientApproveFunc func6, - GSHandleClientDenyFunc func7, - GSHandleClientKickFunc func8 ) + GSHandleClientApproveFunc func5, + GSHandleClientDenyFunc func6, + GSHandleClientKickFunc func7, + Steam2GetValueFunc func8 ) { PRINT_DEBUG_ENTRY(); get_steam_client()->steamclient_version = 2; ISteamUser002 *su = reinterpret_cast(get_steam_client()->GetISteamUser(hUser, "SteamUser002")); - CCAPICMCallBack new_cmcallback(func1, func2, func3, func4, func5, func6, func7, func8); + CAPI_CMCallback new_cmcallback(func1, func2, func3, func4, func5, func6, func7, func8); auto [it, _] = capi_cmcallback_map.insert_or_assign(hUser, new_cmcallback); - su->Init(&it->second, nullptr); + su->Init(static_cast(&it->second), static_cast(&it->second)); +} +// SteamClient002 ----------------------------------------------------- + +// SteamClient004 ----------------------------------------------------- +STEAMCLIENT_API HSteamUser Steam3_ConnectToGlobalInstance() +{ + PRINT_DEBUG_ENTRY(); + get_steam_client()->steamclient_version = 4; + return get_steam_client()->ConnectToGlobalInstance(); } STEAMCLIENT_API steam_bool Steam3_GSSendUserConnect( HSteamUser hUser, uint64 steamID, uint32 unIPPublic, uint32 unk ) @@ -1912,7 +2069,7 @@ STEAMCLIENT_API steam_bool Steam3_GSSendUserStatusResponse( HSteamUser hUser, ui return gs->GSSendUserStatusResponse(steamID, nSecondsConnected, nSecondsSinceLast); } -STEAMCLIENT_API steam_bool Steam3_BGetCallback( HSteamUser hUser, int *piCallback, uint8_t **ppubParam, int *unk ) +STEAMCLIENT_API steam_bool Steam3_BGetCallback( HSteamUser hUser, int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); if (get_steam_client()->steamclient_version != 4) @@ -1947,4 +2104,5 @@ STEAMCLIENT_API void Steam3_SetLocalIPBinding( uint32 unIP, uint16 usLocalPort ) PRINT_DEBUG_ENTRY(); get_steam_client()->SetLocalIPBinding(unIP, usLocalPort); } +// SteamClient004 ----------------------------------------------------- // older sdk ---------------------------------------------------------- diff --git a/dll/dll/auth.h b/dll/dll/auth.h index 0a15b72b6..663ef4184 100644 --- a/dll/dll/auth.h +++ b/dll/dll/auth.h @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + // source: https://github.com/Detanup01/stmsrv/blob/main/Steam3Server/Others/AppTickets.cs // thanks Detanup01 diff --git a/dll/dll/callback_wrapper.h b/dll/dll/callback_wrapper.h new file mode 100644 index 000000000..5b7463507 --- /dev/null +++ b/dll/dll/callback_wrapper.h @@ -0,0 +1,70 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#ifndef __INCLUDED_CALLBACK_WRAPPER_H__ +#define __INCLUDED_CALLBACK_WRAPPER_H__ + +#include "base.h" + +class CCallbackBase001 +{ +public: + CCallbackBase001() { m_nCallbackFlags = 0; m_iCallback = 0; } + // don't add a virtual destructor because we export this binary interface across dll's + virtual void Run( void *pvParam ) = 0; + int GetICallback() { return m_iCallback; } + +protected: + enum { k_ECallbackFlagsRegistered = 0x01, k_ECallbackFlagsGameServer = 0x02 }; + uint8 m_nCallbackFlags; + int m_iCallback; + friend class CCallbackMgr; + friend class CCallBackWrapper; + +private: + CCallbackBase001( const CCallbackBase001& ); + CCallbackBase001& operator=( const CCallbackBase001& ); +}; + +class CCallBackWrapper : public CCallbackBase +{ +public: + CCallBackWrapper( CCallbackBase *callback ) + { + old_callback = reinterpret_cast( callback ); + m_nCallbackFlags = old_callback->m_nCallbackFlags; + m_iCallback = old_callback->m_iCallback; + } + + void Run( void *pvParam ) + { + old_callback->Run( pvParam ); + } + void Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall ) + { + old_callback->Run( pvParam ); + } + int GetCallbackSizeBytes() + { + return 0; + } + +private: + CCallbackBase001 *old_callback{}; +}; + +#endif // __INCLUDED_CALLBACK_WRAPPER_H__ diff --git a/dll/dll/callsystem.h b/dll/dll/callsystem.h index 955867c1d..095d19e38 100644 --- a/dll/dll/callsystem.h +++ b/dll/dll/callsystem.h @@ -87,6 +87,8 @@ class SteamCallResults { void setCbAll(void (*cb_all)(std::vector result, int callback)); + void clear(); + void runCallResults(); }; diff --git a/dll/dll/capicmcallback.h b/dll/dll/capicmcallback.h index 5a8dd5be5..5a9d43b46 100644 --- a/dll/dll/capicmcallback.h +++ b/dll/dll/capicmcallback.h @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #ifndef __INCLUDED_CAPICMCALLBACK_H__ #define __INCLUDED_CAPICMCALLBACK_H__ @@ -5,54 +22,56 @@ typedef void (*OnLogonSuccessFunc)(); typedef void (*OnLogonFailureFunc)(EResult); -typedef void (*OnLoggedOffFunc)(); +typedef void (*OnLoggedOffFunc)(EResult); typedef void (*OnBeginLogonRetryFunc)(); -typedef void (*HandleVACChallengeFunc)(int, void *, int); -typedef void (*GSHandleClientApproveFunc)(CSteamID *); -typedef void (*GSHandleClientDenyFunc)(CSteamID *, EDenyReason); -typedef void (*GSHandleClientKickFunc)(CSteamID *, EDenyReason); +typedef void (*GSHandleClientApproveFunc)(uint64); +typedef void (*GSHandleClientDenyFunc)(uint64, EDenyReason); +typedef void (*GSHandleClientKickFunc)(uint64, EDenyReason); +typedef int (*Steam2GetValueFunc)(const char *, char *, int); -class CCAPICMCallBack : public ICMCallback +class CAPI_CMCallback : public ICMCallback, public ISteam2Auth { + OnLogonSuccessFunc OnLogonSuccess_ptr{}; + OnLogonFailureFunc OnLogonFailure_ptr{}; + OnLoggedOffFunc OnLoggedOff_ptr{}; + OnBeginLogonRetryFunc OnBeginLogonRetry_ptr{}; + GSHandleClientApproveFunc GSHandleClientApprove_ptr{}; + GSHandleClientDenyFunc GSHandleClientDeny_ptr{}; + GSHandleClientKickFunc GSHandleClientKick_ptr{}; + Steam2GetValueFunc GetValue_ptr{}; + public: - CCAPICMCallBack(OnLogonSuccessFunc func1, + CAPI_CMCallback(OnLogonSuccessFunc func1, OnLogonFailureFunc func2, OnLoggedOffFunc func3, OnBeginLogonRetryFunc func4, - HandleVACChallengeFunc func5, - GSHandleClientApproveFunc func6, - GSHandleClientDenyFunc func7, - GSHandleClientKickFunc func8) + GSHandleClientApproveFunc func5, + GSHandleClientDenyFunc func6, + GSHandleClientKickFunc func7, + Steam2GetValueFunc func8) { OnLogonSuccess_ptr = func1; OnLogonFailure_ptr = func2; OnLoggedOff_ptr = func3; OnBeginLogonRetry_ptr = func4; - HandleVACChallenge_ptr = func5; - GSHandleClientApprove_ptr = func6; - GSHandleClientDeny_ptr = func7; - GSHandleClientKick_ptr = func8; + GSHandleClientApprove_ptr = func5; + GSHandleClientDeny_ptr = func6; + GSHandleClientKick_ptr = func7; + GetValue_ptr = func8; } - ~CCAPICMCallBack() {} + ~CAPI_CMCallback() {} void OnLogonSuccess() { OnLogonSuccess_ptr(); } - void OnLogonFailure(EResult result) { OnLogonFailure_ptr(result); } - void OnLoggedOff() { OnLoggedOff_ptr(); } + void OnLogonFailure(EResult eResult) { OnLogonFailure_ptr(eResult); } + void OnLoggedOff(EResult eResult) { OnLoggedOff_ptr(eResult); } void OnBeginLogonRetry() { OnBeginLogonRetry_ptr(); } - void HandleVACChallenge(int unk1, void *unk2, int unk3) { HandleVACChallenge_ptr(unk1, unk2, unk3); } - void GSHandleClientApprove(CSteamID *steamID) { GSHandleClientApprove_ptr(steamID); } - void GSHandleClientDeny(CSteamID *steamID, EDenyReason reason) { GSHandleClientDeny_ptr(steamID, reason); } - void GSHandleClientKick(CSteamID *steamID, EDenyReason reason) { GSHandleClientKick_ptr(steamID, reason); } + void HandleVACChallenge(int nClientGameID, uint8 *pubChallenge, int cubChallenge) {} + void GSHandleClientApprove(CSteamID &steamID) { GSHandleClientApprove_ptr(steamID.ConvertToUint64()); } + void GSHandleClientDeny(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientDeny_ptr(steamID.ConvertToUint64(), eDenyReason); } + void GSHandleClientKick(CSteamID &steamID, EDenyReason eDenyReason) { GSHandleClientKick_ptr(steamID.ConvertToUint64(), eDenyReason); } -private: - OnLogonSuccessFunc OnLogonSuccess_ptr{}; - OnLogonFailureFunc OnLogonFailure_ptr{}; - OnLoggedOffFunc OnLoggedOff_ptr{}; - OnBeginLogonRetryFunc OnBeginLogonRetry_ptr{}; - HandleVACChallengeFunc HandleVACChallenge_ptr{}; - GSHandleClientApproveFunc GSHandleClientApprove_ptr{}; - GSHandleClientDenyFunc GSHandleClientDeny_ptr{}; - GSHandleClientKickFunc GSHandleClientKick_ptr{}; + int GetValue(const char *var, char *buf, int bufsize) { return GetValue_ptr(var, buf, bufsize); } + int GetServerReadableTicket(uint32 unk1, uint32 unk2, void *unk3, uint32 unk4, uint32 *unk5) { return 0; } }; #endif // __INCLUDED_CAPICMCALLBACK_H__ diff --git a/dll/dll/client_known_interfaces.h b/dll/dll/client_known_interfaces.h index 61605a734..22b330b8d 100644 --- a/dll/dll/client_known_interfaces.h +++ b/dll/dll/client_known_interfaces.h @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #ifndef _CLIENT_KNOWN_INTERFACES_H_ #define _CLIENT_KNOWN_INTERFACES_H_ diff --git a/dll/dll/common_includes.h b/dll/dll/common_includes.h index 1457155ee..b8716556b 100644 --- a/dll/dll/common_includes.h +++ b/dll/dll/common_includes.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include diff --git a/dll/dll/econ_item.h b/dll/dll/econ_item.h new file mode 100644 index 000000000..ca12cabcc --- /dev/null +++ b/dll/dll/econ_item.h @@ -0,0 +1,102 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#ifndef __INCLUDED_ECON_ITEM_H__ +#define __INCLUDED_ECON_ITEM_H__ + +#include "base.h" + +//=============================================================================================================== +// POSITION HANDLING +//=============================================================================================================== +// TF Inventory Position cracking + +// REALLY OLD FORMAT (??): +// Bits 17-32 are the bag index (class index + 999, 1 is unequipped). +// Bits 1-16 are the position of the item within the bag. +// 0 means item hasn't been acknowledged by the player yet. +// +// LESS OLD FORMAT (up through July, 2011): +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Bits 17-26 are a bool for whether the item is equipped in the matching class. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Bit 32 is 1, to note the new format. +// +// CURRENT FORMAT: +// If Bit 31 is 0: +// Bits 1-16 are the backpack position. +// Otherwise, if Bit 31 is 1: +// Item hasn't been acknowledged by the player yet. +// Bits 1-16 are the method by the player found the item (see unacknowledged_item_inventory_positions_t) +// Equipped state is stored elsewhere. +// This is the only format that should exist on clients. +// Note (1/15/2013) For backwards compatibility, if the value is 0 item is considered unacknowledged too + +struct Econ_Item_Attribute +{ + enum Attribute_Type + { + ATTR_TYPE_DEFAULT = 0, + ATTR_TYPE_FLOAT, + ATTR_TYPE_INT, + ATTR_TYPE_STRING, + }; + uint32 def{}; + float value{}; // for backwards compatibility + std::string value_bytes; + Attribute_Type type{}; // this is only used on the client side to know how to save attribute into file +}; + +struct Econ_Item +{ + uint64 id{}; + uint32 def{}; + uint32 level{}; + EItemQuality quality{}; + uint32 inv_pos{}; + uint32 quantity{}; + uint8 flags{}; + uint8 origin{}; + std::string custom_name; + std::string custom_desc; + bool in_use{}; + uint64 original_id{}; + uint8 style{}; + std::map equip_states; + std::vector attributes; +}; + +inline bool check_econ_item_name(const std::string &name) +{ + if (name.size() > 40) + return false; + + return true; +} + +inline bool check_econ_item_desc(const std::string &desc) +{ + if (desc.size() > 80) + return false; + + return true; +} + +#endif diff --git a/dll/dll/network.h b/dll/dll/network.h index 63c6cbdf9..fc4a6d94e 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -22,6 +22,7 @@ #include #define DEFAULT_PORT 47584 +#define NUM_QUERY_PORTS 10 #if defined(STEAM_WIN32) typedef unsigned int sock_t; @@ -67,6 +68,7 @@ enum Callback_Ids { CALLBACK_ID_GAMESERVER_STATS, CALLBACK_ID_LEADERBOARDS_STATS, CALLBACK_ID_USER_STATS, + CALLBACK_ID_GAMESERVER_ITEMS, CALLBACK_IDS_MAX }; @@ -145,7 +147,17 @@ class Networking // send to a specific user, set_dest_id() must be called bool sendTo(Common_Message *msg, bool reliable, Connection *conn = NULL); - + + // send to EVERY connection that matches msg->dest_id() (not just the first). + // Needed when multiple local processes share one Steam account (e.g. a + // bootstrap launcher exe + the shipping game exe both loading steam_api): + // P2P connection-establishment + DATA messages are addressed by Steam ID, + // and routing to a single connection delivers them to the wrong process, + // which has no matching socket and silently drops them. Delivering to all + // matching connections lets the process that owns the socket handle it; + // the others ignore it (their socket lookup fails). + bool sendToAllWithID(Common_Message *msg, bool reliable); + // send to all users whose account type is Individual, no need to call set_dest_id(), this is done automatically bool sendToAllIndividuals(Common_Message *msg, bool reliable); diff --git a/dll/dll/p2p_manager.hpp b/dll/dll/p2p_manager.hpp deleted file mode 100644 index 0a04f2bdf..000000000 --- a/dll/dll/p2p_manager.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* Copyright (C) 2019 Mr Goldberg - This file is part of the Goldberg Emulator - - The Goldberg Emulator is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - The Goldberg Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the Goldberg Emulator; if not, see - . */ - -#pragma once - -#include "base.h" -#include - - -class P2p_Manager -{ -private: - struct Peer_Src_t - { - CSteamID remote_id{}; - CSteamID my_dest_id{}; - }; - - struct Packet_t - { - private: - std::chrono::high_resolution_clock::time_point time_created = std::chrono::high_resolution_clock::now(); - - public: - bool is_processed = false; - std::vector data{}; - EP2PSend send_type = EP2PSend::k_EP2PSendUnreliable; - - const std::chrono::high_resolution_clock::time_point& get_time_created() const; - }; - - struct Channel_t - { - std::list packets{}; - }; - - struct Connection_t - { - Peer_Src_t peer_conn{}; - std::chrono::high_resolution_clock::time_point time_added = std::chrono::high_resolution_clock::now(); - bool is_accepted = false; - std::unordered_map channels{}; - }; - - std::recursive_mutex p2p_mtx{}; - std::list connections{}; - - class Settings *settings_client{}; - class Settings *settings_server{}; - class SteamCallBacks *callbacks_client{}; - class SteamCallBacks *callbacks_server{}; - class Networking *network{}; - class RunEveryRunCB *run_every_runcb{}; - - SteamCallBacks* get_my_callbacks(const CSteamID &my_id) const; - bool is_same_peer(const CSteamID &id, const CSteamID &peer_id) const; - void send_peer_session_failure(const Peer_Src_t &peer_conn); - void trigger_session_request(const CSteamID &remote_id, const CSteamID &my_id); - - bool remove_connection(const CSteamID &remote_id, const CSteamID &my_id); - Connection_t* create_connection(const CSteamID &remote_id, const CSteamID &my_id); - Connection_t* get_connection(const CSteamID &remote_id, const CSteamID &my_id); - - // true if the connection was already accepted, - // false otherwise. - bool store_packet( - CSteamID my_id, CSteamID steamIDRemote, - const void *pubData, uint32 cubData, int nChannel, - EP2PSend send_type - ); - - std::optional> get_next_packet(CSteamID my_id, int nChannel); - - void periodic_handle_connections(const std::chrono::high_resolution_clock::time_point &now); - void periodic_handle_channels(const std::chrono::high_resolution_clock::time_point &now); - void periodic_handle_packets(const std::chrono::high_resolution_clock::time_point &now); - void periodic_callback(); - - void network_data_packets(Common_Message *msg); - void network_low_level(Common_Message *msg); - void network_callback(Common_Message *msg); - - static void steam_networking_callback(void *object, Common_Message *msg); - static void steam_run_every_runcb(void *object); - - -public: - P2p_Manager( - class Settings *settings_client, class Settings *settings_server, - class SteamCallBacks *callbacks_client, class SteamCallBacks *callbacks_server, - class Networking *network, class RunEveryRunCB *run_every_runcb - ); - ~P2p_Manager(); - - bool send_packet(CSteamID my_id, CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel); - bool is_packet_available(CSteamID my_id, uint32 *pcubMsgSize, int nChannel); - bool read_packet(CSteamID my_id, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel); - bool close_channel(CSteamID my_id, CSteamID steamIDRemote, int nChannel); - bool close_session(CSteamID my_id, CSteamID steamIDRemote); - bool get_session_state(CSteamID my_id, CSteamID steamIDRemote, P2PSessionState_t *pConnectionState); - bool accept_session(CSteamID my_id, CSteamID steamIDRemote); - -}; diff --git a/dll/dll/settings.h b/dll/dll/settings.h index e1299a01a..c80f133e9 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -109,6 +109,8 @@ struct Controller_Settings { std::map, std::string>>> action_sets{}; std::map action_set_layer_parents{}; std::map, std::string>>> action_set_layers{}; + std::string controller_type_override{}; + bool enabled{}; }; struct Group_Clans { @@ -126,7 +128,13 @@ struct Overlay_Appearance { constexpr const static NotificationPosition default_pos = NotificationPosition::top_right; std::string font_override{}; // path to a custom user-provided TTF font + std::string font_override_ach_title{}; // path to custom font for achievement title + std::string font_override_ach_desc{}; // path to custom font for achievement description float font_size = 16.0f; + float font_size_fps = 0.0f; + float font_size_ach_title = 0.0f; + float font_size_ach_desc = 0.0f; + bool font_ach_title_bold = false; float icon_size = 64.0f; @@ -200,32 +208,6 @@ struct Branch_Info { bool active = false; }; -struct OldP2pBehavior { - enum class EPacketShareMode { - // if the sending type is unreliable (UDP), share packets between gameserver and client - // otherwise, don't share packets - DEFAULT, - - // always share packets between gameserver and client - ALWAYS_SHARE, - - // never share packets between gameserver and client - NEVER_SHARE, - - _LAST, - }; - - static EPacketShareMode to_share_mode(int val) { - if (val < 0 || val >= (unsigned)EPacketShareMode::_LAST) { - return EPacketShareMode::DEFAULT; - } - - return (EPacketShareMode)val; - } - - EPacketShareMode mode = EPacketShareMode::DEFAULT; -}; - class Settings { private: CSteamID steam_id{}; // user id @@ -245,6 +227,7 @@ class Settings { std::set installed_app_ids{}; std::map app_paths{}; + std::map purchased_keys{}; std::vector mods{}; std::map leaderboards{}; std::map stats{}; @@ -303,7 +286,7 @@ class Settings { // allow stats not defined by the user? bool allow_unknown_stats = false; - // whether to enable the functionality which reports an achievement progress for stats that are tied to achievements + //whether to enable the functionality which reports an achievement progress for stats that are tied to achievements // only used internally for a stat that's tied to an achievement, the normal achievement progress requests made by the game are not impacted bool stat_achievement_progress_functionality = true; // when a stat that's tied to an achievement gets a new value, should the emu save that progress only if it's higher? @@ -348,7 +331,6 @@ class Settings { struct Controller_Settings controller_settings{}; std::string glyphs_directory{}; - // allow Steam_User_Stats::FindLeaderboard() to always succeed and create the given unknown leaderboard bool disable_leaderboards_create_unknown = false; // share leaderboards with other players playing the same game on the same network @@ -368,7 +350,7 @@ class Settings { int overlay_renderer_detector_timeout_sec = 15; // "Saints Row (2022)" takes almost ~8 sec to detect renderer (DX12) bool disable_overlay_achievement_notification = false; bool disable_overlay_friend_notification = false; - bool disable_overlay_achievement_progress = false; + bool disable_overlay_achievement_progress = true; unsigned overlay_fps_avg_window = 10; float overlay_stats_pos_x = 0.0f; float overlay_stats_pos_y = 0.0f; @@ -395,20 +377,26 @@ class Settings { bool overlay_always_show_fps = false; bool overlay_always_show_frametime = false; bool overlay_always_show_playtime = false; + // keys used to toggle the overlay, default = Shift + Tab + std::vector overlay_toggle_keys{}; + // minimum time interval between achievement notifications (in milliseconds) + int achievement_notification_delay_ms = 0; // free weekend bool free_weekend = false; - // old P2P (ISteamNetworking) behavior - OldP2pBehavior old_p2p_behavior{}; - // P2P session auto-accept (fixes games that don't properly handle P2PSessionRequest_t) + // splitux tweak — old_p2p_behavior removed upstream; consumer (p2p_manager) refactored, + // re-home the auto-accept check later. Storage kept so config parsing stays valid. bool auto_accept_p2p_sessions_any = false; std::set auto_accept_p2p_sessions_friends{}; // voice chat bool enable_voice_chat = false; + // only use 32 bits for inventory item ids + bool use_32bit_inventory_item_ids = false; + #ifdef LOBBY_CONNECT static constexpr const bool is_lobby_connect = true; @@ -462,6 +450,10 @@ class Settings { void setAppInstallPath(AppId_t appID, const std::string &path); bool getAppInstallPath(AppId_t appID, std::string &path); + //Purchased keys + void setPurchasedKey(AppId_t appID, const std::string &key); + bool getPurchasedKey(AppId_t appID, std::string &key) const; + //mod stuff void addMod(PublishedFileId_t id, const std::string &title, const std::string &path); void addModDetails(PublishedFileId_t id, const Mod_entry &details); diff --git a/dll/dll/steam_app_ids.h b/dll/dll/steam_app_ids.h index 244b03cb4..ff0523a6a 100644 --- a/dll/dll/steam_app_ids.h +++ b/dll/dll/steam_app_ids.h @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #ifndef _STEAM_APP_IDS_H_ #define _STEAM_APP_IDS_H_ diff --git a/dll/dll/steam_apps.h b/dll/dll/steam_apps.h index e44e004a3..784d7da4c 100644 --- a/dll/dll/steam_apps.h +++ b/dll/dll/steam_apps.h @@ -28,6 +28,7 @@ public ISteamApps004, public ISteamApps005, public ISteamApps006, public ISteamApps007, +public ISteamApps008, public ISteamApps { class Settings *settings{}; @@ -144,6 +145,7 @@ public ISteamApps // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ); // iterate through + bool GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated); // iterate through // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch bool SetActiveBeta( const char *pchBetaName ); diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index fcb61dcfe..0582cf59d 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -51,24 +51,37 @@ #include "steam_parties.h" #include "steam_remoteplay.h" #include "steam_tv.h" -#include "steam_billing.h" - -#include "steam_gameserver.h" -#include "steam_gameserverstats.h" #include "steam_gamestats.h" #include "steam_timeline.h" #include "steam_app_disable_update.h" +#include "steam_billing.h" +#include "steam_user_items.h" + +#include "steam_gameserver.h" #include "steam_masterserver_updater.h" +#include "steam_gameserverstats.h" +#include "steam_gameserver_items.h" #include "overlay/steam_overlay.h" #include "playtime.h" +#include "callback_wrapper.h" -enum Steam_Pipe { +enum Steam_Pipe_Type { NO_USER, CLIENT, SERVER }; +struct Steam_Pipe { + Steam_Pipe_Type type{}; + bool got_last_cb{}; +}; + +struct User_Ref { + HSteamUser user{}; + HSteamPipe pipe{}; +}; + class Steam_Client : public ISteamClient001, public ISteamClient002, @@ -109,6 +122,8 @@ public ISteamClient common_helpers::KillableWorker *background_thread{}; void background_thread_proc(); + std::map old_callbacks_map; + public: Networking *network{}; SteamCallResults *callback_results_server{}, *callback_results_client{}; @@ -153,6 +168,7 @@ public ISteamClient Steam_Timeline *steam_timeline{}; Steam_App_Disable_Update *steam_app_disable_update{}; Steam_Billing *steam_billing{}; + Steam_User_Items *steam_user_items{}; Steam_GameServer *steam_gameserver{}; Steam_User *steam_gameserver_user{}; @@ -169,162 +185,193 @@ public ISteamClient Steam_Game_Coordinator *steam_gameserver_game_coordinator{}; Steam_Masterserver_Updater *steam_masterserver_updater{}; Steam_GameStats *steam_gameserver_gamestats{}; + Steam_GameServer_Items *steam_gameserver_items{}; Steam_AppTicket *steam_app_ticket{}; Steam_Overlay* steam_overlay{}; - P2p_Manager *p2p_manager{}; PlaytimeCounter* playtime_counter{}; - bool steamclient_server_inited = false; - bool gameserver_has_ipv6_functions{}; int steamclient_version{}; + bool using_old_callbacks{}; + + int client_user_ref_count{}; + + template + class Reusable_Numbers { + T counter{}; + std::priority_queue, std::greater<>> freed_numbers{}; + + public: + T get_number() + { + T retval{}; + if (!freed_numbers.empty()) { + retval = freed_numbers.top(); + freed_numbers.pop(); + } else { + if (!counter) ++counter; + retval = counter; + ++counter; + } + + return retval; + } + + void free_number(T number) + { + freed_numbers.push(number); + } + }; - unsigned steam_pipe_counter = 1; - std::map steam_pipes{}; + Reusable_Numbers steam_pipe_numbers{}; + std::map steam_pipes{}; + + Reusable_Numbers old_user_ref_numbers{}; + std::map old_user_refs{}; Steam_Client(); ~Steam_Client(); // Creates a communication pipe to the Steam client. - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - HSteamPipe CreateSteamPipe(); + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamPipe CreateSteamPipe(); - // Releases a previously created communications pipe - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - bool BReleaseSteamPipe( HSteamPipe hSteamPipe ); + // Releases a previously created communications pipe + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + bool BReleaseSteamPipe( HSteamPipe hSteamPipe ); - // connects to an existing global user, failing if none exists - // used by the game to coordinate with the steamUI - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ); + // connects to an existing global user, failing if none exists + // used by the game to coordinate with the steamUI + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamUser ConnectToGlobalUser( HSteamPipe hSteamPipe ); - // used by game servers, create a steam user that won't be shared with anyone else - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType eAccountType ); + // used by game servers, create a steam user that won't be shared with anyone else + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType eAccountType ); HSteamUser CreateLocalUser( HSteamPipe *phSteamPipe ); - // removes an allocated user - // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling - void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ); + // removes an allocated user + // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling + void ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ); - // retrieves the ISteamUser interface associated with the handle - ISteamUser *GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // retrieves the ISteamUser interface associated with the handle + ISteamUser *GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // retrieves the ISteamGameServer interface associated with the handle - ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // retrieves the ISteamGameServer interface associated with the handle + ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // set the local IP and Port to bind to - // this must be set before CreateLocalUser() - void SetLocalIPBinding( uint32 unIP, uint16 usPort ); + // set the local IP and Port to bind to + // this must be set before CreateLocalUser() + void SetLocalIPBinding( uint32 unIP, uint16 usPort ); void SetLocalIPBinding( const SteamIPAddress_t &unIP, uint16 usPort ); - // returns the ISteamFriends interface - ISteamFriends *GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamFriends interface + ISteamFriends *GetISteamFriends( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamUtils interface - ISteamUtils *GetISteamUtils( HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamUtils interface + ISteamUtils *GetISteamUtils( HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamMatchmaking interface - ISteamMatchmaking *GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamMatchmaking interface + ISteamMatchmaking *GetISteamMatchmaking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamMatchmakingServers interface - ISteamMatchmakingServers *GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamMatchmakingServers interface + ISteamMatchmakingServers *GetISteamMatchmakingServers( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the a generic interface - void *GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the a generic interface + void *GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamUserStats interface - ISteamUserStats *GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamUserStats interface + ISteamUserStats *GetISteamUserStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns the ISteamGameServerStats interface - ISteamGameServerStats *GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns the ISteamGameServerStats interface + ISteamGameServerStats *GetISteamGameServerStats( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns apps interface - ISteamApps *GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns apps interface + ISteamApps *GetISteamApps( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // networking - ISteamNetworking *GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // networking + ISteamNetworking *GetISteamNetworking( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // remote storage - ISteamRemoteStorage *GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // remote storage + ISteamRemoteStorage *GetISteamRemoteStorage( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // user screenshots - ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // user screenshots + ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // game stats - ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // game stats + ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // steam timeline - ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // steam timeline + ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // steam app disable update - ISteamAppDisableUpdate *GetISteamAppDisableUpdate( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // steam app disable update + ISteamAppDisableUpdate *GetISteamAppDisableUpdate( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); // steam billing ISteamBilling *GetISteamBilling( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. - STEAM_PRIVATE_API( void RunFrame() ); + // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. + STEAM_PRIVATE_API( void RunFrame() ); - // returns the number of IPC calls made since the last time this function was called - // Used for perf debugging so you can understand how many IPC calls your game makes per frame - // Every IPC call is at minimum a thread context switch if not a process one so you want to rate - // control how often you do them. - uint32 GetIPCCallCount(); + // returns the number of IPC calls made since the last time this function was called + // Used for perf debugging so you can understand how many IPC calls your game makes per frame + // Every IPC call is at minimum a thread context switch if not a process one so you want to rate + // control how often you do them. + uint32 GetIPCCallCount(); - // API warning handling - // 'int' is the severity; 0 for msg, 1 for warning - // 'const char *' is the text of the message - // callbacks will occur directly after the API function is called that generated the warning or message. - void SetWarningMessageHook( SteamAPIWarningMessageHook_t pFunction ); + // API warning handling + // 'int' is the severity; 0 for msg, 1 for warning + // 'const char *' is the text of the message + // callbacks will occur directly after the API function is called that generated the warning or message. + void SetWarningMessageHook( SteamAPIWarningMessageHook_t pFunction ); - // Trigger global shutdown for the DLL - bool BShutdownIfAllPipesClosed(); + // Trigger global shutdown for the DLL + bool BShutdownIfAllPipesClosed(); - // Expose HTTP interface - ISteamHTTP *GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Expose HTTP interface + ISteamHTTP *GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Deprecated - the ISteamUnifiedMessages interface is no longer intended for public consumption. - STEAM_PRIVATE_API( void *DEPRECATED_GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) ; ) + // Deprecated - the ISteamUnifiedMessages interface is no longer intended for public consumption. + STEAM_PRIVATE_API( void *DEPRECATED_GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ) ; ) ISteamUnifiedMessages *GetISteamUnifiedMessages( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Exposes the ISteamController interface - ISteamController *GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Exposes the ISteamController interface + ISteamController *GetISteamController( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Exposes the ISteamUGC interface - ISteamUGC *GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Exposes the ISteamUGC interface + ISteamUGC *GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - // returns app list interface, only available on specially registered apps - ISteamAppList *GetISteamAppList( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - - // Music Player - ISteamMusic *GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // returns app list interface, only available on specially registered apps + ISteamAppList *GetISteamAppList( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + + // Music Player + ISteamMusic *GetISteamMusic( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Music Player Remote - ISteamMusicRemote *GetISteamMusicRemote(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); + // Music Player Remote + ISteamMusicRemote *GetISteamMusicRemote(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); - // html page display - ISteamHTMLSurface *GetISteamHTMLSurface(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); + // html page display + ISteamHTMLSurface *GetISteamHTMLSurface(HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion); - // Helper functions for internal Steam usage - STEAM_PRIVATE_API( void DEPRECATED_Set_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) - STEAM_PRIVATE_API( void DEPRECATED_Remove_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) - STEAM_PRIVATE_API( void Set_SteamAPI_CCheckCallbackRegisteredInProcess( SteamAPI_CheckCallbackRegistered_t func ); ) + // Helper functions for internal Steam usage + STEAM_PRIVATE_API( void DEPRECATED_Set_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) + STEAM_PRIVATE_API( void DEPRECATED_Remove_SteamAPI_CPostAPIResultInProcess( void (*)() ); ) + STEAM_PRIVATE_API( void Set_SteamAPI_CCheckCallbackRegisteredInProcess( SteamAPI_CheckCallbackRegistered_t func ); ) void Set_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResultInProcess_t func ); void Remove_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResultInProcess_t func ); - // inventory - ISteamInventory *GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // inventory + ISteamInventory *GetISteamInventory( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Video - ISteamVideo *GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Video + ISteamVideo *GetISteamVideo( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); - // Parental controls - ISteamParentalSettings *GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Parental controls + ISteamParentalSettings *GetISteamParentalSettings( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); // ISteamMasterServerUpdater *GetISteamMasterServerUpdater( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); @@ -344,11 +391,11 @@ public ISteamClient ISteamAppTicket *GetAppTicket( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - void RegisterCallback( class CCallbackBase *pCallback, int iCallback); - void UnregisterCallback( class CCallbackBase *pCallback); + void RegisterCallback( class CCallbackBase *pCallback, int iCallback ); + void UnregisterCallback( class CCallbackBase *pCallback) ; - void RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall); - void UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall); + void RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ); + void UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ); void RunCallbacks(bool runClientCB, bool runGameserverCB); void setAppID(uint32 appid); @@ -365,41 +412,58 @@ public ISteamClient // https://github.com/ValveSoftware/Proton/blob/proton_9.0/lsteamclient/steamworks_sdk_099v/isteamclient.h // https://workshop.perforce.com/files/guest/knut_wikstrom/ValveSDKCode/public/steam/isteamclient.h - // creates a global instance of a steam user, so that other processes can share it - // used by the steam UI, to share it's account info/connection with any games it launches - // fails (returns NULL) if an existing instance already exists - HSteamUser CreateGlobalUser( HSteamPipe *phSteamPipe ); - // retrieves the IVac interface associated with the handle - // there is normally only one instance of VAC running, but using this connects it to the right user/account - void *GetIVAC( HSteamUser hSteamUser ); - // returns the name of a universe - const char *GetUniverseName( EUniverse eUniverse ); - void *GetISteamBilling_old( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); - + // SteamClient001 ----------------------------------------------------- HSteamUser CreateGlobalInstance(); HSteamUser ConnectToGlobalInstance(); HSteamUser CreateLocalInstance(); void ReleaseInstance( HSteamUser hSteamUser ); ISteamUser *GetISteamUser( HSteamUser hSteamUser, const char *pchVersion ); - ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ); - bool BMainLoop( uint64 time ); + // retrieves the IVac interface associated with the handle + // there is normally only one instance of VAC running, but using this connects it to the right user/account + void *GetIVAC( HSteamUser hSteamUser ); bool BMainLoop( uint64 time, bool unk ); - EUniverse GetConnectedUniverse(); - bool BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ); - void FreeLastCallback( HSteamPipe hSteamPipe ); - void SetEUniverse( EUniverse universe ); void Test_SetSpew( const char *unk1, int unk2 ) {} void Test_SetSpewFunc( void *unk ) {} void Test_OverrideIPs( uint32 unIPPublic, uint32 unIPPrivate ) {} void Test_SetServerLoadState( bool unk1, bool unk2 ) {} void Test_SetStressMode( bool unk ) {} int Test_GetStatsVConn() { return 0; } + // SteamClient001 ----------------------------------------------------- + + // SteamClient002 ----------------------------------------------------- void Test_RemoveAllClients() {} + // SteamClient002 ----------------------------------------------------- + + // SteamClient003 ----------------------------------------------------- + bool BMainLoop( uint64 time ); + // SteamClient003 ----------------------------------------------------- + + // SteamClient004 ----------------------------------------------------- + ISteamGameServer *GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ); + // SteamClient004 ----------------------------------------------------- + + // SteamClient005 ----------------------------------------------------- + // creates a global instance of a steam user, so that other processes can share it + // used by the steam UI, to share it's account info/connection with any games it launches + // fails (returns NULL) if an existing instance already exists + HSteamUser CreateGlobalUser( HSteamPipe *phSteamPipe ); + EUniverse GetConnectedUniverse(); + // returns the name of a universe + const char *GetUniverseName( EUniverse eUniverse ); + bool BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ); + void FreeLastCallback( HSteamPipe hSteamPipe ); + void SetEUniverse( EUniverse universe ); + // SteamClient005 ----------------------------------------------------- + + // SteamClient006 ----------------------------------------------------- + void *GetISteamBilling_old( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // SteamClient006 ----------------------------------------------------- // older sdk ---------------------------------------------------------- void report_missing_impl(std::string_view itf, std::string_view caller); [[noreturn]] void report_missing_impl_and_exit(std::string_view itf, std::string_view caller); + HSteamUser create_old_user_ref(HSteamUser hUser, HSteamPipe hSteamPipe); HSteamPipe get_pipe_for_user(HSteamUser hUser); }; diff --git a/dll/dll/steam_game_coordinator.h b/dll/dll/steam_game_coordinator.h index 376343d22..612437091 100644 --- a/dll/dll/steam_game_coordinator.h +++ b/dll/dll/steam_game_coordinator.h @@ -19,32 +19,137 @@ #define __INCLUDED_STEAM_GAME_COORDINATOR_H__ #include "base.h" +#include "econ_item.h" + +class Steam_User_Items; +class Steam_GameServer_Items; +struct GCMsgHdr_t; +struct GCMsgHdrEx_t; +struct ProtoBufMsgHeader_t; +class CMsgProtoBufHeader; class Steam_Game_Coordinator : public ISteamGameCoordinator { + static constexpr const auto items_user_file = "items.json"; + static constexpr const auto gc_config_file = "gc.json"; constexpr const static uint32 protobuf_mask = 0x80000000; class Settings *settings{}; class Networking *network{}; - class SteamCallResults *callback_results{}; + class Local_Storage *local_storage{}; class SteamCallBacks *callbacks{}; class RunEveryRunCB *run_every_runcb{}; + bool is_server{}; + + struct GC_Message + { + uint32 msg_type{}; + std::string msg_body; + std::chrono::high_resolution_clock::time_point created{}; + double post_in{}; + }; + + std::vector pending_messages; + std::queue incoming_messages; + + enum GC_Profile + { + GC_PROFILE_INVALID = 0, + GC_PROFILE_TF2, + //GC_PROFILE_PORTAL2, + }; + + int gc_version{}; + GC_Profile gc_profile{}; + bool is_portal2{}; + bool gc_initialized{}; + bool delay_init{}; + bool welcome_received{}; + std::chrono::high_resolution_clock::time_point welcome_time{}; + + std::vector items; + bool items_loaded{}; + + struct RequestInventory + { + std::chrono::high_resolution_clock::time_point created{}; + CSteamID steam_id{}; + SteamAPICall_t steam_api_call{}; + bool is_gc{}; + }; + + std::map> all_user_items; + std::vector pending_items_requests; + + bool gc_enabled(); + Steam_User_Items *client_items(); + Steam_GameServer_Items *server_items(); + void parse_gc_config(); + bool is_welcome_message(const GC_Message &message); + void push_incoming(uint32 msg_type, const std::string &message, double delay = 0.1); + + std::string build_msg_header(JobID_t target_job = k_GIDNil, JobID_t source_job = k_GIDNil); + GCMsgHdrEx_t parse_msg_header(const char *&p); + std::string build_protomsg_header(uint32 msg_type, JobID_t target_job = k_GIDNil, JobID_t source_job = k_GIDNil); + template + std::tuple parse_protomsg(const void *input, uint32 input_size); + uint64 item_id_local_to_network(uint64 item_id); + uint64 item_id_network_to_local(uint64 item_id); + std::string item_to_gcstruct(const Econ_Item &item, CSteamID steam_id); + std::string item_to_gcprotobuf(const Econ_Item &item, CSteamID steam_id); + + void handle_set_item_pos(const void *input, uint32 input_size); + void handle_delete_item(const void *input, uint32 input_size); + void handle_motd_request(const void *input, uint32 input_size); + void handle_respawn(const void *input, uint32 input_size); + void handle_set_item_style(const void *input, uint32 input_size); + void handle_adjust_equip_state(const void *input, uint32 input_size); + void handle_set_multiple_item_pos(const void *input, uint32 input_size); + + void callback_client_welcome(); + void callback_server_welcome(); + void callback_items_received(CSteamID steam_id, const std::vector &items); + void callback_items_removed(CSteamID steam_id); + void callback_item_updated(CSteamID steam_id, const Econ_Item &item); + void callback_item_deleted(CSteamID steam_id, uint64 item_id); + void callback_respawn_request(CSteamID steam_id); + + void network_callback_inventory_request(Common_Message *msg); + void network_callback_inventory_response(Common_Message *msg); + void network_callback_item_update(Common_Message *msg); + void network_callback_item_deletion(Common_Message *msg); + void network_callback_respawn_request(Common_Message *msg); + void network_callback(Common_Message *msg); + void RunCallbacks(); - std::queue outgoing_messages{}; - - void push_incoming(std::string message); - - static void steam_callback(void *object, Common_Message *msg); + static void steam_network_callback(void *object, Common_Message *msg); static void steam_run_every_runcb(void *object); - void RunCallbacks(); - void Callback(Common_Message *msg); - public: - Steam_Game_Coordinator(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + Steam_Game_Coordinator(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, bool is_server); ~Steam_Game_Coordinator(); + void initialize_gc(); + void shutdown_gc(); + + const std::vector &get_items() { return items; } + const std::map> &get_all_user_items() { return all_user_items; } + const bool has_items_for_user(CSteamID steam_id) { return (all_user_items.count(steam_id) != 0); } + const std::vector &get_items_for_user(CSteamID steam_id) { return all_user_items.at(steam_id); } + + const std::vector &load_items_from_file(); + void save_items_to_file(); + const Econ_Item *set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc, bool save = true); + bool delete_item(uint64 item_id, bool is_gc); + + void request_user_items(CSteamID steam_id, SteamAPICall_t api_call, bool is_gc); + SteamAPICall_t find_items_request(CSteamID steam_id); + void remove_user_items(CSteamID steam_id); + + void on_client_connected(CSteamID steam_id); + void on_client_disconnected(CSteamID steam_id); + // sends a message to the Game Coordinator EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ); diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 7637a0109..411dfe068 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -57,28 +57,29 @@ public ISteamGameServer class Networking *network{}; class SteamCallBacks *callbacks{}; - CSteamID steam_id{}; - - bool call_servers_connected = false; - bool logged_in = false; - bool call_servers_disconnected = false; + bool call_servers_connected{}; + std::chrono::high_resolution_clock::time_point logon_time{}; + bool call_servers_disconnected{}; + std::chrono::high_resolution_clock::time_point logoff_time{}; + bool logged_in{}; Gameserver server_data{}; std::vector> players{}; uint32 flags{}; - bool policy_response_called{}; std::chrono::high_resolution_clock::time_point last_sent_server_info{}; Auth_Manager *auth_manager{}; std::vector outgoing_packets{}; + bool set_appid(int32 appid, const char *version_str, bool autodetect); void set_version(const char *pchVersionString); public: Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks); ~Steam_GameServer(); + void set_protocol_version(unsigned short nProtocolVersion); std::vector>* get_players(); void add_player(CSteamID steamID); void remove_player(CSteamID steamID); @@ -377,14 +378,20 @@ public ISteamGameServer // older sdk ----------------------------------------------- + // SteamGameServer001 -------------------------------------- + bool GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ); + bool GSSendUserDisconnect( CSteamID steamID ); + bool GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ); + bool Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + // SteamGameServer001 -------------------------------------- + + // SteamGameServer002 -------------------------------------- void GSSetSpawnCount( uint32 ucSpawn ); bool GSGetSteam2GetEncryptionKeyToSendToNewClient( void *pvEncryptionKey, uint32 *pcbEncryptionKey, uint32 cbMaxEncryptionKey ); bool GSSendSteam2UserConnect( uint32 unUserID, const void *pvRawKey, uint32 unKeyLen, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ); bool GSSendSteam3UserConnect( CSteamID steamID, uint32 unIPPublic, const void *pvCookie, uint32 cubCookie ); bool GSRemoveUserConnect( uint32 unUserID ); bool GSSendUserDisconnect( CSteamID steamID, uint32 unUserID ); - bool GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ); - bool Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); bool GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pchMapName ); bool GSSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint32 unGamePort, const char *pchGameDir, const char *pchVersion ); bool GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ); @@ -393,14 +400,14 @@ public ISteamGameServer bool GSSetUserData( CSteamID steamID, const char *pPlayerName, uint32 nFrags ); void GSUpdateSpectatorPort( uint16 unSpectatorPort ); void GSSetGameType( const char *pchType ); + // SteamGameServer002 -------------------------------------- + // SteamGameServer003 -------------------------------------- bool GSSendUserConnect( uint32 unUserID, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ); bool GSSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ); bool GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ); bool GSGetUserAchievementStatus( CSteamID steamID, const char *pchAchievementName ); - - bool GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ); - bool GSSendUserDisconnect( CSteamID steamID ); + // SteamGameServer003 -------------------------------------- // older sdk ----------------------------------------------- }; diff --git a/dll/dll/steam_gameserver_items.h b/dll/dll/steam_gameserver_items.h new file mode 100644 index 000000000..2daaf94fe --- /dev/null +++ b/dll/dll/steam_gameserver_items.h @@ -0,0 +1,75 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#ifndef __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ +#define __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ + +#include "base.h" + +class Steam_Game_Coordinator; + +class Steam_GameServer_Items : +public ISteamGameServerItems001, +public ISteamGameServerItems002, +public ISteamGameServerItems003, +public ISteamGameServerItems +{ + class Settings *settings{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + + Steam_Game_Coordinator *gc(); + +public: + Steam_GameServer_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); + ~Steam_GameServer_Items(); + + void callback_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success); + void callback_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos); + void callback_item_deleted(CSteamID steam_id, uint64 item_id); + + SteamAPICall_t LoadItems( CSteamID ownerID ); + void LoadItems_old( CSteamID ownerID ); + SteamAPICall_t GetItemCount( CSteamID ownerID ); + void GetItemCount_old( CSteamID ownerID ); + bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ); + HNewItemRequest CreateNewItemRequest( CSteamID steamID ); + HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ); + bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ); + bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ); + bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ); + bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ); + bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ); + bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ); + SteamAPICall_t SendNewItemRequest( HNewItemRequest handle ); + void SendNewItemRequest_old( HNewItemRequest handle ); + SteamAPICall_t GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ); + void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ); + SteamAPICall_t DeleteTemporaryItem( uint64 ulItemID ); + void DeleteTemporaryItem_old( uint64 ulItemID ); + SteamAPICall_t DeleteAllTemporaryItems(); + void DeleteAllTemporaryItems_old(); + SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ); + SteamAPICall_t GetItemBlob( uint64 ulItemID ); + SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ); +}; + +#endif // __INCLUDED_STEAM_GAMESERVER_ITEMS_H__ diff --git a/dll/dll/steam_gameserverstats.h b/dll/dll/steam_gameserverstats.h index 31cb431ba..21f894c9b 100644 --- a/dll/dll/steam_gameserverstats.h +++ b/dll/dll/steam_gameserverstats.h @@ -36,10 +36,16 @@ public ISteamGameServerStats std::chrono::high_resolution_clock::time_point created{}; SteamAPICall_t steamAPICall{}; CSteamID steamIDUser{}; - - bool timeout = false; }; + struct RequestAchievement { + std::chrono::high_resolution_clock::time_point created{}; + CSteamID steamIDUser{}; + std::string achievement_name; + + bool timeout = false; + }; + struct CachedStat { bool dirty = false; // true means it was changed on the server and should be sent to the user StatInfo stat{}; @@ -55,6 +61,7 @@ public ISteamGameServerStats }; std::vector pending_RequestUserStats{}; + std::vector pending_RequestAchievement{}; std::map all_users_data{}; CachedStat* find_stat(CSteamID steamIDUser, const std::string &key); @@ -67,6 +74,7 @@ public ISteamGameServerStats // reponses from player void network_callback_initial_stats(Common_Message *msg); void network_callback_updated_stats(Common_Message *msg); + void network_callback_achievement_response(Common_Message *msg); void network_callback(Common_Message *msg); // user connect/disconnect @@ -79,6 +87,8 @@ public ISteamGameServerStats public: Steam_GameServerStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); ~Steam_GameServerStats(); + + bool request_user_achievement(CSteamID steamIDUser, const char *ach_name); // downloads stats for the user // returns a GSStatsReceived_t callback when completed diff --git a/dll/dll/steam_networking.h b/dll/dll/steam_networking.h index e12387fbf..b838e7fed 100644 --- a/dll/dll/steam_networking.h +++ b/dll/dll/steam_networking.h @@ -19,7 +19,11 @@ #define __INCLUDED_STEAM_NETWORKING_H__ #include "base.h" -#include "p2p_manager.hpp" + +struct Steam_Networking_Connection { + CSteamID remote{}; + std::set open_channels{}; +}; struct steam_listen_socket { SNetListenSocket_t id{}; @@ -57,25 +61,36 @@ public ISteamNetworking { class Settings *settings{}; class Networking *network{}; - class P2p_Manager *p2p_manager{}; class SteamCallBacks *callbacks{}; class RunEveryRunCB *run_every_runcb{}; + std::recursive_mutex messages_mutex{}; + std::list messages{}; + std::list unprocessed_messages{}; + + std::recursive_mutex connections_edit_mutex{}; + std::vector connections{}; std::vector listen_sockets{}; std::vector connection_sockets{}; + std::map new_connection_times{}; + std::queue new_connections_to_call_cb{}; + SNetListenSocket_t socket_number = 0; + bool connection_exists(CSteamID id); + struct Steam_Networking_Connection *get_or_create_connection(CSteamID id); + void remove_connection(CSteamID id); SNetSocket_t create_connection_socket(CSteamID target, int nVirtualPort, uint32 nIP, uint16 nPort, SNetListenSocket_t id=0, enum steam_socket_connection_status status=SOCKET_CONNECTING, SNetSocket_t other_id=0); struct steam_connection_socket *get_connection_socket(SNetSocket_t id); void remove_killed_connection_sockets(); static void steam_networking_callback(void *object, Common_Message *msg); - static void steam_run_every_runcb(void *object); + static void steam_networking_run_every_runcp(void *object); public: - Steam_Networking(class Settings *settings, class Networking *network, class P2p_Manager *p2p_manager, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); ~Steam_Networking(); //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/dll/dll/steam_networking_sockets.h b/dll/dll/steam_networking_sockets.h index 036376d33..900b8c378 100644 --- a/dll/dll/steam_networking_sockets.h +++ b/dll/dll/steam_networking_sockets.h @@ -115,6 +115,8 @@ public ISteamNetworkingSockets ESteamNetworkingConnectionState convert_status(enum connect_socket_status old_status); void set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo_t *pInfo); + void set_steamnetconnectioninfo_001(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo); + void launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status); diff --git a/dll/dll/steam_remoteplay.h b/dll/dll/steam_remoteplay.h index 8cdcc8ea6..750c77443 100644 --- a/dll/dll/steam_remoteplay.h +++ b/dll/dll/steam_remoteplay.h @@ -23,6 +23,7 @@ class Steam_RemotePlay : public ISteamRemotePlay001, public ISteamRemotePlay002, +public ISteamRemotePlay003, public ISteamRemotePlay { class Settings *settings{}; @@ -47,9 +48,28 @@ public ISteamRemotePlay // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. uint32 GetSessionID( int iSessionIndex ); + // Return true if the session has joined using a Remote Play Together invitation + bool BSessionRemotePlayTogether(RemotePlaySessionID_t unSessionID); + // Get the SteamID of the connected user CSteamID GetSessionSteamID( uint32 unSessionID ); + // Get the guest ID of the connected user if they are a Remote Play Together guest + // This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest + uint32 GetSessionGuestID(RemotePlaySessionID_t unSessionID); + + // gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetSmallSessionAvatar(RemotePlaySessionID_t unSessionID); + + // gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetMediumSessionAvatar(RemotePlaySessionID_t unSessionID); + + // gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + int GetLargeSessionAvatar(RemotePlaySessionID_t unSessionID); + // Get the name of the session client device // This returns NULL if the sessionID is not valid const char *GetSessionClientName( uint32 unSessionID ); diff --git a/dll/dll/steam_ugc.h b/dll/dll/steam_ugc.h index 15e9085e8..4bd96ce42 100644 --- a/dll/dll/steam_ugc.h +++ b/dll/dll/steam_ugc.h @@ -434,7 +434,16 @@ public ISteamUGC // Set the local load order for these items. If there are any items not in the given list, they will sort by the time subscribed. bool SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileIDs, uint32 unNumPublishedFileIDs ); - + + // Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine + bool MarkDownloadedItemAsUnused(PublishedFileId_t nPublishedFileID); + + // Returns the number of items actually downloaded locally + uint32 GetNumDownloadedItems(); + + // Returns the ids of the items downloaded + uint32 GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries); + }; #endif // __INCLUDED_STEAM_UGC_H__ diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index af8eaec08..e21c87d75 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -53,6 +53,7 @@ public ISteamUser class SteamCallBacks *callbacks{}; class SteamCallResults *callback_results{}; Local_Storage *local_storage{}; + bool is_server{}; std::string encrypted_app_ticket{}; Auth_Manager *auth_manager{}; @@ -69,7 +70,7 @@ public ISteamUser std::vector> player_auths{}; public: - Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks); + Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, bool is_server); ~Steam_User(); // returns the HSteamUser this interface represents @@ -106,10 +107,10 @@ public ISteamUser // logon cookie - this is obsolete and never used int GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ); - bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ); - bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ); - bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ); - bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ); + bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ); + bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ); + bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ); + bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ); // Multiplayer Authentication functions @@ -329,21 +330,40 @@ public ISteamUser void RunCallbacks(); // older sdk ----------------------------------------------- + // SteamUser001 -------------------------------------------- void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ); - void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ); int ProcessCall( int unk ); - virtual void LogOn( CSteamID *steamID ); - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ); - virtual bool GSSendLogonRequest( CSteamID *steamID ); - virtual bool GSSendDisconnect( CSteamID *steamID ); - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ); - virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ); - virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + void LogOn_old( CSteamID &steamID ); + int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ); + bool GSSendLogonRequest( CSteamID &steamID ); + bool GSSendDisconnect( CSteamID &steamID ); + bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ); + bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ); + void Test_SuspendActivity() {} + void Test_ResumeActivity() {} + void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) {} + void Test_SetFakePrivateIP( uint32 unIPPrivate ) {} + void Test_SendBigMessage() {} + bool Test_BBigMessageResponseReceived() { return true; } + void Test_SetPktLossPct( int nPct ) {} + void Test_SetForceTCP( bool bForceTCP ) {} + void Test_Heartbeat() {} + void Test_FakeDisconnect() {} + EUniverse Test_GetEUniverse() { return k_EUniversePublic; } + // SteamUser001 -------------------------------------------- + + // SteamUser002 (old) -------------------------------------- + bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ); + void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} + // SteamUser002 (old) -------------------------------------- + + // SteamUser002 (new) -------------------------------------- + void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ); + // SteamUser002 (new) -------------------------------------- + // SteamUser004 (old) -------------------------------------- bool BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ); void FreeLastCallback(); - int GetSteamTicket( void *pBlob, int cbMaxBlob ); - const char *GetPlayerName(); void SetPlayerName( const char *pchPersonaName ); EPersonaState GetFriendStatus(); @@ -356,19 +376,8 @@ public ISteamUser bool GetFriendGamePlayed( CSteamID steamIDFriend, int32 *pnGameID, uint32 *punGameIP, uint16 *pusGamePort ); const char *GetPlayerName( CSteamID steamIDFriend ); int32 AddFriendByName( const char *pchEmailOrAccountName ); - - virtual void Test_SuspendActivity() {} - virtual void Test_ResumeActivity() {} - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) {} - virtual void Test_SetFakePrivateIP( uint32 ip ) {} - virtual void Test_SendBigMessage() {} - virtual bool Test_BBigMessageResponseReceived() { return true; } - virtual void Test_SetPktLossPct( int unk1 ) {} - virtual void Test_SetForceTCP( bool unk1 ) {} - virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) {} - virtual void Test_Heartbeat() {} - virtual void Test_FakeDisconnect() {} - virtual EUniverse Test_GetEUniverse() { return k_EUniversePublic; } + int GetSteamTicket( void *pBlob, int cbMaxBlob ); + // SteamUser004 (old) -------------------------------------- // older sdk ----------------------------------------------- }; diff --git a/dll/dll/steam_user_items.h b/dll/dll/steam_user_items.h new file mode 100644 index 000000000..eaefa6c36 --- /dev/null +++ b/dll/dll/steam_user_items.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#ifndef __INCLUDED_STEAM_USER_ITEMS_H__ +#define __INCLUDED_STEAM_USER_ITEMS_H__ + +#include "base.h" + +class Steam_Game_Coordinator; + +class Steam_User_Items : +public ISteamUserItems001, +public ISteamUserItems002, +public ISteamUserItems003, +public ISteamUserItems +{ + class Settings *settings{}; + class SteamCallBacks *callbacks{}; + class SteamCallResults *callback_results{}; + + Steam_Game_Coordinator *gc(); + +public: + Steam_User_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results); + ~Steam_User_Items(); + + SteamAPICall_t LoadItems(); + void LoadItems_old(); + SteamAPICall_t GetItemCount(); + void GetItemCount_old(); + bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ); + bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ); + bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ); + SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ); + void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ); + SteamAPICall_t DeleteItem( uint64 ulItemID ); + void DropItem_old( uint64 ulItemID ); + SteamAPICall_t DropItem_old2( uint64 ulItemID ); + SteamAPICall_t GetItemBlob( uint64 ulItemID ); + SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ); + virtual SteamAPICall_t DropItem( uint64 ulItemID ); +}; + +#endif // __INCLUDED_STEAM_USER_ITEMS_H__ diff --git a/dll/dll/steam_user_stats.h b/dll/dll/steam_user_stats.h index 55eac3206..9c6592450 100644 --- a/dll/dll/steam_user_stats.h +++ b/dll/dll/steam_user_stats.h @@ -157,6 +157,7 @@ public ISteamUserStats // requests from server void network_stats_initial(Common_Message *msg); void network_stats_updated(Common_Message *msg); + void network_achievement_request(Common_Message *msg); void network_callback_stats(Common_Message *msg); // requests from other users to share leaderboards diff --git a/dll/flat.cpp b/dll/flat.cpp index d313d222f..317fe275d 100644 --- a/dll/flat.cpp +++ b/dll/flat.cpp @@ -117,6 +117,7 @@ STEAMAPI_API ISteamScreenshots * SteamAPI_ISteamClient_GetISteamScreenshots( ISt return get_steam_client()->GetISteamScreenshots(hSteamuser, hSteamPipe, pchVersion); } +// removed in SDK 1.63 STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ) { return get_steam_client()->GetISteamGameSearch(hSteamuser, hSteamPipe, pchVersion); @@ -167,6 +168,7 @@ STEAMAPI_API ISteamMusic * SteamAPI_ISteamClient_GetISteamMusic( ISteamClient* s return get_steam_client()->GetISteamMusic(hSteamuser, hSteamPipe, pchVersion); } +// removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ) { return get_steam_client()->GetISteamMusicRemote(hSteamuser, hSteamPipe, pchVersion); @@ -1646,6 +1648,7 @@ STEAMAPI_API void SteamAPI_ISteamMatchmakingServers_CancelServerQuery( ISteamMat return (get_steam_client()->steam_matchmaking_servers)->CancelServerQuery(hServerQuery); } +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamGameSearch *SteamAPI_SteamGameSearch_v001() { return get_steam_client()->GetISteamGameSearch(flat_hsteamuser(), flat_hsteampipe(), "SteamMatchGameSearch001"); @@ -2381,6 +2384,16 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFlo return (get_steam_client()->steam_user_stats)->GetAchievementProgressLimits(pchName, pfMinProgress, pfMaxProgress); } +STEAMAPI_API ISteamApps* SteamAPI_SteamApps_v009() +{ + return get_steam_client()->GetISteamApps(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION009"); +} + +STEAMAPI_API ISteamApps* SteamAPI_SteamGameServerApps_v009() +{ + return get_steam_client()->GetISteamApps(flat_gs_hsteamuser(), flat_gs_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION009"); +} + STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v008() { return get_steam_client()->GetISteamApps(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPS_INTERFACE_VERSION008"); @@ -2548,10 +2561,18 @@ STEAMAPI_API int SteamAPI_ISteamApps_GetNumBetas(ISteamApps* self, int* pnAvaila } // SDK 1.61 removed unAppID param +/* STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo(ISteamApps* self, int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription) { return self->GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); } +*/ + +// SDK 1.64 added punLastUpdated. +STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo(ISteamApps* self, int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated) +{ + return self->GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, punLastUpdated); +} // SDK 1.61 removed unAppID param STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetActiveBeta(ISteamApps* self, const char* pchBetaName) @@ -2806,6 +2827,7 @@ STEAMAPI_API float SteamAPI_ISteamMusic_GetVolume( ISteamMusic* self ) return self->GetVolume(); } +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote *SteamAPI_SteamMusicRemote_v001() { return get_steam_client()->GetISteamMusicRemote(flat_hsteamuser(), flat_hsteampipe(), "STEAMMUSICREMOTE_INTERFACE_VERSION001"); @@ -4808,6 +4830,42 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUGC_SetSubscriptionsLoadOrder( ISteamUGC return (ptr)->SetSubscriptionsLoadOrder(pvecPublishedFileIDs, unNumPublishedFileIDs); } +STEAMAPI_API steam_bool SteamAPI_ISteamUGC_MarkDownloadedItemAsUnused(ISteamUGC* self, PublishedFileId_t nPublishedFileID) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->MarkDownloadedItemAsUnused(nPublishedFileID); +} + +STEAMAPI_API uint32 SteamAPI_ISteamUGC_GetNumDownloadedItems(ISteamUGC* self) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->GetNumDownloadedItems(); +} + +STEAMAPI_API uint32 SteamAPI_ISteamUGC_GetDownloadedItems(ISteamUGC* self, PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries) +{ + long long client_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_ugc); + long long server_vftable_distance = ((char*)self - (char*)get_steam_client()->steam_gameserver_ugc); + auto ptr = get_steam_client()->steam_gameserver_ugc; + if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) { + ptr = get_steam_client()->steam_ugc; + } + + return (ptr)->GetDownloadedItems(pvecPublishedFileIDs, cMaxEntries); +} + STEAMAPI_API ISteamAppList *SteamAPI_SteamAppList_v001() { return get_steam_client()->GetISteamAppList(flat_hsteamuser(), flat_hsteampipe(), "STEAMAPPLIST_INTERFACE_VERSION001"); @@ -5787,6 +5845,11 @@ STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v003() return get_steam_client()->GetISteamRemotePlay(flat_hsteamuser(), flat_hsteampipe(), "STEAMREMOTEPLAY_INTERFACE_VERSION003"); } +STEAMAPI_API ISteamRemotePlay* SteamAPI_SteamRemotePlay_v004() +{ + return get_steam_client()->GetISteamRemotePlay(flat_hsteamuser(), flat_hsteampipe(), "STEAMREMOTEPLAY_INTERFACE_VERSION004"); +} + STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionCount( ISteamRemotePlay* self ) { return (get_steam_client()->steam_remoteplay)->GetSessionCount(); @@ -5797,11 +5860,36 @@ STEAMAPI_API RemotePlaySessionID_t SteamAPI_ISteamRemotePlay_GetSessionID( IStea return (get_steam_client()->steam_remoteplay)->GetSessionID(iSessionIndex); } +STEAMAPI_API bool SteamAPI_ISteamRemotePlay_BSessionRemotePlayTogether(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->BSessionRemotePlayTogether(unSessionID); +} + STEAMAPI_API uint64_steamid SteamAPI_ISteamRemotePlay_GetSessionSteamID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ) { return (get_steam_client()->steam_remoteplay)->GetSessionSteamID(unSessionID).ConvertToUint64(); } +STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionGuestID(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetSessionGuestID(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetSmallSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetSmallSessionAvatar(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetMediumSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetMediumSessionAvatar(unSessionID); +} + +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetLargeSessionAvatar(ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID) +{ + return (get_steam_client()->steam_remoteplay)->GetLargeSessionAvatar(unSessionID); +} + STEAMAPI_API const char * SteamAPI_ISteamRemotePlay_GetSessionClientName( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ) { return (get_steam_client()->steam_remoteplay)->GetSessionClientName(unSessionID); diff --git a/dll/gc_steam/steammessages.proto b/dll/gc_steam/steammessages.proto new file mode 100644 index 000000000..f09971df9 --- /dev/null +++ b/dll/gc_steam/steammessages.proto @@ -0,0 +1,577 @@ +import "google/protobuf/descriptor.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +extend google.protobuf.FieldOptions { + optional bool key_field = 60000 [default = false]; +} + +extend google.protobuf.MessageOptions { + optional int32 msgpool_soft_limit = 60000 [default = 32]; + optional int32 msgpool_hard_limit = 60001 [default = 384]; +} + +enum GCProtoBufMsgSrc { + GCProtoBufMsgSrc_Unspecified = 0; + GCProtoBufMsgSrc_FromSystem = 1; + GCProtoBufMsgSrc_FromSteamID = 2; + GCProtoBufMsgSrc_FromGC = 3; + GCProtoBufMsgSrc_ReplySystem = 4; +} + +message CMsgProtoBufHeader { + option (msgpool_soft_limit) = 256; + option (msgpool_hard_limit) = 1024; + + optional fixed64 client_steam_id = 1; + optional int32 client_session_id = 2; + optional uint32 source_app_id = 3; + optional fixed64 job_id_source = 10 [default = 18446744073709551615]; + optional fixed64 job_id_target = 11 [default = 18446744073709551615]; + optional string target_job_name = 12; + optional int32 eresult = 13 [default = 2]; + optional string error_message = 14; + optional GCProtoBufMsgSrc gc_msg_src = 200 [default = GCProtoBufMsgSrc_Unspecified]; + optional uint32 gc_dir_index_source = 201; +} + +message CMsgWebAPIKey { + optional uint32 status = 1 [default = 255]; + optional uint32 account_id = 2 [default = 0]; + optional uint32 publisher_group_id = 3 [default = 0]; + optional uint32 key_id = 4; + optional string domain = 5; +} + +message CMsgHttpRequest { + message RequestHeader { + optional string name = 1; + optional string value = 2; + } + + message QueryParam { + optional string name = 1; + optional bytes value = 2; + } + + optional uint32 request_method = 1; + optional string hostname = 2; + optional string url = 3; + repeated CMsgHttpRequest.RequestHeader headers = 4; + repeated CMsgHttpRequest.QueryParam get_params = 5; + repeated CMsgHttpRequest.QueryParam post_params = 6; + optional bytes body = 7; + optional uint32 absolute_timeout = 8; +} + +message CMsgWebAPIRequest { + optional string UNUSED_job_name = 1; + optional string interface_name = 2; + optional string method_name = 3; + optional uint32 version = 4; + optional CMsgWebAPIKey api_key = 5; + optional CMsgHttpRequest request = 6; + optional uint32 routing_app_id = 7; +} + +message CMsgHttpResponse { + message ResponseHeader { + optional string name = 1; + optional string value = 2; + } + + optional uint32 status_code = 1; + repeated CMsgHttpResponse.ResponseHeader headers = 2; + optional bytes body = 3; +} + +message CMsgAMFindAccounts { + optional uint32 search_type = 1; + optional string search_string = 2; +} + +message CMsgAMFindAccountsResponse { + repeated fixed64 steam_id = 1; +} + +message CMsgNotifyWatchdog { + optional uint32 source = 1; + optional uint32 alert_type = 2; + optional uint32 alert_destination = 3; + optional bool critical = 4; + optional uint32 time = 5; + optional uint32 appid = 6; + optional string text = 7; +} + +message CMsgAMGetLicenses { + optional fixed64 steamid = 1; +} + +message CMsgPackageLicense { + optional uint32 package_id = 1; + optional uint32 time_created = 2; + optional uint32 owner_id = 3; +} + +message CMsgAMGetLicensesResponse { + repeated CMsgPackageLicense license = 1; + optional uint32 result = 2; +} + +message CMsgAMGetUserGameStats { + optional fixed64 steam_id = 1; + optional fixed64 game_id = 2; + repeated uint32 stats = 3; +} + +message CMsgAMGetUserGameStatsResponse { + message Stats { + optional uint32 stat_id = 1; + optional uint32 stat_value = 2; + } + + message Achievement_Blocks { + optional uint32 achievement_id = 1; + optional uint32 achievement_bit_id = 2; + optional fixed32 unlock_time = 3; + } + + optional fixed64 steam_id = 1; + optional fixed64 game_id = 2; + optional int32 eresult = 3 [default = 2]; + repeated CMsgAMGetUserGameStatsResponse.Stats stats = 4; + repeated CMsgAMGetUserGameStatsResponse.Achievement_Blocks achievement_blocks = 5; +} + +message CMsgGCGetCommandList { + optional uint32 app_id = 1; + optional string command_prefix = 2; +} + +message CMsgGCGetCommandListResponse { + repeated string command_name = 1; +} + +message CGCMsgMemCachedGet { + repeated string keys = 1; +} + +message CGCMsgMemCachedGetResponse { + message ValueTag { + optional bool found = 1; + optional bytes value = 2; + } + + repeated CGCMsgMemCachedGetResponse.ValueTag values = 1; +} + +message CGCMsgMemCachedSet { + message KeyPair { + optional string name = 1; + optional bytes value = 2; + } + + repeated CGCMsgMemCachedSet.KeyPair keys = 1; +} + +message CGCMsgMemCachedDelete { + repeated string keys = 1; +} + +message CGCMsgMemCachedStats { +} + +message CGCMsgMemCachedStatsResponse { + optional uint64 curr_connections = 1; + optional uint64 cmd_get = 2; + optional uint64 cmd_set = 3; + optional uint64 cmd_flush = 4; + optional uint64 get_hits = 5; + optional uint64 get_misses = 6; + optional uint64 delete_hits = 7; + optional uint64 delete_misses = 8; + optional uint64 bytes_read = 9; + optional uint64 bytes_written = 10; + optional uint64 limit_maxbytes = 11; + optional uint64 curr_items = 12; + optional uint64 evictions = 13; + optional uint64 bytes = 14; +} + +message CGCMsgSQLStats { + optional uint32 schema_catalog = 1; +} + +message CGCMsgSQLStatsResponse { + optional uint32 threads = 1; + optional uint32 threads_connected = 2; + optional uint32 threads_active = 3; + optional uint32 operations_submitted = 4; + optional uint32 prepared_statements_executed = 5; + optional uint32 non_prepared_statements_executed = 6; + optional uint32 deadlock_retries = 7; + optional uint32 operations_timed_out_in_queue = 8; + optional uint32 errors = 9; +} + +message CMsgAMAddFreeLicense { + optional fixed64 steamid = 1; + optional uint32 ip_public = 2; + optional uint32 packageid = 3; + optional string store_country_code = 4; +} + +message CMsgAMAddFreeLicenseResponse { + optional int32 eresult = 1 [default = 2]; + optional int32 purchase_result_detail = 2; + optional fixed64 transid = 3; +} + +message CGCMsgGetIPLocation { + repeated fixed32 ips = 1; +} + +message CIPLocationInfo { + optional uint32 ip = 1; + optional float latitude = 2; + optional float longitude = 3; + optional string country = 4; + optional string state = 5; + optional string city = 6; +} + +message CGCMsgGetIPLocationResponse { + repeated CIPLocationInfo infos = 1; +} + +message CGCMsgSystemStatsSchema { + optional uint32 gc_app_id = 1; + optional bytes schema_kv = 2; +} + +message CGCMsgGetSystemStats { +} + +message CGCMsgGetSystemStatsResponse { + optional uint32 gc_app_id = 1; + optional bytes stats_kv = 2; + optional uint32 active_jobs = 3; + optional uint32 yielding_jobs = 4; + optional uint32 user_sessions = 5; + optional uint32 game_server_sessions = 6; + optional uint32 socaches = 7; + optional uint32 socaches_to_unload = 8; + optional uint32 socaches_loading = 9; + optional uint32 writeback_queue = 10; + optional uint32 steamid_locks = 11; + optional uint32 logon_queue = 12; + optional uint32 logon_jobs = 13; +} + +message CMsgAMSendEmail { + message ReplacementToken { + optional string token_name = 1; + optional string token_value = 2; + } + + message PersonaNameReplacementToken { + optional fixed64 steamid = 1; + optional string token_name = 2; + } + + optional fixed64 steamid = 1; + optional uint32 email_msg_type = 2; + optional uint32 email_format = 3; + repeated CMsgAMSendEmail.PersonaNameReplacementToken persona_name_tokens = 5; + optional uint32 source_gc = 6; + repeated CMsgAMSendEmail.ReplacementToken tokens = 7; +} + +message CMsgAMSendEmailResponse { + optional uint32 eresult = 1 [default = 2]; +} + +message CMsgGCGetEmailTemplate { + optional uint32 app_id = 1; + optional uint32 email_msg_type = 2; + optional int32 email_lang = 3; + optional int32 email_format = 4; +} + +message CMsgGCGetEmailTemplateResponse { + optional uint32 eresult = 1 [default = 2]; + optional bool template_exists = 2; + optional string template = 3; +} + +message CMsgAMGrantGuestPasses2 { + optional fixed64 steam_id = 1; + optional uint32 package_id = 2; + optional int32 passes_to_grant = 3; + optional int32 days_to_expiration = 4; + optional int32 action = 5; +} + +message CMsgAMGrantGuestPasses2Response { + optional int32 eresult = 1 [default = 2]; + optional int32 passes_granted = 2 [default = 0]; +} + +message CGCSystemMsg_GetAccountDetails { + option (msgpool_soft_limit) = 128; + option (msgpool_hard_limit) = 512; + + optional fixed64 steamid = 1; + optional uint32 appid = 2; +} + +message CGCSystemMsg_GetAccountDetails_Response { + option (msgpool_soft_limit) = 128; + option (msgpool_hard_limit) = 512; + + optional uint32 eresult_deprecated = 1 [default = 2]; + optional string account_name = 2; + optional string persona_name = 3; + optional bool is_profile_public = 4; + optional bool is_inventory_public = 5; + optional bool is_vac_banned = 7; + optional bool is_cyber_cafe = 8; + optional bool is_school_account = 9; + optional bool is_limited = 10; + optional bool is_subscribed = 11; + optional uint32 package = 12; + optional bool is_free_trial_account = 13; + optional uint32 free_trial_expiration = 14; + optional bool is_low_violence = 15; + optional bool is_account_locked_down = 16; + optional bool is_community_banned = 17; + optional bool is_trade_banned = 18; + optional uint32 trade_ban_expiration = 19; + optional uint32 accountid = 20; + optional uint32 suspension_end_time = 21; + optional string currency = 22; + optional uint32 steam_level = 23; + optional uint32 friend_count = 24; + optional uint32 account_creation_time = 25; + optional bool is_steamguard_enabled = 27; + optional bool is_phone_verified = 28; + optional bool is_two_factor_auth_enabled = 29; + optional uint32 two_factor_enabled_time = 30; + optional uint32 phone_verification_time = 31; + optional uint64 phone_id = 33; + optional bool is_phone_identifying = 34; + optional uint32 rt_identity_linked = 35; + optional uint32 rt_birth_date = 36; + optional string txn_country_code = 37; +} + +message CMsgGCCheckClanMembership { + optional fixed64 steamid = 1; + optional uint32 clanid = 2; +} + +message CMsgGCCheckClanMembership_Response { + optional bool ismember = 1; +} + +message CMsgGCGetPersonaNames { + repeated fixed64 steamids = 1; +} + +message CMsgGCGetPersonaNames_Response { + message PersonaName { + optional fixed64 steamid = 1; + optional string persona_name = 2; + } + + repeated CMsgGCGetPersonaNames_Response.PersonaName succeeded_lookups = 1; + repeated fixed64 failed_lookup_steamids = 2; +} + +message CMsgGCCheckFriendship { + optional fixed64 steamid_left = 1; + optional fixed64 steamid_right = 2; +} + +message CMsgGCCheckFriendship_Response { + optional bool success = 1; + optional bool found_friendship = 2; +} + +message CMsgGCMsgMasterSetDirectory { + message SubGC { + optional uint32 dir_index = 1; + optional string name = 2; + optional string box = 3; + optional string command_line = 4; + optional string gc_binary = 5; + } + + optional uint32 master_dir_index = 1; + repeated CMsgGCMsgMasterSetDirectory.SubGC dir = 2; +} + +message CMsgGCMsgMasterSetDirectory_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgWebAPIJobRequestForwardResponse { + optional uint32 dir_index = 1; +} + +message CGCSystemMsg_GetPurchaseTrust_Request { + optional fixed64 steamid = 1; +} + +message CGCSystemMsg_GetPurchaseTrust_Response { + optional bool has_prior_purchase_history = 1; + optional bool has_no_recent_password_resets = 2; + optional bool is_wallet_cash_trusted = 3; + optional uint32 time_all_trusted = 4; +} + +message CMsgGCHAccountVacStatusChange { + optional fixed64 steam_id = 1; + optional uint32 app_id = 2; + optional uint32 rtime_vacban_starts = 3; + optional bool is_banned_now = 4; + optional bool is_banned_future = 5; +} + +message CMsgGCHAccountTradeBanStatusChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_banned = 3; + optional uint32 time_banned_until = 4; +} + +message CMsgGCHAccountLockStatusChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_locked = 3; +} + +message CMsgGCHVacVerificationChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool is_verified = 3; +} + +message CMsgGCHAccountPhoneNumberChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional uint64 phone_id = 3; + optional bool is_verified = 4; + optional bool is_identifying = 5; +} + +message CMsgGCHAccountTwoFactorChange { + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional bool twofactor_enabled = 3; +} + +message CMsgGCGetPartnerAccountLink { + optional fixed64 steamid = 1; +} + +message CMsgGCGetPartnerAccountLink_Response { + optional uint32 pwid = 1; + optional uint32 nexonid = 2; +} + +message CMsgGCRoutingInfo { + enum RoutingMethod { + RANDOM = 0; + DISCARD = 1; + CLIENT_STEAMID = 2; + PROTOBUF_FIELD_UINT64 = 3; + WEBAPI_PARAM_UINT64 = 4; + } + + repeated uint32 dir_index = 1; + optional CMsgGCRoutingInfo.RoutingMethod method = 2 [default = RANDOM]; + optional CMsgGCRoutingInfo.RoutingMethod fallback = 3 [default = DISCARD]; + optional uint32 protobuf_field = 4; + optional string webapi_param = 5; +} + +message CMsgGCMsgMasterSetWebAPIRouting { + message Entry { + optional string interface_name = 1; + optional string method_name = 2; + optional CMsgGCRoutingInfo routing = 3; + } + + repeated CMsgGCMsgMasterSetWebAPIRouting.Entry entries = 1; +} + +message CMsgGCMsgMasterSetClientMsgRouting { + message Entry { + optional uint32 msg_type = 1; + optional CMsgGCRoutingInfo routing = 2; + } + + repeated CMsgGCMsgMasterSetClientMsgRouting.Entry entries = 1; +} + +message CMsgGCMsgMasterSetWebAPIRouting_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgMasterSetClientMsgRouting_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CMsgGCMsgSetOptions { + message MessageRange { + required uint32 low = 1; + required uint32 high = 2; + } + + enum Option { + NOTIFY_USER_SESSIONS = 0; + NOTIFY_SERVER_SESSIONS = 1; + NOTIFY_ACHIEVEMENTS = 2; + NOTIFY_VAC_ACTION = 3; + } + + enum GCSQLVersion { + GCSQL_VERSION_BASELINE = 1; + GCSQL_VERSION_BOOLTYPE = 2; + } + + repeated CMsgGCMsgSetOptions.Option options = 1; + repeated CMsgGCMsgSetOptions.MessageRange client_msg_ranges = 2; + optional CMsgGCMsgSetOptions.GCSQLVersion gcsql_version = 3 [default = GCSQL_VERSION_BASELINE]; +} + +message CMsgGCHUpdateSession { + message ExtraField { + optional string name = 1; + optional string value = 2; + } + + optional fixed64 steam_id = 1; + optional uint32 app_id = 2; + optional bool online = 3; + optional fixed64 server_steam_id = 4; + optional uint32 server_addr = 5; + optional uint32 server_port = 6; + optional uint32 os_type = 7; + optional uint32 client_addr = 8; + repeated CMsgGCHUpdateSession.ExtraField extra_fields = 9; +} + +message CMsgNotificationOfSuspiciousActivity { + message MultipleGameInstances { + optional uint32 app_instance_count = 1; + repeated fixed64 other_steamids = 2; + } + + optional fixed64 steamid = 1; + optional uint32 appid = 2; + optional CMsgNotificationOfSuspiciousActivity.MultipleGameInstances multiple_instances = 3; +} diff --git a/dll/gc_tf2/base_gcmessages.proto b/dll/gc_tf2/base_gcmessages.proto new file mode 100644 index 000000000..4274f09e8 --- /dev/null +++ b/dll/gc_tf2/base_gcmessages.proto @@ -0,0 +1,598 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCBaseMsg { + k_EMsgGCSystemMessage = 4001; + k_EMsgGCReplicateConVars = 4002; + k_EMsgGCConVarUpdated = 4003; + k_EMsgGCServerAvailable = 4506; + k_EMsgGCClientConnectToServer = 4507; + k_EMsgGCGameServerInfo = 4508; + k_EMsgGCError = 4509; + k_EMsgGCReplay_UploadedToYouTube = 4510; + k_EMsgGCLANServerAvailable = 4511; +} + +enum EGCBaseProtoObjectTypes { + k_EProtoObjectPartyInvite = 1001; + k_EProtoObjectLobbyInvite = 1002; +} + +enum GCGoodbyeReason { + GCGoodbyeReason_GC_GOING_DOWN = 1; + GCGoodbyeReason_NO_SESSION = 2; +} + +message CGCStorePurchaseInit_LineItem { + optional uint32 item_def_id = 1; + optional uint32 quantity = 2; + optional uint32 cost_in_local_currency = 3; + optional uint32 purchase_type = 4; +} + +message CMsgGCStorePurchaseInit { + optional string country = 1; + optional int32 language = 2; + optional int32 currency = 3; + repeated CGCStorePurchaseInit_LineItem line_items = 4; +} + +message CMsgGCStorePurchaseInitResponse { + optional int32 result = 1; + optional uint64 txn_id = 2; +} + +message CMsgSystemBroadcast { + optional string message = 1; +} + +message CMsgClientHello { + optional uint32 version = 1; +} + +message CMsgServerHello { + optional uint32 version = 1; +} + +message CMsgClientWelcome { + optional uint32 version = 1; + optional bytes game_data = 2; + optional string txn_country_code = 3; +} + +message CMsgServerWelcome { + optional uint32 min_allowed_version = 1; + optional uint32 active_version = 2; +} + +message CMsgClientGoodbye { + optional GCGoodbyeReason reason = 1 [default = GCGoodbyeReason_GC_GOING_DOWN]; +} + +message CMsgServerGoodbye { + optional GCGoodbyeReason reason = 1 [default = GCGoodbyeReason_GC_GOING_DOWN]; +} + +message CMsgServerAvailable { +} + +message CMsgLANServerAvailable { + optional fixed64 lobby_id = 1; +} + +message CSOEconGameAccountClient { + optional uint32 additional_backpack_slots = 1 [default = 0]; + optional bool trial_account = 2 [default = false]; + optional bool need_to_choose_most_helpful_friend = 4; + optional bool in_coaches_list = 5; + optional fixed32 trade_ban_expiration = 6; + optional fixed32 duel_ban_expiration = 7; + optional uint32 preview_item_def = 8 [default = 0]; + optional bool phone_verified = 19 [default = false]; + optional uint32 skill_rating_6v6 = 20; + optional uint32 skill_rating_9v9 = 21; + optional bool competitive_access = 23 [default = false]; + optional uint32 matchmaking_ranked_ban_expiration = 18; + optional uint32 matchmaking_ranked_low_priority_expiration = 24; + optional uint32 matchmaking_ranked_ban_last_duration = 25; + optional uint32 matchmaking_ranked_low_priority_last_duration = 26; + optional uint32 matchmaking_casual_ban_expiration = 27; + optional uint32 matchmaking_casual_low_priority_expiration = 28; + optional uint32 matchmaking_casual_ban_last_duration = 29; + optional uint32 matchmaking_casual_low_priority_last_duration = 30; + optional bool phone_identifying = 31 [default = false]; + optional bool disable_party_quest_progress = 32 [default = false]; + optional uint32 quest_reward_credits = 33; + optional uint32 matchmaking_last_casual_excessive_reports_auto_ban_time = 34; + optional uint32 matchmaking_last_comp_excessive_reports_auto_ban_time = 35; +} + +message CSOItemCriteriaCondition { + optional int32 op = 1; + optional string field = 2; + optional bool required = 3; + optional float float_value = 4; + optional string string_value = 5; +} + +message CSOItemCriteria { + optional uint32 item_level = 1; + optional int32 item_quality = 2; + optional bool item_level_set = 3; + optional bool item_quality_set = 4; + optional uint32 initial_inventory = 5; + optional uint32 initial_quantity = 6; + optional bool ignore_enabled_flag = 8; + repeated CSOItemCriteriaCondition conditions = 9; + optional bool recent_only = 10; + optional string tags = 11; + optional string equip_regions = 12; +} + +message CSOItemRecipe { + optional uint32 def_index = 1; + optional string name = 2; + optional string n_a = 3; + optional string desc_inputs = 4; + optional string desc_outputs = 5; + optional string di_a = 6; + optional string di_b = 7; + optional string di_c = 8; + optional string do_a = 9; + optional string do_b = 10; + optional string do_c = 11; + optional bool requires_all_same_class = 12; + optional bool requires_all_same_slot = 13; + optional int32 class_usage_for_output = 14; + optional int32 slot_usage_for_output = 15; + optional int32 set_for_output = 16; + repeated CSOItemCriteria input_items_criteria = 20; + repeated CSOItemCriteria output_items_criteria = 21; + repeated uint32 input_item_dupe_counts = 22; +} + +message CMsgDevNewItemRequest { + optional fixed64 receiver = 1; + optional CSOItemCriteria criteria = 2; +} + +message CMsgDevDebugRollLootRequest { + optional fixed64 receiver = 1; + optional string loot_list_name = 2; +} + +message CMsgIncrementKillCountAttribute { + optional uint64 killer_steam_id = 1; + optional uint64 victim_steam_id = 2; + optional uint64 item_id = 3; + optional uint32 event_type = 4; + optional uint32 increment_value = 5; +} + +message CMsgIncrementKillCountAttribute_Multiple { + repeated CMsgIncrementKillCountAttribute msgs = 1; +} + +message CMsgTrackUniquePlayerPairEvent { + optional uint64 killer_steam_id = 1; + optional uint64 victim_steam_id = 2; + optional uint64 item_id = 3; + optional uint32 event_type = 4; +} + +message CMsgApplyStrangeCountTransfer { + optional uint64 tool_item_id = 1; + optional uint64 item_src_item_id = 2; + optional uint64 item_dest_item_id = 3; +} + +message CMsgApplyStrangePart { + optional uint64 strange_part_item_id = 1; + optional uint64 item_item_id = 2; +} + +message CMsgApplyStrangeRestriction { + optional uint64 strange_part_item_id = 1; + optional uint64 item_item_id = 2; + optional uint32 strange_attr_index = 3; +} + +message CMsgApplyUpgradeCard { + optional uint64 upgrade_card_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CSOEconItemAttribute { + optional uint32 def_index = 1; + optional uint32 value = 2; + optional bytes value_bytes = 3; +} + +message CSOEconItemEquipped { + optional uint32 new_class = 1; + optional uint32 new_slot = 2; +} + +message CSOEconItem { + optional uint64 id = 1; + optional uint32 account_id = 2; + optional uint32 inventory = 3; + optional uint32 def_index = 4; + optional uint32 quantity = 5; + optional uint32 level = 6; + optional uint32 quality = 7; + optional uint32 flags = 8 [default = 0]; + optional uint32 origin = 9; + optional string custom_name = 10; + optional string custom_desc = 11; + repeated CSOEconItemAttribute attribute = 12; + optional CSOEconItem interior_item = 13; + optional bool in_use = 14 [default = false]; + optional uint32 style = 15 [default = 0]; + optional uint64 original_id = 16 [default = 0]; + optional bool contains_equipped_state = 17; + repeated CSOEconItemEquipped equipped_state = 18; + optional bool contains_equipped_state_v2 = 19; +} + +message CMsgAdjustItemEquippedState { + optional uint64 item_id = 1; + optional uint32 new_class = 2; + optional uint32 new_slot = 3; +} + +message CMsgSortItems { + optional uint32 sort_type = 1; +} + +message CSOEconClaimCode { + optional uint32 account_id = 1; + optional uint32 code_type = 2; + optional uint32 time_acquired = 3; + optional string code = 4; +} + +message CMsgStoreGetUserData { + optional fixed32 price_sheet_version = 1; +} + +message CMsgStoreGetUserDataResponse { + optional int32 result = 1; + optional int32 currency = 2; + optional string country = 3; + optional fixed32 price_sheet_version = 4; + optional uint64 experiment_data = 5 [default = 0]; + optional int32 featured_item_idx = 6; + optional bool show_hat_descriptions = 7 [default = true]; + optional bytes price_sheet = 8; + optional int32 default_item_sort = 9 [default = 0]; + repeated uint32 popular_items = 10; +} + +message CMsgUpdateItemSchema { + optional bytes items_game = 1; + optional fixed32 item_schema_version = 2; + optional string items_game_url = 3; + optional bytes signature = 4; +} + +message CMsgGCError { + optional string error_text = 1; +} + +message CMsgRequestInventoryRefresh { +} + +message CMsgConVarValue { + optional string name = 1; + optional string value = 2; +} + +message CMsgReplicateConVars { + repeated CMsgConVarValue convars = 1; +} + +message CMsgUseItem { + optional uint64 item_id = 1; + optional fixed64 target_steam_id = 2; + repeated uint32 gift__potential_targets = 3; + optional uint32 duel__class_lock = 4; + optional fixed64 initiator_steam_id = 5; + optional bool itempack__ack_immediately = 6; +} + +message CMsgReplayUploadedToYouTube { + optional string youtube_url = 1; + optional string youtube_account_name = 2; + optional uint64 session_id = 3; +} + +message CMsgConsumableExhausted { + optional int32 item_def_id = 1; +} + +message CMsgItemAcknowledged { + optional uint32 account_id = 1; + optional uint32 inventory = 2; + optional uint32 def_index = 3; + optional uint32 quality = 4; + optional uint32 rarity = 5; + optional uint32 origin = 6; + optional uint32 is_strange = 7; + optional uint32 is_unusual = 8; + optional float wear = 9; +} + +message CMsgSetPresetItemPosition { + optional uint32 class_id = 1; + optional uint32 preset_id = 2; + optional uint32 slot_id = 3; + optional uint64 item_id = 4; +} + +message CMsgSetItemPositions { + message ItemPosition { + optional uint64 item_id = 1; + optional uint32 position = 2; + } + + repeated CMsgSetItemPositions.ItemPosition item_positions = 1; +} + +message CSOEconItemPresetInstance { + optional uint32 class_id = 2 [(key_field) = true]; + optional uint32 preset_id = 3 [(key_field) = true]; + optional uint32 slot_id = 4 [(key_field) = true]; + optional uint64 item_id = 5; +} + +message CMsgSelectPresetForClass { + optional uint32 class_id = 1; + optional uint32 preset_id = 2; +} + +message CSOClassPresetClientData { + optional uint32 account_id = 1; + optional uint32 class_id = 2; + optional uint32 active_preset_id = 3; +} + +message CMsgGCReportAbuse { + optional fixed64 target_steam_id = 1; + optional string description = 4; + optional uint64 gid = 5; + optional uint32 abuse_type = 2; + optional uint32 content_type = 3; + optional fixed32 target_game_server_ip = 6; + optional uint32 target_game_server_port = 7; +} + +message CMsgGCReportAbuseResponse { + optional fixed64 target_steam_id = 1; + optional uint32 result = 2; + optional string error_message = 3; +} + +message CMsgGCNameItemNotification { + optional fixed64 player_steamid = 1; + optional uint32 item_def_index = 2; + optional string item_name_custom = 3; +} + +message CMsgGCClientDisplayNotification { + optional string notification_title_localization_key = 1; + optional string notification_body_localization_key = 2; + repeated string body_substring_keys = 3; + repeated string body_substring_values = 4; +} + +message CMsgGCShowItemsPickedUp { + optional fixed64 player_steamid = 1; +} + +message CMsgUpdatePeriodicEvent { + optional uint32 account_id = 1; + optional uint32 event_type = 2; + optional uint32 amount = 3; +} + +message CMsgGCIncrementKillCountResponse { + optional uint32 killer_account_id = 1 [(key_field) = true]; + optional uint32 num_kills = 2; + optional uint32 item_def = 3; + optional uint32 level_type = 4; +} + +message CMsgGCRemoveStrangePart { + optional uint64 item_id = 1; + optional uint32 strange_part_score_type = 2; +} + +message CMsgGCRemoveUpgradeCard { + optional uint64 item_id = 1; + optional uint32 attribute_index = 2; +} + +message CMsgGCRemoveCustomizationAttributeSimple { + optional uint64 item_id = 1; +} + +message CMsgGCResetStrangeScores { + optional uint64 item_id = 1; +} + +message CMsgGCItemPreviewItemBoughtNotification { + optional uint32 item_def_index = 1; +} + +message CMsgGCStorePurchaseCancel { + optional uint64 txn_id = 1; +} + +message CMsgGCStorePurchaseCancelResponse { + optional uint32 result = 1; +} + +message CMsgGCStorePurchaseFinalize { + optional uint64 txn_id = 1; +} + +message CMsgGCStorePurchaseFinalizeResponse { + optional uint32 result = 1; + repeated uint64 item_ids = 2; +} + +message CMsgGCBannedWordListRequest { + optional uint32 ban_list_group_id = 1; + optional uint32 word_id = 2; +} + +message CMsgGCGiftedItems { + optional uint64 gifter_steam_id = 1; + optional bool was_random_person = 2; + repeated uint32 recipient_account_ids = 3; +} + +message CMsgGCCollectItem { + optional uint64 collection_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CMsgGCClientMarketDataRequest { + optional uint32 user_currency = 1; +} + +message CMsgGCClientMarketDataEntry { + optional uint32 item_def_index = 1; + optional uint32 item_quality = 2; + optional uint32 item_sell_listings = 3; + optional uint32 price_in_local_currency = 4; +} + +message CMsgGCClientMarketData { + repeated CMsgGCClientMarketDataEntry entries = 1; +} + +message CMsgApplyToolToItem { + optional uint64 tool_item_id = 1; + optional uint64 subject_item_id = 2; +} + +message CMsgApplyToolToBaseItem { + optional uint64 tool_item_id = 1; + optional uint32 baseitem_def_index = 2; +} + +message CMsgRecipeComponent { + optional uint64 subject_item_id = 1; + optional uint64 attribute_index = 2; +} + +message CMsgFulfillDynamicRecipeComponent { + optional uint64 tool_item_id = 1; + repeated CMsgRecipeComponent consumption_components = 2; +} + +message CMsgSetItemEffectVerticalOffset { + optional uint64 item_id = 1; + optional float offset = 2; +} + +message CMsgSetHatEffectUseHeadOrigin { + optional uint64 item_id = 1; + optional bool use_head = 2; +} + +message CMsgDeliverGiftResponseGiver { + optional uint32 response_code = 1; + optional string receiver_account_name = 2; +} + +message CSOEconGameAccountForGameServers { + optional bool disable_party_quest_progress = 6 [default = false]; +} + +message CWorkshop_PopulateItemDescriptions_Request { + message SingleItemDescription { + optional uint32 gameitemid = 1; + optional string item_description = 2; + } + + message ItemDescriptionsLanguageBlock { + optional string language = 1; + repeated CWorkshop_PopulateItemDescriptions_Request.SingleItemDescription descriptions = 2; + } + + optional uint32 appid = 1; + repeated CWorkshop_PopulateItemDescriptions_Request.ItemDescriptionsLanguageBlock languages = 2; +} + +message CWorkshop_GetContributors_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; +} + +message CWorkshop_GetContributors_Response { + repeated fixed64 contributors = 1; +} + +message CWorkshop_SetItemPaymentRules_Request { + message WorkshopItemPaymentRule { + optional uint64 workshop_file_id = 1; + optional float revenue_percentage = 2; + optional string rule_description = 3; + } + + message WorkshopDirectPaymentRule { + optional uint64 workshop_file_id = 1; + optional string rule_description = 2; + } + + message PartnerItemPaymentRule { + optional uint32 account_id = 1; + optional float revenue_percentage = 2; + optional string rule_description = 3; + } + + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + repeated CWorkshop_SetItemPaymentRules_Request.WorkshopItemPaymentRule associated_workshop_files = 3; + repeated CWorkshop_SetItemPaymentRules_Request.PartnerItemPaymentRule partner_accounts = 4; + optional CWorkshop_SetItemPaymentRules_Request.WorkshopDirectPaymentRule associated_workshop_file_for_direct_payments = 7; +} + +message CWorkshop_SetItemPaymentRules_Response { +} + +message CWorkshop_AddSpecialPayment_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; + optional uint64 payment_us_usd = 4; + optional uint64 payment_row_usd = 5; +} + +message CWorkshop_AddSpecialPayment_Response { +} + +message CWorkshop_GetSpecialPayments_Request { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; +} + +message CWorkshop_GetSpecialPayments_Response { + message SpecialPayment { + optional uint32 appid = 1; + optional uint32 gameitemid = 2; + optional string date = 3; + optional uint64 net_payment_us_usd = 4; + optional uint64 net_payment_row_usd = 5; + } + + repeated CWorkshop_GetSpecialPayments_Response.SpecialPayment special_payments = 1; +} diff --git a/dll/gc_tf2/econ_gcmessages.proto b/dll/gc_tf2/econ_gcmessages.proto new file mode 100644 index 000000000..be1cecd9c --- /dev/null +++ b/dll/gc_tf2/econ_gcmessages.proto @@ -0,0 +1,370 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCItemMsg { + k_EMsgGCBase = 1000; + k_EMsgGCSetSingleItemPosition = 1001; + k_EMsgGCCraft = 1002; + k_EMsgGCCraftResponse = 1003; + k_EMsgGCDelete = 1004; + k_EMsgGCVerifyCacheSubscription = 1005; + k_EMsgGCNameItem = 1006; + k_EMsgGCUnlockCrate = 1007; + k_EMsgGCUnlockCrateResponse = 1008; + k_EMsgGCPaintItem = 1009; + k_EMsgGCPaintItemResponse = 1010; + k_EMsgGCGoldenWrenchBroadcast = 1011; + k_EMsgGCMOTDRequest = 1012; + k_EMsgGCMOTDRequestResponse = 1013; + k_EMsgGCNameBaseItem = 1019; + k_EMsgGCNameBaseItemResponse = 1020; + k_EMsgGCRemoveSocketItem_DEPRECATED = 1021; + k_EMsgGCRemoveSocketItemResponse_DEPRECATED = 1022; + k_EMsgGCCustomizeItemTexture = 1023; + k_EMsgGCCustomizeItemTextureResponse = 1024; + k_EMsgGCUseItemRequest = 1025; + k_EMsgGCUseItemResponse = 1026; + k_EMsgGCRespawnPostLoadoutChange = 1029; + k_EMsgGCRemoveItemName = 1030; + k_EMsgGCRemoveItemPaint = 1031; + k_EMsgGCGiftWrapItem = 1032; + k_EMsgGCGiftWrapItemResponse = 1033; + k_EMsgGCDeliverGift = 1034; + k_EMsgGCDeliverGiftResponseReceiver = 1036; + k_EMsgGCUnwrapGiftRequest = 1037; + k_EMsgGCUnwrapGiftResponse = 1038; + k_EMsgGCSetItemStyle = 1039; + k_EMsgGCUsedClaimCodeItem = 1040; + k_EMsgGCSortItems = 1041; + k_EMsgGC_RevolvingLootList_DEPRECATED = 1042; + k_EMsgGCLookupAccount = 1043; + k_EMsgGCLookupAccountResponse = 1044; + k_EMsgGCLookupAccountName = 1045; + k_EMsgGCLookupAccountNameResponse = 1046; + k_EMsgGCUpdateItemSchema = 1049; + k_EMsgGCRequestInventoryRefresh = 1050; + k_EMsgGCRemoveCustomTexture = 1051; + k_EMsgGCRemoveCustomTextureResponse = 1052; + k_EMsgGCRemoveMakersMark = 1053; + k_EMsgGCRemoveMakersMarkResponse = 1054; + k_EMsgGCRemoveUniqueCraftIndex = 1055; + k_EMsgGCRemoveUniqueCraftIndexResponse = 1056; + k_EMsgGCSaxxyBroadcast = 1057; + k_EMsgGCBackpackSortFinished = 1058; + k_EMsgGCAdjustItemEquippedState = 1059; + k_EMsgGCCollectItem = 1061; + k_EMsgGCItemAcknowledged = 1062; + k_EMsgGCPresets_SelectPresetForClass = 1063; + k_EMsgGCPresets_SetItemPosition = 1064; + k_EMsgGC_ReportAbuse = 1065; + k_EMsgGC_ReportAbuseResponse = 1066; + k_EMsgGCPresets_SelectPresetForClassReply = 1067; + k_EMsgGCNameItemNotification = 1068; + k_EMsgGCClientDisplayNotification = 1069; + k_EMsgGCApplyStrangePart = 1070; + k_EMsgGC_IncrementKillCountAttribute = 1071; + k_EMsgGC_IncrementKillCountResponse = 1072; + k_EMsgGCRemoveStrangePart = 1073; + k_EMsgGCResetStrangeScores = 1074; + k_EMsgGCGiftedItems = 1075; + k_EMsgGCApplyUpgradeCard = 1077; + k_EMsgGCRemoveUpgradeCard = 1078; + k_EMsgGCApplyStrangeRestriction = 1079; + k_EMsgGCClientRequestMarketData = 1080; + k_EMsgGCClientRequestMarketDataResponse = 1081; + k_EMsgGCApplyXifier = 1082; + k_EMsgGCApplyXifierResponse = 1083; + k_EMsgGC_TrackUniquePlayerPairEvent = 1084; + k_EMsgGCFulfillDynamicRecipeComponent = 1085; + k_EMsgGCFulfillDynamicRecipeComponentResponse = 1086; + k_EMsgGCSetItemEffectVerticalOffset = 1087; + k_EMsgGCSetHatEffectUseHeadOrigin = 1088; + k_EMsgGCItemEaterRecharger = 1089; + k_EMsgGCItemEaterRechargerResponse = 1090; + k_EMsgGCApplyBaseItemXifier = 1091; + k_EMsgGCApplyClassTransmogrifier = 1092; + k_EMsgGCApplyHalloweenSpellbookPage = 1093; + k_EMsgGCRemoveKillStreak = 1094; + k_EMsgGCRemoveKillStreakResponse = 1095; + k_EMsgGCTFSpecificItemBroadcast = 1096; + k_EMsgGC_IncrementKillCountAttribute_Multiple = 1097; + k_EMsgGCDeliverGiftResponseGiver = 1098; + k_EMsgGCSetItemPositions = 1100; + k_EMsgGCLookupMultipleAccountNames = 1101; + k_EMsgGCLookupMultipleAccountNamesResponse = 1102; + k_EMsgGCTradingBase = 1500; + k_EMsgGCTrading_InitiateTradeRequest = 1501; + k_EMsgGCTrading_InitiateTradeResponse = 1502; + k_EMsgGCTrading_StartSession = 1503; + k_EMsgGCTrading_SessionClosed = 1509; + k_EMsgGCTrading_CancelSession = 1510; + k_EMsgGCTrading_InitiateTradeRequestResponse = 1514; + k_EMsgGCServerBrowser_FavoriteServer = 1601; + k_EMsgGCServerBrowser_BlacklistServer = 1602; + k_EMsgGCServerRentalsBase = 1700; + k_EMsgGCItemPreviewCheckStatus = 1701; + k_EMsgGCItemPreviewStatusResponse = 1702; + k_EMsgGCItemPreviewRequest = 1703; + k_EMsgGCItemPreviewRequestResponse = 1704; + k_EMsgGCItemPreviewExpire = 1705; + k_EMsgGCItemPreviewExpireNotification = 1706; + k_EMsgGCItemPreviewItemBoughtNotification = 1708; + k_EMsgGCDev_NewItemRequest = 2001; + k_EMsgGCDev_NewItemRequestResponse = 2002; + k_EMsgGCDev_DebugRollLootRequest = 2003; + k_EMsgGCStoreGetUserData = 2500; + k_EMsgGCStoreGetUserDataResponse = 2501; + k_EMsgGCStorePurchaseInit_DEPRECATED = 2502; + k_EMsgGCStorePurchaseInitResponse_DEPRECATED = 2503; + k_EMsgGCStorePurchaseFinalize = 2512; + k_EMsgGCStorePurchaseFinalizeResponse = 2513; + k_EMsgGCStorePurchaseCancel = 2514; + k_EMsgGCStorePurchaseCancelResponse = 2515; + k_EMsgGCStorePurchaseQueryTxn = 2508; + k_EMsgGCStorePurchaseQueryTxnResponse = 2509; + k_EMsgGCStorePurchaseInit = 2510; + k_EMsgGCStorePurchaseInitResponse = 2511; + k_EMsgGCToGCDirtySDOCache = 2516; + k_EMsgGCToGCDirtyMultipleSDOCache = 2517; + k_EMsgGCToGCUpdateSQLKeyValue = 2518; + k_EMsgGCToGCBroadcastConsoleCommand = 2521; + k_EMsgGCServerVersionUpdated = 2522; + k_EMsgGCApplyAutograph = 2523; + k_EMsgGCToGCWebAPIAccountChanged = 2524; + k_EMsgGCRequestAnnouncements = 2525; + k_EMsgGCRequestAnnouncementsResponse = 2526; + k_EMsgGCRequestPassportItemGrant = 2527; + k_EMsgGCClientVersionUpdated = 2528; + k_EMsgGCItemPurgatory_FinalizePurchase = 2531; + k_EMsgGCItemPurgatory_FinalizePurchaseResponse = 2532; + k_EMsgGCItemPurgatory_RefundPurchase = 2533; + k_EMsgGCItemPurgatory_RefundPurchaseResponse = 2534; + k_EMsgGCToGCPlayerStrangeCountAdjustments = 2535; + k_EMsgGCRequestStoreSalesData = 2536; + k_EMsgGCRequestStoreSalesDataResponse = 2537; + k_EMsgGCRequestStoreSalesDataUpToDateResponse = 2538; + k_EMsgGCToGCPingRequest = 2539; + k_EMsgGCToGCPingResponse = 2540; + k_EMsgGCToGCGetUserSessionServer = 2541; + k_EMsgGCToGCGetUserSessionServerResponse = 2542; + k_EMsgGCToGCGetUserServerMembers = 2543; + k_EMsgGCToGCGetUserServerMembersResponse = 2544; + k_EMsgGCToGCGrantSelfMadeItemToAccount = 2555; + k_EMsgGCToGCThankedByNewUser = 2556; + k_EMsgGCShuffleCrateContents = 2557; + k_EMsgGCQuestObjective_Progress = 2558; + k_EMsgGCQuestCompleted = 2559; + k_EMsgGCApplyDuckToken = 2560; + k_EMsgGCQuestObjective_PointsChange = 2562; + k_EMsgGCQuestObjective_RequestLoanerItems = 2564; + k_EMsgGCQuestObjective_RequestLoanerResponse = 2565; + k_EMsgGCApplyStrangeCountTransfer = 2566; + k_EMsgGCCraftCollectionUpgrade = 2567; + k_EMsgGCCraftHalloweenOffering = 2568; + k_EMsgGCQuestDiscard_Request = 2569; + k_EMsgGCRemoveGiftedBy = 2570; + k_EMsgGCRemoveGiftedByResponse = 2571; + k_EMsgGCRemoveFestivizer = 2572; + k_EMsgGCRemoveFestivizerResponse = 2573; + k_EMsgGCCraftCommonStatClock = 2574; +} + +enum EGCMsgResponse { + k_EGCMsgResponseOK = 0; + k_EGCMsgResponseDenied = 1; + k_EGCMsgResponseServerError = 2; + k_EGCMsgResponseTimeout = 3; + k_EGCMsgResponseInvalid = 4; + k_EGCMsgResponseNoMatch = 5; + k_EGCMsgResponseUnknownError = 6; + k_EGCMsgResponseNotLoggedOn = 7; + k_EGCMsgFailedToCreate = 8; +} + +enum EUnlockStyle { + k_UnlockStyle_Succeeded = 0; + k_UnlockStyle_Failed_PreReq = 1; + k_UnlockStyle_Failed_CantAfford = 2; + k_UnlockStyle_Failed_CantCommit = 3; + k_UnlockStyle_Failed_CantLockCache = 4; + k_UnlockStyle_Failed_CantAffordAttrib = 5; + k_UnlockStyle_Failed_CantAffordGem = 6; +} + +enum EItemPurgatoryResponse_Finalize { + k_ItemPurgatoryResponse_Finalize_Succeeded = 0; + k_ItemPurgatoryResponse_Finalize_Failed_Incomplete = 1; + k_ItemPurgatoryResponse_Finalize_Failed_ItemsNotInPurgatory = 2; + k_ItemPurgatoryResponse_Finalize_Failed_CouldNotFindItems = 3; + k_ItemPurgatoryResponse_Finalize_Failed_NoSOCache = 4; + k_ItemPurgatoryResponse_Finalize_BackpackFull = 5; +} + +enum EItemPurgatoryResponse_Refund { + k_ItemPurgatoryResponse_Refund_Succeeded = 0; + k_ItemPurgatoryResponse_Refund_Failed_ItemNotInPurgatory = 1; + k_ItemPurgatoryResponse_Refund_Failed_CouldNotFindItem = 2; + k_ItemPurgatoryResponse_Refund_Failed_NoSOCache = 3; + k_ItemPurgatoryResponse_Refund_Failed_NoDetail = 4; + k_ItemPurgatoryResponse_Refund_Failed_NexonWebAPI = 5; +} + +message CMsgApplyAutograph { + optional uint64 autograph_item_id = 1; + optional uint64 item_item_id = 2; +} + +message CMsgEconPlayerStrangeCountAdjustment { + message CStrangeCountAdjustment { + optional uint32 event_type = 1; + optional uint64 item_id = 2; + optional uint32 adjustment = 3; + } + + optional uint32 account_id = 1; + repeated CMsgEconPlayerStrangeCountAdjustment.CStrangeCountAdjustment strange_count_adjustments = 2; +} + +message CMsgRequestItemPurgatory_FinalizePurchase { + repeated uint64 item_ids = 1; +} + +message CMsgRequestItemPurgatory_FinalizePurchaseResponse { + optional uint32 result = 1; +} + +message CMsgRequestItemPurgatory_RefundPurchase { + optional uint64 item_id = 1; +} + +message CMsgRequestItemPurgatory_RefundPurchaseResponse { + optional uint32 result = 1; +} + +message CMsgCraftingResponse { + repeated uint64 item_ids = 1; +} + +message CMsgGCRequestStoreSalesData { + optional uint32 version = 1; + optional uint32 currency = 2; +} + +message CMsgGCRequestStoreSalesDataResponse { + message Price { + optional uint32 item_def = 1; + optional uint32 price = 2; + } + + repeated CMsgGCRequestStoreSalesDataResponse.Price sale_price = 1; + optional uint32 version = 2; + optional uint32 expiration_time = 3; +} + +message CMsgGCRequestStoreSalesDataUpToDateResponse { + optional uint32 version = 1; + optional uint32 expiration_time = 2; +} + +message CMsgGCToGCPingRequest { +} + +message CMsgGCToGCPingResponse { +} + +message CMsgGCToGCGetUserSessionServer { + optional uint32 account_id = 1; +} + +message CMsgGCToGCGetUserSessionServerResponse { + optional fixed64 server_steam_id = 1; +} + +message CMsgGCToGCGetUserServerMembers { + optional uint32 account_id = 1; + optional uint32 max_spectators = 2; +} + +message CMsgGCToGCGetUserServerMembersResponse { + repeated uint32 member_account_id = 1; +} + +message CMsgLookupMultipleAccountNames { + repeated uint32 accountids = 1 [packed = true]; +} + +message CMsgLookupMultipleAccountNamesResponse { + message Account { + optional uint32 accountid = 1; + optional string persona = 2; + } + + repeated CMsgLookupMultipleAccountNamesResponse.Account accounts = 1; +} + +message CMsgGCToGCGrantSelfMadeItemToAccount { + optional uint32 item_def_index = 1; + optional uint32 accountid = 2; +} + +message CMsgGCToGCThankedByNewUser { + optional uint32 new_user_accountid = 1; + optional uint32 thanked_user_accountid = 2; +} + +message CMsgGCShuffleCrateContents { + optional uint64 crate_item_id = 1; + optional string user_code_string = 2; +} + +message CMsgGCQuestObjective_Progress { + optional uint64 quest_id = 1; + optional uint32 quest_attrib_index = 2; + optional uint32 delta = 3; + optional fixed64 owner_steamid = 4; +} + +message CMsgGCQuestObjective_PointsChange { + optional uint64 quest_id = 1; + optional fixed64 owner_steamid = 4; + optional bool update_base_points = 5 [default = false]; + optional uint32 points_0 = 6; + optional uint32 points_1 = 7; + optional uint32 points_2 = 8; +} + +message CMsgGCQuestComplete_Request { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestCompleted { +} + +message CMsgGCQuestObjective_RequestLoanerItems { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestObjective_RequestLoanerResponse { +} + +message CMsgCraftCollectionUpgrade { + repeated uint64 item_id = 1; +} + +message CMsgCraftHalloweenOffering { + optional uint64 tool_id = 1; + repeated uint64 item_id = 2; +} + +message CMsgCraftCommonStatClock { + optional uint64 tool_id = 1; + repeated uint64 item_id = 2; +} + +message CMsgGCQuestDiscard_Request { + optional uint64 quest_id = 1; +} diff --git a/dll/gc_tf2/gcsdk_gcmessages.proto b/dll/gc_tf2/gcsdk_gcmessages.proto new file mode 100644 index 000000000..9fea4f735 --- /dev/null +++ b/dll/gc_tf2/gcsdk_gcmessages.proto @@ -0,0 +1,188 @@ +import "steammessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum PartnerAccountType { + PARTNER_NONE = 0; + PARTNER_PERFECT_WORLD = 1; + PARTNER_NEXON = 2; +} + +enum GCConnectionStatus { + GCConnectionStatus_HAVE_SESSION = 0; + GCConnectionStatus_GC_GOING_DOWN = 1; + GCConnectionStatus_NO_SESSION = 2; + GCConnectionStatus_NO_SESSION_IN_LOGON_QUEUE = 3; + GCConnectionStatus_NO_STEAM = 4; + GCConnectionStatus_SUSPENDED = 5; +} + +message CMsgSOIDOwner { + optional uint32 type = 1; + optional uint64 id = 2; +} + +message CMsgSOSingleObject { + optional fixed64 owner = 1; + optional int32 type_id = 2; + optional bytes object_data = 3; + optional fixed64 version = 4; + optional CMsgSOIDOwner owner_soid = 5; + optional uint32 service_id = 6; +} + +message CMsgSOMultipleObjects { + message SingleObject { + optional int32 type_id = 1; + optional bytes object_data = 2; + } + + optional fixed64 owner = 1; + repeated CMsgSOMultipleObjects.SingleObject objects = 2; + optional fixed64 version = 3; + optional CMsgSOIDOwner owner_soid = 6; + optional uint32 service_id = 7; +} + +message CMsgSOCacheSubscribed { + message SubscribedType { + optional int32 type_id = 1; + repeated bytes object_data = 2; + } + + optional fixed64 owner = 1; + repeated CMsgSOCacheSubscribed.SubscribedType objects = 2; + optional fixed64 version = 3; + optional CMsgSOIDOwner owner_soid = 4; + optional uint32 service_id = 5; + repeated uint32 service_list = 6; + optional fixed64 sync_version = 7; +} + +message CMsgSOCacheSubscribedUpToDate { + optional fixed64 version = 1; + optional CMsgSOIDOwner owner_soid = 2; + optional uint32 service_id = 3; + repeated uint32 service_list = 4; + optional fixed64 sync_version = 5; +} + +message CMsgSOCacheUnsubscribed { + optional fixed64 owner = 1; +} + +message CMsgSOCacheSubscriptionCheck { + optional fixed64 owner = 1; + optional fixed64 version = 2; + optional CMsgSOIDOwner owner_soid = 3; + optional uint32 service_id = 4; + repeated uint32 service_list = 5; + optional fixed64 sync_version = 6; +} + +message CMsgSOCacheSubscriptionRefresh { + optional fixed64 owner = 1; + optional CMsgSOIDOwner owner_soid = 2; +} + +message CMsgSOCacheVersion { + optional fixed64 version = 1; +} + +message CMsgGCMultiplexMessage { + optional uint32 msgtype = 1; + optional bytes payload = 2; + repeated fixed64 steamids = 3; +} + +message CGCToGCMsgMasterAck { + optional uint32 dir_index = 1; + optional string machine_name = 3; + optional string process_name = 4; + repeated uint32 type_instances = 5; +} + +message CGCToGCMsgMasterAck_Response { + optional int32 eresult = 1 [default = 2]; +} + +message CGCToGCMsgMasterStartupComplete { + message GCInfo { + optional uint32 dir_index = 1; + optional string machine_name = 2; + } + + repeated CGCToGCMsgMasterStartupComplete.GCInfo gc_info = 1; +} + +message CGCToGCMsgRouted { + optional uint32 msg_type = 1; + optional fixed64 sender_id = 2; + optional bytes net_message = 3; +} + +message CGCToGCMsgRoutedReply { + optional uint32 msg_type = 1; + optional bytes net_message = 2; +} + +message CMsgGCUpdateSubGCSessionInfo { + message CMsgUpdate { + optional fixed64 steamid = 1; + optional fixed32 ip = 2; + optional bool trusted = 3; + } + + repeated CMsgGCUpdateSubGCSessionInfo.CMsgUpdate updates = 1; +} + +message CMsgGCRequestSubGCSessionInfo { + optional fixed64 steamid = 1; +} + +message CMsgGCRequestSubGCSessionInfoResponse { + optional fixed32 ip = 1; + optional bool trusted = 2; +} + +message CMsgGCToGCIncrementRecruitmentLevel { + optional fixed64 steamid = 1; +} + +message CMsgSOCacheHaveVersion { + optional CMsgSOIDOwner soid = 1; + optional fixed64 version = 2; + optional uint32 service_id = 3; +} + +message CMsgConnectionStatus { + optional GCConnectionStatus status = 1 [default = GCConnectionStatus_HAVE_SESSION]; + optional uint32 client_session_need = 2; + optional int32 queue_position = 3; + optional int32 queue_size = 4; + optional int32 wait_seconds = 5; + optional int32 estimated_wait_seconds_remaining = 6; +} + +message CMsgGCToGCSOCacheSubscribe { + message CMsgHaveVersions { + optional uint32 service_id = 1; + optional uint64 version = 2; + } + + optional fixed64 subscriber = 1; + optional fixed64 subscribe_to = 2; + optional fixed64 sync_version = 3; + repeated CMsgGCToGCSOCacheSubscribe.CMsgHaveVersions have_versions = 4; +} + +message CMsgGCToGCSOCacheUnsubscribe { + optional fixed64 subscriber = 1; + optional fixed64 unsubscribe_from = 2; +} + +message CMsgGCClientPing { +} diff --git a/dll/gc_tf2/gcsystemmsgs.proto b/dll/gc_tf2/gcsystemmsgs.proto new file mode 100644 index 000000000..e30f3840e --- /dev/null +++ b/dll/gc_tf2/gcsystemmsgs.proto @@ -0,0 +1,180 @@ +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum EGCSystemMsg { + k_EGCMsgInvalid = 0; + k_EGCMsgMulti = 1; + k_EGCMsgGenericReply = 10; + k_EGCMsgSystemBase = 50; + k_EGCMsgAchievementAwarded = 51; + k_EGCMsgConCommand = 52; + k_EGCMsgStartPlaying = 53; + k_EGCMsgStopPlaying = 54; + k_EGCMsgStartGameserver = 55; + k_EGCMsgStopGameserver = 56; + k_EGCMsgWGRequest = 57; + k_EGCMsgWGResponse = 58; + k_EGCMsgGetUserGameStatsSchema = 59; + k_EGCMsgGetUserGameStatsSchemaResponse = 60; + k_EGCMsgGetUserStatsDEPRECATED = 61; + k_EGCMsgGetUserStatsResponse = 62; + k_EGCMsgAppInfoUpdated = 63; + k_EGCMsgValidateSession = 64; + k_EGCMsgValidateSessionResponse = 65; + k_EGCMsgLookupAccountFromInput = 66; + k_EGCMsgSendHTTPRequest = 67; + k_EGCMsgSendHTTPRequestResponse = 68; + k_EGCMsgPreTestSetup = 69; + k_EGCMsgRecordSupportAction = 70; + k_EGCMsgGetAccountDetails_DEPRECATED = 71; + k_EGCMsgReceiveInterAppMessage = 73; + k_EGCMsgFindAccounts = 74; + k_EGCMsgPostAlert = 75; + k_EGCMsgGetLicenses = 76; + k_EGCMsgGetUserStats = 77; + k_EGCMsgGetCommands = 78; + k_EGCMsgGetCommandsResponse = 79; + k_EGCMsgAddFreeLicense = 80; + k_EGCMsgAddFreeLicenseResponse = 81; + k_EGCMsgGetIPLocation = 82; + k_EGCMsgGetIPLocationResponse = 83; + k_EGCMsgSystemStatsSchema = 84; + k_EGCMsgGetSystemStats = 85; + k_EGCMsgGetSystemStatsResponse = 86; + k_EGCMsgSendEmail = 87; + k_EGCMsgSendEmailResponse = 88; + k_EGCMsgGetEmailTemplate = 89; + k_EGCMsgGetEmailTemplateResponse = 90; + k_EGCMsgGrantGuestPass = 91; + k_EGCMsgGrantGuestPassResponse = 92; + k_EGCMsgGetAccountDetails = 93; + k_EGCMsgGetAccountDetailsResponse = 94; + k_EGCMsgGetPersonaNames = 95; + k_EGCMsgGetPersonaNamesResponse = 96; + k_EGCMsgMultiplexMsg = 97; + k_EGCMsgWebAPIRegisterInterfaces = 101; + k_EGCMsgWebAPIJobRequest = 102; + k_EGCMsgWebAPIJobRequestHttpResponse = 104; + k_EGCMsgWebAPIJobRequestForwardResponse = 105; + k_EGCMsgMemCachedGet = 200; + k_EGCMsgMemCachedGetResponse = 201; + k_EGCMsgMemCachedSet = 202; + k_EGCMsgMemCachedDelete = 203; + k_EGCMsgMemCachedStats = 204; + k_EGCMsgMemCachedStatsResponse = 205; + k_EGCMsgSQLStats = 210; + k_EGCMsgSQLStatsResponse = 211; + k_EGCMsgMasterSetDirectory = 220; + k_EGCMsgMasterSetDirectoryResponse = 221; + k_EGCMsgMasterSetWebAPIRouting = 222; + k_EGCMsgMasterSetWebAPIRoutingResponse = 223; + k_EGCMsgMasterSetClientMsgRouting = 224; + k_EGCMsgMasterSetClientMsgRoutingResponse = 225; + k_EGCMsgSetOptions = 226; + k_EGCMsgSetOptionsResponse = 227; + k_EGCMsgSystemBase2 = 500; + k_EGCMsgGetPurchaseTrustStatus = 501; + k_EGCMsgGetPurchaseTrustStatusResponse = 502; + k_EGCMsgUpdateSession = 503; + k_EGCMsgGCAccountVacStatusChange = 504; + k_EGCMsgCheckFriendship = 505; + k_EGCMsgCheckFriendshipResponse = 506; + k_EGCMsgGetPartnerAccountLink = 507; + k_EGCMsgGetPartnerAccountLinkResponse = 508; + k_EGCMsgVSReportedSuspiciousActivity = 509; + k_EGCMsgAccountTradeBanStatusChange = 510; + k_EGCMsgAccountLockStatusChange = 511; + k_EGCMsgDPPartnerMicroTxns = 512; + k_EGCMsgDPPartnerMicroTxnsResponse = 513; + k_EGCMsgGetIPASN = 514; + k_EGCMsgGetIPASNResponse = 515; + k_EGCMsgGetAppFriendsList = 516; + k_EGCMsgGetAppFriendsListResponse = 517; + k_EGCMsgVacVerificationChange = 518; + k_EGCMsgAccountPhoneNumberChange = 519; + k_EGCMsgAccountTwoFactorChange = 520; + k_EGCMsgCheckClanMembership = 521; + k_EGCMsgCheckClanMembershipResponse = 522; + k_EGCMsgInviteUserToLobby = 523; + k_EGCMsgGetGamePersonalDataCategoriesRequest = 524; + k_EGCMsgGetGamePersonalDataCategoriesResponse = 525; + k_EGCMsgGetGamePersonalDataEntriesRequest = 526; + k_EGCMsgGetGamePersonalDataEntriesResponse = 527; + k_EGCMsgTerminateGamePersonalDataEntriesRequest = 528; + k_EGCMsgTerminateGamePersonalDataEntriesResponse = 529; +} + +enum ESOMsg { + k_ESOMsg_Create = 21; + k_ESOMsg_Update = 22; + k_ESOMsg_Destroy = 23; + k_ESOMsg_CacheSubscribed = 24; + k_ESOMsg_CacheUnsubscribed = 25; + k_ESOMsg_UpdateMultiple = 26; + k_ESOMsg_CacheSubscriptionCheck = 27; + k_ESOMsg_CacheSubscriptionRefresh = 28; + k_ESOMsg_CacheSubscribedUpToDate = 29; +} + +enum EGCBaseClientMsg { + k_EMsgGCPingRequest = 3001; + k_EMsgGCPingResponse = 3002; + k_EMsgGCClientWelcome = 4004; + k_EMsgGCServerWelcome = 4005; + k_EMsgGCClientHello = 4006; + k_EMsgGCServerHello = 4007; + k_EMsgGCClientGoodbye = 4008; + k_EMsgGCServerGoodbye = 4009; +} + +enum EGCToGCMsg { + k_EGCToGCMsgMasterAck = 150; + k_EGCToGCMsgMasterAckResponse = 151; + k_EGCToGCMsgRouted = 152; + k_EGCToGCMsgRoutedReply = 153; + k_EMsgGCUpdateSubGCSessionInfo = 154; + k_EMsgGCRequestSubGCSessionInfo = 155; + k_EMsgGCRequestSubGCSessionInfoResponse = 156; + k_EGCToGCMsgMasterStartupComplete = 157; + k_EMsgGCToGCSOCacheSubscribe = 158; + k_EMsgGCToGCSOCacheUnsubscribe = 159; +} + +message CCommunity_GamePersonalDataCategoryInfo { + optional string type = 1; + optional string localization_token = 2; + optional string template_file = 3; +} + +message CCommunity_GetGamePersonalDataCategories_Request { + optional uint32 appid = 1; +} + +message CCommunity_GetGamePersonalDataCategories_Response { + repeated CCommunity_GamePersonalDataCategoryInfo categories = 1; + optional string app_assets_basename = 2; +} + +message CCommunity_GetGamePersonalDataEntries_Request { + optional uint32 appid = 1; + optional uint64 steamid = 2; + optional string type = 3; + optional string continue_token = 4; +} + +message CCommunity_GetGamePersonalDataEntries_Response { + optional uint32 gceresult = 1; + repeated string entries = 2; + optional string continue_token = 3; +} + +message CCommunity_TerminateGamePersonalDataEntries_Request { + optional uint32 appid = 1; + optional uint64 steamid = 2; +} + +message CCommunity_TerminateGamePersonalDataEntries_Response { + optional uint32 gceresult = 1; +} diff --git a/dll/gc_tf2/tf_gcmessages.proto b/dll/gc_tf2/tf_gcmessages.proto new file mode 100644 index 000000000..29bc0e78a --- /dev/null +++ b/dll/gc_tf2/tf_gcmessages.proto @@ -0,0 +1,1650 @@ +import "steammessages.proto"; +import "base_gcmessages.proto"; + +option optimize_for = SPEED; +option cc_generic_services = false; + +package gamecoordinator.tf2; + +enum ETFGCMsg { + k_EMsgGCReportWarKill = 5001; + k_EMsgGCKickPlayer_DEPRECATED = 5020; + k_EMsgGCStartedTraining_DEPRECATED = 5021; + k_EMsgGCFreeTrial_ChooseMostHelpfulFriend = 5022; + k_EMsgGCRequestTF2Friends = 5023; + k_EMsgGCRequestTF2FriendsResponse = 5024; + k_EMsgGCReplay_SubmitContestEntry = 5026; + k_EMsgGCReplay_SubmitContestEntryResponse = 5027; + k_EMsgGCSaxxy_Awarded = 5029; + k_EMsgGCFreeTrial_ThankedBySomeone = 5028; + k_EMsgGCFreeTrial_ThankedSomeone = 5030; + k_EMsgGCFreeTrial_ConvertedToPremium = 5031; + k_EMsgGCMeetThePyroSilliness_BananaCraft_DEPRECATED = 5032; + k_EMsgGCMVMARG_HighFiveSuccessResponse_DEPRECATED = 5033; + k_EMsgGCMVMARG_HighFiveOnClient_DEPRECATED = 5034; + k_EMsgGCCoaching_AddToCoaches = 5200; + k_EMsgGCCoaching_AddToCoachesResponse = 5201; + k_EMsgGCCoaching_RemoveFromCoaches = 5202; + k_EMsgGCCoaching_RemoveFromCoachesResponse = 5203; + k_EMsgGCCoaching_FindCoach = 5204; + k_EMsgGCCoaching_FindCoachResponse = 5205; + k_EMsgGCCoaching_AskCoach = 5206; + k_EMsgGCCoaching_AskCoachResponse = 5207; + k_EMsgGCCoaching_CoachJoinGame = 5208; + k_EMsgGCCoaching_CoachJoining = 5209; + k_EMsgGCCoaching_CoachJoined = 5210; + k_EMsgGCCoaching_LikeCurrentCoach = 5211; + k_EMsgGCCoaching_RemoveCurrentCoach = 5212; + k_EMsgGCCoaching_AlreadyRatedCoach = 5213; + k_EMsgGC_Duel_Request = 5500; + k_EMsgGC_Duel_Response = 5501; + k_EMsgGC_Duel_Results = 5502; + k_EMsgGC_Duel_Status = 5503; + k_EMsgGC_Halloween_ReservedItem_DEPRECATED = 5600; + k_EMsgGC_Halloween_GrantItem_DEPRECATED = 5601; + k_EMsgGC_Halloween_GrantItemResponse_DEPRECATED = 5604; + k_EMsgGC_Halloween_Cheat_QueryResponse_DEPRECATED = 5605; + k_EMsgGC_Halloween_ItemClaimed_DEPRECATED = 5606; + k_EMsgGC_Halloween_ReservedItem = 5607; + k_EMsgGC_Halloween_GrantItem = 5608; + k_EMsgGC_Halloween_GrantItemResponse = 5609; + k_EMsgGC_Halloween_Cheat_QueryResponse_DEPRECATED_2 = 5610; + k_EMsgGC_Halloween_ItemClaimed_DEPRECATED_2 = 5611; + k_EMsgGC_Halloween_ServerBossEvent = 5612; + k_EMsgGC_Halloween_Merasmus2012 = 5613; + k_EMsgGC_Halloween_UpdateMerasmusLootLevel = 5614; + k_EMsgGC_GameServer_LevelInfo = 5700; + k_EMsgGC_GameServer_AuthChallenge = 5701; + k_EMsgGC_GameServer_AuthChallengeResponse = 5702; + k_EMsgGC_GameServer_CreateIdentity = 5703; + k_EMsgGC_GameServer_CreateIdentityResponse = 5704; + k_EMsgGC_GameServer_List = 5705; + k_EMsgGC_GameServer_ListResponse = 5706; + k_EMsgGC_GameServer_AuthResult = 5707; + k_EMsgGC_GameServer_ResetIdentity = 5708; + k_EMsgGC_GameServer_ResetIdentityResponse = 5709; + k_EMsgGC_Client_UseServerModificationItem = 5710; + k_EMsgGC_Client_UseServerModificationItem_Response = 5711; + k_EMsgGC_GameServer_UseServerModificationItem = 5712; + k_EMsgGC_GameServer_UseServerModificationItem_Response = 5713; + k_EMsgGC_GameServer_ServerModificationItemExpired = 5714; + k_EMsgGC_GameServer_ModificationItemState = 5715; + k_EMsgGC_GameServer_AckPolicy = 5716; + k_EMsgGC_GameServer_AckPolicyResponse = 5717; + k_EMsgGC_QP_ScoreServers = 5800; + k_EMsgGC_QP_ScoreServersResponse = 5801; + k_EMsgGC_QP_PlayerJoining = 5802; + k_EMsgGC_PickupItemEligibility_Query_DEPRECATED = 6000; + k_EMsgGC_PickupItemEligibility_Query_DEPRECATED_2 = 6001; + k_EMsgGC_IncrementKillCountAttribute_DEPRECATED = 6100; + k_EMsgGC_IncrementKillCountResponse_DEPRECATED = 6101; + k_EMsgGCAbandonCurrentGame = 6235; + k_EMsgForceSOCacheResend = 6237; + k_EMsgGCRequestChatChannelList = 6260; + k_EMsgGCRequestChatChannelListResponse = 6261; + k_EMsgGCReadyUp = 6270; + k_EMsgGCKickedFromMatchmakingQueue = 6271; + k_EMsgGCLeaverDetected = 6272; + k_EMsgGCLeaverDetectedResponse = 6287; + k_EMsgGCExitMatchmaking = 6289; + k_EMsgGCMatchmakingProgress = 6293; + k_EMsgGCMvMVictoryInfo = 6294; + k_EMsgGCGameServerMatchmakingStatus = 6295; + k_EMsgGCMvMVictory = 6297; + k_EMsgGCMvMVictoryReply = 6298; + k_EMsgGCGameServerKickingLobby = 6299; + k_EMsgGCLeaveGameAndPrepareToJoinParty = 6300; + k_EMsgGC_UpdatePeriodicEvent = 6400; + k_EMsgGC_DuckLeaderboard_IndividualUpdate = 6401; + k_EMsgGC_Client2GCEconPreviewDataBlockRequest = 6402; + k_EMsgGC_Client2GCEconPreviewDataBlockResponse = 6403; + k_EMsgGC_ClientVerificationChallenge = 6500; + k_EMsgGC_ClientVerificationChallengeResponse = 6501; + k_EMsgGC_ClientVerificationVerboseResponse = 6502; + k_EMsgGC_ClientSetItemSlotAttribute = 6503; + k_EMsgGC_War_IndividualUpdate = 6505; + k_EMsgGC_War_JoinWar = 6506; + k_EMsgGC_War_RequestGlobalStats = 6507; + k_EMsgGC_War_GlobalStatsResponse = 6508; + k_EMsgGC_WorldItemPlacement_Attribute = 6510; + k_EMsgGC_WorldItemPlacement_Update = 6511; + k_EMsgGC_Match_Result = 6512; + k_EMsgGCVoteKickPlayerRequest = 6513; + k_EMsgGCVoteKickPlayerRequestResponse = 6514; + k_EMsgGC_DailyCompetitiveStatsRollup = 6516; + k_EMsgGC_DailyCompetitiveStatsRollup_Response = 6517; + k_EMsgGC_WorldStatusBroadcast = 6518; + k_EMsgGC_ReportPlayer = 6519; + k_EMsgGC_Match_ResultResponse = 6520; + k_EMsgGCGameServerKickingLobbyResponse = 6521; + k_EMsgGCPlayerLeftMatch = 6522; + k_EMsgGCPlayerLeftMatchResponse = 6523; + k_EMsgGCRequestMatchMakerStats = 6524; + k_EMsgGCMatchMakerStatsResponse = 6525; + k_EMsgGCMatchHistoryLoad = 6526; + k_EMsgGC_AcknowledgeXP = 6527; + k_EMsgGCDataCenterPing_Update = 6528; + k_EMsgGC_NotificationAcknowledge = 6529; + k_EMsgGC_NotificationAcknowledgeReply = 6530; + k_EMsgGC_KickPlayerFromLobby = 6531; + k_EMsgGC_SurveyQuestionRequest = 6534; + k_EMsgGC_SurveyQuestionResponse = 6535; + k_EMsgGC_TFClientInit = 6536; + k_EMsgGC_NewMatchForLobbyRequest = 6537; + k_EMsgGC_NewMatchForLobbyResponse = 6538; + k_EMsgGC_ChangeMatchPlayerTeamsRequest = 6539; + k_EMsgGC_ChangeMatchPlayerTeamsResponse = 6540; + k_EMsgGC_QuestIdentify = 6541; + k_EMsgGC_QuestDevGive = 6542; + k_EMsgGCQuestComplete_Debug = 6544; + k_EMsgGC_QuestMapDebug = 6545; + k_EMsgGC_QuestMapUnlockNode = 6547; + k_EMsgGC_QuestMapPurchaseReward = 6549; + k_EMsgGC_SetDisablePartyQuestProgress = 6550; + k_EMsgGCQuestProgressReport = 6553; + k_EMsgGCParty_SetOptions = 6554; + k_EMsgGCParty_SetOptionsResponse = 6555; + k_EMsgGCParty_QueueForMatch = 6556; + k_EMsgGCParty_QueueForMatchResponse = 6557; + k_EMsgGCParty_RemoveFromQueue = 6558; + k_EMsgGCParty_RemoveFromQueueResponse = 6559; + k_EMsgGCParty_InvitePlayer = 6560; + k_EMsgGCParty_RequestJoinPlayer = 6561; + k_EMsgGCParty_SendChat = 6562; + k_EMsgGCParty_ChatMsg = 6563; + k_EMsgGCQuestNodeTurnIn = 6564; + k_EMsgGCConsumePaintKit = 6565; + k_EMsgGC_Painkit_DevGrant = 6566; + k_EMsgGCParty_QueueForStandby = 6567; + k_EMsgGCParty_QueueForStandbyResponse = 6568; + k_EMsgGCParty_RemoveFromStandbyQueue = 6569; + k_EMsgGCParty_RemoveFromStandbyQueueResponse = 6570; + k_EMsgGCParty_ClearPendingPlayer = 6571; + k_EMsgGCParty_ClearPendingPlayerResponse = 6572; + k_EMsgGCParty_ClearOtherPartyRequest = 6573; + k_EMsgGCParty_ClearOtherPartyRequestResponse = 6574; + k_EMsgGCParty_PromoteToLeader = 6575; + k_EMsgGCParty_KickMember = 6576; + k_EMsgGCQuestStrangeEvent = 6577; + k_EMsgGC_AcceptLobbyInvite = 6578; + k_EMsgGC_AcceptLobbyInviteReply = 6579; + k_EMsgGC_SDRTicket = 6580; + k_EMsgGC_ProcessMatchVoteKick = 6581; + k_EMsgGC_ProcessMatchVoteKickResponse = 6582; + k_EMsgGCToGC_SendAccountBannedNotifications = 6584; + k_EMsgGCToGC_SendNotification = 6585; + k_EMsgGCParty_MMError = 6586; + k_EMsgGCDev_GrantWarKill = 10001; +} + +enum EServerModificationItemType { + kGameServerModificationItem_Halloween = 1; +} + +enum ETFSyncedMMMenuStep { + k_eTFSyncedMMMenuStep_Invalid = -1; + k_eTFSyncedMMMenuStep_None = 0; + k_eTFSyncedMMMenuStep_Configuring_Mode = 1; + k_eTFSyncedMMMenuStep_MvM_Selecting_Mode = 2; + k_eTFSyncedMMMenuStep_MvM_Selecting_Tour = 3; + k_eTFSyncedMMMenuStep_MvM_Selecting_Missions = 4; +} + +enum ETFMatchGroup { + option allow_alias = true; + k_eTFMatchGroup_Invalid = -1; + k_eTFMatchGroup_MvM_Practice = 0; + k_eTFMatchGroup_MvM_MannUp = 1; + k_eTFMatchGroup_First = 0; + k_eTFMatchGroup_MvM_Default = 0; + k_eTFMatchGroup_MvM_First = 0; + k_eTFMatchGroup_MvM_Last = 1; + k_eTFMatchGroup_Ladder_6v6 = 2; + k_eTFMatchGroup_Ladder_9v9 = 3; + k_eTFMatchGroup_Ladder_12v12 = 4; + k_eTFMatchGroup_Ladder_Default = 2; + k_eTFMatchGroup_Ladder_First = 2; + k_eTFMatchGroup_Ladder_Last = 4; + k_eTFMatchGroup_Casual_6v6 = 5; + k_eTFMatchGroup_Casual_9v9 = 6; + k_eTFMatchGroup_Casual_12v12 = 7; + k_eTFMatchGroup_Casual_Default = 7; + k_eTFMatchGroup_Casual_First = 5; + k_eTFMatchGroup_Casual_Last = 7; + k_eTFMatchGroup_Event_Placeholder = 8; + k_eTFMatchGroup_Event_Default = 8; + k_eTFMatchGroup_Event_First = 8; + k_eTFMatchGroup_Event_Last = 8; +} + +enum ETFPartyChatType { + k_eTFPartyChatType_Invalid = 0; + k_eTFPartyChatType_MemberChat = 1; + k_eTFPartyChatType_Synthetic_MemberJoin = 1000; + k_eTFPartyChatType_Synthetic_MemberLeave = 1001; + k_eTFPartyChatType_Synthetic_SendFailed = 1002; + k_eTFPartyChatType_Synthetic_MemberOnline = 1003; + k_eTFPartyChatType_Synthetic_MemberOffline = 1004; +} + +enum TF_GC_GameState { + TF_GC_GAMESTATE_STATE_INIT = 0; + TF_GC_GAMESTATE_WAIT_FOR_PLAYERS_TO_LOAD = 1; + TF_GC_GAMESTATE_STRATEGY_TIME = 3; + TF_GC_GAMESTATE_GAME_IN_PROGRESS = 5; + TF_GC_GAMESTATE_POST_GAME = 6; + TF_GC_GAMESTATE_DISCONNECT = 7; + TF_GC_GAMESTATE_LAST = 8; +} + +enum TF_GC_TEAM { + TF_GC_TEAM_DEFENDERS = 0; + TF_GC_TEAM_INVADERS = 1; + TF_GC_TEAM_BROADCASTER = 2; + TF_GC_TEAM_SPECTATOR = 3; + TF_GC_TEAM_PLAYER_POOL = 4; + TF_GC_TEAM_NOTEAM = 5; +} + +enum TFMatchLeaveReason { + TFMatchLeaveReason_UNSPECIFIED = 0; + TFMatchLeaveReason_VOTE_KICK = 1; + TFMatchLeaveReason_IDLE = 2; + TFMatchLeaveReason_ADMIN_KICK = 3; + TFMatchLeaveReason_AWOL = 4; + TFMatchLeaveReason_NO_SHOW = 5; + TFMatchLeaveReason_GC_REMOVED = 6; +} + +enum TFVoteKickReason { + TFVoteKickReason_Invalid = -1; + TFVoteKickReason_Other = 0; + TFVoteKickReason_Cheating = 1; + TFVoteKickReason_Idle = 2; + TFVoteKickReason_Scamming = 3; +} + +enum ServerMatchmakingState { + ServerMatchmakingState_INVALID = 0; + ServerMatchmakingState_NOT_PARTICIPATING = 1; + ServerMatchmakingState_EMPTY = 2; + ServerMatchmakingState_ACTIVE_MATCH_REQUESTING_LATE_JOIN = 3; + ServerMatchmakingState_ACTIVE_MATCH = 4; +} + +enum SurveyQuestionType { + QUESTION_MATCH_QUALITY = 0; + QUESTION_MAP_QUALITY = 1; + QUESTION_COMP_INQUIRY = 2; + QUESTION_CASUAL_INQUIRY = 3; + QUESTION_RANDOM_CRIT = 4; +} + +message CMsgTFGoldenWrenchBroadcast { + optional int32 wrench_number = 1; + optional bool deleted = 2; + optional string user_name = 3; +} + +message CMsgTFSaxxyBroadcast { + optional int32 category_number = 1; + optional string user_name = 2; +} + +message CMsgGCTFSpecificItemBroadcast { + optional uint32 item_def_index = 1; + optional bool was_destruction = 2; + optional string user_name = 3; +} + +message CMsgTFWorldStatus { + optional bool beta_stress_test_event_active = 1 [default = false]; + optional ETFMatchGroup event_match_group = 2 [default = k_eTFMatchGroup_Invalid]; + optional fixed32 event_expire_time = 3 [default = 0]; + optional uint32 active_client_version = 4; + repeated ETFMatchGroup disabled_match_groups = 5; +} + +message CSOTFDuelSummary { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 duel_wins = 2; + optional uint32 duel_losses = 3; + optional uint32 last_duel_account_id = 4; + optional uint32 last_duel_timestamp = 5; + optional uint32 last_duel_status = 6; +} + +message CSOTFMapContribution { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 def_index = 2 [(key_field) = true]; + optional uint32 contribution_level = 3; +} + +message CMsgTFFreeTrialChooseMostHelpfulFriend { + optional uint32 account_id_friend = 1; +} + +message CMsgTFRequestTF2Friends { + repeated uint32 account_ids = 1; +} + +message CMsgTFRequestTF2FriendsResponse { + repeated uint32 account_ids = 1; +} + +message CSOTFPlayerInfo { + optional uint32 num_new_users_helped = 1; +} + +message CMsgTFThankedBySomeone { + optional uint64 thanker_steam_id = 1; +} + +message CMsgTFThankedSomeone { +} + +message CMsgTFFreeTrialConvertedToPremium { +} + +message CMsgSaxxyAwarded { + optional uint32 category = 1; + repeated string winner_names = 2; +} + +message CMsgReplaySubmitContestEntry { + optional string youtube_url = 1; + optional uint32 category = 2; +} + +message CMsgReplaySubmitContestEntryResponse { + optional bool success = 1; +} + +message CReplayCachedContestData { + optional fixed32 timestamp = 1; + optional uint32 num_votes_last_day = 2; + repeated uint32 video_entry_ids = 3; + optional uint32 num_flags_last_day = 4; +} + +message CMsgTFCoaching_AddToCoaches { +} + +message CMsgTFCoaching_RemoveFromCoaches { +} + +message CMsgTFCoaching_FindCoach { + optional uint32 account_id_friend_as_coach = 1; +} + +message CMsgTFCoaching_FindCoachResponse { + optional bool found_coach = 1; + optional uint32 num_likes = 2; + optional string coach_name = 3; +} + +message CMsgTFCoaching_AskCoach { + optional uint32 account_id_student = 1; + optional bool student_is_friend = 2; +} + +message CMsgTFCoaching_AskCoachResponse { + optional bool accept_coaching_assignment = 1; +} + +message CMsgTFCoaching_CoachJoinGame { + optional bool join_game = 1; + optional uint32 server_address = 2; + optional uint32 server_port = 3; + optional uint32 account_id_student = 4; +} + +message CMsgTFCoaching_CoachJoining { + optional uint32 account_id_coach = 1; + optional uint32 account_id_student = 2; +} + +message CMsgTFCoaching_CoachJoined { + optional uint32 account_id_coach = 1; +} + +message CMsgTFCoaching_LikeCurrentCoach { + optional bool like_coach = 1; +} + +message CMsgTFCoaching_RemoveCurrentCoach { + optional uint32 account_id_coach = 1; +} + +message CMsgTFQuickplay_ScoreServers { + message ServerInfo { + optional uint32 server_address = 1; + optional uint32 server_port = 2; + optional uint32 num_users = 3; + optional uint64 steam_id = 4; + optional uint32 max_users = 5; + optional float user_score = 6; + } + + repeated CMsgTFQuickplay_ScoreServers.ServerInfo servers = 1; +} + +message CMsgTFQuickplay_ScoreServersResponse { + message ServerInfo { + optional uint32 server_address = 1; + optional uint32 server_port = 2; + optional float total_score = 3; + optional uint64 steam_id = 4; + optional uint32 options_score = 5; + } + + repeated CMsgTFQuickplay_ScoreServersResponse.ServerInfo servers = 1; +} + +message CMsgTFQuickplay_PlayerJoining { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_LevelInfo { + optional bool level_loaded = 1; + optional string level_name = 2; +} + +message CMsgGC_GameServer_AuthChallenge { + optional string challenge_string = 1; +} + +message CMsgGC_GameServer_AuthResult { + optional bool authenticated = 1; + optional int32 game_server_standing = 2; + optional int32 game_server_standing_trend = 3; + optional bool is_valve_server = 4; + optional string message = 5; +} + +message CMsgGC_GameServer_AuthChallengeResponse { + optional uint32 game_server_account_id = 1; + optional bytes hashed_challenge_string = 2; +} + +message CMsgGC_GameServer_CreateIdentity { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_CreateIdentityResponse { + enum EStatus { + kStatus_GenericFailure = 0; + kStatus_TooMany = -1; + kStatus_NoPrivs = -2; + kStatus_Created = 1; + } + + optional bool account_created = 1; + optional uint32 game_server_account_id = 2; + optional string game_server_identity_token = 3; + optional CMsgGC_GameServer_CreateIdentityResponse.EStatus status = 4 [default = kStatus_GenericFailure]; +} + +message CMsgGC_GameServer_List { + optional uint32 account_id = 1; +} + +message CMsgGC_GameServer_ListResponse { + message GameServerIdentity { + optional uint32 game_server_account_id = 1; + optional string game_server_identity_token = 2; + optional int32 game_server_standing = 3; + optional int32 game_server_standing_trend = 4; + } + + repeated CMsgGC_GameServer_ListResponse.GameServerIdentity owned_game_servers = 1; +} + +message CMsgGC_GameServer_ResetIdentity { + optional uint32 game_server_account_id = 1; +} + +message CMsgGC_GameServer_ResetIdentityResponse { + optional bool game_server_identity_token_reset = 1; + optional uint32 game_server_account_id = 2; + optional string game_server_identity_token = 3; +} + +message CMsgGC_GameServer_AckPolicy { +} + +message CMsgGC_GameServer_AckPolicyResponse { + optional uint32 result = 1; + optional string message = 2; +} + +message CMsgGC_Client_UseServerModificationItem { + optional uint64 item_id = 1; +} + +message CMsgGC_Client_UseServerModificationItem_Response { + enum EServerModificationItemResponse { + kServerModificationItemResponse_AlreadyInUse = 1; + kServerModificationItemResponse_NotOnAuthenticatedServer = 2; + kServerModificationItemResponse_ServerReject = 3; + kServerModificationItemResponse_InternalError = 4; + kServerModificationItemResponse_EventAlreadyActive = 5; + } + + optional CMsgGC_Client_UseServerModificationItem_Response.EServerModificationItemResponse response_code = 1 [default = kServerModificationItemResponse_AlreadyInUse]; +} + +message CMsgGC_GameServer_UseServerModificationItem { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; +} + +message CMsgGC_GameServer_UseServerModificationItem_Response { + enum EServerModificationItemServerResponse { + kServerModificationItemServerResponse_Accepted = 1; + kServerModificationItemServerResponse_NoVoteCalled = 2; + kServerModificationItemServerResponse_VoteFailed = 3; + } + + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; + optional CMsgGC_GameServer_UseServerModificationItem_Response.EServerModificationItemServerResponse server_response_code = 2 [default = kServerModificationItemServerResponse_Accepted]; +} + +message CMsgGC_GameServer_ServerModificationItemExpired { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; +} + +message CMsgGC_GameServer_ServerModificationItem { + optional EServerModificationItemType modification_type = 1 [default = kGameServerModificationItem_Halloween]; + optional bool active = 2; +} + +message CMsgGC_Halloween_ReservedItem { + repeated float x = 1; + repeated float y = 2; + repeated float z = 3; + optional uint32 spawn_meta_info = 7; +} + +message CMsgGC_Halloween_GrantItem { + optional uint32 recipient_account_id = 1; + optional uint32 level_id = 2; + optional bool flagged = 3; +} + +message CMsgGC_Halloween_GrantItemResponse { + optional uint32 recipient_account_id = 1; +} + +message CMsgGC_Halloween_ItemClaimed { +} + +message CMsgGC_PickupItemEligibility_Query { + optional uint32 account_id = 1; + optional uint32 seconds_ago = 2; +} + +message CMsgGC_PickupItemEligibility_QueryResponse { + optional uint32 account_id = 1; + optional bool was_eligible = 2; + optional uint32 level_id = 3; +} + +message CSOTFPartyMember { + message Activity { + optional fixed64 lobby_id = 1; + optional ETFMatchGroup lobby_match_group = 2 [default = k_eTFMatchGroup_Invalid]; + optional bool multiqueue_blocked = 3; + optional bool online = 4; + optional uint32 client_version = 5; + } + + optional bool owns_ticket = 2; + optional uint32 completed_missions = 3; + optional uint32 badge_level = 4; + optional bool competitive_access = 9; + optional uint32 experience = 14; + optional CTFPerPlayerMatchCriteriaProto player_criteria = 16; + optional CSOTFPartyMember.Activity activity = 17; + optional bool casual_banned = 18; + optional bool ranked_banned = 19; + optional bool casual_low_priority = 20; + optional bool ranked_low_priority = 21; + optional bool lobby_standby = 22; +} + +message TFPendingPartyMember { + enum EType { + Invited = 0; + RequestedToJoin = 1; + } + + optional fixed64 steamid = 1; + optional TFPendingPartyMember.EType type = 2 [default = Invited]; + optional fixed64 inviter = 3; +} + +message TFSyncedMMUIState { + optional ETFSyncedMMMenuStep menu_step = 1 [default = k_eTFSyncedMMMenuStep_None]; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CTFGroupMatchCriteriaProto { + optional bool late_join_ok = 5; + optional uint32 custom_ping_tolerance = 13 [default = 0]; + optional string mvm_mannup_tour = 10; + repeated string mvm_mannup_missions = 15; + repeated string mvm_bootcamp_missions = 16; + optional CTFCasualMatchCriteria casual_criteria = 12; +} + +message CTFCasualMatchCriteria { + repeated fixed32 selected_maps_bits = 3; +} + +message CTFPerPlayerMatchCriteriaProto { + optional bool mvm_squad_surplus = 1; +} + +message CTFPartyOptions { + optional bool overwrite_existing = 1; + optional CTFGroupMatchCriteriaProto group_criteria = 2; + optional CTFPerPlayerMatchCriteriaProto player_criteria = 3; + optional TFSyncedMMUIState player_uistate = 5; +} + +message CMsgPartySetOptions { + optional fixed64 party_id = 1; + optional CTFPartyOptions options = 2; +} + +message CMsgPartySetOptionsResponse { +} + +message CMsgPartyQueueForMatch { + optional fixed64 party_id = 1; + optional CTFPartyOptions final_options = 2; + optional ETFMatchGroup match_group = 3 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgPartyQueueForMatchResponse { +} + +message CMsgPartyQueueForStandby { + optional fixed64 party_id = 1; + optional fixed64 party_lobby_id = 2; +} + +message CMsgPartyQueueForStandbyResponse { +} + +message CMsgPartyRemoveFromQueue { + optional fixed64 party_id = 1; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgPartyRemoveFromQueueResponse { +} + +message CMsgPartyRemoveFromStandbyQueue { + optional fixed64 party_id = 1; +} + +message CMsgPartyRemoveFromStandbyQueueResponse { +} + +message CMsgPartyInvitePlayer { + optional fixed64 party_id = 1; + optional fixed64 player_id = 2; + optional bool expecting_request_to_join = 3; +} + +message CMsgPartyRequestJoinPlayer { + optional fixed64 current_party_id = 1; + optional fixed64 join_player_id = 2; + optional fixed64 join_party_id = 3; + optional bool expecting_invite = 4; +} + +message CMsgPartyClearPendingPlayer { + optional fixed64 party_id = 1; + optional fixed64 pending_player_id = 2; +} + +message CMsgPartyMMError { + enum Type { + QUEUE_KICK_NO_PING = 1; + QUEUE_KICK_AUTH = 2; + } + + optional CMsgPartyMMError.Type type = 1 [default = QUEUE_KICK_NO_PING]; +} + +message CMsgPartyClearPendingPlayerResponse { +} + +message CMsgPartyClearOtherPartyRequest { + optional fixed64 other_party_id = 1; +} + +message CMsgPartyClearOtherPartyRequestResponse { +} + +message CMsgPartyPromoteToLeader { + optional fixed64 party_id = 1; + optional fixed64 new_leader_id = 2; +} + +message CMsgPartyKickMember { + optional fixed64 party_id = 1; + optional fixed64 target_id = 2; +} + +message CMsgPartySendChat { + optional fixed64 party_id = 1; + optional string msg = 2; +} + +message CMsgPartyChatMsg { + optional ETFPartyChatType type = 1 [default = k_eTFPartyChatType_Invalid]; + optional fixed64 actor_id = 2; + optional string msg = 3; +} + +message CSOTFParty { + message QueueEntry { + optional ETFMatchGroup match_group = 1 [default = k_eTFMatchGroup_Invalid]; + optional fixed32 queued_time = 2; + } + + optional uint64 party_id = 1 [(key_field) = true]; + optional fixed64 leader_id = 2; + repeated fixed64 member_ids = 3; + repeated CSOTFPartyMember members = 13; + optional uint64 associated_lobby_id = 35; + optional ETFMatchGroup associated_lobby_match_group = 40 [default = k_eTFMatchGroup_Invalid]; + repeated CSOTFParty.QueueEntry matchmaking_queues = 43; + optional CTFGroupMatchCriteriaProto group_criteria = 37; + optional uint32 casual_banned_time = 18; + optional uint32 casual_low_priority_time = 20; + optional uint32 ranked_banned_time = 41; + optional uint32 ranked_low_priority_time = 42; + optional TFSyncedMMUIState leader_ui_state = 44; + repeated TFPendingPartyMember pending_members = 39; +} + +message CSOTFPartyInvite { + message PartyMember { + optional fixed64 steamid = 2; + } + + enum Type { + PENDING_INVITE = 1; + PENDING_JOIN_REQUEST = 2; + } + + optional uint64 group_id = 1 [(key_field) = true]; + optional fixed64 inviter = 2; + repeated CSOTFPartyInvite.PartyMember members = 4; + optional CSOTFPartyInvite.Type type = 5 [default = PENDING_INVITE]; +} + +message CTFLobbyPlayerProto { + enum ConnectState { + INVALID = 0; + RESERVATION_PENDING = 1; + RESERVED = 2; + CONNECTED = 3; + DISCONNECTED = 5; + } + + enum Type { + INVALID_PLAYER = 0; + MATCH_PLAYER = 1; + STANDBY_PLAYER = 2; + OBSERVING_PLAYER = 3; + } + + optional fixed64 id = 1 [(key_field) = true]; + optional TF_GC_TEAM team = 3 [default = TF_GC_TEAM_DEFENDERS]; + optional CTFLobbyPlayerProto.ConnectState connect_state = 13 [default = INVALID]; + optional string name = 6; + optional uint64 original_party_id = 12; + optional bool squad_surplus = 14; + optional uint32 badge_level = 15; + optional uint32 last_connect_time = 17; + optional CTFLobbyPlayerProto.Type type = 19 [default = INVALID_PLAYER]; + optional double normalized_rating = 20; + optional double normalized_uncertainty = 22; + optional uint32 rank = 21; + optional bool chat_suspension = 23; +} + +message CTFLobbyInviteProto { + optional fixed64 lobby_id = 1 [(key_field) = true]; + optional ETFMatchGroup match_group = 2 [default = k_eTFMatchGroup_Invalid]; +} + +message CSOTFGameServerLobby { + enum State { + UNKNOWN = 0; + SERVERSETUP = 1; + RUN = 2; + } + + enum WarMatch { + NOPE = 0; + INVADERS_ARE_PYRO = 1; + INVADERS_ARE_HEAVY = 2; + } + + optional uint64 lobby_id = 1 [(key_field) = true]; + repeated CTFLobbyPlayerProto members = 2; + optional fixed64 server_id = 6 [default = 0]; + optional CSOTFGameServerLobby.State state = 4 [default = UNKNOWN]; + optional string connect = 5; + optional TF_GC_GameState game_state = 22 [default = TF_GC_GAMESTATE_STATE_INIT]; + optional double initial_average_mm_rating = 32; + optional string mannup_tour_name = 42; + optional string map_name = 38; + optional string mission_name = 39; + optional uint32 match_group = 41; + optional uint64 match_id = 30 [default = 0]; + optional uint32 formed_time = 36; + optional uint32 flags = 43; + optional bool late_join_eligible = 44; + optional uint32 fixed_match_size = 45; + optional CSOTFGameServerLobby.WarMatch is_war_match = 46 [default = NOPE]; + repeated uint32 next_maps_for_vote = 47; + optional uint32 lobby_mm_version = 48; + repeated CTFLobbyPlayerProto pending_members = 49; +} + +message CMsgExitMatchmaking { + optional bool explicit_abandon = 1; + optional uint64 party_id = 2; + optional uint64 lobby_id = 3; +} + +message CMsgAcceptLobbyInvite { + optional uint64 invited_lobby_id = 1; + optional uint64 abandoning_match_id = 2; + repeated uint64 abandoning_invite_lobby_ids = 3; +} + +message CMsgAcceptLobbyInviteReply { +} + +message CMsgMatchmakingSearchCountRequest { +} + +message CMsgMatchmakingSearchCountResponse { + repeated uint32 searching_players_by_group = 1; +} + +message CMsgKickedFromMatchmakingQueue { +} + +message CMsgGameServerMatchmakingStatus { + message Player { + optional fixed64 steam_id = 1; + optional CMsgGameServerMatchmakingStatus.PlayerConnectState connect_state = 2 [default = INVALID]; + } + + enum PlayerConnectState { + INVALID = 0; + CONNECTED = 1; + RESERVED = 2; + } + + enum Event { + None = 0; + MvMVictory = 1; + MvMDefeat = 2; + AcknowledgePlayers = 3; + } + + optional uint32 server_version = 16 [default = 1225]; + optional ServerMatchmakingState matchmaking_state = 1 [default = ServerMatchmakingState_INVALID]; + optional string map = 3; + optional string tags = 4; + optional uint32 bot_count = 5; + optional uint32 num_spectators = 6; + optional uint32 max_players = 7; + optional uint32 slots_free = 8; + optional uint32 server_region = 9; + optional float server_loadavg = 10; + optional bool server_trusted = 11; + optional bool server_dedicated = 12; + optional uint32 strict = 17; + optional string fake_ip = 25; + repeated CMsgGameServerMatchmakingStatus.Player players = 13; + optional TF_GC_GameState game_state = 14 [default = TF_GC_GAMESTATE_STATE_INIT]; + optional CMsgGameServerMatchmakingStatus.Event event = 15 [default = None]; + optional uint32 mvm_wave = 18; + optional uint32 mvm_credits_acquired = 19; + optional uint32 mvm_credits_dropped = 20; + optional sint32 match_group = 23 [default = -1]; + optional uint32 lobby_mm_version = 24; +} + +message CMsgMatchmakingProgress { + optional uint32 avg_wait_time_new = 4; + optional uint32 avg_wait_time_join_late = 5; + optional uint32 your_wait_time = 6; + optional uint32 matching_worldwide_searching_players = 8; + optional uint32 matching_near_you_searching_players = 9; + optional uint32 total_worldwide_searching_players = 13; + optional uint32 total_near_you_searching_players = 14; + optional uint32 matching_worldwide_active_players = 15; + optional uint32 matching_near_you_active_players = 16; + optional uint32 total_worldwide_active_players = 17; + optional uint32 total_near_you_active_players = 18; + optional uint32 matching_worldwide_empty_gameservers = 19; + optional uint32 matching_near_you_empty_gameservers = 20; + optional uint32 total_worldwide_empty_gameservers = 21; + optional uint32 total_near_you_empty_gameservers = 22; + optional uint32 urgency_pct = 1; +} + +message CMsgMvMVictoryInfo { + message Item { + optional CMsgMvMVictoryInfo.GrantReason grant_reason = 1 [default = INVALID]; + optional bytes item_data = 2; + optional fixed64 squad_surplus_claimer_steam_id = 3; + } + + message Player { + optional fixed64 steam_id = 1; + optional bool badge_granted = 3; + optional bool badge_progress_updated = 4; + optional bool badge_leveled = 5; + optional uint32 badge_level = 6; + optional uint32 badge_progress_bits = 7; + repeated CMsgMvMVictoryInfo.Item items = 8; + optional bool voucher_missing = 9; + optional uint32 badge_points = 10; + } + + enum GrantReason { + INVALID = 0; + BADGE_LEVELED = 1; + SQUAD_SURPLUS = 2; + MANN_UP = 3; + HELP_A_NOOB = 4; + } + + repeated CMsgMvMVictoryInfo.Player players = 1; + optional string tour_name = 2; + optional string mission_name = 3; +} + +message CGCMsgTFHelloResponse { + optional uint32 version_check = 1; + repeated uint64 version_checksum = 2; + optional uint32 version_verbose = 3; +} + +message CGCMsgTFSync { + optional bytes version_checksum = 1; + optional uint32 version_check = 2; + optional uint32 version_check_ex = 3; + optional uint32 version_check_ex2 = 4; + optional bytes version_checksum_ex = 5; +} + +message CGCMsgTFSyncEx { + optional string version_checksum = 1; + optional bytes version_checksum_ex = 2; + optional uint32 version_check = 3; +} + +message CMsgMvMVictory { + message Player { + optional fixed64 steam_id = 1; + optional bool squad_surplus = 2; + } + + optional uint32 legacy_mission_index = 1; + optional string tour_name_mannup = 5; + optional string mission_name = 6; + repeated CMsgMvMVictory.Player players = 2; + optional uint64 lobby_id = 3; + optional fixed32 event_time = 4; +} + +message CMsgMvMMannUpVictoryReply { +} + +message CMsgGameServerKickingLobby { + optional uint64 lobby_id = 3; + optional uint64 match_id = 4; +} + +message CMsgGameServerKickingLobbyResponse { +} + +message CMsgLeaveGameAndPrepareToJoinParty { + optional fixed64 party_id = 1; +} + +message CMsgPlayerLeftMatch { + optional fixed64 steam_id = 1; + optional TFMatchLeaveReason leave_reason = 2 [default = TFMatchLeaveReason_UNSPECIFIED]; + optional bool was_abandon = 3; + optional uint64 lobby_id = 4; + optional uint64 match_id = 5; + repeated CMsgTFXPSource xp_breakdown = 6; +} + +message CMsgPlayerLeftMatchResponse { +} + +message CMsgProcessMatchVoteKick { + message Vote { + optional fixed64 steam_id = 1; + optional bool vote_yay = 2; + } + + optional fixed64 match_id = 1; + optional fixed64 initiator_steam_id = 2; + optional fixed64 target_steam_id = 3; + optional TFVoteKickReason reason = 4 [default = TFVoteKickReason_Invalid]; + repeated CMsgProcessMatchVoteKick.Vote votes = 5; + optional bool default_pass = 6; +} + +message CMsgProcessMatchVoteKickResponse { + optional bool rip = 1; +} + +message CMsgHalloween_ServerBossEvent { + optional uint32 event_counter = 1; + optional uint32 timestamp = 2; + optional uint32 boss_type = 3; + optional uint32 boss_level = 4; + optional uint32 event_type = 5; + optional uint32 players_involved = 6; + optional float elapsed_time = 7; +} + +message CMsgHalloween_Merasmus2012 { + optional uint32 event_counter = 1; + optional fixed32 time_submitted = 2; + optional bool is_valve_server = 3; + optional uint32 boss_level = 4; + optional uint32 spawned_health = 5; + optional uint32 remaining_health = 6; + optional uint32 life_time = 7; + optional uint32 bomb_kills = 8; + optional uint32 staff_kills = 9; + optional uint32 pvp_kills = 10; + optional uint32 prophunt_time1 = 11; + optional uint32 prophunt_time2 = 12; + optional uint32 dmg_scout = 13; + optional uint32 dmg_sniper = 14; + optional uint32 dmg_soldier = 15; + optional uint32 dmg_demo = 16; + optional uint32 dmg_medic = 17; + optional uint32 dmg_heavy = 18; + optional uint32 dmg_pyro = 19; + optional uint32 dmg_spy = 20; + optional uint32 dmg_engineer = 21; + optional uint32 scout_count = 22; + optional uint32 sniper_count = 23; + optional uint32 solider_count = 24; + optional uint32 demo_count = 25; + optional uint32 medic_count = 26; + optional uint32 heavy_count = 27; + optional uint32 pyro_count = 28; + optional uint32 spy_count = 29; + optional uint32 engineer_count = 30; +} + +message CMsgUpdateHalloweenMerasmusLootLevel { + message Player { + optional fixed64 steam_id = 1; + } + + repeated CMsgUpdateHalloweenMerasmusLootLevel.Player players = 1; + optional uint32 merasmus_level = 2; +} + +message CAttribute_String { + optional string value = 1; +} + +message CAttribute_DynamicRecipeComponent { + optional uint32 def_index = 1; + optional uint32 item_quality = 2; + optional uint32 component_flags = 3; + optional string attributes_string = 4; + optional uint32 num_required = 5; + optional uint32 num_fulfilled = 6; +} + +message CAttribute_DynamicRecipeComponent_COMPAT_NEVER_SERIALIZE_THIS_OUT { + optional uint32 def_index = 1; + optional uint32 item_def = 2; + optional uint32 item_quality = 3; + optional uint32 component_flags = 4; + optional uint32 item_flags = 5; + optional string attributes_string = 6; + optional uint32 num_required = 7; + optional uint32 item_count = 8; + optional uint32 num_fulfilled = 9; + optional uint32 items_fulfilled = 10; +} + +message CAttribute_ItemSlotCriteria { + optional string tags = 1; +} + +message CMsgSetItemSlotAttribute { + optional uint64 item_id = 1; + optional uint64 slot_item_original_id = 2; + optional uint32 slot_index = 3; +} + +message CSOWarData { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 war_id = 2 [(key_field) = true]; + optional uint32 affiliation = 3; + optional uint32 points_scored = 4; +} + +message CGCMsgGC_War_IndividualUpdate { + optional fixed64 steam_id = 1; + optional uint32 war_id = 2; + optional uint32 score = 3; +} + +message CGCMsgGC_War_JoinWar { + optional uint32 affiliation = 1; + optional uint32 war_id = 2; +} + +message CGCMsgGC_War_RequestGlobalStats { + optional uint32 war_id = 1; +} + +message CGCMsgGC_War_GlobalStatsResponse { + message SideScore { + optional uint32 side = 1; + optional uint64 score = 2; + } + + repeated CGCMsgGC_War_GlobalStatsResponse.SideScore side_scores = 1; + optional uint32 war_id = 2; +} + +message CGCMsgGC_PlayerDuckLeaderboard_IndividualUpdate { + optional uint32 score = 2; + optional uint32 type = 3; + optional bytes score_id = 4; + optional uint32 score_check = 5; +} + +message CAttribute_WorldItemPlacement { + optional uint64 original_item_id = 1; + optional float pos_x = 2; + optional float pos_y = 3; + optional float pos_z = 4; + optional float ang_x = 5; + optional float ang_y = 6; + optional float ang_z = 7; +} + +message CGCMsg_WorldItemPlacement_Update { + optional uint64 original_item_id = 1; + optional float pos_x = 2; + optional float pos_y = 3; + optional float pos_z = 4; + optional float ang_x = 5; + optional float ang_y = 6; + optional float ang_z = 7; + optional bool force_remove_all = 8; + optional string attrib_name = 9; +} + +message CMsgAcknowledgeXP { + optional int32 match_group = 1; + optional uint32 predicted_experience = 2; +} + +message CMsgTFXPSource { + enum XPSourceType { + SOURCE_SCORE = 0; + SOURCE_OBJECTIVE_BONUS = 1; + SOURCE_COMPLETED_MATCH = 2; + SOURCE_COMPETITIVE_ABANDON = 3; + SOURCE_COMPETITIVE_WIN = 4; + SOURCE_COMPETITIVE_LOSS = 5; + SOURCE_AUTOBALANCE_BONUS = 6; + SOURCE_PRESTIGE_BONUS = 7; + } + + optional CMsgTFXPSource.XPSourceType type = 1 [default = SOURCE_SCORE, (key_field) = true]; + optional int32 amount = 2; + optional int32 match_group = 3; + optional uint32 account_id = 4 [(key_field) = true]; + optional uint64 match_id = 5 [(key_field) = true]; +} + +message CMsgTFXPSourceBreakdown { + repeated CMsgTFXPSource sources = 1; +} + +message CMsgTFClientInit { + optional uint32 client_version = 1; + optional int32 language = 2; +} + +message CMsgGCNotification { + enum NotificationType { + NOTIFICATION_REPORTED_PLAYER_BANNED = 0; + NOTIFICATION_CUSTOM_STRING = 1; + NOTIFICATION_MM_BAN_DUE_TO_EXCESSIVE_REPORTS = 2; + NOTIFICATION_REPORTED_PLAYER_WAS_BANNED = 3; + NOTIFICATION_SUPPORT_MESSAGE = 4; + NOTIFICATION_NUM_TYPES = 5; + } + + optional uint64 notification_id = 1 [(key_field) = true]; + optional uint32 account_id = 2; + optional fixed32 expiration_time = 3; + optional CMsgGCNotification.NotificationType type = 4 [default = NOTIFICATION_CUSTOM_STRING]; + optional string notification_string = 5; +} + +message CMsgGCNotificationQueue { + repeated CMsgGCNotification notifications = 1; +} + +message CMsgNotificationAcknowledge { + optional uint32 account_id = 1; + optional uint64 notification_id = 2; +} + +message CMsgNotificationAcknowledgeReply { +} + +message CMsgGC_Match_Result { + message Player { + optional fixed64 steam_id = 1; + optional uint64 original_party_id = 2; + optional uint32 team = 3; + optional uint32 score = 4; + optional uint32 ping = 5; + optional uint32 flags = 6; + optional uint32 rank = 9; + optional uint32 classes_played = 10; + optional uint32 kills = 11; + optional uint32 deaths = 12; + optional uint32 damage = 13; + optional uint32 healing = 14; + optional uint32 support = 15; + optional uint32 score_medal = 16; + optional uint32 kills_medal = 17; + optional uint32 damage_medal = 18; + optional uint32 healing_medal = 19; + optional uint32 support_medal = 20; + repeated CMsgTFXPSource xp_breakdown = 21; + optional uint32 leave_time = 22; + optional TFMatchLeaveReason leave_reason = 23 [default = TFMatchLeaveReason_UNSPECIFIED]; + optional uint32 connect_time = 24; + } + + enum Status { + MATCH_SUCCEEDED = 0; + MATCH_FAILED_GC = 1; + MATCH_FAILED_TRUSTED = 2; + MATCH_FAILED_ABANDON = 3; + MATCH_FAILED_UNKNOWN = 5; + MATCH_FAILED_TIMEOUT = 6; + MATCH_FINISHED_ABANDON = 7; + } + + optional uint64 match_id = 1 [(key_field) = true]; + optional int32 match_group = 2; + optional CMsgGC_Match_Result.Status status = 3 [default = MATCH_SUCCEEDED]; + optional uint32 duration = 4; + optional uint32 red_score = 5; + optional uint32 blue_score = 6; + optional uint32 winning_team = 7; + optional uint32 map_index = 8; + optional uint32 game_type = 9 [default = 0]; + repeated CMsgGC_Match_Result.Player players = 10; + optional uint32 win_reason = 13; + optional uint32 flags = 14; + optional uint32 bots = 16; +} + +message CMsgGC_Match_ResultResponse { +} + +message CEconItemPreviewDataBlock { + optional CSOEconItem econitem = 1; +} + +message CMsgGC_Client2GCEconPreviewDataBlockRequest { + optional uint64 param_s = 1; + optional uint64 param_a = 2; + optional uint64 param_d = 3; + optional uint64 param_m = 4; +} + +message CMsgGC_Client2GCEconPreviewDataBlockResponse { + optional CEconItemPreviewDataBlock iteminfo = 1; +} + +message CSOTFLadderPlayerStats { + optional uint32 account_id = 1 [(key_field) = true]; + optional int32 match_group = 2 [(key_field) = true]; + optional uint32 season_id = 3 [(key_field) = true]; + optional uint32 games = 9; + optional uint32 score = 10; + optional uint32 kills = 11; + optional uint32 deaths = 12; + optional uint32 damage = 13; + optional uint32 healing = 14; + optional uint32 support = 15; + optional uint32 score_bronze = 16; + optional uint32 score_silver = 17; + optional uint32 score_gold = 18; + optional uint32 kills_bronze = 19; + optional uint32 kills_silver = 20; + optional uint32 kills_gold = 21; + optional uint32 damage_bronze = 22; + optional uint32 damage_silver = 23; + optional uint32 damage_gold = 24; + optional uint32 healing_bronze = 25; + optional uint32 healing_silver = 26; + optional uint32 healing_gold = 27; + optional uint32 support_bronze = 28; + optional uint32 support_silver = 29; + optional uint32 support_gold = 30; +} + +message CSOTFRatingData { + optional uint32 account_id = 1 [(key_field) = true]; + optional int32 rating_type = 2 [(key_field) = true]; + optional uint32 rating_primary = 3; + optional uint32 rating_secondary = 4; + optional uint32 rating_tertiary = 5; +} + +message CMsgGC_TFVoteKickPlayerRequest { + optional uint64 target_id = 2; + optional uint64 voter_id = 3; + optional TFVoteKickReason reason = 4 [default = TFVoteKickReason_Invalid]; + optional uint64 match_id = 5; +} + +message CMsgGC_VoteKickPlayerRequestResponse { + optional bool allowed = 1; + optional bool voter_inhibit = 3; + optional bool target_inhibit = 4; +} + +message CMsgGC_DailyCompetitiveStatsRollup { +} + +message CMsgGC_DailyCompetitiveStatsRollup_Response { + message RankBucketEntry { + optional uint32 rank = 1; + optional uint32 records = 2; + optional uint32 avg_score = 3; + optional uint32 stdev_score = 4; + optional uint32 avg_kills = 5; + optional uint32 stdev_kills = 6; + optional uint32 avg_damage = 7; + optional uint32 stdev_damage = 8; + optional uint32 avg_healing = 9; + optional uint32 stdev_healing = 10; + optional uint32 avg_support = 11; + optional uint32 stdev_support = 12; + } + + repeated CMsgGC_DailyCompetitiveStatsRollup_Response.RankBucketEntry rankdata = 1; +} + +message CMsgGC_ReportPlayer { + enum EReason { + kReason_INVALID = 0; + kReason_CHEATING = 1; + kReason_IDLE = 2; + kReason_HARASSMENT = 3; + kReason_GRIEFING = 4; + kReason_COUNT = 5; + } + + optional uint32 account_id_target = 1; + optional CMsgGC_ReportPlayer.EReason reason = 2 [default = kReason_INVALID]; +} + +message CSOTFMatchResultPlayerStats { + optional uint64 match_id = 1 [(key_field) = true]; + optional uint32 account_id = 2 [(key_field) = true]; + optional int32 match_group = 3 [(key_field) = true]; + optional uint32 endtime = 4; + optional uint32 season_id = 5; + optional uint32 status = 6; + optional uint32 original_party_id = 7; + optional uint32 team = 8; + optional uint32 score = 9; + optional uint32 ping = 10; + optional uint32 flags = 11; + optional uint32 display_rating = 12; + optional int32 display_rating_change = 13; + optional uint32 rank = 14; + optional uint32 classes_played = 15; + optional uint32 kills = 16; + optional uint32 deaths = 17; + optional uint32 damage = 18; + optional uint32 healing = 19; + optional uint32 support = 20; + optional uint32 score_medal = 21; + optional uint32 kills_medal = 22; + optional uint32 damage_medal = 23; + optional uint32 healing_medal = 24; + optional uint32 support_medal = 25; + optional uint32 map_index = 26; + optional uint32 winning_team = 27; +} + +message CMsgGCRequestMatchMakerStats { +} + +message CMsgGCDataCenterPopulation { + optional string name = 1; + optional float health_ratio = 2; +} + +message CMsgGCMatchGroupDataCenterPopulation { + repeated CMsgGCDataCenterPopulation data_center_population = 1; +} + +message CMsgGCMatchMakerStatsResponse { + repeated uint32 map_count = 1; + repeated CMsgGCMatchGroupDataCenterPopulation matchgroup_data_center_population = 2; +} + +message CMsgGCMatchHistoryLoad { + optional ETFMatchGroup match_group = 1 [default = k_eTFMatchGroup_Invalid]; +} + +message CMsgGCDataCenterPing_Update { + message PingEntry { + optional string name = 1; + optional uint32 ping = 2; + optional CMsgGCDataCenterPing_Update.Status ping_status = 3 [default = Normal]; + } + + enum Status { + Invalid = 0; + Normal = 1; + Unreachable = 2; + FallbackToDCPing = 3; + } + + repeated CMsgGCDataCenterPing_Update.PingEntry pingdata = 1; +} + +message CMsgGC_KickPlayerFromLobby { + optional uint64 targetID = 1; +} + +message CMsgGCSurveyRequest { + optional SurveyQuestionType question_type = 1 [default = QUESTION_MATCH_QUALITY]; + optional uint64 match_id = 2; +} + +message CMsgGCSurveyResponse { + optional SurveyQuestionType question_type = 1 [default = QUESTION_MATCH_QUALITY]; + optional uint64 match_id = 2; + optional int32 response = 3; +} + +message CSOQuestMapNode { + optional uint32 account_id = 1 [(key_field) = true]; + optional uint32 defindex = 3 [(key_field) = true]; + optional uint32 node_id = 4; + optional bool star_0_earned = 6 [default = false]; + optional bool star_1_earned = 7 [default = false]; + optional bool star_2_earned = 8 [default = false]; + optional bool loot_claimed = 9 [default = false]; + optional uint32 selected_quest_def = 10 [default = 0]; + optional uint32 map_cycle = 11; +} + +message CSOQuest { + optional uint32 account_id = 1; + optional uint64 quest_id = 2 [(key_field) = true]; + optional uint32 defindex = 3; + optional bool active = 4 [default = false]; + optional uint32 points_0 = 5; + optional uint32 points_1 = 6; + optional uint32 points_2 = 7; + optional uint32 quest_map_node_source_id = 8; + optional uint32 map_cycle = 9; +} + +message CSOQuestMapRewardPurchase { + optional uint32 account_id = 1; + optional uint32 defindex = 2 [(key_field) = true]; + optional uint32 count = 3; + optional uint32 map_cycle = 4; + optional uint32 purchase_id = 5; +} + +message CMsgGCQuestIdentify { + optional uint64 quest_id = 1; +} + +message CMsgGCQuestDevGive { + optional uint32 quest_def_index = 1; +} + +message CMsgGCQuestNodeTurnIn { + optional uint32 node_defindex = 1; +} + +message CMsgGCQuestMapUnlockNode { + optional uint32 node_defindex = 1; + optional uint32 quest_defindex = 2; +} + +message CMsgGCNewMatchForLobbyRequest { + optional uint64 current_match_id = 1; + optional uint32 next_map_id = 2; + optional uint64 lobby_id = 3; +} + +message CMsgGCNewMatchForLobbyResponse { + optional bool success = 1; +} + +message CMsgGCChangeMatchPlayerTeamsRequest { + message Member { + optional uint64 member_id = 1; + optional TF_GC_TEAM new_team = 2 [default = TF_GC_TEAM_NOTEAM]; + } + + optional uint64 match_id = 1; + optional uint64 lobby_id = 2; + repeated CMsgGCChangeMatchPlayerTeamsRequest.Member member = 3; +} + +message CMsgGCChangeMatchPlayerTeamsResponse { + optional bool success = 1; +} + +message CMsgGCQuestComplete_Debug { + optional uint64 quest_id = 1; + optional uint32 points_type = 2; +} + +message CMsgGCQuestMap_Debug { + optional uint32 reset_operation = 1; + optional uint32 give_credit = 2; + optional CMsgGCQuestMapUnlockNode unlock_node = 3; +} + +message CMsgGCQuestMapPurchaseReward { + optional uint32 store_item_defindex = 1; +} + +message CMsgGCQuestResponse { + optional bool success = 1 [default = false]; +} + +message CMsgGCSetDisablePartyQuestProgress { + optional bool state = 1; +} + +message CMsgQuestProgressReport { + optional uint64 quest_id = 1; + optional bool star_0_earned = 2; + optional bool star_1_earned = 3; + optional bool star_2_earned = 4; + repeated uint64 items_earned = 5; + optional uint32 reward_credits_earned = 6; + optional bool contract_completed = 7; +} + +message CMsgConsumePaintkit { + optional fixed64 source_id = 1; + optional uint32 target_defindex = 2; +} + +message CMsgPainkitDevGrant { + optional uint32 paintkit_defindex = 1; + optional float wear = 2; + optional uint32 item_defindex = 3; +} + +message GCQuestStrangeEvent { + optional uint32 owner_account_id = 1; + optional uint32 scorer_account_id = 2; + optional uint64 quest_id = 3; + optional uint32 strange_event_id = 4; + optional uint32 score = 5; +} + +message CMsgSDRTicket { + optional bytes serialized_ticket = 1; +} + +message CMsgAuthorizeServerItemRetrieval { + repeated uint64 item_id = 1; +} + +message CMsgGCToGCSendAccountBannedNotifications { + optional uint32 banned_accountid = 1; + optional uint32 report_period_begin = 2; + optional uint32 report_period_end = 3; +} + +message CMsgGCToGCSendNotification { + optional CMsgGCNotification notification = 1; +} diff --git a/dll/net.proto b/dll/net.proto index 6eb98e739..08834c9dc 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -81,15 +81,18 @@ message Low_Level { } message Network_pb { + uint32 channel = 1; + bytes data = 2; + enum Types { DATA = 0; - FAILED_CONNECT = 1; + NEW_CONNECTION = 1; } - Types type = 1; - int32 channel = 2; - bytes data = 3; - uint32 send_type = 4; // EP2PSend + Types type = 3; + + bool processed = 128; + uint64 time_processed = 129; } message Network_Old { @@ -123,6 +126,7 @@ message Networking_Sockets { uint64 connection_id_from = 4; bytes data = 5; uint64 message_number = 7; + uint32 lane = 8; } message Networking_Messages { @@ -140,6 +144,12 @@ message Networking_Messages { } message Gameserver { + message PlayerInfo { + string name = 1; + uint32 score = 2; + float playtime = 3; + } + uint64 id = 1; bytes game_description = 2; bytes mod_dir = 3; @@ -165,9 +175,13 @@ message Gameserver { uint32 port = 33; uint32 query_port = 34; uint32 appid = 35; + bytes version_name = 36; + uint32 protocol_version = 37; bool offline = 48; uint32 type = 49; + + repeated PlayerInfo players = 50; } message Friend { @@ -203,14 +217,14 @@ message Friend_Messages { } message Steam_Messages { - enum Types { - FRIEND_CHAT = 0; - } - - Types type = 1; - oneof message_data { - bytes message = 2; - } + enum Types { + FRIEND_CHAT = 0; + } + + Types type = 1; + oneof message_data { + bytes message = 2; + } } message StatInfo { @@ -247,10 +261,19 @@ message GameServerStats_Messages { // sent from server as a request, response sent by the user message InitialAllStats { uint64 steam_api_call = 1; - + // optional because the server send doesn't send any data, just steam api call id optional AllStats all_data = 2; } + + message AchievementRequest { + string name = 1; + } + message AchievementResponse { + string name = 1; + AchievementInfo info = 2; + } + // Request_: from Steam_GameServerStats // Response_: from Steam_User_Stats enum Types { @@ -259,12 +282,17 @@ message GameServerStats_Messages { UpdateUserStatsFromServer = 2; // sent by Steam_GameServerStats UpdateUserStatsFromUser = 3; // sent by Steam_User_Stats + + Request_UserAchievement = 4; + Response_UserAchievement = 5; } - Types type = 1; + Types type = 1; oneof data_messages { InitialAllStats initial_user_stats = 2; AllStats update_user_stats = 3; + AchievementRequest achievement_request = 4; + AchievementResponse achievement_response = 5; } } @@ -285,7 +313,7 @@ message Leaderboards_Messages { RequestUserScore = 2; // request score data from a single user } - Types type = 1; + Types type = 1; uint32 appid = 2; LeaderboardInfo leaderboard_info = 3; oneof data_messages { @@ -307,6 +335,69 @@ message Steam_User_Stats_Messages { optional Steam_User_Stats_Data steam_user_stats_data = 2; } +message GameServer_Items_Messages { + message Attribute { + uint32 def = 1; + float value = 2; + bytes value_bytes = 3; + } + message EquipState { + uint32 class_id = 1; + uint32 slot_id = 2; + } + message Item { + uint64 id = 1; + uint32 def = 2; + uint32 level = 3; + int32 quality = 4; + uint32 inv_pos = 5; + uint32 quantity = 6; + repeated Attribute attributes = 7; + uint32 flags = 8; + uint32 origin = 9; + string custom_name = 10; + string custom_desc = 11; + uint64 original_id = 12; + bool in_use = 13; + uint32 style = 14; + repeated EquipState equip_states = 15; + } + message InventoryRequest { + uint64 steam_api_call = 1; + } + message InventoryResponse { + uint64 steam_api_call = 1; + repeated Item items = 2; + } + message ItemUpdate { + uint64 id = 1; + optional uint32 inv_pos = 2; + optional uint32 style = 3; + optional bool has_equip_states = 4; + repeated EquipState equip_states = 5; + } + message ItemDeletion { + uint64 item_id = 1; + } + + enum Types { + Request_Inventory = 0; + Response_Inventory = 1; + Request_UpdateItem = 2; + Request_DeleteItem = 3; + Request_Respawn = 4; + } + + Types type = 1; + bool is_gc = 2; + oneof data_messages { + InventoryRequest inventory_request = 3; + InventoryResponse inventory_response = 4; + ItemUpdate item_update = 5; + ItemDeletion item_deletion = 6; + } +} + message Common_Message { uint64 source_id = 1; // SteamID64 of the sender uint64 dest_id = 2; // SteamID64 of the target receiver @@ -327,6 +418,7 @@ message Common_Message { GameServerStats_Messages gameserver_stats_messages = 16; Leaderboards_Messages leaderboards_messages = 17; Steam_User_Stats_Messages steam_user_stats_messages = 18; + GameServer_Items_Messages gameserver_items_messages = 19; } uint32 source_ip = 128; @@ -336,9 +428,9 @@ message Common_Message { //Non networking related protobufs message SteamAppTicket_pb { - uint32 ticket_version_no = 1; - uint32 crc_encryptedticket = 2; - uint32 cb_encrypteduserdata = 3; - uint32 cb_encrypted_appownershipticket = 4; - bytes encrypted_ticket = 5; + uint32 ticket_version_no = 1; + uint32 crc_encryptedticket = 2; + uint32 cb_encrypteduserdata = 3; + uint32 cb_encrypted_appownershipticket = 4; + bytes encrypted_ticket = 5; } diff --git a/dll/network.cpp b/dll/network.cpp index 2aeaa6907..b11177ae3 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -32,95 +32,56 @@ static uint32_t upper_range_ips[MAX_BROADCASTS]; #if defined(STEAM_WIN32) -//windows xp support -static int -inet_pton4(const char *src, uint32_t *dst) -{ - static const char digits[] = "0123456789"; - int saw_digit, octets, ch; - u_char tmp[sizeof(uint32_t)], *tp; - - saw_digit = 0; - octets = 0; - *(tp = tmp) = 0; - while ((ch = *src++) != '\0') { - const char *pch; - - if ((pch = strchr(digits, ch)) != NULL) { - size_t nx = *tp * 10 + (pch - digits); - - if (nx > 255) - return (0); - *tp = (u_char) nx; - if (! saw_digit) { - if (++octets > 4) - return (0); - saw_digit = 1; - } - } else if (ch == '.' && saw_digit) { - if (octets == 4) - return (0); - *++tp = 0; - saw_digit = 0; - } else - return (0); - } - if (octets < 4) - return (0); - memcpy(dst, tmp, sizeof(uint32_t)); - return (1); -} - static void get_broadcast_info(uint16 port) { number_broadcasts = 0; - IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); - unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO); - - if (pAdapterInfo == NULL) { + ULONG ulOutBufLen = 16384; + IP_ADAPTER_ADDRESSES *pAdapterInfo = (IP_ADAPTER_ADDRESSES *)malloc(ulOutBufLen); + if (pAdapterInfo == nullptr) { return; } - if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) { - free(pAdapterInfo); - pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen); + ULONG ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAdapterInfo, &ulOutBufLen); - if (pAdapterInfo == NULL) { + if (ret == ERROR_BUFFER_OVERFLOW) { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_ADDRESSES *)malloc(ulOutBufLen); + if (pAdapterInfo == nullptr) { return; } - } - int ret; + ret = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAdapterInfo, &ulOutBufLen); + } - if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) { - IP_ADAPTER_INFO *pAdapter = pAdapterInfo; + if (ret == NO_ERROR) { + for (IP_ADAPTER_ADDRESSES *pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) { + if (pAdapter->OperStatus == IfOperStatusUp && + pAdapter->IfType != IF_TYPE_SOFTWARE_LOOPBACK && + pAdapter->FirstUnicastAddress && + pAdapter->FirstUnicastAddress->OnLinkPrefixLength <= 32) { + sockaddr_in *addr_ptr = (sockaddr_in *)(pAdapter->FirstUnicastAddress->Address.lpSockaddr); + uint32 iface_ip = ntohl(addr_ptr->sin_addr.s_addr); - while (pAdapter) { - uint32_t iface_ip = 0, subnet_mask = 0; + ULONG prefix = pAdapter->FirstUnicastAddress->OnLinkPrefixLength; + uint32 subnet_mask = (prefix == 0) ? 0 : (0xFFFFFFFFu << (32 - prefix)); - if (inet_pton4(pAdapter->IpAddressList.IpMask.String, &subnet_mask) == 1 - && inet_pton4(pAdapter->IpAddressList.IpAddress.String, &iface_ip) == 1) { - IP_PORT *ip_port = &broadcasts[number_broadcasts]; - uint32 broadcast_ip = iface_ip | ~subnet_mask; - ip_port->ip = broadcast_ip; - ip_port->port = port; - lower_range_ips[number_broadcasts] = iface_ip & subnet_mask; - upper_range_ips[number_broadcasts] = broadcast_ip; - number_broadcasts++; + IP_PORT *ip_port = &broadcasts[number_broadcasts]; + uint32 broadcast_ip = htonl(iface_ip | ~subnet_mask); + ip_port->ip = broadcast_ip; + ip_port->port = port; + lower_range_ips[number_broadcasts] = htonl(iface_ip & subnet_mask); + upper_range_ips[number_broadcasts] = broadcast_ip; + number_broadcasts++; - if (number_broadcasts >= MAX_BROADCASTS) { - break; - } + if (number_broadcasts >= MAX_BROADCASTS) { + break; } - - pAdapter = pAdapter->Next; + } } } - if (pAdapterInfo) { - free(pAdapterInfo); - } + free(pAdapterInfo); } #elif defined(__linux__) @@ -352,8 +313,9 @@ static bool send_broadcasts(sock_t sock, uint16 port, char *data, unsigned long return false; for (int i = 0; i < number_broadcasts; i++) { - ret = send_packet_to(sock, broadcasts[i], data, length); IP_PORT ip_port = broadcasts[i]; + ip_port.port = port; + ret = send_packet_to(sock, ip_port, data, length); } /** @@ -613,7 +575,11 @@ void Networking::do_callbacks_message(Common_Message *msg) PRINT_DEBUG("has_steam_user_stats_messages"); run_callbacks(CALLBACK_ID_USER_STATS, msg); } - + + if (msg->has_gameserver_items_messages()) { + PRINT_DEBUG("has_gameserver_items_messages"); + run_callbacks(CALLBACK_ID_GAMESERVER_ITEMS, msg); + } } bool Networking::handle_tcp(Common_Message *msg, struct TCP_Socket &socket) @@ -715,33 +681,34 @@ bool Networking::handle_announce(Common_Message *msg, IP_PORT ip_port) Common_Message msg_ = create_announce(true); size_t size = msg_.ByteSizeLong(); - std::vector buffer(size); - msg_.SerializeToArray(buffer.data(), static_cast(size)); + char *buffer = new char[size]; + msg_.SerializeToArray(buffer, static_cast(size)); IP_PORT ipp; ipp.ip = msg->announce().peers(i).ip(); ipp.port = htons(msg->announce().peers(i).udp_port()); - send_packet_to(udp_socket, ipp, buffer.data(), static_cast(size)); + send_packet_to(udp_socket, ipp, buffer, static_cast(size)); + delete[] buffer; } } conn->last_received = std::chrono::high_resolution_clock::now(); if (msg->announce().type() == Announce::PING) { - { - Common_Message msg = create_announce(false); - size_t size = msg.ByteSizeLong(); - std::vector buffer(size); - msg.SerializeToArray(buffer.data(), static_cast(size)); - send_packet_to(udp_socket, ip_port, buffer.data(), static_cast(size)); - } + Common_Message msg = create_announce(false); + size_t size = msg.ByteSizeLong(); + char *buffer = new char[size]; + msg.SerializeToArray(buffer, static_cast(size)); + send_packet_to(udp_socket, ip_port, buffer, static_cast(size)); + delete[] buffer; //send ping packet if not pinged if (!conn->udp_pinged) { Common_Message msg = create_announce(true); - size_t size = msg.ByteSizeLong(); - std::vector buffer(size); - msg.SerializeToArray(buffer.data(), static_cast(size)); - send_packet_to(udp_socket, ip_port, buffer.data(), static_cast(size)); + size_t size = msg.ByteSizeLong(); + char *buffer = new char[size]; + msg.SerializeToArray(buffer, static_cast(size)); + send_packet_to(udp_socket, ip_port, buffer, static_cast(size)); + delete[] buffer; } } else if (msg->announce().type() == Announce::PONG) { conn->udp_ip_port = ip_port; @@ -927,8 +894,10 @@ void Networking::send_announce_broadcasts() size_t size = msg.ByteSizeLong(); std::vector buffer(size); msg.SerializeToArray(&buffer[0], static_cast(size)); - send_broadcasts(udp_socket, htons(DEFAULT_PORT), &buffer[0], static_cast(size), &this->custom_broadcasts); - if (udp_port != DEFAULT_PORT) { + for (uint16 i = DEFAULT_PORT; i < DEFAULT_PORT + NUM_QUERY_PORTS; i++) { + send_broadcasts(udp_socket, htons(i), &buffer[0], static_cast(size), &this->custom_broadcasts); + } + if (udp_port < DEFAULT_PORT || udp_port >= DEFAULT_PORT + NUM_QUERY_PORTS) { send_broadcasts(udp_socket, htons(udp_port), &buffer[0], static_cast(size), &this->custom_broadcasts); } @@ -1105,15 +1074,13 @@ void Networking::Run() auto i = std::find(c.ids.begin(), c.ids.end(), steam_id); if (i != c.ids.end()) { c.ids.erase(i); - PRINT_DEBUG("REMOVE OLD USER CONNECTION ID [%llu]", steam_id.ConvertToUint64()); run_callback_user(steam_id, false, c.appid); + PRINT_DEBUG("REMOVE OLD CONNECTION ID"); } } } - for (auto &steam_id : conn.ids) { - run_callback_user(steam_id, true, conn.appid); - } + for (auto &steam_id : conn.ids) run_callback_user(steam_id, true, conn.appid); } conn.connected = true; @@ -1150,15 +1117,11 @@ void Networking::Run() auto conn = std::begin(connections); while (conn != std::end(connections)) { if (check_timedout(conn->last_received, USER_TIMEOUT + time_extra)) { - if (conn->connected) { - PRINT_DEBUG("USER TIMEOUT"); - for (auto &steam_id : conn->ids) { - run_callback_user(steam_id, false, conn->appid); - } - } + if (conn->connected) for (auto &steam_id : conn->ids) run_callback_user(steam_id, false, conn->appid); kill_tcp_socket(conn->tcp_socket_outgoing); kill_tcp_socket(conn->tcp_socket_incoming); conn = connections.erase(conn); + PRINT_DEBUG("USER TIMEOUT"); } else { ++conn; } @@ -1167,11 +1130,7 @@ void Networking::Run() for (auto &conn: connections) { if (!(conn.tcp_socket_incoming.received_data || conn.tcp_socket_outgoing.received_data)) { - if (conn.connected) { - for (auto &steam_id : conn.ids) { - run_callback_user(steam_id, false, conn.appid); - } - } + if (conn.connected) for (auto &steam_id : conn.ids) run_callback_user(steam_id, false, conn.appid); conn.connected = false; } } @@ -1279,6 +1238,36 @@ bool Networking::sendTo(Common_Message *msg, bool reliable, Connection *conn) return ret; } +bool Networking::sendToAllWithID(Common_Message *msg, bool reliable) +{ + if (!enabled) return false; + + CSteamID dest_id((uint64)msg->dest_id()); + bool ret = false; + + // Deliver to ourselves if we also hold the dest id (loopback path). + if (std::find(ids.begin(), ids.end(), dest_id) != ids.end()) { + local_send.push_back(*msg); + ret = true; + } + + // Deliver to EVERY connection that holds the dest id, not just the first. + // Multiple local processes can share one Steam account (bootstrap launcher + // exe + shipping game exe); the one owning the P2P socket handles it, the + // rest drop it on socket lookup. + for (auto &conn : connections) { + for (const auto &id : conn.ids) { + if (id == dest_id) { + sendTo(msg, reliable, &conn); + ret = true; + break; + } + } + } + + return ret; +} + bool Networking::sendToAllIndividuals(Common_Message *msg, bool reliable) { for (auto &conn: connections) { @@ -1321,29 +1310,15 @@ bool Networking::sendToAll(Common_Message *msg, bool reliable) void Networking::run_callbacks(Callback_Ids id, Common_Message *msg) { - const uint64 message_destination_steamid = msg->dest_id(); - - for (const auto &cb : callbacks[id].callbacks) { - const uint64 callback_allowed_steamid = cb.steam_id.ConvertToUint64(); - + for (auto &cb : callbacks[id].callbacks) { + uint64 callback_allowed_steamid = cb.steam_id.ConvertToUint64(); + uint64 message_destination_steamid = msg->dest_id(); if (callback_allowed_steamid == 0 || // callback wants to receive all messages (callback for broadcast) message_destination_steamid == 0 || // message was broadcasted to all (broadcast message) callback_allowed_steamid == message_destination_steamid) { // callback destination is the same as the message destination - - // change broadcast destination ID to a specific one - // this is required since otherwise the CSteamID of the destination would be 0 (invalid ID) - if (message_destination_steamid == 0) { - msg->set_dest_id(callback_allowed_steamid); - } - // invoke the callback cb.message_callback(cb.object, msg); } } - - // restore broadcast destination ID - if (message_destination_steamid == 0) { - msg->set_dest_id(0); - } } void Networking::run_callback_user(CSteamID steam_id, bool online, uint32 appid) diff --git a/dll/p2p_manager.cpp b/dll/p2p_manager.cpp deleted file mode 100644 index efbbcd050..000000000 --- a/dll/p2p_manager.cpp +++ /dev/null @@ -1,748 +0,0 @@ -/* Copyright (C) 2019 Mr Goldberg - This file is part of the Goldberg Emulator - - The Goldberg Emulator is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 3 of the License, or (at your option) any later version. - - The Goldberg Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the Goldberg Emulator; if not, see - . */ - -#include "dll/p2p_manager.hpp" -#include "dll/dll.h" - - -//kingdom 2 crowns doesn't work with a 0.3 delay or lower -// appid 353090 becomes unstable when joining a lobby if the time is too low, -// it takes between ~5-12 seconds to get past the message "Waiting for our local client to connect..." !!! -constexpr static double SESSION_REQUEST_DELAY = 2.0; - -// https://partner.steamgames.com/doc/api/ISteamNetworking#SendP2PPacket -// "if we can't get through to the user after a timeout of 20 seconds, then an error will be posted" -constexpr static double SESSION_REQUEST_TIMEOUT = 20.0; - -constexpr static double PACKET_MAX_TIME_TO_LIVE = 20.0; - - - -const std::chrono::high_resolution_clock::time_point& P2p_Manager::Packet_t::get_time_created() const -{ - return time_created; -} - - -void P2p_Manager::steam_networking_callback(void *object, Common_Message *msg) -{ - // PRINT_DEBUG_ENTRY(); - - auto *obj = (P2p_Manager *)object; - obj->network_callback(msg); -} - -void P2p_Manager::steam_run_every_runcb(void *object) -{ - // PRINT_DEBUG_ENTRY(); - - auto *obj = (P2p_Manager *)object; - obj->periodic_callback(); -} - - - - -SteamCallBacks* P2p_Manager::get_my_callbacks(const CSteamID &my_id) const -{ - const CSteamID our_id_client = settings_client->get_local_steam_id(); - const CSteamID our_id_server = settings_server->get_local_steam_id(); - - SteamCallBacks *callbacks = nullptr; - if (my_id == our_id_client) { - PRINT_DEBUG(" using our client callbacks"); - return callbacks_client; - } else if (my_id == our_id_server) { - PRINT_DEBUG(" using our server callbacks"); - return callbacks_server; - } - - PRINT_DEBUG("[X] Id=[%llu] is not ours!", my_id.ConvertToUint64()); - return nullptr; -} - -bool P2p_Manager::is_same_peer(const CSteamID &id, const CSteamID &peer_id) const -{ - if (id == peer_id) { - return true; - } - - return false; -} - -void P2p_Manager::send_peer_session_failure(const Peer_Src_t &peer_conn) -{ - Common_Message update_msg{}; - update_msg.set_source_id(peer_conn.my_dest_id.ConvertToUint64()); - update_msg.set_dest_id(peer_conn.remote_id.ConvertToUint64()); - update_msg.set_allocated_network(new Network_pb); - - update_msg.mutable_network()->set_type(Network_pb::FAILED_CONNECT); - - PRINT_DEBUG( - "sent a connection failue packet, src id (our client/gameserver)=[%llu], dest id (peer)=[%llu]", - (uint64)update_msg.source_id(), (uint64)update_msg.dest_id() - ); - network->sendTo(&update_msg, true); -} - -void P2p_Manager::trigger_session_request(const CSteamID &remote_id, const CSteamID &my_id) -{ - PRINT_DEBUG( - "triggering session request callback for source steamid=[%llu], I am=[%llu]", - remote_id.ConvertToUint64(), my_id.ConvertToUint64() - ); - P2PSessionRequest_t data{}; - data.m_steamIDRemote = remote_id; - - get_my_callbacks(my_id)->addCBResult(data.k_iCallback, &data, sizeof(data), SESSION_REQUEST_DELAY); -} - - - - - -bool P2p_Manager::remove_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto rem_it = std::remove_if(connections.begin(), connections.end(), [&remote_id, &my_id, this](const Connection_t &item){ - return is_same_peer(remote_id, item.peer_conn.remote_id) - && is_same_peer(my_id, item.peer_conn.my_dest_id) - ; - }); - - if (connections.end() != rem_it) { - connections.erase(rem_it, connections.end()); - return true; - } - - return false; -} - -P2p_Manager::Connection_t* P2p_Manager::create_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto conn = get_connection(remote_id, my_id); - if (conn) { - return conn; - } - - Connection_t connection{}; - connection.peer_conn.remote_id = remote_id; - connection.peer_conn.my_dest_id = my_id; - - // Check if we should auto-accept P2P sessions from this remote - const auto my_settings = is_same_peer(my_id, settings_client->get_local_steam_id()) - ? settings_client - : settings_server; - - if (my_settings->hasP2PAutoAcceptFromFriend(remote_id.ConvertToUint64())) { - connection.is_accepted = true; - PRINT_DEBUG( - "auto-accepting P2P session from [%llu], I am [%llu]", - remote_id.ConvertToUint64(), my_id.ConvertToUint64() - ); - } - - auto &conn_ref = connections.emplace_back(std::move(connection)); - PRINT_DEBUG( - "created for/them=[%llu], from/me=[%llu], is_accepted=%d", - conn_ref.peer_conn.remote_id.ConvertToUint64(), conn_ref.peer_conn.my_dest_id.ConvertToUint64(), - (int)conn_ref.is_accepted - ); - return &conn_ref; -} - -P2p_Manager::Connection_t* P2p_Manager::get_connection(const CSteamID &remote_id, const CSteamID &my_id) -{ - auto conn = std::find_if(connections.begin(), connections.end(), [&remote_id, &my_id, this](const Connection_t &item) { - return is_same_peer(remote_id, item.peer_conn.remote_id) - && is_same_peer(my_id, item.peer_conn.my_dest_id) - ; - }); - - if (connections.end() == conn) { - return nullptr; - } - - return &(*conn); -} - - - - - -P2p_Manager::P2p_Manager( - class Settings *settings_client, class Settings *settings_server, - class SteamCallBacks *callbacks_client, class SteamCallBacks *callbacks_server, - class Networking *network, class RunEveryRunCB *run_every_runcb -) -{ - this->settings_client = settings_client; - this->settings_server = settings_server; - - this->callbacks_client = callbacks_client; - this->callbacks_server = callbacks_server; - - this->network = network; - this->run_every_runcb = run_every_runcb; - - for (auto settings : { settings_client, settings_server }) { - network->setCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - } - - run_every_runcb->add(&P2p_Manager::steam_run_every_runcb, this); -} - -P2p_Manager::~P2p_Manager() -{ - for (auto settings : { settings_client, settings_server }) { - network->rmCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &P2p_Manager::steam_networking_callback, this); - } - - run_every_runcb->remove(&P2p_Manager::steam_run_every_runcb, this); -} - - -bool P2p_Manager::store_packet( - CSteamID my_id, CSteamID steamIDRemote, - const void *pubData, uint32 cubData, int nChannel, - EP2PSend send_type -) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = create_connection(steamIDRemote, my_id); - - { - Packet_t channel_msg{}; - channel_msg.is_processed = conn->is_accepted; - channel_msg.send_type = send_type; - if (pubData && cubData > 0) { - channel_msg.data.assign((const char *)pubData, (const char *)pubData + cubData); - } - - auto &channel = conn->channels[nChannel]; - channel.packets.emplace_back(std::move(channel_msg)); - } - - PRINT_DEBUG( - "stored msg, size=[%u], from=[%llu] (connection accepted=%i), channel=[%i]", - cubData, steamIDRemote.ConvertToUint64(), (int)conn->is_accepted, nChannel - ); - - return conn->is_accepted; -} - -std::optional> P2p_Manager::get_next_packet(CSteamID my_id, int nChannel) -{ - const auto my_settings = is_same_peer(my_id, settings_client->get_local_steam_id()) - ? settings_client - : settings_server; - - for (auto conn_it = connections.begin(); connections.end() != conn_it; ++conn_it) { - if (!conn_it->is_accepted) { - continue; - } - - auto ch_it = conn_it->channels.find(nChannel); - // channel doesn't exist for this connection - if (conn_it->channels.end() == ch_it) { - continue; - } - - auto &packets = ch_it->second.packets; - auto packet_it = packets.begin(); - // channel has no packets - if (packets.end() == packet_it) { - continue; - } - - // no need to check next packets in this channel if the first one isn't processed yet - // once the connection is accepted, all messages in all channels will be marked as processed - if (!packet_it->is_processed) { - continue; - } - - const bool is_packet_for_me = is_same_peer(my_id, conn_it->peer_conn.my_dest_id); - if (!is_packet_for_me) { - bool can_share_packet = false; - - switch (my_settings->old_p2p_behavior.mode) { - default: - case OldP2pBehavior::EPacketShareMode::DEFAULT: { - // appids (353090, 301300) do this: - // - send packet from client >>> to gameserver - // - use the **client** to check for these packets - // it should use the gameserver instead - // but the client is expected to return these packets to the game - // this seems to be an old behavior - // - // on the contrary appids (701160, 248390) use k_EP2PSendReliable - // and they do not need the gameserver to share its packets with the client - // even multiplayer in appid 248390 won't work if packets were shared - can_share_packet = - packet_it->send_type == EP2PSend::k_EP2PSendUnreliable || - packet_it->send_type == EP2PSend::k_EP2PSendUnreliableNoDelay; - } - break; - - case OldP2pBehavior::EPacketShareMode::ALWAYS_SHARE: { - can_share_packet = true; - } - break; - - case OldP2pBehavior::EPacketShareMode::NEVER_SHARE: { - can_share_packet = false; - } - break; - } - - // avoid sharing packets between client and gameserver - if (!can_share_packet) { - continue; - } - } - - return std::make_tuple(conn_it, ch_it, packet_it); - } - - return {}; -} - - -bool P2p_Manager::send_packet(CSteamID my_id, CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel) -{ - bool reliable = false; - if (eP2PSendType == EP2PSend::k_EP2PSendReliable || eP2PSendType == EP2PSend::k_EP2PSendReliableWithBuffering) { - reliable = true; - } - - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // don't lock 2 or more mutexes at the same time - // to avoid the problem of lock ordering deadlock - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - { - std::lock_guard lock(p2p_mtx); - - auto conn = create_connection(steamIDRemote, my_id); - if (!conn->is_accepted) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#AcceptP2PSessionWithUser - // "If you've called SendP2PPacket on the other user, this implicitly accepts the session request" - conn->is_accepted = true; - PRINT_DEBUG( - "auto-accepting remote connections from=[%llu], I am=[%llu]", - conn->peer_conn.remote_id.ConvertToUint64(), conn->peer_conn.my_dest_id.ConvertToUint64() - ); - } - } - - Common_Message msg{}; - msg.set_source_id(my_id.ConvertToUint64()); - msg.set_dest_id(steamIDRemote.ConvertToUint64()); - msg.set_allocated_network(new Network_pb); - - msg.mutable_network()->set_type(Network_pb::DATA); - msg.mutable_network()->set_channel(nChannel); - msg.mutable_network()->set_data(pubData, cubData); - msg.mutable_network()->set_send_type(eP2PSendType); - - bool ret = false; - { - std::lock_guard lock(global_mutex); - - ret = network->sendTo(&msg, reliable); - PRINT_DEBUG( - "Sent remote message with size=[%zu] from=[%llu] to=[%llu], is_ok=%u", - msg.network().data().size(), (uint64)msg.source_id(), (uint64)msg.dest_id(), ret - ); - } - - return ret; -} - -bool P2p_Manager::is_packet_available(CSteamID my_id, uint32 *pcubMsgSize, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - const CSteamID our_id_client = settings_client->get_local_steam_id(); - const CSteamID our_id_server = settings_server->get_local_steam_id(); - - if (pcubMsgSize) *pcubMsgSize = 0; - - auto packet_opt = get_next_packet(my_id, nChannel); - if (!packet_opt) { - return false; - } - - auto &[conn_it, ch_it, packet_it] = packet_opt.value(); - uint32 size = static_cast(packet_it->data.size()); - if (pcubMsgSize) { - *pcubMsgSize = size; - } - PRINT_DEBUG( - " available message from=[%llu], size=[%u]", - conn_it->peer_conn.remote_id.ConvertToUint64(), size - ); - - return true; -} - -bool P2p_Manager::read_packet(CSteamID my_id, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - if (pcubMsgSize) *pcubMsgSize = 0; - if (psteamIDRemote) *psteamIDRemote = k_steamIDNil; - - auto packet_opt = get_next_packet(my_id, nChannel); - if (!packet_opt) { - return false; - } - - auto &[conn_it, ch_it, packet_it] = packet_opt.value(); - if (psteamIDRemote) { - *psteamIDRemote = conn_it->peer_conn.remote_id; - } - - uint32 size = static_cast(packet_it->data.size()); - if (cubDest < size) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#ReadP2PPacket - // "If the cubDest buffer is too small for the packet, then the message will be truncated" - size = cubDest; - } - - if (pcubMsgSize) { - *pcubMsgSize = size; - } - - if (pubDest) { - memcpy(pubDest, packet_it->data.data(), size); - } - - ch_it->second.packets.erase(packet_it); - - PRINT_DEBUG( - " copied message from=[%llu], size=[%u]", - conn_it->peer_conn.remote_id.ConvertToUint64(), size - ); - - return true; -} - -bool P2p_Manager::close_channel(CSteamID my_id, CSteamID steamIDRemote, int nChannel) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - PRINT_DEBUG( - "[X] no connection to remote user [%llu] was found, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return false; - } - - conn->channels.erase(nChannel); - PRINT_DEBUG( - "closed channel [%i] with remote user [%llu], I am [%llu]", - nChannel, steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - - if (conn->channels.empty()) { - // https://partner.steamgames.com/doc/api/ISteamNetworking#CloseP2PChannelWithUser - // "Once all channels to a user have been closed," - // "the open session to the user will be closed and new data from this user will trigger a new P2PSessionRequest_t callback." - PRINT_DEBUG( - "[?] all channels with remote user [%llu] are closed, removing connection, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - remove_connection(steamIDRemote, my_id); - remove_connection(my_id, steamIDRemote); - } - - return true; -} - -bool P2p_Manager::close_session(CSteamID my_id, CSteamID steamIDRemote) -{ - std::lock_guard lock(p2p_mtx); - - const bool res_1 = remove_connection(steamIDRemote, my_id); - const bool res_2 = remove_connection(my_id, steamIDRemote); - return res_1 || res_2; -} - -bool P2p_Manager::get_session_state(CSteamID my_id, CSteamID steamIDRemote, P2PSessionState_t *pConnectionState) -{ - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // don't lock 2 or more mutexes at the same time - // to avoid the problem of lock ordering deadlock - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - { - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - if (pConnectionState) { - pConnectionState->m_bConnectionActive = false; - pConnectionState->m_bConnecting = false; - pConnectionState->m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorTimeout; - pConnectionState->m_bUsingRelay = false; - pConnectionState->m_nBytesQueuedForSend = 0; - pConnectionState->m_nPacketsQueuedForSend = 0; - pConnectionState->m_nRemoteIP = 0; - pConnectionState->m_nRemotePort = 0; - } - - PRINT_DEBUG( - "no connection to remote user [%llu], I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return false; - } - - if (pConnectionState) { - int32 pending_packets = 0; - int32 pending_bytes = 0; - for (const auto& [ch_idx, channel] : conn->channels) { - pending_packets += (int32)channel.packets.size(); - for (const auto &msg : channel.packets) { - pending_bytes += (int32)msg.data.size(); - } - } - - pConnectionState->m_bConnectionActive = conn->is_accepted; - pConnectionState->m_bConnecting = !conn->is_accepted; - pConnectionState->m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorNone; - pConnectionState->m_bUsingRelay = false; // TODO - pConnectionState->m_nPacketsQueuedForSend = pending_packets; - pConnectionState->m_nBytesQueuedForSend = pending_bytes; - } - } - - if (pConnectionState) { - std::lock_guard lock(global_mutex); - - pConnectionState->m_nRemoteIP = network->getIP(steamIDRemote); - pConnectionState->m_nRemotePort = network->getPort(steamIDRemote); - } - - PRINT_DEBUG( - "remote user [%llu] has a session/connection, I am [%llu]", - steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64() - ); - return true; -} - -bool P2p_Manager::accept_session(CSteamID my_id, CSteamID steamIDRemote) -{ - std::lock_guard lock(p2p_mtx); - - auto conn = get_connection(steamIDRemote, my_id); - if (!conn) { - PRINT_DEBUG("[X] no connection from=[%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64()); - return false; - } - - if (!conn->is_accepted) { - conn->is_accepted = true; - PRINT_DEBUG("accepted new session from=[%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), my_id.ConvertToUint64()); - } - return true; -} - - - - -void P2p_Manager::periodic_handle_connections(const std::chrono::high_resolution_clock::time_point &now) -{ - auto conn_it = connections.begin(); - while (connections.end() != conn_it) { - bool is_remove = false; - if (!conn_it->is_accepted) { - if (check_timedout(conn_it->time_added, SESSION_REQUEST_TIMEOUT, now)) { - is_remove = true; - send_peer_session_failure(conn_it->peer_conn); - } - } - - if (is_remove) { - conn_it = connections.erase(conn_it); - } else { - ++conn_it; - } - } -} - -void P2p_Manager::periodic_handle_channels(const std::chrono::high_resolution_clock::time_point &now) -{ - for (auto &conn : connections) { - if (!conn.is_accepted) { - continue; - } - - // remove channels with no packets - auto ch_it = conn.channels.begin(); - while (conn.channels.end() != ch_it) { - auto &channel = ch_it->second; - if (channel.packets.empty()) { - ch_it = conn.channels.erase(ch_it); - } else { - ++ch_it; - } - } - } -} - -void P2p_Manager::periodic_handle_packets(const std::chrono::high_resolution_clock::time_point &now) -{ - for (auto &conn : connections) { - if (!conn.is_accepted) { - continue; - } - - for (auto &[ch_num, channel] : conn.channels) { - // remove outadated packets, and mark the rest as processed - auto packet_it = channel.packets.begin(); - while (channel.packets.end() != packet_it) { - bool is_remove = false; - if (check_timedout(packet_it->get_time_created(), PACKET_MAX_TIME_TO_LIVE, now)) { - is_remove = true; - } else { - packet_it->is_processed = true; - } - - if (is_remove) { - packet_it = channel.packets.erase(packet_it); - } else { - ++packet_it; - } - } - } - } -} - -void P2p_Manager::periodic_callback() -{ - std::lock_guard lock(p2p_mtx); - - auto now = std::chrono::high_resolution_clock::now(); - periodic_handle_connections(now); - periodic_handle_channels(now); - periodic_handle_packets(now); -} - - - - -void P2p_Manager::network_data_packets(Common_Message *msg) -{ - const CSteamID src_id = (uint64)msg->source_id(); - const CSteamID my_dest_id = (uint64)msg->dest_id(); // this is us - - PRINT_DEBUG("got network msg from [%llu], I am [%llu], type <%u>", - src_id.ConvertToUint64(), my_dest_id.ConvertToUint64(), msg->network().type() - ); - - switch (msg->network().type()) { - case Network_pb::DATA: { - PRINT_DEBUG("got network data message"); - const bool conn_is_accepted = store_packet( - my_dest_id, src_id, - msg->network().data().c_str(), (uint32)msg->network().data().size(), - (int)msg->network().channel(), - (EP2PSend)msg->network().send_type() - ); - if (!conn_is_accepted) { - trigger_session_request(src_id, my_dest_id); - } - } - break; - - case Network_pb::FAILED_CONNECT: { - PRINT_DEBUG("[X] got connection failure packet"); - P2PSessionConnectFail_t data{}; - data.m_steamIDRemote = src_id; - data.m_eP2PSessionError = EP2PSessionError::k_EP2PSessionErrorTimeout; - - get_my_callbacks(my_dest_id)->addCBResult(data.k_iCallback, &data, sizeof(data)); - - { - std::lock_guard lock(p2p_mtx); - - remove_connection(src_id, my_dest_id); - remove_connection(my_dest_id, src_id); - } - } - break; - } -} - -void P2p_Manager::network_low_level(Common_Message *msg) -{ - const CSteamID src_id = (uint64)msg->source_id(); - const CSteamID my_dest_id = (uint64)msg->dest_id(); // this is us - - switch (msg->low_level().type()) { - case Low_Level::CONNECT: { - - } - break; - - case Low_Level::DISCONNECT: { - bool any_conn_removed = false; - { - std::lock_guard lock(p2p_mtx); - - any_conn_removed |= remove_connection(src_id, my_dest_id); - any_conn_removed |= remove_connection(my_dest_id, src_id); - } - - if (any_conn_removed) { - PRINT_DEBUG( - "[X] remote user [%llu] disconnected, sending P2PSessionConnectFail_t, I am [%llu]", - src_id.ConvertToUint64(), my_dest_id.ConvertToUint64() - ); - P2PSessionConnectFail_t data{}; - data.m_steamIDRemote = src_id; - data.m_eP2PSessionError = k_EP2PSessionErrorDestinationNotLoggedIn; - - get_my_callbacks(my_dest_id)->addCBResult(data.k_iCallback, &data, sizeof(data)); - } - - } - break; - } -} - -void P2p_Manager::network_callback(Common_Message *msg) -{ - if (msg->has_network()) { - network_data_packets(msg); - } - - if (msg->has_low_level()) { - network_low_level(msg); - } -} diff --git a/dll/settings.cpp b/dll/settings.cpp index 1a0d09952..c32b8ea4e 100644 --- a/dll/settings.cpp +++ b/dll/settings.cpp @@ -361,6 +361,23 @@ bool Settings::getAppInstallPath(AppId_t appID, std::string &path) return false; } +void Settings::setPurchasedKey(AppId_t appID, const std::string &key) +{ + purchased_keys[appID] = key; +} + +bool Settings::getPurchasedKey(AppId_t appID, std::string &key) const +{ + auto it = purchased_keys.find(appID); + if (purchased_keys.end() != it) + { + key = it->second; + return true; + } + + return false; +} + void Settings::setLeaderboard(const std::string &leaderboard, enum ELeaderboardSortMethod sort_method, enum ELeaderboardDisplayType display_type) { Leaderboard_config leader{}; diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 4859ef326..767e593f3 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -31,7 +31,6 @@ constexpr const static char config_ini_overlay[] = "configs.overlay.ini"; constexpr const static char config_ini_user[] = "configs.user.ini"; static CSimpleIniA ini{}; - typedef struct IniValue { enum class Type { STR, @@ -198,6 +197,28 @@ static void load_subscribed_groups_clans(const std::string &base_path, Settings } } +// overlay::hotkeys +static void parse_overlay_hotkeys(class Settings *settings_client, class Settings *settings_server) +{ + // avoid including overlay header here, just save the strings and let the overlay class translate them + auto combo_str = ini.GetValue("overlay::hotkeys", "key_combo"); + if (combo_str && combo_str[0]) { + auto combo = common_helpers::str_split(combo_str, "+"); + std::for_each(combo.begin(), combo.end(), + [](std::string &item){ item = common_helpers::str_strip(item); } + ); + combo.erase( + std::remove_if( + combo.begin(), combo.end(), + [](const std::string& item) { return item.empty(); } + ), + combo.end() + ); + settings_client->overlay_toggle_keys = combo; + settings_server->overlay_toggle_keys = combo; + } +} + // overlay::appearance static void load_overlay_appearance(class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) { @@ -235,10 +256,64 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti } else { PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); } + } else if (name.compare("Font_Override_Achievement_Title") == 0) { + value = common_helpers::string_strip(value); + std::string nfont_override(common_helpers::to_absolute(value, Local_Storage::get_game_settings_path() + "fonts")); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + if (nfont_override.empty()) { + nfont_override = common_helpers::to_absolute(value, local_storage->get_global_settings_path() + "fonts"); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + } + if (nfont_override.size()) { + settings_client->overlay_appearance.font_override_ach_title = nfont_override; + settings_server->overlay_appearance.font_override_ach_title = nfont_override; + PRINT_DEBUG(" loaded font '%s'", nfont_override.c_str()); + } else { + PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); + } + } else if (name.compare("Font_Override_Achievement_Description") == 0) { + value = common_helpers::string_strip(value); + std::string nfont_override(common_helpers::to_absolute(value, Local_Storage::get_game_settings_path() + "fonts")); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + if (nfont_override.empty()) { + nfont_override = common_helpers::to_absolute(value, local_storage->get_global_settings_path() + "fonts"); + if (!common_helpers::file_exist(nfont_override)) { + nfont_override.clear(); + } + } + if (nfont_override.size()) { + settings_client->overlay_appearance.font_override_ach_desc = nfont_override; + settings_server->overlay_appearance.font_override_ach_desc = nfont_override; + PRINT_DEBUG(" loaded font '%s'", nfont_override.c_str()); + } else { + PRINT_DEBUG(" ERROR font file '%s' doesn't exist and will be ignored", value.c_str()); + } } else if (name.compare("Font_Size") == 0) { float nfont_size = std::stof(value, NULL); settings_client->overlay_appearance.font_size = nfont_size; settings_server->overlay_appearance.font_size = nfont_size; + } else if (name.compare("Font_Size_FPS") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_fps = nfont_size; + settings_server->overlay_appearance.font_size_fps = nfont_size; + } else if (name.compare("Font_Size_Achievement_Title") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_ach_title = nfont_size; + settings_server->overlay_appearance.font_size_ach_title = nfont_size; + } else if (name.compare("Font_Size_Achievement_Description") == 0) { + float nfont_size = std::stof(value, NULL); + settings_client->overlay_appearance.font_size_ach_desc = nfont_size; + settings_server->overlay_appearance.font_size_ach_desc = nfont_size; + } else if (name.compare("Font_Achievement_Title_Bold") == 0) { + bool bold = (std::stol(value, NULL) != 0); + settings_client->overlay_appearance.font_ach_title_bold = bold; + settings_server->overlay_appearance.font_ach_title_bold = bold; } else if (name.compare("Icon_Size") == 0) { float nicon_size = std::stof(value, NULL); settings_client->overlay_appearance.icon_size = nicon_size; @@ -302,6 +377,10 @@ static void load_overlay_appearance(class Settings *settings_client, class Setti } else if (name.compare("Achievement_Unlock_Datetime_Format") == 0) { settings_client->overlay_appearance.ach_unlock_datetime_format = value; settings_server->overlay_appearance.ach_unlock_datetime_format = value; + } else if (name.compare("Achievement_Notification_Delay") == 0) { + float delay_sec = std::stof(value, NULL); + settings_client->achievement_notification_delay_ms = static_cast(delay_sec * 1000.0f); + settings_server->achievement_notification_delay_ms = static_cast(delay_sec * 1000.0f); } else if (name.compare("Background_R") == 0) { float nbackground_r = std::stof(value, NULL); settings_client->overlay_appearance.background_r = nbackground_r; @@ -518,18 +597,86 @@ static void load_gamecontroller_settings(Settings *settings) settings->glyphs_directory = path + (PATH_SEPARATOR "glyphs" PATH_SEPARATOR); } +// controller +static void parse_controller_config(class Settings *settings_client) +{ + std::string type(common_helpers::to_upper(common_helpers::string_strip(ini.GetValue("app::controller", "type", "")))); + settings_client->controller_settings.controller_type_override = type; + PRINT_DEBUG("Setting Controller type override to: '%s'", type.c_str()); + + bool enabled = ini.GetBoolValue("app::controller", "steam_input", false); + settings_client->controller_settings.enabled = enabled; + if (enabled) { + PRINT_DEBUG("Enable SteamInput"); + } + else { + PRINT_DEBUG("Disable SteamInput"); + } +} + // steam_appid.txt static uint32 parse_steam_app_id(const std::string &program_path) { uint32 appid = 0; + // try env vars + std::string str_appid = get_env_variable("SteamAppId"); + std::string str_gameid = get_env_variable("SteamGameId"); + std::string str_overlay_gameid = get_env_variable("SteamOverlayGameId"); + + PRINT_DEBUG("str_appid %s str_gameid: %s str_overlay_gameid: %s", str_appid.c_str(), str_gameid.c_str(), str_overlay_gameid.c_str()); + uint32 appid_env = 0; + uint32 gameid_env = 0; + uint32 overlay_gameid = 0; + + if (str_appid.size() > 0) { + try { + appid_env = std::stoul(str_appid); + } catch (...) { + appid_env = 0; + } + } + + if (str_gameid.size() > 0) { + try { + gameid_env = std::stoul(str_gameid); + } catch (...) { + gameid_env = 0; + } + } + + if (str_overlay_gameid.size() > 0) { + try { + overlay_gameid = std::stoul(str_overlay_gameid); + } catch (...) { + overlay_gameid = 0; + } + } + + PRINT_DEBUG("appid_env %u gameid_env: %u overlay_gameid: %u", appid_env, gameid_env, overlay_gameid); + + // some games set env-vars fake value as '1' in case of 2780710 + if (appid_env != 0 && appid_env != 1) { + appid = appid_env; + } + + if (gameid_env != 0 && gameid_env != 1) { + appid = gameid_env; + } + + if (overlay_gameid != 0 && overlay_gameid != 1) { + appid = overlay_gameid; + } + // try steam_settings folder char array[10] = {}; array[0] = '0'; - Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); - try { - appid = std::stoul(array); - } catch (...) {} + if (!appid) { + Local_Storage::get_file_data(Local_Storage::get_game_settings_path() + "steam_appid.txt", array, sizeof(array) - 1); + try { + appid = std::stoul(array); + } catch (...) {} + } // try current dir if (!appid) { @@ -551,55 +698,6 @@ static uint32 parse_steam_app_id(const std::string &program_path) } catch (...) {} } - // try env vars - if (!appid) { - std::string str_appid = get_env_variable("SteamAppId"); - std::string str_gameid = get_env_variable("SteamGameId"); - std::string str_overlay_gameid = get_env_variable("SteamOverlayGameId"); - - PRINT_DEBUG("str_appid %s str_gameid: %s str_overlay_gameid: %s", str_appid.c_str(), str_gameid.c_str(), str_overlay_gameid.c_str()); - uint32 appid_env = 0; - uint32 gameid_env = 0; - uint32 overlay_gameid = 0; - - if (str_appid.size() > 0) { - try { - appid_env = std::stoul(str_appid); - } catch (...) { - appid_env = 0; - } - } - - if (str_gameid.size() > 0) { - try { - gameid_env = std::stoul(str_gameid); - } catch (...) { - gameid_env = 0; - } - } - - if (str_overlay_gameid.size() > 0) { - try { - overlay_gameid = std::stoul(str_overlay_gameid); - } catch (...) { - overlay_gameid = 0; - } - } - - PRINT_DEBUG("appid_env %u gameid_env: %u overlay_gameid: %u", appid_env, gameid_env, overlay_gameid); - if (appid_env) { - appid = appid_env; - } - - if (gameid_env) { - appid = gameid_env; - } - - if (overlay_gameid) { - appid = overlay_gameid; - } - } - PRINT_DEBUG("final appid = %u", appid); return appid; } @@ -989,6 +1087,41 @@ static void parse_subscribed_groups(class Settings *settings_client, class Setti } +// purchased_keys.txt +static void parse_purchased_keys(class Settings *settings_client, class Settings *settings_server) +{ + std::string purchased_keys_path = Local_Storage::get_game_settings_path() + "purchased_keys.txt"; + std::ifstream input( std::filesystem::u8path(purchased_keys_path) ); + if (input.is_open()) { + PRINT_DEBUG("Reading purchased keys"); + common_helpers::consume_bom(input); + for( std::string line; getline( input, line ); ) { + if (!line.empty() && line[line.length()-1] == '\n') { + line.pop_back(); + } + + if (!line.empty() && line[line.length()-1] == '\r') { + line.pop_back(); + } + + // skip empty lines and comments + if (line.empty() || line[0] == '#') continue; + + try { + size_t delimiter = line.find('='); + if (delimiter != std::string::npos) { + AppId_t app_id = std::stoul(line.substr(0, delimiter)); + std::string key = line.substr(delimiter + 1); + settings_client->setPurchasedKey(app_id, key); + settings_server->setPurchasedKey(app_id, key); + PRINT_DEBUG("Added purchased key for app ID %u", app_id); + } + } catch (...) {} + } + } + +} + // installed_app_ids.txt static void parse_installed_app_Ids(class Settings *settings_client, class Settings *settings_server) { @@ -1548,7 +1681,7 @@ static void parse_overlay_general_config(class Settings *settings_client, class settings_server->overlay_fps_avg_window = val; } } - + } // main::misc::steam_game_stats_reports_dir @@ -1616,13 +1749,6 @@ static void parse_simple_features(class Settings *settings_client, class Setting settings_client->download_steamhttp_requests = ini.GetBoolValue("main::connectivity", "download_steamhttp_requests", settings_client->download_steamhttp_requests); settings_server->download_steamhttp_requests = ini.GetBoolValue("main::connectivity", "download_steamhttp_requests", settings_server->download_steamhttp_requests); - settings_client->old_p2p_behavior.mode = OldP2pBehavior::to_share_mode( - (int)ini.GetLongValue("main::connectivity", "old_p2p_packet_sharing_mode", (unsigned)settings_client->old_p2p_behavior.mode) - ); - settings_server->old_p2p_behavior.mode = OldP2pBehavior::to_share_mode( - (int)ini.GetLongValue("main::connectivity", "old_p2p_packet_sharing_mode", (unsigned)settings_server->old_p2p_behavior.mode) - ); - // [main::misc] settings_client->achievement_bypass = ini.GetBoolValue("main::misc", "achievements_bypass", settings_client->achievement_bypass); @@ -1639,6 +1765,9 @@ static void parse_simple_features(class Settings *settings_client, class Setting settings_client->free_weekend = ini.GetBoolValue("main::misc", "free_weekend", settings_client->free_weekend); settings_server->free_weekend = ini.GetBoolValue("main::misc", "free_weekend", settings_server->free_weekend); + + settings_client->use_32bit_inventory_item_ids = ini.GetBoolValue("main::misc", "use_32bit_inventory_item_ids", settings_client->use_32bit_inventory_item_ids); + settings_server->use_32bit_inventory_item_ids = ini.GetBoolValue("main::misc", "use_32bit_inventory_item_ids", settings_server->use_32bit_inventory_item_ids); } // [main::stats] @@ -1707,6 +1836,7 @@ static bool try_parse_old_steam_interfaces_file(std::string interfaces_path) OLD_ITF_LINE("SteamUtils", SettingsItf::UTILS); OLD_ITF_LINE("STEAMUSERSTATS_INTERFACE_VERSION", SettingsItf::USER_STATS); OLD_ITF_LINE("STEAMAPPS_INTERFACE_VERSION", SettingsItf::APPS); + OLD_ITF_LINE("SteamApps", SettingsItf::APPS); OLD_ITF_LINE("SteamNetworking", SettingsItf::NETWORKING); OLD_ITF_LINE("STEAMREMOTESTORAGE_INTERFACE_VERSION", SettingsItf::REMOTE_STORAGE); OLD_ITF_LINE("STEAMSCREENSHOTS_INTERFACE_VERSION", SettingsItf::SCREENSHOTS); @@ -1936,6 +2066,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s parse_dlc(settings_client, settings_server); parse_installed_app_Ids(settings_client, settings_server); parse_app_paths(settings_client, settings_server, program_path); + parse_purchased_keys(settings_client, settings_server); parse_leaderboards(settings_client, settings_server); parse_stats(settings_client, settings_server, local_storage); @@ -1946,6 +2077,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s parse_mods_folder(settings_client, settings_server, local_storage); load_gamecontroller_settings(settings_client); + parse_controller_config(settings_client); parse_auto_accept_invite(settings_client, settings_server); parse_auto_send_invite(settings_client, settings_server); parse_auto_accept_p2p(settings_client, settings_server); @@ -1959,6 +2091,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s } parse_overlay_general_config(settings_client, settings_server); + parse_overlay_hotkeys(settings_client, settings_server); load_overlay_appearance(settings_client, settings_server, local_storage); parse_steam_game_stats_reports_dir(settings_client, settings_server); parse_cloud_save(&ini, settings_client, settings_server, local_storage); diff --git a/dll/settings_parser_ufs.cpp b/dll/settings_parser_ufs.cpp index 5d4be09db..5d1f29d71 100644 --- a/dll/settings_parser_ufs.cpp +++ b/dll/settings_parser_ufs.cpp @@ -16,7 +16,6 @@ . */ #include "dll/settings_parser_ufs.h" -#include #include @@ -251,8 +250,9 @@ static std::filesystem::path factory_default_cloud_dir(CSimpleIniA *ini, class S // app::cloud_save void parse_cloud_save(CSimpleIniA *ini, class Settings *settings_client, class Settings *settings_server, class Local_Storage *local_storage) { - constexpr static bool DEFAULT_CREATE_DEFAULT_DIR = true; - constexpr static bool DEFAULT_CREATE_SPECIFIC_DIRS = true; +#ifndef LOBBY_CONNECT + constexpr static bool DEFAULT_CREATE_DEFAULT_DIR = false; + constexpr static bool DEFAULT_CREATE_SPECIFIC_DIRS = false; constexpr static const char SPECIFIC_INI_KEY[] = "app::cloud_save::" // then concat the OS specific part @@ -317,4 +317,5 @@ void parse_cloud_save(CSimpleIniA *ini, class Settings *settings_client, class S PRINT_DEBUG(" [X] cloud save dir has unprocessed identifiers, skipping"); } } +#endif // LOBBY_CONNECT } diff --git a/dll/source_query.cpp b/dll/source_query.cpp index 300c4d5f5..c1517be79 100644 --- a/dll/source_query.cpp +++ b/dll/source_query.cpp @@ -152,11 +152,11 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, source_query_magic::simple); serialize_response(output_buffer, source_response_header::A2S_INFO); - serialize_response(output_buffer, static_cast(2)); + serialize_response(output_buffer, static_cast(gs.protocol_version())); serialize_response(output_buffer, gs.server_name()); serialize_response(output_buffer, gs.map_name()); serialize_response(output_buffer, gs.mod_dir()); - serialize_response(output_buffer, gs.product()); + serialize_response(output_buffer, gs.game_description()); serialize_response(output_buffer, static_cast(gs.appid())); serialize_response(output_buffer, static_cast(players.size())); serialize_response(output_buffer, static_cast(gs.max_player_count())); @@ -165,7 +165,7 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, my_server_env); serialize_response(output_buffer, (gs.password_protected() ? source_server_visibility::_private : source_server_visibility::_public)); serialize_response(output_buffer, (gs.secure() ? source_server_vac::secured : source_server_vac::unsecured)); - serialize_response(output_buffer, std::to_string(gs.version())); + serialize_response(output_buffer, gs.version_name()); uint8_t flags = source_server_extra_flag::none; @@ -206,11 +206,13 @@ std::vector Source_Query::handle_source_query(const void* buffer, size_ serialize_response(output_buffer, source_response_header::A2S_PLAYER); serialize_response(output_buffer, static_cast(players.size())); // num_players + auto cur_time = std::chrono::steady_clock::now(); + for (unsigned i = 0; i < players.size(); ++i) { serialize_response(output_buffer, static_cast(i)); // player index serialize_response(output_buffer, players[i].second.name); // player name serialize_response(output_buffer, players[i].second.score); // player score - serialize_response(output_buffer, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - players[i].second.join_time).count())); + serialize_response(output_buffer, std::chrono::duration(cur_time - players[i].second.join_time).count()); } } } diff --git a/dll/steam_app_ids.cpp b/dll/steam_app_ids.cpp index 06c6bbf6f..1757243c3 100644 --- a/dll/steam_app_ids.cpp +++ b/dll/steam_app_ids.cpp @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #include "dll/steam_app_ids.h" // https://developer.valvesoftware.com/wiki/Steam_Application_IDs diff --git a/dll/steam_apps.cpp b/dll/steam_apps.cpp index 13e9a161c..30f50206a 100644 --- a/dll/steam_apps.cpp +++ b/dll/steam_apps.cpp @@ -23,9 +23,6 @@ void Steam_Apps::FillProofOfPurchaseKey( AppProofOfPurchaseKeyResponse_t& data, { data.m_nAppID = nAppID; if (ok_result) { - // TODO maybe read this from a config file "purchased_keys.txt": - // 480=AAAAA-BBBBB-CCCCC-DDDDD - // 218620=XYZFJ-13370-98765 size_t min_len = key.size() < k_cubAppProofOfPurchaseKeyMax ? key.size() : k_cubAppProofOfPurchaseKeyMax - 1; // -1 because we need space for null @@ -45,9 +42,6 @@ void Steam_Apps::FillProofOfPurchaseKey( AppProofOfPurchaseKeyResponse007_t& dat { data.m_nAppID = nAppID; if (ok_result) { - // TODO maybe read this from a config file "purchased_keys.txt": - // 480=AAAAA-BBBBB-CCCCC-DDDDD - // 218620=XYZFJ-13370-98765 size_t min_len = key.size() < k_cubAppProofOfPurchaseKeyMax ? key.size() : k_cubAppProofOfPurchaseKeyMax - 1; // -1 because we need space for null @@ -272,7 +266,7 @@ void Steam_Apps::UninstallDLC( AppId_t nAppID ) // the key is available (which may be immediately). void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%u", nAppID); std::lock_guard lock(global_mutex); AppProofOfPurchaseKeyResponse_t data{}; @@ -282,9 +276,14 @@ void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) if (nAppID == 0 || nAppID == UINT32_MAX) { FillProofOfPurchaseKey(data, nAppID, false); } else if (nAppID == settings->get_local_game_id().AppID() || settings->hasDLC(nAppID)) { - FillProofOfPurchaseKey(data, nAppID, true); + std::string key{}; + settings->getPurchasedKey(nAppID, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, nAppID, true, key); + } else { + FillProofOfPurchaseKey(data, nAppID, true); + } } else { - //TODO what to do here? FillProofOfPurchaseKey(data, nAppID, false); } @@ -293,7 +292,7 @@ void Steam_Apps::RequestAppProofOfPurchaseKey( AppId_t nAppID ) void Steam_Apps::RequestAppProofOfPurchaseKey_OLD( AppId_t nAppID ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%u", nAppID); std::lock_guard lock(global_mutex); AppProofOfPurchaseKeyResponse007_t data{}; @@ -303,9 +302,14 @@ void Steam_Apps::RequestAppProofOfPurchaseKey_OLD( AppId_t nAppID ) if (nAppID == 0 || nAppID == UINT32_MAX) { FillProofOfPurchaseKey(data, nAppID, false); } else if (nAppID == settings->get_local_game_id().AppID() || settings->hasDLC(nAppID)) { - FillProofOfPurchaseKey(data, nAppID, true); + std::string key{}; + settings->getPurchasedKey(nAppID, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, nAppID, true, key); + } else { + FillProofOfPurchaseKey(data, nAppID, true); + } } else { - //TODO what to do here? FillProofOfPurchaseKey(data, nAppID, false); } @@ -457,24 +461,36 @@ int Steam_Apps::GetAppBuildId() // member is k_uAppIdInvalid (zero). void Steam_Apps::RequestAllProofOfPurchaseKeys() { - PRINT_DEBUG_TODO(); + PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); // current app { AppProofOfPurchaseKeyResponse_t data{}; - FillProofOfPurchaseKey(data, settings->get_local_game_id().AppID(), true); + std::string key{}; + AppId_t app_id = settings->get_local_game_id().AppID(); + settings->getPurchasedKey(app_id, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, app_id, true, key); + } else { + FillProofOfPurchaseKey(data, app_id, true); + } callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } // DLCs - const auto count = settings->DLCCount(); for (unsigned i = 0; i < settings->DLCCount(); i++) { AppId_t app_id{}; bool available{}; std::string name{}; if (settings->getDLC(i, app_id, available, name)) { AppProofOfPurchaseKeyResponse_t data{}; - FillProofOfPurchaseKey(data, app_id, true); + std::string key{}; + settings->getPurchasedKey(app_id, key); + if (!key.empty()) { + FillProofOfPurchaseKey(data, app_id, true, key); + } else { + FillProofOfPurchaseKey(data, app_id, true); + } callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } } @@ -596,22 +612,30 @@ int Steam_Apps::GetNumBetas( int *pnAvailable, int *pnPrivate ) return static_cast(settings->branches.size()); // we always return at least 1 since "public" branch } -// TODO no public docs +bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) +{ + PRINT_DEBUG("OLD, before SDK v1.64"); + return GetBetaInfo(iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, nullptr); +} + // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) -bool Steam_Apps::GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) // iterate through +// iterate through +bool Steam_Apps::GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription, uint32* punLastUpdated) { // I assume this API is like "Steam_User_Stats::GetNextMostAchievedAchievementInfo()", it returns 'ok' until index is out of range - PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription); + PRINT_DEBUG("[%i] %p %p --- %p %i --- %p %i --- %p", iBetaIndex, punFlags, punBuildID, pchBetaName, cchBetaName, pchDescription, cchDescription, punLastUpdated); std::lock_guard lock(global_mutex); if (iBetaIndex < 0) return false; if (static_cast(iBetaIndex) >= settings->branches.size()) return false; - - const auto &branch = settings->branches[iBetaIndex]; + + const auto& branch = settings->branches[iBetaIndex]; if (punFlags) *punFlags = branch.flags; if (punBuildID) *punBuildID = branch.build_id; - + // added in SDK 1.64 + if (punLastUpdated) *punLastUpdated = branch.time_updated_epoch; + if (pchBetaName && cchBetaName > 0 && static_cast(cchBetaName) > branch.name.size()) { memset(pchBetaName, 0, cchBetaName); memcpy(pchBetaName, branch.name.c_str(), branch.name.size()); diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index 22c135f3b..8ea3b91cb 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -100,22 +100,17 @@ Steam_Client::Steam_Client() PRINT_DEBUG("init client"); steam_overlay = new Steam_Overlay(settings_client, local_storage, callback_results_client, callbacks_client, run_every_runcb, network); - steam_user = new Steam_User(settings_client, local_storage, network, callback_results_client, callbacks_client); + steam_user = new Steam_User(settings_client, local_storage, network, callback_results_client, callbacks_client, false); steam_friends = new Steam_Friends(settings_client, local_storage, network, callback_results_client, callbacks_client, run_every_runcb, steam_overlay); steam_utils = new Steam_Utils(settings_client, callback_results_client, callbacks_client, steam_overlay); ugc_bridge = new Ugc_Remote_Storage_Bridge(settings_client); - p2p_manager = new P2p_Manager( - settings_client, settings_server, - callbacks_client, callbacks_server, - network, run_every_runcb - ); steam_matchmaking = new Steam_Matchmaking(settings_client, local_storage, network, callback_results_client, callbacks_client, run_every_runcb); steam_matchmaking_servers = new Steam_Matchmaking_Servers(settings_client, local_storage, network); steam_user_stats = new Steam_User_Stats(settings_client, network, local_storage, callback_results_client, callbacks_client, run_every_runcb, steam_overlay); steam_apps = new Steam_Apps(settings_client, callback_results_client, callbacks_client); - steam_networking = new Steam_Networking(settings_client, network, p2p_manager, callbacks_client, run_every_runcb); + steam_networking = new Steam_Networking(settings_client, network, callbacks_client, run_every_runcb); steam_remote_storage = new Steam_Remote_Storage(settings_client, ugc_bridge, local_storage, callback_results_client, callbacks_client, run_every_runcb); steam_screenshots = new Steam_Screenshots(local_storage, callbacks_client); steam_http = new Steam_HTTP(settings_client, network, callback_results_client, callbacks_client); @@ -131,7 +126,7 @@ Steam_Client::Steam_Client() steam_networking_sockets = new Steam_Networking_Sockets(settings_client, network, callback_results_client, callbacks_client, run_every_runcb, NULL); steam_networking_sockets_serialized = new Steam_Networking_Sockets_Serialized(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_networking_messages = new Steam_Networking_Messages(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); - steam_game_coordinator = new Steam_Game_Coordinator(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_game_coordinator = new Steam_Game_Coordinator(settings_client, network, local_storage, callbacks_client, run_every_runcb, false); steam_networking_utils = new Steam_Networking_Utils(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_unified_messages = new Steam_Unified_Messages(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_game_search = new Steam_Game_Search(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); @@ -142,15 +137,16 @@ Steam_Client::Steam_Client() steam_timeline = new Steam_Timeline(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_app_disable_update = new Steam_App_Disable_Update(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_billing = new Steam_Billing(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_user_items = new Steam_User_Items(settings_client, callbacks_client, callback_results_client); // server PRINT_DEBUG("init gameserver"); steam_gameserver = new Steam_GameServer(settings_server, network, callbacks_server); - steam_gameserver_user = new Steam_User(settings_server, local_storage, network, callback_results_server, callbacks_server); + steam_gameserver_user = new Steam_User(settings_server, local_storage, network, callback_results_server, callbacks_server, true); steam_gameserver_utils = new Steam_Utils(settings_server, callback_results_server, callbacks_server, steam_overlay); steam_gameserverstats = new Steam_GameServerStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); - steam_gameserver_networking = new Steam_Networking(settings_server, network, p2p_manager, callbacks_server, run_every_runcb); + steam_gameserver_networking = new Steam_Networking(settings_server, network, callbacks_server, run_every_runcb); steam_gameserver_http = new Steam_HTTP(settings_server, network, callback_results_server, callbacks_server); steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, local_storage); steam_gameserver_ugc = new Steam_UGC(settings_server, ugc_bridge, local_storage, callback_results_server, callbacks_server); @@ -158,9 +154,10 @@ Steam_Client::Steam_Client() steam_gameserver_networking_sockets = new Steam_Networking_Sockets(settings_server, network, callback_results_server, callbacks_server, run_every_runcb, steam_networking_sockets->get_shared_between_client_server()); steam_gameserver_networking_sockets_serialized = new Steam_Networking_Sockets_Serialized(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); steam_gameserver_networking_messages = new Steam_Networking_Messages(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); - steam_gameserver_game_coordinator = new Steam_Game_Coordinator(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); + steam_gameserver_game_coordinator = new Steam_Game_Coordinator(settings_server, network, local_storage, callbacks_server, run_every_runcb, true); steam_masterserver_updater = new Steam_Masterserver_Updater(settings_server, network, callback_results_server, callbacks_server, run_every_runcb, steam_gameserver); steam_gameserver_gamestats = new Steam_GameStats(settings_server, network, callback_results_server, callbacks_server, run_every_runcb); + steam_gameserver_items = new Steam_GameServer_Items(settings_server, callbacks_server, callback_results_server); PRINT_DEBUG("init AppTicket"); steam_app_ticket = new Steam_AppTicket(settings_client); @@ -204,6 +201,7 @@ Steam_Client::~Steam_Client() DEL_INST(steam_masterserver_updater); DEL_INST(steam_gameserver); DEL_INST(steam_gameserver_gamestats); + DEL_INST(steam_gameserver_items); DEL_INST(steam_matchmaking); DEL_INST(steam_matchmaking_servers); @@ -236,8 +234,8 @@ Steam_Client::~Steam_Client() DEL_INST(steam_timeline); DEL_INST(steam_app_disable_update); DEL_INST(steam_billing); + DEL_INST(steam_user_items); - DEL_INST(p2p_manager); DEL_INST(ugc_bridge); DEL_INST(steam_utils); @@ -259,12 +257,14 @@ Steam_Client::~Steam_Client() void Steam_Client::userLogIn() { + callback_results_client->clear(); network->addListenId(settings_client->get_local_steam_id()); user_logged_in = true; } void Steam_Client::serverInit() { + callback_results_server->clear(); server_init = true; } @@ -280,11 +280,13 @@ bool Steam_Client::IsUserLogIn() void Steam_Client::serverShutdown() { + callback_results_server->clear(); server_init = false; } void Steam_Client::clientShutdown() { + callback_results_client->clear(); user_logged_in = false; } @@ -309,18 +311,17 @@ void Steam_Client::setAppID(uint32 appid) } - // Creates a communication pipe to the Steam client. +// Creates a communication pipe to the Steam client. // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling HSteamPipe Steam_Client::CreateSteamPipe() { PRINT_DEBUG_ENTRY(); - if (!steam_pipe_counter) ++steam_pipe_counter; - HSteamPipe pipe = steam_pipe_counter; - ++steam_pipe_counter; + + HSteamPipe pipe = steam_pipe_numbers.get_number(); + PRINT_DEBUG(" returned pipe handle %i", pipe); + steam_pipes[pipe] = { Steam_Pipe_Type::NO_USER, false }; - steam_pipes[pipe] = Steam_Pipe::NO_USER; - return pipe; } @@ -332,6 +333,7 @@ bool Steam_Client::BReleaseSteamPipe( HSteamPipe hSteamPipe ) { PRINT_DEBUG("%i", hSteamPipe); if (steam_pipes.count(hSteamPipe)) { + steam_pipe_numbers.free_number(hSteamPipe); return steam_pipes.erase(hSteamPipe) > 0; } @@ -348,18 +350,24 @@ HSteamUser Steam_Client::ConnectToGlobalUser( HSteamPipe hSteamPipe ) return 0; } - userLogIn(); - - // games like appid 1740720 and 2379780 do not call SteamAPI_RunCallbacks() or SteamAPI_ManualDispatch_RunFrame() or Steam_BGetCallback() - // hence all run_callbacks() will never run, which might break the assumption that these callbacks are always run - // also networking callbacks won't run - // hence we spawn the background thread here which trigger all run_callbacks() and run networking callbacks - PRINT_DEBUG("started background thread"); - background_thread->start(this); + if (client_user_ref_count == 0) { + userLogIn(); - steam_overlay->SetupOverlay(); - - steam_pipes[hSteamPipe] = Steam_Pipe::CLIENT; + // initialize GC now so that we have user's inventory ready right away + steam_game_coordinator->initialize_gc(); + + // games like appid 1740720 and 2379780 do not call SteamAPI_RunCallbacks() or SteamAPI_ManualDispatch_RunFrame() or Steam_BGetCallback() + // hence all run_callbacks() will never run, which might break the assumption that these callbacks are always run + // also networking callbacks won't run + // hence we spawn the background thread here which trigger all run_callbacks() and run networking callbacks + PRINT_DEBUG("started background thread"); + background_thread->start(this); + + steam_overlay->SetupOverlay(); + } + + client_user_ref_count++; + steam_pipes[hSteamPipe] = {Steam_Pipe_Type::CLIENT, false}; return CLIENT_HSTEAMUSER; } @@ -381,8 +389,7 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe, EAccountType HSteamPipe pipe = CreateSteamPipe(); if (phSteamPipe) *phSteamPipe = pipe; - steam_pipes[pipe] = Steam_Pipe::SERVER; - steamclient_server_inited = true; + steam_pipes[pipe] = {Steam_Pipe_Type::SERVER, false}; return SERVER_HSTEAMUSER; //} } @@ -396,9 +403,25 @@ HSteamUser Steam_Client::CreateLocalUser( HSteamPipe *phSteamPipe ) // NOT THREADSAFE - ensure that no other threads are accessing Steamworks API when calling void Steam_Client::ReleaseUser( HSteamPipe hSteamPipe, HSteamUser hUser ) { - PRINT_DEBUG_ENTRY(); - if (hUser == SERVER_HSTEAMUSER && steam_pipes.count(hSteamPipe)) { - steamclient_server_inited = false; + PRINT_DEBUG("%i %i", hSteamPipe, hUser); + + if (!steam_pipes.count(hSteamPipe)) + return; + + if (hUser == SERVER_HSTEAMUSER) { + if (steam_gameserver->BLoggedOn()) { + steam_gameserver->LogOff(); + } + + serverShutdown(); + } else if (hUser == CLIENT_HSTEAMUSER) { + if (client_user_ref_count > 0) { + client_user_ref_count--; + + if (client_user_ref_count == 0) { + clientShutdown(); + } + } } } @@ -484,11 +507,22 @@ void Steam_Client::Remove_SteamAPI_CPostAPIResultInProcess( SteamAPI_PostAPIResu PRINT_DEBUG_TODO(); } -void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallback) +void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallback ) { + CCallbackBase *callback_to_add = pCallback; + + // 1.02x added an overload for CCallbackBase::Run. Because of how MSVC handles virtual function + // overloads, this changed the order of functions in vtable from 1.02, which breaks old games. + // We get around this using a wrapper for the old CCallbackBase variant. + if (using_old_callbacks) { + PRINT_DEBUG("creating a wrapper for old callback %08X %i", pCallback, iCallback); + auto [it, _] = old_callbacks_map.emplace(pCallback, pCallback); + callback_to_add = &(it->second); + } + int base_callback = (iCallback / 100) * 100; int callback_id = iCallback % 100; - bool isGameServer = CCallbackMgr::isServer(pCallback); + bool isGameServer = CCallbackMgr::isServer(callback_to_add); PRINT_DEBUG("isGameServer %u %i %i", isGameServer, iCallback, base_callback); switch (base_callback) { @@ -701,18 +735,26 @@ void Steam_Client::RegisterCallback( class CCallbackBase *pCallback, int iCallba }; if (isGameServer) { - callbacks_server->addCallBack(iCallback, pCallback); + callbacks_server->addCallBack(iCallback, callback_to_add); } else { - callbacks_client->addCallBack(iCallback, pCallback); + callbacks_client->addCallBack(iCallback, callback_to_add); } } -void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback) +void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback ) { - int iCallback = pCallback->GetICallback(); + CCallbackBase *callback_to_rm = pCallback; + if (using_old_callbacks) { + if (!old_callbacks_map.count(pCallback)) + return; + + callback_to_rm = &(old_callbacks_map.at(pCallback)); + } + + int iCallback = callback_to_rm->GetICallback(); int base_callback = (iCallback / 100) * 100; int callback_id = iCallback % 100; - bool isGameServer = CCallbackMgr::isServer(pCallback); + bool isGameServer = CCallbackMgr::isServer(callback_to_rm); PRINT_DEBUG("isGameServer %u %i", isGameServer, base_callback); switch (base_callback) { @@ -925,13 +967,17 @@ void Steam_Client::UnregisterCallback( class CCallbackBase *pCallback) }; if (isGameServer) { - callbacks_server->rmCallBack(iCallback, pCallback); + callbacks_server->rmCallBack(iCallback, callback_to_rm); } else { - callbacks_client->rmCallBack(iCallback, pCallback); + callbacks_client->rmCallBack(iCallback, callback_to_rm); + } + + if (using_old_callbacks) { + old_callbacks_map.erase(pCallback); } } -void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall) +void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ) { PRINT_DEBUG("%llu %i", hAPICall, pCallback->GetICallback()); std::lock_guard lock(global_mutex); @@ -940,7 +986,7 @@ void Steam_Client::RegisterCallResult( class CCallbackBase *pCallback, SteamAPIC } -void Steam_Client::UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall) +void Steam_Client::UnregisterCallResult( class CCallbackBase *pCallback, SteamAPICall_t hAPICall ) { PRINT_DEBUG("%llu %i", hAPICall, pCallback->GetICallback()); std::lock_guard lock(global_mutex); @@ -959,6 +1005,9 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) // PRINT_DEBUG("steam_matchmaking_servers *********"); steam_matchmaking_servers->RunCallbacks(); + + // PRINT_DEBUG("run_every_runcb *********"); + run_every_runcb->run(); // PRINT_DEBUG("steam_gameserver *********"); steam_gameserver->RunCallbacks(); @@ -966,15 +1015,12 @@ void Steam_Client::RunCallbacks(bool runClientCB, bool runGameserverCB) // PRINT_DEBUG("steam_user *********"); steam_gameserver_user->RunCallbacks(); - // PRINT_DEBUG("run_every_runcb *********"); - run_every_runcb->run(); - - if (runClientCB) { + if (runClientCB && IsUserLogIn()) { // PRINT_DEBUG("callback_results_client *********"); callback_results_client->runCallResults(); } - if (runGameserverCB) { + if (runGameserverCB && IsServerInit()) { // PRINT_DEBUG("callback_results_server *********"); callback_results_server->runCallResults(); } @@ -997,100 +1043,67 @@ void Steam_Client::DestroyAllInterfaces() // older sdk ---------------------------------------------------------- -// creates a global instance of a steam user, so that other processes can share it -// used by the steam UI, to share it's account info/connection with any games it launches -// fails (returns NULL) if an existing instance already exists -HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) -{ - // TODO not sure if this implementation is correct - PRINT_DEBUG_TODO(); - for (const auto& [pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::CLIENT) { - if (phSteamPipe) *phSteamPipe = pipe_handle; - return 0; - } - } - - HSteamPipe pipe = CreateSteamPipe(); - if (phSteamPipe) *phSteamPipe = pipe; - - steam_pipes[pipe] = Steam_Pipe::CLIENT; - return CLIENT_HSTEAMUSER; -} - -// retrieves the IVac interface associated with the handle -// there is normally only one instance of VAC running, but using this connects it to the right user/account -void *Steam_Client::GetIVAC( HSteamUser hSteamUser ) -{ - PRINT_DEBUG_ENTRY(); - // actual value from steamclient64.dll - return nullptr; -} - -// returns the name of a universe -const char *Steam_Client::GetUniverseName( EUniverse eUniverse ) -{ - PRINT_DEBUG("%i", (int)eUniverse); - // actual values returned by steamclient64.dll - switch (eUniverse) - { - case EUniverse::k_EUniverseInvalid: return "Invalid"; - case EUniverse::k_EUniversePublic: return "Public"; - case EUniverse::k_EUniverseBeta: return "Beta"; - case EUniverse::k_EUniverseInternal: return "Internal"; - case EUniverse::k_EUniverseDev: return "Dev"; - } - - return "Unknown"; -} - +// SteamClient001 ----------------------------------------------------- HSteamUser Steam_Client::CreateGlobalInstance() { PRINT_DEBUG_ENTRY(); HSteamPipe pipe = 0; - return CreateGlobalUser(&pipe); + HSteamUser user = CreateGlobalUser(&pipe); + return create_old_user_ref(user, pipe); } HSteamUser Steam_Client::ConnectToGlobalInstance() { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(CLIENT_HSTEAMUSER); - if (!pipe) { - pipe = CreateSteamPipe(); + if (steamclient_version < 4) { + for (auto &[key, val] : old_user_refs) { + if (val.user == CLIENT_HSTEAMUSER) { + return key; + } + } } - return ConnectToGlobalUser(pipe); + HSteamPipe pipe = CreateSteamPipe(); + HSteamUser user = ConnectToGlobalUser(pipe); + return create_old_user_ref(user, pipe); } HSteamUser Steam_Client::CreateLocalInstance() { PRINT_DEBUG_ENTRY(); HSteamPipe pipe = 0; - return CreateLocalUser(&pipe); + HSteamUser user = CreateLocalUser(&pipe); + return create_old_user_ref(user, pipe); } void Steam_Client::ReleaseInstance( HSteamUser hSteamUser ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - if (pipe) { - ReleaseUser(pipe, hSteamUser); - BReleaseSteamPipe(pipe); - } + auto it = old_user_refs.find(hSteamUser); + if (it == old_user_refs.end()) return; + + ReleaseUser(it->second.pipe, it->second.user); + BReleaseSteamPipe(it->second.pipe); + + old_user_ref_numbers.free_number(hSteamUser); + old_user_refs.erase(it); } ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, const char *pchVersion ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - return GetISteamUser(hSteamUser, pipe, pchVersion); + if (!old_user_refs.count(hSteamUser)) return nullptr; + User_Ref &user_ref = old_user_refs[hSteamUser]; + return GetISteamUser(user_ref.user, user_ref.pipe, pchVersion); } -ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ) +// retrieves the IVac interface associated with the handle +// there is normally only one instance of VAC running, but using this connects it to the right user/account +void *Steam_Client::GetIVAC( HSteamUser hSteamUser ) { PRINT_DEBUG_ENTRY(); - HSteamPipe pipe = get_pipe_for_user(hSteamUser); - return GetISteamGameServer(hSteamUser, pipe, pchVersion); + // actual value from steamclient64.dll + return nullptr; } bool Steam_Client::BMainLoop( uint64 time, bool unk ) @@ -1099,13 +1112,48 @@ bool Steam_Client::BMainLoop( uint64 time, bool unk ) RunCallbacks(true, true); return true; } +// SteamClient001 ----------------------------------------------------- +// SteamClient003 ----------------------------------------------------- bool Steam_Client::BMainLoop( uint64 time ) { PRINT_DEBUG_ENTRY(); RunCallbacks(true, true); return true; } +// SteamClient003 ----------------------------------------------------- + +// SteamClient004 ----------------------------------------------------- +ISteamGameServer *Steam_Client::GetISteamGameServer( HSteamUser hSteamUser, const char *pchVersion ) +{ + PRINT_DEBUG_ENTRY(); + if (!old_user_refs.count(hSteamUser)) return nullptr; + User_Ref &user_ref = old_user_refs[hSteamUser]; + return GetISteamGameServer(user_ref.user, user_ref.pipe, pchVersion); +} +// SteamClient004 ----------------------------------------------------- + +// SteamClient005 ----------------------------------------------------- +// creates a global instance of a steam user, so that other processes can share it +// used by the steam UI, to share it's account info/connection with any games it launches +// fails (returns NULL) if an existing instance already exists +HSteamUser Steam_Client::CreateGlobalUser( HSteamPipe *phSteamPipe ) +{ + // TODO not sure if this implementation is correct + PRINT_DEBUG_TODO(); + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::CLIENT) { + if (phSteamPipe) *phSteamPipe = pipe_handle; + return 0; + } + } + + HSteamPipe pipe = CreateSteamPipe(); + if (phSteamPipe) *phSteamPipe = pipe; + + steam_pipes[pipe] = {Steam_Pipe_Type::CLIENT, false}; + return CLIENT_HSTEAMUSER; +} EUniverse Steam_Client::GetConnectedUniverse() { @@ -1113,9 +1161,20 @@ EUniverse Steam_Client::GetConnectedUniverse() return k_EUniversePublic; } -void Steam_Client::SetEUniverse( EUniverse universe ) +// returns the name of a universe +const char *Steam_Client::GetUniverseName( EUniverse eUniverse ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("%i", (int)eUniverse); + // actual values returned by steamclient64.dll + switch (eUniverse) { + case EUniverse::k_EUniverseInvalid: return "Invalid"; + case EUniverse::k_EUniversePublic: return "Public"; + case EUniverse::k_EUniverseBeta: return "Beta"; + case EUniverse::k_EUniverseInternal: return "Internal"; + case EUniverse::k_EUniverseDev: return "Dev"; + } + + return "Unknown"; } bool Steam_Client::BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg, int *unk ) @@ -1130,17 +1189,30 @@ void Steam_Client::FreeLastCallback( HSteamPipe hSteamPipe ) steamclient_free_callback(hSteamPipe); } +void Steam_Client::SetEUniverse( EUniverse universe ) +{ + PRINT_DEBUG_TODO(); +} +// SteamClient005 ----------------------------------------------------- + +HSteamUser Steam_Client::create_old_user_ref(HSteamUser hUser, HSteamPipe hSteamPipe) +{ + HSteamUser ref = old_user_ref_numbers.get_number(); + old_user_refs[ref] = { hUser, hSteamPipe }; + return ref; +} + HSteamPipe Steam_Client::get_pipe_for_user(HSteamUser hUser) { if (hUser == CLIENT_HSTEAMUSER) { - for (const auto &[pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::CLIENT) { + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::CLIENT) { return pipe_handle; } } } else if (hUser == SERVER_HSTEAMUSER) { - for (const auto &[pipe_handle, pipe_type] : steam_pipes) { - if (pipe_type == Steam_Pipe::SERVER) { + for (const auto &[pipe_handle, pipe_struct] : steam_pipes) { + if (pipe_struct.type == Steam_Pipe_Type::SERVER) { return pipe_handle; } } diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index 264628a09..cdde57ebb 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -79,7 +79,7 @@ ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteam Steam_GameStats *steam_gamestats_tmp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_gamestats_tmp = steam_gameserver_gamestats; } else { steam_gamestats_tmp = steam_gamestats; @@ -96,22 +96,11 @@ ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteam ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) { PRINT_DEBUG("%s", pchVersion); - - if (!hSteamUser) { - return NULL; - } - if (!steam_pipes.count(hSteamPipe)) { - // Fallback for steamclient_experimental build: if pipe 1 is requested but not found, - // and we have other valid pipes, continue execution instead of returning NULL - if (hSteamPipe !=1 || steam_pipes.empty()) { - return NULL; - } - } - + if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return nullptr; Steam_User *steam_user_tmp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_user_tmp = steam_gameserver_user; } else { steam_user_tmp = steam_user; @@ -276,7 +265,7 @@ ISteamUtils *Steam_Client::GetISteamUtils( HSteamPipe hSteamPipe, const char *pc Steam_Utils *steam_utils_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_utils_temp = steam_gameserver_utils; } else { steam_utils_temp = steam_utils; @@ -359,7 +348,7 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe if (!steam_pipes.count(hSteamPipe)) return NULL; bool server = false; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { // PRINT_DEBUG("requesting interface with server pipe"); server = true; } else { @@ -463,6 +452,26 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe if (strcmp(pchVersion, STEAMTV_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_tv)); } + } else if (strstr(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION") == pchVersion) { + if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION001") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION002") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, "STEAMUSERITEMS_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } else if (strcmp(pchVersion, STEAMUSERITEMS_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_user_items)); + } + } else if (strstr(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION") == pchVersion) { + if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION001") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION002") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, "STEAMGAMESERVERITEMS_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } else if (strcmp(pchVersion, STEAMGAMESERVERITEMS_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_gameserver_items)); + } } else if (strstr(pchVersion, "STEAMREMOTESTORAGE_INTERFACE_VERSION") == pchVersion) { return GetISteamRemoteStorage(hSteamUser, hSteamPipe, pchVersion); } else if (strstr(pchVersion, "SteamGameServerStats") == pchVersion) { @@ -591,7 +600,7 @@ ISteamApps *Steam_Client::GetISteamApps( HSteamUser hSteamUser, HSteamPipe hStea Steam_Apps *steam_apps_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_apps_temp = steam_gameserver_apps; } else { steam_apps_temp = steam_apps; @@ -610,6 +619,8 @@ ISteamApps *Steam_Client::GetISteamApps( HSteamUser hSteamUser, HSteamPipe hStea return reinterpret_cast(static_cast(steam_apps_temp)); } else if (strcmp(pchVersion, "STEAMAPPS_INTERFACE_VERSION007") == 0) { return reinterpret_cast(static_cast(steam_apps_temp)); + } else if (strcmp(pchVersion, "STEAMAPPS_INTERFACE_VERSION008") == 0) { + return reinterpret_cast(static_cast(steam_apps_temp)); } else if (strcmp(pchVersion, STEAMAPPS_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_apps_temp)); } @@ -625,7 +636,7 @@ ISteamNetworking *Steam_Client::GetISteamNetworking( HSteamUser hSteamUser, HSte Steam_Networking *steam_networking_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_networking_temp = steam_gameserver_networking; } else { steam_networking_temp = steam_networking; @@ -716,7 +727,7 @@ ISteamHTTP *Steam_Client::GetISteamHTTP( HSteamUser hSteamuser, HSteamPipe hStea if (!steam_pipes.count(hSteamPipe) || !hSteamuser) return NULL; Steam_HTTP *steam_http_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_http_temp = steam_gameserver_http; } else { steam_http_temp = steam_http; @@ -794,7 +805,7 @@ ISteamUGC *Steam_Client::GetISteamUGC( HSteamUser hSteamUser, HSteamPipe hSteamP if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return NULL; Steam_UGC *steam_ugc_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_ugc_temp = steam_gameserver_ugc; } else { steam_ugc_temp = steam_ugc; @@ -917,7 +928,7 @@ ISteamInventory *Steam_Client::GetISteamInventory( HSteamUser hSteamuser, HSteam if (!steam_pipes.count(hSteamPipe) || !hSteamuser) return NULL; Steam_Inventory *steam_inventory_temp{}; - if (steam_pipes[hSteamPipe] == Steam_Pipe::SERVER) { + if (steam_pipes[hSteamPipe].type == Steam_Pipe_Type::SERVER) { steam_inventory_temp = steam_gameserver_inventory; } else { steam_inventory_temp = steam_inventory; @@ -1039,6 +1050,8 @@ ISteamRemotePlay *Steam_Client::GetISteamRemotePlay( HSteamUser hSteamUser, HSte return reinterpret_cast(static_cast(steam_remoteplay)); } else if (strcmp(pchVersion, "STEAMREMOTEPLAY_INTERFACE_VERSION002") == 0) { return reinterpret_cast(static_cast(steam_remoteplay)); + } else if (strcmp(pchVersion, "STEAMREMOTEPLAY_INTERFACE_VERSION003") == 0) { + return reinterpret_cast(static_cast(steam_remoteplay)); } else if (strcmp(pchVersion, STEAMREMOTEPLAY_INTERFACE_VERSION) == 0) { return reinterpret_cast(static_cast(steam_remoteplay)); } diff --git a/dll/steam_controller.cpp b/dll/steam_controller.cpp index 27c15e813..b6c40b5b9 100644 --- a/dll/steam_controller.cpp +++ b/dll/steam_controller.cpp @@ -221,7 +221,7 @@ Steam_Controller::Steam_Controller(class Settings *settings, class SteamCallResu this->run_every_runcb = run_every_runcb; set_handles(settings->controller_settings.action_sets); - disabled = action_handles.empty(); + disabled = !settings->controller_settings.enabled && action_handles.empty(); initialized = false; this->run_every_runcb->add(&Steam_Controller::steam_run_every_runcb, this); @@ -1086,6 +1086,17 @@ ESteamInputType Steam_Controller::GetInputTypeForHandle( ControllerHandle_t cont PRINT_DEBUG("%llu", controllerHandle); auto controller = controllers.find(controllerHandle); if (controller == controllers.end()) return k_ESteamInputType_Unknown; + + // Playstation + if (settings->controller_settings.controller_type_override == "PS3") return k_ESteamInputType_PS3Controller; + if (settings->controller_settings.controller_type_override == "PS4") return k_ESteamInputType_PS4Controller; + if (settings->controller_settings.controller_type_override == "PS5") return k_ESteamInputType_PS5Controller; + // Xbox + if (settings->controller_settings.controller_type_override == "XBOX360") return k_ESteamInputType_XBox360Controller; + if (settings->controller_settings.controller_type_override == "XBOXONE") return k_ESteamInputType_XBoxOneController; + // Nintendo + if (settings->controller_settings.controller_type_override == "SWITCH") return k_ESteamInputType_SwitchProController; + return k_ESteamInputType_XBox360Controller; } diff --git a/dll/steam_friends.cpp b/dll/steam_friends.cpp index e776b5d8b..27f640f06 100644 --- a/dll/steam_friends.cpp +++ b/dll/steam_friends.cpp @@ -71,6 +71,11 @@ struct Avatar_Numbers Steam_Friends::add_friend_avatars(CSteamID id) "account_avatar.jpg", "account_avatar.jpeg", }; + static const std::initializer_list avatar_icons_default = { + "account_avatar_default.png", + "account_avatar_default.jpg", + "account_avatar_default.jpeg", + }; if (!settings->disable_account_avatar && (id == settings->get_local_steam_id())) { std::string file_path{}; @@ -104,7 +109,7 @@ struct Avatar_Numbers Steam_Friends::add_friend_avatars(CSteamID id) // try local location first, then try global location for (const auto &settings_path : { Local_Storage::get_game_settings_path(), local_storage->get_global_settings_path() }) { - for (const auto &file_name : avatar_icons) { + for (const auto &file_name : avatar_icons_default) { file_path = settings_path + file_name; file_size = file_size_(file_path); if (file_size) break; diff --git a/dll/steam_game_coordinator.cpp b/dll/steam_game_coordinator.cpp index 4a9baca32..f62d38c17 100644 --- a/dll/steam_game_coordinator.cpp +++ b/dll/steam_game_coordinator.cpp @@ -16,22 +16,701 @@ . */ #include "dll/steam_game_coordinator.h" +#include "dll/dll.h" +#include +#include +#include +#include +#include +#include -void Steam_Game_Coordinator::push_incoming(std::string message) +using namespace gamecoordinator::tf2; + +constexpr int GC_MIN_VERSION = 20091217; + +#pragma pack( push, 1 ) +//----------------------------------------------------------------------------- +// Purpose: Header for messages from a client or gameserver to or from the GC +//----------------------------------------------------------------------------- +struct GCMsgHdr_t +{ + uint32 m_eMsg; // The message type + uint64 m_ulSteamID; // User's SteamID +}; + +//----------------------------------------------------------------------------- +// Purpose: Header for messages from a client or gameserver to or from the GC +// That contains source and destination jobs for the purpose of +// replying messages. +//----------------------------------------------------------------------------- +struct GCMsgHdrEx_t { - outgoing_messages.push(message); + uint32 m_eMsg; // The message type + uint64 m_ulSteamID; // User's SteamID + uint16 m_nHdrVersion; + JobID_t m_JobIDTarget; + JobID_t m_JobIDSource; +}; - struct GCMessageAvailable_t data{}; - data.m_nMessageSize = static_cast(message.size()); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +struct ProtoBufMsgHeader_t +{ + uint32 m_EMsgFlagged; // High bit should be set to indicate this message header type is in use. The rest of the bits indicate message type. + uint32 m_cubProtoBufExtHdr; // Size of the extended header which is a serialized protobuf object. Indicates where it ends and the serialized body protobuf begins. +}; +#pragma pack(pop) + +template +static void ser_var(std::string &buf, const T &input) +{ + buf.append(reinterpret_cast(&input), sizeof(T)); } -void Steam_Game_Coordinator::steam_callback(void *object, Common_Message *msg) +static void ser_varstring(std::string &buf, const std::string &input) { - // PRINT_DEBUG_ENTRY(); + uint16 len = static_cast(input.size()); + if (len != 0) { + ser_var(buf, len + 1); + buf.append(input + '\0'); + } else { + ser_var(buf, 0); + } +} - Steam_Game_Coordinator *steam_gamecoordinator = (Steam_Game_Coordinator *)object; - steam_gamecoordinator->Callback(msg); +template +static T deser_var(const char *&p) +{ + T output; + memcpy(&output, p, sizeof(T)); + p += sizeof(T); + return output; +} + +bool Steam_Game_Coordinator::gc_enabled() +{ + return (gc_version >= GC_MIN_VERSION && gc_profile != GC_PROFILE_INVALID); +} + +Steam_User_Items *Steam_Game_Coordinator::client_items() +{ + return get_steam_client()->steam_user_items; +} + +Steam_GameServer_Items *Steam_Game_Coordinator::server_items() +{ + return get_steam_client()->steam_gameserver_items; +} + +void Steam_Game_Coordinator::parse_gc_config() +{ + std::string file_path = Local_Storage::get_game_settings_path() + gc_config_file; + nlohmann::json gc_json; + if (!local_storage->load_json(file_path, gc_json)) + return; + + try { + std::string gc_profile_name = gc_json.value("gc_profile", std::string()); + std::transform(gc_profile_name.begin(), gc_profile_name.end(), gc_profile_name.begin(), + [](auto c) { return std::tolower(c); }); + if (gc_profile_name == "tf2") { + gc_profile = GC_PROFILE_TF2; + } else if (gc_profile_name == "portal2") { + // Portal 2 is pretty much entirely compatible with TF2 protobuf structs so we can just + // make it an alias for TF2 profile. + //gc_profile = GC_PROFILE_PORTAL2; + gc_profile = GC_PROFILE_TF2; + is_portal2 = true; + } else { + gc_profile = GC_PROFILE_INVALID; + } + + gc_version = gc_json.value("gc_version", 0); + } catch (std::exception &e) { + const char *errorMessage = e.what(); + PRINT_DEBUG("error parsing GC config: %s", errorMessage); + gc_version = 0; + gc_profile = GC_PROFILE_INVALID; + } +} + +bool Steam_Game_Coordinator::is_welcome_message(const GC_Message &message) +{ + uint32 msg_type = message.msg_type & (~protobuf_mask); + return (msg_type == 4004 || + msg_type == 4005); +} + +void Steam_Game_Coordinator::push_incoming(uint32 msg_type, const std::string &message, double delay) +{ + PRINT_DEBUG("%u %.2f", msg_type, delay); + + GC_Message new_item; + new_item.msg_type = msg_type; + new_item.msg_body = message; + new_item.created = std::chrono::high_resolution_clock::now(); + new_item.post_in = delay; + pending_messages.push_back(new_item); +} + +std::string Steam_Game_Coordinator::build_msg_header(JobID_t target_job, JobID_t source_job) +{ + std::string message; + GCMsgHdrEx_t hdr{}; + hdr.m_nHdrVersion = 1; + hdr.m_JobIDTarget = target_job; + hdr.m_JobIDSource = source_job; + ser_var(message, hdr); + return message.substr(sizeof(GCMsgHdr_t)); +} + +GCMsgHdrEx_t Steam_Game_Coordinator::parse_msg_header(const char *&p) +{ + size_t write_offset = sizeof(GCMsgHdr_t); + size_t hdr_size = sizeof(GCMsgHdrEx_t) - write_offset; + GCMsgHdrEx_t hdr{}; + memcpy(reinterpret_cast(&hdr) + write_offset, p, hdr_size); + p += hdr_size; + return hdr; +} + +std::string Steam_Game_Coordinator::build_protomsg_header(uint32 msg_type, JobID_t target_job, JobID_t source_job) +{ + std::string message; + ProtoBufMsgHeader_t hdr{}; + hdr.m_EMsgFlagged = msg_type; + + CMsgProtoBufHeader protohdr; + protohdr.set_client_steam_id(settings->get_local_steam_id().ConvertToUint64()); + protohdr.set_client_session_id(1); + protohdr.set_source_app_id(settings->get_local_game_id().AppID()); + protohdr.set_job_id_source(source_job); + protohdr.set_job_id_target(target_job); + hdr.m_cubProtoBufExtHdr = static_cast(protohdr.ByteSizeLong()); + + ser_var(message, hdr); + protohdr.AppendToString(&message); + + return message; +} + +template +std::tuple Steam_Game_Coordinator::parse_protomsg(const void *input, uint32 input_size) +{ + const char *p = reinterpret_cast(input); + const char *end = p + input_size; + + ProtoBufMsgHeader_t hdr{}; + CMsgProtoBufHeader protohdr; + T protomsg; + + if (input_size < sizeof(ProtoBufMsgHeader_t)) + return { hdr, protohdr, protomsg, false }; + + hdr = deser_var(p); + + if (!protohdr.ParseFromArray(p, hdr.m_cubProtoBufExtHdr)) + return { hdr, protohdr, protomsg, false }; + + p += hdr.m_cubProtoBufExtHdr; + + int protomsg_size = static_cast(end - p); + if (!protomsg.ParseFromArray(p, protomsg_size)) + return { hdr, protohdr, protomsg, false }; + + return { hdr, protohdr, protomsg, true }; +} + +uint64 Steam_Game_Coordinator::item_id_local_to_network(uint64 item_id) +{ + if (item_id == 0) + return 0; + + // Add SteamID to item ID to avoid ID collisions in multiplayer games. + uint32 account_id = settings->get_local_steam_id().GetAccountID(); + + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + item_id <<= 20ull; + item_id |= static_cast(account_id) & 0x000FFFFFull; + } else { + // 64-bit mode + item_id <<= 32ull; + item_id |= static_cast(account_id); + } + + return item_id; +} + +uint64 Steam_Game_Coordinator::item_id_network_to_local(uint64 item_id) +{ + if (settings->use_32bit_inventory_item_ids) { + // 32-bit mode + item_id >>= 20ull; + } else { + // 64-bit mode + item_id >>= 32ull; + } + + return item_id; +} + +std::string Steam_Game_Coordinator::item_to_gcstruct(const Econ_Item &item, CSteamID steam_id) +{ + std::string message; + + ser_var(message, item.id); + ser_var(message, steam_id.GetAccountID()); + ser_var(message, item.def); + ser_var(message, item.level); + ser_var(message, item.quality); + ser_var(message, item.inv_pos); + ser_var(message, item.quantity); + + if (gc_version >= 20100428) { + // Strings are passed as UTF-8 which is good for us since we can just copy std::string as is. + ser_varstring(message, item.custom_name); + + if (gc_version >= 20100930) { + ser_var(message, item.flags); + + if (gc_version >= 20101027) { + ser_var(message, item.origin); + ser_varstring(message, item.custom_desc); + ser_var(message, item.in_use); + } + } + } + + ser_var(message, static_cast(item.attributes.size())); + + for (const Econ_Item_Attribute &attr : item.attributes) { + ser_var(message, attr.def); + ser_var(message, attr.value); + } + + if (gc_version >= 20101217) { + ser_var(message, item.original_id); + } + + return message; +} + +std::string Steam_Game_Coordinator::item_to_gcprotobuf(const Econ_Item &item, CSteamID steam_id) +{ + CSOEconItem proto_item; + proto_item.set_id(item.id); + proto_item.set_account_id(steam_id.GetAccountID()); + proto_item.set_inventory(item.inv_pos); + proto_item.set_def_index(item.def); + proto_item.set_quantity(item.quantity); + proto_item.set_level(item.level); + proto_item.set_quality(item.quality); + proto_item.set_flags(item.flags); + proto_item.set_origin(item.origin); + + if (!item.custom_name.empty()) + proto_item.set_custom_name(item.custom_name); + + if (!item.custom_desc.empty()) + proto_item.set_custom_desc(item.custom_desc); + + proto_item.set_in_use(item.in_use); + proto_item.set_style(item.style); + proto_item.set_original_id(item.original_id); + + proto_item.set_contains_equipped_state(true); + proto_item.set_contains_equipped_state_v2(true); + + for (const auto &[class_id, slot_id] : item.equip_states) { + auto proto_equip = proto_item.add_equipped_state(); + proto_equip->set_new_class(class_id); + proto_equip->set_new_slot(slot_id); + } + + for (const Econ_Item_Attribute &attr : item.attributes) { + auto proto_attr = proto_item.add_attribute(); + proto_attr->set_def_index(attr.def); + if (gc_version < 20130319 || is_portal2) { + // Derp. + uint32 value; + memcpy(&value, &attr.value, sizeof(uint32)); + proto_attr->set_value(value); + } else { + proto_attr->set_value_bytes(attr.value_bytes); + } + } + + return proto_item.SerializeAsString(); +} + +void Steam_Game_Coordinator::handle_set_item_pos(const void *input, uint32 input_size) +{ + if (is_server || input_size < 30) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint64 item_id = deser_var(p); + uint32 inv_pos = deser_var(p); + PRINT_DEBUG("%llu %u", item_id, inv_pos); + + if (const Econ_Item *item = set_item_pos(item_id, inv_pos, true)) { + callback_item_updated(settings->get_local_steam_id(), *item); + } +} + +void Steam_Game_Coordinator::handle_delete_item(const void *input, uint32 input_size) +{ + if (is_server || input_size < 26) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint64 item_id = deser_var(p); + PRINT_DEBUG("%llu", item_id); + + if (delete_item(item_id, true)) { + callback_item_deleted(settings->get_local_steam_id(), item_id); + } +} + +void Steam_Game_Coordinator::handle_motd_request(const void *input, uint32 input_size) +{ + if (is_server || input_size < 24) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint32 last_req_time = deser_var(p); + uint16 language = deser_var(p); + PRINT_DEBUG("%u %u", last_req_time, language); + + uint32 msg_type = EGCItemMsg::k_EMsgGCMOTDRequestResponse; + std::string message = build_msg_header(); + uint16 num_entries = 0; + ser_var(message, num_entries); + + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::handle_respawn(const void *input, uint32 input_size) +{ + if (is_server || input_size < 19) + return; + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Respawn); + gameserver_items_msg->set_is_gc(true); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAllGameservers(&msg, true); +} + +void Steam_Game_Coordinator::handle_set_item_style(const void *input, uint32 input_size) +{ + if (is_server || input_size < 27) + return; + + const char *p = reinterpret_cast(input); + GCMsgHdrEx_t hdr = parse_msg_header(p); + uint64 item_id = deser_var(p); + uint8 style = deser_var(p); + PRINT_DEBUG("%llu %u", item_id, style); + + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.style = style; + save_items_to_file(); + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item_id); + inventory_msg->set_style(style); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); + gameserver_items_msg->set_is_gc(true); + gameserver_items_msg->set_allocated_item_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + callback_item_updated(settings->get_local_steam_id(), item); + break; + } +} + +void Steam_Game_Coordinator::handle_adjust_equip_state(const void *input, uint32 input_size) +{ + if (is_server) + return; + + auto [hdr, protohdr, protomsg, success] = parse_protomsg(input, input_size); + if (!success) + return; + + uint64 item_id = protomsg.item_id(); + uint32 new_class_id = protomsg.new_class(); + uint32 new_slot_id = protomsg.new_slot(); + PRINT_DEBUG("%llu %u %u", item_id, new_class_id, new_slot_id); + + for (Econ_Item &item : items) { + if (item_id != UINT64_MAX && item.id == item_id) { + // Equip the item into this slot. + item.equip_states.insert_or_assign(new_class_id, new_slot_id); + } else { + // Unequip whatever else we had in this slot. + auto it = item.equip_states.find(new_class_id); + if (it == item.equip_states.end() || it->second != new_slot_id) + continue; + + item.equip_states.erase(it); + } + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item.id); + inventory_msg->set_has_equip_states(true); + for (const auto &[class_id, slot_id] : item.equip_states) { + auto new_state = inventory_msg->add_equip_states(); + new_state->set_class_id(class_id); + new_state->set_slot_id(slot_id); + } + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); + gameserver_items_msg->set_is_gc(true); + gameserver_items_msg->set_allocated_item_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + callback_item_updated(settings->get_local_steam_id(), item); + } + + save_items_to_file(); +} + +void Steam_Game_Coordinator::handle_set_multiple_item_pos(const void *input, uint32 input_size) +{ + if (is_server) + return; + + auto [hdr, protohdr, protomsg, success] = parse_protomsg(input, input_size); + if (!success) + return; + + for (auto &entry : protomsg.item_positions()) { + uint64 item_id = entry.item_id(); + uint32 inv_pos = entry.position(); + + if (const Econ_Item *item = set_item_pos(item_id, inv_pos, true, false)) { + callback_item_updated(settings->get_local_steam_id(), *item); + } + } + + save_items_to_file(); +} + +void Steam_Game_Coordinator::callback_client_welcome() +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCBaseClientMsg::k_EMsgGCClientWelcome | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgClientWelcome protomsg; + protomsg.set_version(0); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::callback_server_welcome() +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCBaseClientMsg::k_EMsgGCServerWelcome | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgServerWelcome protomsg; + protomsg.set_min_allowed_version(0); + protomsg.set_active_version(0); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::callback_items_received(CSteamID steam_id, const std::vector &items) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheSubscribed; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint16 num_types = 1; + + ser_var(message, owner_id); + ser_var(message, num_types); + + // econ items (1) + uint32 object_type = 1; + uint16 num_objects = static_cast(items.size()); + + ser_var(message, object_type); + ser_var(message, num_objects); + + for (const Econ_Item &item : items) { + message.append(item_to_gcstruct(item, steam_id)); + } + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheSubscribed | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOCacheSubscribed protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + auto objects = protomsg.add_objects(); + objects->set_type_id(1); + + for (const Econ_Item &item : items) { + objects->add_object_data(item_to_gcprotobuf(item, steam_id)); + } + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_items_removed(CSteamID steam_id) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheUnsubscribed; + std::string message = build_msg_header(); + ser_var(message, steam_id.ConvertToUint64()); + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_CacheUnsubscribed | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOCacheUnsubscribed protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_item_updated(CSteamID steam_id, const Econ_Item &item) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_Update; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + uint8 num_fields = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item.id); + ser_var(message, num_fields); + + uint8 field_idx = 5; + ser_var(message, field_idx); + ser_var(message, item.inv_pos); + + if (gc_version >= 20101027) { + ser_var(message, item.in_use); + } + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_Update | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOSingleObject protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + protomsg.set_type_id(1); + protomsg.set_object_data(item_to_gcprotobuf(item, steam_id)); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_item_deleted(CSteamID steam_id, uint64 item_id) +{ + if (!gc_initialized) + return; + + if (gc_version < 20110414) { + uint32 msg_type = ESOMsg::k_ESOMsg_Destroy; + std::string message = build_msg_header(); + + uint64 owner_id = steam_id.ConvertToUint64(); + uint32 object_type = 1; + + ser_var(message, owner_id); + ser_var(message, object_type); + ser_var(message, item_id); + + push_incoming(msg_type, message); + } else { + uint32 msg_type = ESOMsg::k_ESOMsg_Destroy | protobuf_mask; + std::string message = build_protomsg_header(msg_type); + + CMsgSOSingleObject protomsg; + protomsg.set_owner(steam_id.ConvertToUint64()); + protomsg.set_type_id(1); + + CSOEconItem proto_item; + proto_item.set_id(item_id); + protomsg.set_object_data(proto_item.SerializeAsString()); + + protomsg.AppendToString(&message); + push_incoming(msg_type, message); + } +} + +void Steam_Game_Coordinator::callback_respawn_request(CSteamID steam_id) +{ + if (!gc_initialized) + return; + + uint32 msg_type = EGCItemMsg::k_EMsgGCRespawnPostLoadoutChange; + std::string message = build_msg_header(); + ser_var(message, steam_id.ConvertToUint64()); + + push_incoming(msg_type, message); +} + +void Steam_Game_Coordinator::steam_network_callback(void *object, Common_Message *msg) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_Game_Coordinator *)object; + inst->network_callback(msg); } void Steam_Game_Coordinator::steam_run_every_runcb(void *object) @@ -42,41 +721,415 @@ void Steam_Game_Coordinator::steam_run_every_runcb(void *object) steam_gamecoordinator->RunCallbacks(); } - -Steam_Game_Coordinator::Steam_Game_Coordinator(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +Steam_Game_Coordinator::Steam_Game_Coordinator(class Settings *settings, class Networking *network, class Local_Storage *local_storage, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, bool is_server) { this->settings = settings; this->network = network; - this->callback_results = callback_results; + this->local_storage = local_storage; this->callbacks = callbacks; this->run_every_runcb = run_every_runcb; - - this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_callback, this); + this->is_server = is_server; + + this->network->setCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); this->run_every_runcb->add(&Steam_Game_Coordinator::steam_run_every_runcb, this); + + parse_gc_config(); } Steam_Game_Coordinator::~Steam_Game_Coordinator() { - this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_callback, this); + this->network->rmCallback(CALLBACK_ID_GAMESERVER_ITEMS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Game_Coordinator::steam_network_callback, this); this->run_every_runcb->remove(&Steam_Game_Coordinator::steam_run_every_runcb, this); } +void Steam_Game_Coordinator::initialize_gc() +{ + if (!gc_enabled() || gc_initialized) + return; + + gc_initialized = true; + + if (is_server) { + callback_server_welcome(); + } else { + callback_client_welcome(); + + // Load user's items. + const auto &items = load_items_from_file(); + callback_items_received(settings->get_local_steam_id(), items); + } + + // Wait a bit until after the game has received welcome message from us before posting anything else. + // This avoids a race condition that can cause the game to receive inventory before parsing item schema. + // For old versions of TF2, we can't do that because they instead have a different bug where receiving + // the inventory late causes erroneous "new items" notifications. + if (gc_version >= 20110414) { + delay_init = true; + } +} + +void Steam_Game_Coordinator::shutdown_gc() +{ + if (!gc_initialized) + return; + + items_loaded = false; + items.clear(); + all_user_items.clear(); + pending_items_requests.clear(); + pending_messages.clear(); + while (incoming_messages.size()) + incoming_messages.pop(); + + welcome_received = false; + delay_init = false; + gc_initialized = false; +} + +const std::vector &Steam_Game_Coordinator::load_items_from_file() +{ + if (items_loaded) + return items; + + items_loaded = true; + + nlohmann::json items_json; + if (!local_storage->load_json_file("", items_user_file, items_json)) + return items; + + for (auto it = items_json.begin(); it != items_json.end(); it++) { + Econ_Item new_item{}; + try { + new_item.id = std::stoull(it.key()); + } catch (...) { + continue; + } + if (new_item.id == 0) + continue; + + try { + new_item.def = it->value("definition", 0u); // 0 is a valid item definition + new_item.level = it->value("level", 1u); + new_item.quality = static_cast(it->value("quality", 0)); + new_item.inv_pos = it->value("inventory_pos", 0u); + new_item.quantity = it->value("quantity", 1u); + new_item.flags = it->value("flags", 0u); + new_item.origin = it->value("origin", 0u); + new_item.custom_name = it->value("custom_name", std::string()); + new_item.custom_desc = it->value("custom_desc", std::string()); + new_item.original_id = it->value("original_id", 0ull); + new_item.style = it->value("style", 0u); + new_item.in_use = false; + + if (it->contains("equip_states")) { + for (const auto &equip : it->at("equip_states")) { + uint32 class_id = equip.value("class", 0u); + uint32 slot_id = equip.value("slot", 0u); + + new_item.equip_states.insert({ class_id, slot_id }); + } + } + + if (it->contains("attributes")) { + for (const auto &attr : it->at("attributes")) { + Econ_Item_Attribute new_attr{}; + new_attr.def = attr.value("definition", 0u); + if (new_attr.def == 0) // 0 is not a valid attribute definition, however + continue; + + if (attr.contains("value")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_DEFAULT; + float value = attr.value("value", 0.0f); + ser_var(new_attr.value_bytes, value); + new_attr.value = value; + } else if (attr.contains("value_float")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_FLOAT; + float value = attr.value("value_float", 0.0f); + ser_var(new_attr.value_bytes, value); + new_attr.value = value; + } else if (attr.contains("value_int")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_INT; + uint32 value = attr.value("value_int", 0u); + ser_var(new_attr.value_bytes, value); + memcpy(&new_attr.value, &value, sizeof(float)); + } else if (attr.contains("value_string")) { + new_attr.type = Econ_Item_Attribute::ATTR_TYPE_STRING; + std::string value = attr.value("value_string", std::string()); + new_attr.value_bytes = value + '\0'; + new_attr.value = 0.0f; + } else { + continue; + } + + new_item.attributes.push_back(new_attr); + } + } + } catch (std::exception &e) { + const char *errorMessage = e.what(); + PRINT_DEBUG("error parsing item %llu: %s", new_item.id, errorMessage); + continue; + } + + new_item.id = item_id_local_to_network(new_item.id); + new_item.original_id = item_id_local_to_network(new_item.original_id); + + // Check custom name and custom description limits. + if (!check_econ_item_name(new_item.custom_name)) { + new_item.custom_name.clear(); + } + + if (!check_econ_item_desc(new_item.custom_desc)) { + new_item.custom_desc.clear(); + } + + items.push_back(new_item); + } + + return items; +} + +void Steam_Game_Coordinator::save_items_to_file() +{ + nlohmann::json items_json; + + for (const Econ_Item &item : items) { + uint64 item_id = item_id_network_to_local(item.id); + + nlohmann::json json_item; + json_item["definition"] = item.def; + json_item["level"] = item.level; + json_item["quality"] = item.quality; + json_item["inventory_pos"] = item.inv_pos; + json_item["quantity"] = item.quantity; + json_item["flags"] = item.flags; + json_item["origin"] = item.origin; + json_item["custom_name"] = item.custom_name; + json_item["custom_desc"] = item.custom_desc; + json_item["original_id"] = item_id_network_to_local(item.original_id); + json_item["style"] = item.style; + + for (auto &[class_id, slot_id] : item.equip_states) { + nlohmann::json json_equip; + json_equip["class"] = class_id; + json_equip["slot"] = slot_id; + json_item["equip_states"].push_back(json_equip); + } + + for (const Econ_Item_Attribute &attr : item.attributes) { + nlohmann::json json_attr; + json_attr["definition"] = attr.def; + + switch (attr.type) { + case Econ_Item_Attribute::ATTR_TYPE_DEFAULT: { + float value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(float)); + json_attr["value"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_FLOAT: { + float value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(float)); + json_attr["value_float"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_INT: { + uint32 value; + attr.value_bytes.copy(reinterpret_cast(&value), sizeof(uint32)); + json_attr["value_int"] = value; + break; + } + case Econ_Item_Attribute::ATTR_TYPE_STRING: { + const char *value = attr.value_bytes.c_str(); + json_attr["value_string"] = value; + break; + } + } + + json_item["attributes"].push_back(json_attr); + } + + items_json[std::to_string(item_id)] = json_item; + } + + local_storage->write_json_file("", items_user_file, items_json); +} + +const Econ_Item *Steam_Game_Coordinator::set_item_pos(uint64 item_id, uint32 inv_pos, bool is_gc, bool save) +{ + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + item.inv_pos = inv_pos; + if (save) { + save_items_to_file(); + } + + // Let the others know, too. + auto inventory_msg = new GameServer_Items_Messages::ItemUpdate(); + inventory_msg->set_id(item_id); + inventory_msg->set_inv_pos(inv_pos); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_UpdateItem); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_item_update(inventory_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + return &item; + } + + return nullptr; +} + +bool Steam_Game_Coordinator::delete_item(uint64 item_id, bool is_gc) +{ + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != item_id) + continue; + + items.erase(it); + save_items_to_file(); + + // Let the others know, too. + auto delete_msg = new GameServer_Items_Messages::ItemDeletion(); + delete_msg->set_item_id(item_id); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_DeleteItem); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_item_deletion(delete_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + network->sendToAll(&msg, true); + + return true; + } + + return false; +} + +void Steam_Game_Coordinator::request_user_items(CSteamID steam_id, SteamAPICall_t api_call, bool is_gc) +{ + RequestInventory new_request{}; + new_request.created = std::chrono::high_resolution_clock::now(); + new_request.steam_id = steam_id; + new_request.steam_api_call = api_call; + new_request.is_gc = is_gc; + pending_items_requests.push_back(new_request); + + auto request_msg = new GameServer_Items_Messages::InventoryRequest(); + request_msg->set_steam_api_call(new_request.steam_api_call); + + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Request_Inventory); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_inventory_request(request_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steam_id.ConvertToUint64()); + network->sendTo(&msg, true); +} + +SteamAPICall_t Steam_Game_Coordinator::find_items_request(CSteamID steam_id) +{ + auto it = std::find_if( + pending_items_requests.begin(), pending_items_requests.end(), + [=](const RequestInventory &item) { + return item.steam_id == steam_id; + } + ); + + if (it == pending_items_requests.end()) + return k_uAPICallInvalid; + + return it->steam_api_call; +} + +void Steam_Game_Coordinator::remove_user_items(CSteamID steam_id) +{ + all_user_items.erase(steam_id); + + // Clean up any pending requests we have. + for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { + if (it->steam_id == steam_id) { + it = pending_items_requests.erase(it); + } else { + it++; + } + } + + callback_items_removed(steam_id); +} + +void Steam_Game_Coordinator::on_client_connected(CSteamID steam_id) +{ + if (!steam_id.BIndividualAccount()) + return; + + if (gc_initialized) { + request_user_items(steam_id, generate_steam_api_call_id(), true); + } +} + +void Steam_Game_Coordinator::on_client_disconnected(CSteamID steam_id) +{ + if (!steam_id.BIndividualAccount()) + return; + + remove_user_items(steam_id); +} // sends a message to the Game Coordinator EGCResults Steam_Game_Coordinator::SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData ) { - PRINT_DEBUG("%X %u len %u", unMsgType, (~protobuf_mask) & unMsgType, cubData); + PRINT_DEBUG("0x%08X %u len %u", unMsgType, (~protobuf_mask) & unMsgType, cubData); std::lock_guard lock(global_mutex); - if (protobuf_mask & unMsgType) { - uint32 message_type = (~protobuf_mask) & unMsgType; - if (message_type == 4006) { //client hello - std::string message("\xA4\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); - push_incoming(message); - } else - if (message_type == 4007) { //server hello - std::string message("\xA5\x0F\x00\x80\x00\x00\x00\x00\x08\x00", 10); - push_incoming(message); - } + + if (!gc_initialized) + return k_EGCResultOK; + + switch (unMsgType) { + case EGCItemMsg::k_EMsgGCSetSingleItemPosition: + PRINT_DEBUG("k_EMsgGCSetSingleItemPosition"); + handle_set_item_pos(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCDelete: + PRINT_DEBUG("k_EMsgGCDelete"); + handle_delete_item(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCMOTDRequest: + PRINT_DEBUG("k_EMsgGCMOTDRequest"); + handle_motd_request(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCRespawnPostLoadoutChange: + PRINT_DEBUG("k_EMsgGCRespawnPostLoadoutChange"); + handle_respawn(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCSetItemStyle: + PRINT_DEBUG("k_EMsgGCSetItemStyle"); + handle_set_item_style(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCAdjustItemEquippedState | protobuf_mask: + PRINT_DEBUG("k_EMsgGCAdjustItemEquippedState"); + handle_adjust_equip_state(pubData, cubData); + break; + case EGCItemMsg::k_EMsgGCSetItemPositions | protobuf_mask: + PRINT_DEBUG("k_EMsgGCSetItemPositions"); + handle_set_multiple_item_pos(pubData, cubData); + break; + default: + break; } return k_EGCResultOK; @@ -87,12 +1140,16 @@ bool Steam_Game_Coordinator::IsMessageAvailable( uint32 *pcubMsgSize ) { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (pcubMsgSize) *pcubMsgSize = static_cast(outgoing_messages.front().size()); - return true; - } else { + + if (!gc_initialized || incoming_messages.empty()) { + *pcubMsgSize = 0; return false; } + + GC_Message &message = incoming_messages.front(); + *pcubMsgSize = static_cast(message.msg_body.size()); + + return true; } // fills the provided buffer with the first message in the queue and returns k_EGCResultOK or @@ -103,44 +1160,384 @@ EGCResults Steam_Game_Coordinator::RetrieveMessage( uint32 *punMsgType, void *pu { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (outgoing_messages.size()) { - if (outgoing_messages.front().size() > cubDest) { - return k_EGCResultBufferTooSmall; + + if (!gc_initialized || incoming_messages.empty()) { + *pcubMsgSize = 0; + return k_EGCResultNoMessage; + } + + GC_Message &message = incoming_messages.front(); + + uint32 outsize = static_cast(message.msg_body.size()); + if (outsize > cubDest) { + return k_EGCResultBufferTooSmall; + } + + if (is_welcome_message(message)) { + welcome_received = true; + welcome_time = std::chrono::high_resolution_clock::now(); + } + + *punMsgType = message.msg_type; + *pcubMsgSize = outsize; + message.msg_body.copy(reinterpret_cast(pubDest), cubDest); + incoming_messages.pop(); + + return k_EGCResultOK; +} + +// server requested our inventory +void Steam_Game_Coordinator::network_callback_inventory_request(Common_Message *msg) +{ + // Server instance should never receive this. + if (is_server) + return; + + uint64 server_steamid = msg->source_id(); + + if (!msg->gameserver_items_messages().has_inventory_request()) { + PRINT_DEBUG("error empty msg"); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &request_msg = msg->gameserver_items_messages().inventory_request(); + auto response_msg = new GameServer_Items_Messages::InventoryResponse(); + response_msg->set_steam_api_call(request_msg.steam_api_call()); + + for (const Econ_Item &item : items) { + auto new_item = response_msg->add_items(); + new_item->set_id(item.id); + new_item->set_def(item.def); + new_item->set_level(item.level); + new_item->set_quality(static_cast(item.quality)); + new_item->set_inv_pos(item.inv_pos); + new_item->set_quantity(item.quantity); + new_item->set_flags(item.flags); + new_item->set_origin(item.origin); + new_item->set_custom_name(item.custom_name); + new_item->set_custom_desc(item.custom_desc); + new_item->set_original_id(item.original_id); + new_item->set_in_use(item.in_use); + new_item->set_style(item.style); + + for (const auto &[class_id, slot_id] : item.equip_states) { + auto new_state = new_item->add_equip_states(); + new_state->set_class_id(class_id); + new_state->set_slot_id(slot_id); } - outgoing_messages.front().copy((char *)pubDest, cubDest); - if (pcubMsgSize) *pcubMsgSize = static_cast(outgoing_messages.front().size()); - if (punMsgType && outgoing_messages.front().size() >= sizeof(uint32)) { - outgoing_messages.front().copy((char *)punMsgType, sizeof(uint32)); - *punMsgType = ntohl(*punMsgType); + for (const Econ_Item_Attribute &attr : item.attributes) { + auto new_attr = new_item->add_attributes(); + new_attr->set_def(attr.def); + new_attr->set_value(attr.value); + new_attr->set_value_bytes(attr.value_bytes); } + } - outgoing_messages.pop(); - return k_EGCResultOK; + auto gameserver_items_msg = new GameServer_Items_Messages(); + gameserver_items_msg->set_type(GameServer_Items_Messages::Response_Inventory); + gameserver_items_msg->set_is_gc(is_gc); + gameserver_items_msg->set_allocated_inventory_response(response_msg); + + Common_Message new_msg{}; + new_msg.set_allocated_gameserver_items_messages(gameserver_items_msg); + new_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + new_msg.set_dest_id(server_steamid); + network->sendTo(&new_msg, true); + + PRINT_DEBUG("server requested inventory, sent %u items", static_cast(items.size())); +} + +// user sent their inventory +void Steam_Game_Coordinator::network_callback_inventory_response(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player sent their inventory %llu", user_steamid); + if (!msg->gameserver_items_messages().has_inventory_response()) { + PRINT_DEBUG("error empty msg"); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &response_msg = msg->gameserver_items_messages().inventory_response(); + SteamAPICall_t api_call = response_msg.steam_api_call(); + + // Find this pending request. + auto it = std::find_if( + pending_items_requests.begin(), pending_items_requests.end(), + [=](const RequestInventory &item) { + return item.steam_api_call == response_msg.steam_api_call() && + item.steam_id == user_steamid; + } + ); + if (pending_items_requests.end() == it) { + PRINT_DEBUG("error got player inventory but pending request timedout/removed (doesn't exist)"); + return; + } + pending_items_requests.erase(it); + + auto &items = all_user_items[user_steamid]; + items.clear(); + + for (const auto &item : response_msg.items()) { + Econ_Item new_item{}; + new_item.id = item.id(); + new_item.def = item.def(); + new_item.level = item.level(); + new_item.quality = static_cast(item.quality()); + new_item.inv_pos = item.inv_pos(); + new_item.quantity = item.quantity(); + new_item.flags = item.flags(); + new_item.origin = item.origin(); + new_item.custom_name = item.custom_name(); + new_item.custom_desc = item.custom_desc(); + new_item.original_id = item.original_id(); + new_item.in_use = item.in_use(); + new_item.style = item.style(); + if (new_item.id == 0) + continue; + + for (const auto &state : item.equip_states()) { + new_item.equip_states.insert({ state.class_id(), state.slot_id() }); + } + + for (const auto &attr : item.attributes()) { + Econ_Item_Attribute new_attr{}; + new_attr.def = attr.def(); + new_attr.value = attr.value(); + new_attr.value_bytes = attr.value_bytes(); + if (new_attr.def == 0) + continue; + + new_item.attributes.push_back(new_attr); + } + + // Check custom name and custom description limits. + if (!check_econ_item_name(new_item.custom_name)) { + new_item.custom_name.clear(); + } + + if (!check_econ_item_desc(new_item.custom_desc)) { + new_item.custom_desc.clear(); + } + + items.push_back(new_item); + } + + if (is_gc) { + callback_items_received(user_steamid, items); } else { - return k_EGCResultNoMessage; + server_items()->callback_items_received(user_steamid, items.size(), api_call, true); + } + + PRINT_DEBUG("got player inventory: %u items", response_msg.items_size()); +} + +// user updated an item +void Steam_Game_Coordinator::network_callback_item_update(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player updated an item %llu", user_steamid); + if (!msg->gameserver_items_messages().has_item_update()) { + PRINT_DEBUG("error empty msg"); + return; + } + + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } + + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &inventory_msg = msg->gameserver_items_messages().item_update(); + uint64 item_id = inventory_msg.id(); + + auto &items = all_user_items.at(user_steamid); + + for (Econ_Item &item : items) { + if (item.id != item_id) + continue; + + if (inventory_msg.has_inv_pos()) { + item.inv_pos = inventory_msg.inv_pos(); + PRINT_DEBUG("got updated item inventory pos: %llu 0x%08X", item_id, inventory_msg.inv_pos()); + } + + if (inventory_msg.has_style()) { + item.style = inventory_msg.style(); + PRINT_DEBUG("got updated item style: %llu %u", item_id, inventory_msg.style()); + } + + if (inventory_msg.has_equip_states()) { + item.equip_states.clear(); + for (const auto &state : inventory_msg.equip_states()) { + item.equip_states.insert({ state.class_id(), state.slot_id() }); + } + PRINT_DEBUG("got updated item equip states: %llu", item_id); + } + + if (is_gc) { + callback_item_updated(user_steamid, item); + } else if (inventory_msg.has_inv_pos()) { + server_items()->callback_item_pos_updated(user_steamid, item_id, inventory_msg.inv_pos()); + } + + return; } + + PRINT_DEBUG("error item %llu not found", item_id); } +// user deleted an item +void Steam_Game_Coordinator::network_callback_item_deletion(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player deleted inventory item %llu", user_steamid); + if (!msg->gameserver_items_messages().has_item_deletion()) { + PRINT_DEBUG("error empty msg"); + return; + } + if (!all_user_items.count(user_steamid)) { + PRINT_DEBUG("error no inventory for player", user_steamid); + return; + } -void Steam_Game_Coordinator::RunCallbacks() + bool is_gc = msg->gameserver_items_messages().is_gc(); + const auto &delete_msg = msg->gameserver_items_messages().item_deletion(); + uint64 item_id = delete_msg.item_id(); + + auto &items = all_user_items.at(user_steamid); + + for (auto it = items.begin(); it != items.end(); it++) { + if (it->id != item_id) + continue; + + items.erase(it); + + if (is_gc) { + callback_item_deleted(user_steamid, item_id); + } else { + server_items()->callback_item_deleted(user_steamid, item_id); + } + + PRINT_DEBUG("deleted player's inventory item: %llu", item_id); + return; + } + + PRINT_DEBUG("error item %llu not found", item_id); +} + +// user wants to respawn after loadout change +void Steam_Game_Coordinator::network_callback_respawn_request(Common_Message *msg) { + if (!is_server) + return; + + uint64 user_steamid = msg->source_id(); + if (!all_user_items.count(user_steamid)) + return; + + callback_respawn_request(user_steamid); +} + +// only triggered when we have a message +void Steam_Game_Coordinator::network_callback(Common_Message *msg) +{ + if (msg->source_id() == settings->get_local_steam_id().ConvertToUint64()) return; + + if (msg->has_gameserver_items_messages()) { + switch (msg->gameserver_items_messages().type()) { + // server requested our inventory + case GameServer_Items_Messages::Request_Inventory: + network_callback_inventory_request(msg); + break; + + // user sent their inventory + case GameServer_Items_Messages::Response_Inventory: + network_callback_inventory_response(msg); + break; + + // user updated an item + case GameServer_Items_Messages::Request_UpdateItem: + network_callback_item_update(msg); + break; + + // user deleted an item + case GameServer_Items_Messages::Request_DeleteItem: + network_callback_item_deletion(msg); + break; + + // user wants to respawn after loadout change + case GameServer_Items_Messages::Request_Respawn: + network_callback_respawn_request(msg); + break; + + default: + PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_items_messages().type()); + break; + } + } else if (msg->has_low_level()) { + if (!is_server && gc_initialized) { + CSteamID user_steamid; + user_steamid.SetFromUint64(msg->source_id()); + + if (user_steamid.BIndividualAccount()) { + // Client needs to know other players' inventories as well since the game uses them to + // validate cosmetic items. + switch (msg->low_level().type()) { + case Low_Level::CONNECT: + request_user_items(user_steamid, generate_steam_api_call_id(), true); + break; + + case Low_Level::DISCONNECT: + remove_user_items(user_steamid); + break; + } + } + } + } } -void Steam_Game_Coordinator::Callback(Common_Message *msg) +void Steam_Game_Coordinator::RunCallbacks() { - if (msg->has_low_level()) { - if (msg->low_level().type() == Low_Level::CONNECT) { - + if (delay_init && welcome_received && check_timedout(welcome_time, 0.2)) { + delay_init = false; + } + + for (auto it = pending_messages.begin(); it != pending_messages.end();) { + if (delay_init && !is_welcome_message(*it)) { + it++; + continue; } - if (msg->low_level().type() == Low_Level::DISCONNECT) { + if (check_timedout(it->created, it->post_in)) { + incoming_messages.push(*it); + + GCMessageAvailable_t data{}; + data.m_nMessageSize = static_cast(it->msg_body.size()); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); + it = pending_messages.erase(it); + } else { + it++; } } - if (msg->has_networking_sockets()) { + for (auto it = pending_items_requests.begin(); it != pending_items_requests.end();) { + if (check_timedout(it->created, 7.0)) { + if (!it->is_gc) { + server_items()->callback_items_received(it->steam_id, items.size(), it->steam_api_call, false); + } + PRINT_DEBUG("player inventory request timeout %llu", it->steam_id); + it = pending_items_requests.erase(it); + } else { + it++; + } } } diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index c883b1e70..d045a03d1 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -17,12 +17,63 @@ #include "dll/steam_gameserver.h" #include "dll/source_query.h" +#include "dll/dll.h" #define SEND_SERVER_RATE 5.0 -// appid 353090 takes ~1.2-1.4 sec to create the gameserver -#define LOGON_DELAY 1.3 +bool Steam_GameServer::set_appid(int32 input_appid, const char *game_dir, bool autodetect) +{ + if (server_data.appid() != 0) { + if (input_appid == 0 || input_appid == server_data.appid()) + return true; + } + + uint32 appid = input_appid; + + // If appid has not been provided by the game code, try reading it from steam_appid.txt. + // Some dedicated servers write it there and expect steamclient.dll to read it. + if (!appid && autodetect) { + char buf[10] = {}; + + // Try game dir. + if (game_dir && game_dir[0]) { + std::string file_path = std::string(game_dir) + PATH_SEPARATOR + "steam_appid.txt"; + + memset(buf, 0, sizeof(buf)); + Local_Storage::get_file_data(file_path, buf, sizeof(buf) - 1); + appid = atoi(buf); + } + + // Try current dir. + if (!appid) { + std::string file_path = "steam_appid.txt"; + + memset(buf, 0, sizeof(buf)); + Local_Storage::get_file_data(file_path, buf, sizeof(buf) - 1); + appid = atoi(buf); + } + + // Finally, fall back to config appid. + if (!appid) { + appid = settings->get_local_game_id().AppID(); + } + + if (!appid) { + PRINT_DEBUG("error cannot determine server appid", appid); + } + } + + if (!appid) + return false; + + server_data.set_appid(appid); + settings->set_game_id(CGameID(appid)); + network->setAppID(appid); + + PRINT_DEBUG("set server appid to %u", appid); + return true; +} void Steam_GameServer::set_version(const char *pchVersionString) { @@ -42,12 +93,13 @@ void Steam_GameServer::set_version(const char *pchVersionString) try { auto ver = std::stoul(version); server_data.set_version(ver); + server_data.set_version_name(pchVersionString); PRINT_DEBUG("set version to %lu", ver); } catch (...) { server_data.set_version(0); + server_data.set_version_name(""); PRINT_DEBUG("not a number '%s'", pchVersionString); } - } void Steam_GameServer::add_player(CSteamID steamID) @@ -58,6 +110,8 @@ void Steam_GameServer::add_player(CSteamID steamID) infos.second.score = 0; infos.second.name = "unnamed"; players.emplace_back(std::move(infos)); + + get_steam_client()->steam_gameserver_game_coordinator->on_client_connected(steamID); } void Steam_GameServer::remove_player(CSteamID steamID) @@ -68,6 +122,8 @@ void Steam_GameServer::remove_player(CSteamID steamID) if (player_it != players.end()) { players.erase(player_it); + + get_steam_client()->steam_gameserver_game_coordinator->on_client_disconnected(steamID); } } @@ -88,6 +144,10 @@ Steam_GameServer::~Steam_GameServer() auth_manager = nullptr; } +void Steam_GameServer::set_protocol_version(unsigned short nProtocolVersion) +{ + server_data.set_protocol_version(nProtocolVersion); +} std::vector>* Steam_GameServer::get_players() { @@ -102,25 +162,36 @@ std::vector>* Steam_GameServer::ge /// This is called by SteamGameServer_Init, and you will usually not need to call it directly bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, uint32 unFlags, AppId_t nGameAppId, const char *pchVersionString ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u %u %u %u %u '%s'", unIP, usGamePort, usQueryPort, unFlags, nGameAppId, pchVersionString); std::lock_guard lock(global_mutex); - if (logged_in) return false; // may not be changed after logged in. + if (logged_in) // may not be changed after logged in. + return false; + + if (nGameAppId == 0) + return false; - server_data.set_appid(nGameAppId); - set_version(pchVersionString); server_data.set_ip(unIP); server_data.set_port(usGamePort); server_data.set_query_port(usQueryPort); + set_appid(nGameAppId, "", false); + set_version(pchVersionString); server_data.set_offline(false); + // Fixed versions sent by SteamGameServer011+. Actual steamclient.dll behavior. + if (nGameAppId < 200) { + server_data.set_protocol_version(48); + } else { + server_data.set_protocol_version(17); + } + + //TODO: flags should be k_unServerFlag + flags = unFlags; if (!settings->disable_source_query) network->startQuery({ unIP, usQueryPort }); - if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nGameAppId)); - //TODO: flags should be k_unServerFlag - flags = unFlags; - policy_response_called = false; + get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); + call_servers_connected = false; call_servers_disconnected = false; @@ -188,8 +259,7 @@ void Steam_GameServer::LogOn( const char *pszToken ) std::lock_guard lock(global_mutex); call_servers_connected = true; call_servers_disconnected = false; - logged_in = true; - settings->set_offline(false); + logon_time = std::chrono::high_resolution_clock::now(); } void Steam_GameServer::LogOn( @@ -211,8 +281,7 @@ void Steam_GameServer::LogOnAnonymous() std::lock_guard lock(global_mutex); call_servers_connected = true; call_servers_disconnected = false; - logged_in = true; - settings->set_offline(false); + logon_time = std::chrono::high_resolution_clock::now(); } void Steam_GameServer::LogOn() @@ -229,11 +298,18 @@ void Steam_GameServer::LogOff() if (logged_in) { call_servers_connected = false; call_servers_disconnected = true; + logoff_time = std::chrono::high_resolution_clock::now(); + + logged_in = false; + settings->set_offline(true); } - policy_response_called = false; - logged_in = false; - settings->set_offline(true); + // Shutdown Source Query + network->shutDownQuery(); + // And empty the queue if needed + outgoing_packets.clear(); + + get_steam_client()->steam_gameserver_game_coordinator->shutdown_gc(); } @@ -249,7 +325,7 @@ bool Steam_GameServer::BSecure() { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (!policy_response_called) { + if (!logged_in) { server_data.set_secure(0); return false; } @@ -262,7 +338,7 @@ CSteamID Steam_GameServer::GetSteamID() { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - if (!logged_in) return k_steamIDNil; + if (!logged_in) return CSteamID(0, 0, k_EUniversePublic, k_EAccountTypeAnonGameServer); // blank anon server id return settings->get_local_steam_id(); } @@ -334,7 +410,7 @@ void Steam_GameServer::SetPasswordProtected( bool bPasswordProtected ) /// is not used. void Steam_GameServer::SetSpectatorPort( uint16 unSpectatorPort ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u", unSpectatorPort); std::lock_guard lock(global_mutex); server_data.set_spectator_port(unSpectatorPort); } @@ -457,7 +533,7 @@ CSteamID Steam_GameServer::CreateUnauthenticatedUserConnection() // account being logged into multiple servers, showing who is currently on a server, etc. void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu", steamIDUser.ConvertToUint64()); std::lock_guard lock(global_mutex); remove_player(steamIDUser); @@ -472,7 +548,7 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) // Return Value: true if successful, false if failure (ie, steamIDUser wasn't for an active player) bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPlayerName, uint32 uScore ) { - PRINT_DEBUG("%llu %s %u", steamIDUser.ConvertToUint64(), pchPlayerName, uScore); + PRINT_DEBUG("%llu '%s' %u", steamIDUser.ConvertToUint64(), pchPlayerName, uScore); std::lock_guard lock(global_mutex); auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair& player) @@ -510,32 +586,48 @@ bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPla bool Steam_GameServer::BSetServerType( uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) { - PRINT_DEBUG("%u %u '%s' '%s'", unGameIP, unGamePort, pchVersion, pchGameDir); + PRINT_DEBUG("%u %u %u %u %u '%s' '%s' %i", unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, + pchGameDir, pchVersion, bLANMode); std::lock_guard lock(global_mutex); - // Note: appid 55100 doesn't call InitGameServer(), it immediately calls this function - InitGameServer(unGameIP, unGamePort, usQueryPort, unServerFlags, settings->get_local_game_id().AppID(), pchVersion); + if (!set_appid(0, pchGameDir, true)) + return false; server_data.set_ip(unGameIP); server_data.set_port(unGamePort); - server_data.set_query_port(usQueryPort); server_data.set_spectator_port(unSpectatorPort); + server_data.set_query_port(usQueryPort); + server_data.set_mod_dir(pchGameDir ? pchGameDir : ""); set_version(pchVersion); - - server_data.set_game_dir(pchGameDir ? pchGameDir : ""); + server_data.set_offline(false); flags = unServerFlags; - //TODO? + if (bLANMode) { + flags &= ~k_unServerFlagSecure; + flags |= k_unServerFlagPrivate; + } else { + flags &= ~k_unServerFlagPrivate; + } + + if (!settings->disable_source_query && !network->isQueryAlive()) + network->startQuery({ unGameIP, usQueryPort }); + + get_steam_client()->steam_gameserver_game_coordinator->initialize_gc(); + return true; } bool Steam_GameServer::BSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) { - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } // Updates server status values which shows up in the server browser and matchmaking APIs @@ -543,8 +635,9 @@ void Steam_GameServer::UpdateServerStatus( int cPlayers, int cPlayersMax, int cB const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%i %i %i '%s' '%s' '%s'", cPlayers, cPlayersMax, cBotPlayers, pchServerName, pSpectatorServerName, pchMapName); std::lock_guard lock(global_mutex); + server_data.set_num_players(cPlayers); server_data.set_max_player_count(cPlayersMax); server_data.set_bot_player_count(cBotPlayers); @@ -564,22 +657,16 @@ void Steam_GameServer::UpdateSpectatorPort( uint16 unSpectatorPort ) // it allows users to filter in the matchmaking/server-browser interfaces based on the value void Steam_GameServer::SetGameType( const char *pchGameType ) { - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); + PRINT_DEBUG_TODO(); } // Ask if a user has a specific achievement for this game, will get a callback on reply bool Steam_GameServer::BGetUserAchievementStatus( CSteamID steamID, const char *pchAchievementName ) { - PRINT_DEBUG("%llu %s", steamID.ConvertToUint64(), pchAchievementName); + PRINT_DEBUG("%llu '%s'", steamID.ConvertToUint64(), pchAchievementName); std::lock_guard lock(global_mutex); - GSClientAchievementStatus_t data = {}; - data.m_bUnlocked = true; - data.m_SteamID = steamID.ConvertToUint64(); - strncpy(data.m_pchAchievement, pchAchievementName, sizeof(data.m_pchAchievement) - 1); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return true; + return get_steam_client()->steam_gameserverstats->request_user_achievement(steamID, pchAchievementName); } // New auth system APIs - do not mix with the old auth system APIs. @@ -613,8 +700,13 @@ EBeginAuthSessionResult Steam_GameServer::BeginAuthSession( const void *pAuthTic PRINT_DEBUG("%i %llu", cbAuthTicket, steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); - add_player(steamID); - return auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID ); + EBeginAuthSessionResult res = auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID ); + + if (res == k_EBeginAuthSessionResultOK) { + add_player(steamID); + } + + return res; } @@ -643,7 +735,7 @@ void Steam_GameServer::CancelAuthTicket( HAuthTicket hAuthTicket ) // to determine if the user owns downloadable content specified by the provided AppID. EUserHasLicenseForAppResult Steam_GameServer::UserHasLicenseForApp( CSteamID steamID, AppId_t appID ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu %u", steamID.ConvertToUint64(), appID); std::lock_guard lock(global_mutex); return k_EUserHasLicenseResultHasLicense; } @@ -653,26 +745,23 @@ EUserHasLicenseForAppResult Steam_GameServer::UserHasLicenseForApp( CSteamID ste // returns false if we're not connected to the steam servers and thus cannot ask bool Steam_GameServer::RequestUserGroupStatus( CSteamID steamIDUser, CSteamID steamIDGroup ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%llu %llu", steamIDUser.ConvertToUint64(), steamIDGroup.ConvertToUint64()); std::lock_guard lock(global_mutex); return true; } - // these two functions s are deprecated, and will not return results // they will be removed in a future version of the SDK void Steam_GameServer::GetGameplayStats( ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); } STEAM_CALL_RESULT( GSReputation_t ) SteamAPICall_t Steam_GameServer::GetServerReputation() { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -819,7 +908,6 @@ STEAM_CALL_RESULT( AssociateWithClanResult_t ) SteamAPICall_t Steam_GameServer::AssociateWithClan( CSteamID steamIDClan ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -829,7 +917,6 @@ STEAM_CALL_RESULT( ComputeNewPlayerCompatibilityResult_t ) SteamAPICall_t Steam_GameServer::ComputeNewPlayerCompatibility( CSteamID steamIDNewPlayer ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); return 0; } @@ -837,61 +924,123 @@ SteamAPICall_t Steam_GameServer::ComputeNewPlayerCompatibility( CSteamID steamID void Steam_GameServer::RunCallbacks() { - bool temp_call_servers_connected = call_servers_connected; - bool temp_call_servers_disconnected = call_servers_disconnected; - call_servers_disconnected = call_servers_connected = false; - - if (temp_call_servers_connected) { + if (call_servers_connected && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("SteamServersConnected_t"); + + call_servers_connected = false; + logged_in = true; + settings->set_offline(false); + SteamServersConnected_t data{}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), LOGON_DELAY); - } + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); - if (logged_in && !policy_response_called) { PRINT_DEBUG("GSPolicyResponse_t"); - GSPolicyResponse_t data{}; - data.m_bSecure = !!(flags & k_unServerFlagSecure); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), LOGON_DELAY); - policy_response_called = true; + GSPolicyResponse_t data2{}; + data2.m_bSecure = !!(flags & k_unServerFlagSecure); + callbacks->addCBResult(data2.k_iCallback, &data2, sizeof(data2), 0.1); } - if (logged_in && server_data.port() != 0 && check_timedout(last_sent_server_info, SEND_SERVER_RATE)) { - PRINT_DEBUG("Sending Gameserver"); + if (call_servers_disconnected && check_timedout(logoff_time, 0.1)) { + PRINT_DEBUG("Gameserver is disconnected"); + + call_servers_disconnected = false; + + SteamServersDisconnected_t data{}; + data.m_eResult = k_EResultOK; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.0); + + PRINT_DEBUG("notifying all that Gameserver is not logged in"); Common_Message msg{}; msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - if (server_data.appid() == 0) - server_data.set_appid(settings->get_local_game_id().AppID()); - if (server_data.mod_dir().empty()) - server_data.set_mod_dir(server_data.game_dir()); msg.set_allocated_gameserver(new Gameserver(server_data)); - //msg.mutable_gameserver()->set_num_players(auth_manager->countInboundAuth()); + msg.mutable_gameserver()->set_offline(true); network->sendToAllIndividuals(&msg, true); - last_sent_server_info = std::chrono::high_resolution_clock::now(); } - if (temp_call_servers_disconnected) { - PRINT_DEBUG("Gameserver is disconnected"); - SteamServersDisconnected_t data{}; - data.m_eResult = k_EResultOK; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - if (!logged_in) { - PRINT_DEBUG("notifying all that Gameserver is not logged in"); - Common_Message msg{}; - msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); - msg.set_allocated_gameserver(new Gameserver(server_data)); - msg.mutable_gameserver()->set_offline(true); - network->sendToAllIndividuals(&msg, true); - // Shutdown Source Query - network->shutDownQuery(); - // And empty the queue if needed - outgoing_packets.clear(); + if (logged_in && + server_data.appid() != 0 && server_data.port() != 0 && + check_timedout(last_sent_server_info, SEND_SERVER_RATE)) { + PRINT_DEBUG("Sending Gameserver"); + Common_Message msg{}; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_allocated_gameserver(new Gameserver(server_data)); + + // Fill player list and calculate their playtime. + auto cur_time = std::chrono::steady_clock::now(); + + for (const auto &[steam_id, info] : players) { + auto new_player = msg.mutable_gameserver()->add_players(); + new_player->set_name(info.name); + new_player->set_score(info.score); + new_player->set_playtime(std::chrono::duration(cur_time - info.join_time).count()); } + + network->sendToAllIndividuals(&msg, true); + last_sent_server_info = std::chrono::high_resolution_clock::now(); } } // older sdk ----------------------------------------------- +// SteamGameServer001 -------------------------------------- +bool Steam_GameServer::GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ) +{ + PRINT_DEBUG("%llu %u %u", steamID.ConvertToUint64(), unIPPublic, unk); + std::lock_guard lock(global_mutex); + + // At this point, client auth is still primarily done via Steam2 (Steam.dll), Steam3 is supplementary. + // As such, there's not much really needed from us besides firing approve callback. + // Since SteamID passed into this comes from Steam.dll, it won't match the client's Goldberg SteamID. + GSClientApprove_t data{}; + data.m_SteamID = data.m_OwnerSteamID = steamID; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + add_player(steamID); + return true; +} + +bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID ) +{ + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + remove_player(steamID); + return true; +} + +bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) +{ + PRINT_DEBUG("%i %u %i %i %i %i '%s' '%s' '%s' '%s'", + nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, + pchServerName, pchGameDir, pchMapName, pchVersion); + std::lock_guard lock(global_mutex); + + set_appid(nAppIdServed, pchGameDir, false); + server_data.set_num_players(cPlayers); + server_data.set_max_player_count(cPlayersMax); + server_data.set_bot_player_count(cBotPlayers); + server_data.set_port(unGamePort); + server_data.set_query_port(STEAMGAMESERVER_QUERY_PORT_SHARED); + server_data.set_spectator_port(0); + server_data.set_server_name(pchServerName); + server_data.set_spectator_server_name(pchServerName); + server_data.set_mod_dir(pchGameDir); + server_data.set_map_name(pchMapName); + set_version(pchVersion); + server_data.set_offline(false); + + flags = unServerFlags; + + return true; +} +// SteamGameServer001 -------------------------------------- + +// SteamGameServer002 -------------------------------------- void Steam_GameServer::GSSetSpawnCount( uint32 ucSpawn ) { PRINT_DEBUG_TODO(); @@ -933,14 +1082,22 @@ bool Steam_GameServer::GSSendSteam3UserConnect( CSteamID steamID, uint32 unIPPub std::lock_guard lock(global_mutex); CSteamID ticket_steam_id; - return SendUserConnectAndAuthenticate(unIPPublic, pvCookie, cubCookie, &ticket_steam_id); + Auth_Data ticket_data = auth_manager->validateTicket(pvCookie, cubCookie, steamID, &ticket_steam_id); + if (!ticket_data.id.IsValid()) + return false; + + // If this function gets called then SteamID was likely obtained from Steam.dll and won't match + // the client's Goldberg SteamID. So just ignore SteamID we got from auth ticket. + GSClientApprove_t data{}; + data.m_SteamID = data.m_OwnerSteamID = steamID; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + add_player(steamID); + return true; } bool Steam_GameServer::GSRemoveUserConnect( uint32 unUserID ) { PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - return false; } @@ -953,44 +1110,12 @@ bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID, uint32 unUserID ) return true; } -bool Steam_GameServer::GSSendUserStatusResponse( CSteamID steamID, int nSecondsConnected, int nSecondsSinceLast ) -{ - PRINT_DEBUG_TODO(); - std::lock_guard lock(global_mutex); - - return false; -} - -bool Steam_GameServer::Obsolete_GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - server_data.set_appid(nAppIdServed); - server_data.set_num_players(cPlayers); - server_data.set_max_player_count(cPlayersMax); - server_data.set_bot_player_count(cBotPlayers); - server_data.set_port(unGamePort); - server_data.set_query_port(0xFFFF); - server_data.set_server_name(pchServerName); - server_data.set_spectator_server_name(""); - server_data.set_game_dir(pchGameDir ? pchGameDir : ""); - server_data.set_map_name(pchMapName); - set_version(pchVersion); - server_data.set_offline(false); - - if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nAppIdServed)); - flags = unServerFlags; - - return true; -} - bool Steam_GameServer::GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pchMapName ) { PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - UpdateServerStatus(cPlayers, cPlayersMax, cBotPlayers, pchServerName, "", pchMapName); + UpdateServerStatus(cPlayers, cPlayersMax, cBotPlayers, pchServerName, pchServerName, pchMapName); return true; } @@ -999,9 +1124,10 @@ bool Steam_GameServer::GSSetServerType( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, 0xFFFF, 0xFFFF, pchGameDir, pchVersion, false); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, 0, STEAMGAMESERVER_QUERY_PORT_SHARED, pchGameDir, pchVersion, false); } bool Steam_GameServer::GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort, uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode ) @@ -1009,10 +1135,10 @@ bool Steam_GameServer::GSSetServerType2( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - SetSpectatorPort(unSpectatorPort); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } bool Steam_GameServer::GSUpdateStatus2( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) @@ -1047,7 +1173,7 @@ void Steam_GameServer::GSUpdateSpectatorPort( uint16 unSpectatorPort ) PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - UpdateSpectatorPort(unSpectatorPort); + SetSpectatorPort(unSpectatorPort); } void Steam_GameServer::GSSetGameType( const char *pchType ) @@ -1057,7 +1183,9 @@ void Steam_GameServer::GSSetGameType( const char *pchType ) SetGameType(pchType); } +// SteamGameServer002 -------------------------------------- +// SteamGameServer003 -------------------------------------- bool Steam_GameServer::GSSendUserConnect( uint32 unUserID, uint32 unIPPublic, uint16 usPort, const void *pvCookie, uint32 cubCookie ) { PRINT_DEBUG_ENTRY(); @@ -1079,10 +1207,10 @@ bool Steam_GameServer::GSSetServerType( int32 nGameAppId, uint32 unServerFlags, PRINT_DEBUG_ENTRY(); std::lock_guard lock(global_mutex); - SetSpectatorPort(unSpectatorPort); - bool ret = BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); - server_data.set_appid(nGameAppId); - return ret; + if (!set_appid(nGameAppId, pchGameDir, true)) + return false; + + return BSetServerType(unServerFlags, unGameIP, unGamePort, unSpectatorPort, usQueryPort, pchGameDir, pchVersion, bLANMode); } bool Steam_GameServer::GSUpdateStatus( int cPlayers, int cPlayersMax, int cBotPlayers, const char *pchServerName, const char *pSpectatorServerName, const char *pchMapName ) @@ -1101,27 +1229,5 @@ bool Steam_GameServer::GSGetUserAchievementStatus( CSteamID steamID, const char return BGetUserAchievementStatus(steamID, pchAchievementName); } - -bool Steam_GameServer::GSSendUserConnect( CSteamID steamID, uint32 unIPPublic, uint32 unk ) -{ - PRINT_DEBUG("%llu %u %u", steamID.ConvertToUint64(), unIPPublic, unk); - std::lock_guard lock(global_mutex); - - // Note that SteamID passed into this comes from Steam.dll so it won't match the client's Goldberg SteamID. - // POINTLESS TO USE AUTH MANAGER. - GSClientApprove_t data{}; - data.m_SteamID = data.m_OwnerSteamID = steamID; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - add_player(steamID); - return true; -} - -bool Steam_GameServer::GSSendUserDisconnect( CSteamID steamID ) -{ - PRINT_DEBUG("%llu", steamID.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - remove_player(steamID); - return true; -} +// SteamGameServer003 -------------------------------------- // older sdk ----------------------------------------------- diff --git a/dll/steam_gameserver_items.cpp b/dll/steam_gameserver_items.cpp new file mode 100644 index 000000000..c95a0b1dd --- /dev/null +++ b/dll/steam_gameserver_items.cpp @@ -0,0 +1,314 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "dll/steam_gameserver_items.h" +#include "dll/dll.h" + +Steam_Game_Coordinator *Steam_GameServer_Items::gc() +{ + return get_steam_client()->steam_gameserver_game_coordinator; +} + +Steam_GameServer_Items::Steam_GameServer_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) +{ + this->settings = settings; + this->callbacks = callbacks; + this->callback_results = callback_results; +} + +Steam_GameServer_Items::~Steam_GameServer_Items() +{ +} + +void Steam_GameServer_Items::callback_items_received(CSteamID steam_id, size_t num_items, SteamAPICall_t api_call, bool success) +{ + GSItemCount_t data{}; + data.m_OwnerID = steam_id; + if (success) { + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(num_items); + } else { + data.m_eResult = k_EItemRequestResultTimeout; + data.m_unCount = 0; + } + callback_results->addCallResult(api_call, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); +} + +void Steam_GameServer_Items::callback_item_pos_updated(CSteamID steam_id, uint64 item_id, uint32 inv_pos) +{ + GSItemInventoryPosUpdated_t data{}; + data.m_SteamID = steam_id; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); +} + +void Steam_GameServer_Items::callback_item_deleted(CSteamID steam_id, uint64 item_id) +{ + GSItemDeleted_t data{}; + data.m_SteamID = steam_id; + data.m_ulItemID = item_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.15); +} + +SteamAPICall_t Steam_GameServer_Items::LoadItems( CSteamID ownerID ) +{ + PRINT_DEBUG("%llu", ownerID.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + // See if we already have their inventory cached. + if (gc()->has_items_for_user(ownerID)) { + const auto &items = gc()->get_items_for_user(ownerID); + + GSItemCount_t data{}; + data.m_OwnerID = ownerID; + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(items.size()); + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + // See if we've already requested their inventory. + SteamAPICall_t ret = gc()->find_items_request(ownerID); + if (ret != k_uAPICallInvalid) + return ret; + + // Request inventory from this player. + ret = callback_results->reserveCallResult(); + gc()->request_user_items(ownerID, ret, false); + return ret; +} + +void Steam_GameServer_Items::LoadItems_old( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + LoadItems(ownerID); +} + +SteamAPICall_t Steam_GameServer_Items::GetItemCount( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + return LoadItems(ownerID); +} + +void Steam_GameServer_Items::GetItemCount_old( CSteamID ownerID ) +{ + PRINT_DEBUG_ENTRY(); + GetItemCount(ownerID); +} + +bool Steam_GameServer_Items::GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%u", iIndex); + std::lock_guard lock(global_mutex); + + if (!gc()->has_items_for_user(ownerID)) + return false; + + const auto &items = gc()->get_items_for_user(ownerID); + if (iIndex >= items.size()) + return false; + + const Econ_Item &item = items[iIndex]; + *pulItemID = item.id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; +} + +bool Steam_GameServer_Items::GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemIterative(ownerID, iIndex, pulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_GameServer_Items::GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + for (const auto &[steam_id, items] : gc()->get_all_user_items()) { + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + *pOwnerID = steam_id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; + } + } + + return false; +} + +bool Steam_GameServer_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + CSteamID steam_id; + uint32 quantity; + return GetItemByID(ulItemID, &steam_id, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_GameServer_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) +{ + PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); + std::lock_guard lock(global_mutex); + + for (const auto &[steam_id, items] : gc()->get_all_user_items()) { + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + if (unAttributeIndex >= item.attributes.size()) + return false; + + *punAttributeDefIndex = item.attributes[unAttributeIndex].def; + *pflAttributeValue = item.attributes[unAttributeIndex].value; + + return true; + } + } + + return false; +} + +HNewItemRequest Steam_GameServer_Items::CreateNewItemRequest( CSteamID steamID ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + +HNewItemRequest Steam_GameServer_Items::CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) +{ + PRINT_DEBUG_TODO(); + return 0; +} + +bool Steam_GameServer_Items::AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +bool Steam_GameServer_Items::AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) +{ + PRINT_DEBUG_TODO(); + return false; +} + +SteamAPICall_t Steam_GameServer_Items::SendNewItemRequest( HNewItemRequest handle ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::SendNewItemRequest_old( HNewItemRequest handle ) +{ + PRINT_DEBUG_ENTRY(); + SendNewItemRequest(handle); +} + +SteamAPICall_t Steam_GameServer_Items::GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) +{ + PRINT_DEBUG_ENTRY(); + GrantItemToUser(ulItemID, steamIDRecipient); +} + +SteamAPICall_t Steam_GameServer_Items::DeleteTemporaryItem( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::DeleteTemporaryItem_old( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + DeleteTemporaryItem(ulItemID); +} + +SteamAPICall_t Steam_GameServer_Items::DeleteAllTemporaryItems() +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +void Steam_GameServer_Items::DeleteAllTemporaryItems_old() +{ + PRINT_DEBUG_ENTRY(); + DeleteAllTemporaryItems(); +} + +SteamAPICall_t Steam_GameServer_Items::UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_GameServer_Items::GetItemBlob( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_GameServer_Items::SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} diff --git a/dll/steam_gameserverstats.cpp b/dll/steam_gameserverstats.cpp index b96a8bafb..c6243490a 100644 --- a/dll/steam_gameserverstats.cpp +++ b/dll/steam_gameserverstats.cpp @@ -106,6 +106,45 @@ Steam_GameServerStats::~Steam_GameServerStats() } } +bool Steam_GameServerStats::request_user_achievement(CSteamID steamIDUser, const char *ach_name) +{ + std::lock_guard lock(global_mutex); + if (settings->disable_sharing_stats_with_gameserver) return false; + if (!ach_name) return false; + + auto ach = find_ach(steamIDUser, ach_name); + if (ach) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = ach->ach.achieved(); + data.m_SteamID = steamIDUser.ConvertToUint64(); + strncpy(data.m_pchAchievement, ach_name, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return true; + } + + struct RequestAchievement new_request{}; + new_request.created = std::chrono::high_resolution_clock::now(); + new_request.steamIDUser = steamIDUser; + new_request.achievement_name = ach_name; + + pending_RequestAchievement.push_back(new_request); + + auto ach_request_msg = new GameServerStats_Messages::AchievementRequest(); + ach_request_msg->set_name(ach_name); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::Request_UserAchievement); + gameserverstats_msg->set_allocated_achievement_request(ach_request_msg); + + Common_Message msg{}; + msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDUser.ConvertToUint64()); + network->sendTo(&msg, true); + + return true; +} + // downloads stats for the user // returns a GSStatsReceived_t callback when completed @@ -363,30 +402,38 @@ SteamAPICall_t Steam_GameServerStats::StoreUserStats( CSteamID steamIDUser ) void Steam_GameServerStats::remove_timedout_userstats_requests() { - if (pending_RequestUserStats.empty()) return; - // send all pending RequestUserStats() requests - for (auto &pendReq : pending_RequestUserStats) { - if (check_timedout(pendReq.created, PENDING_RequestUserStats_TIMEOUT)) { - pendReq.timeout = true; - + for (auto it = pending_RequestUserStats.begin(); it != pending_RequestUserStats.end();) { + if (check_timedout(it->created, PENDING_RequestUserStats_TIMEOUT)) { GSStatsReceived_t data{}; data.m_eResult = k_EResultTimeout; - data.m_steamIDUser = pendReq.steamIDUser; + data.m_steamIDUser = it->steamIDUser; - callback_results->addCallResult(pendReq.steamAPICall, data.k_iCallback, &data, sizeof(data)); + callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - PRINT_DEBUG("RequestUserStats timeout, %llu", pendReq.steamIDUser.ConvertToUint64()); + + PRINT_DEBUG("RequestUserStats timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestUserStats.erase(it); + } else { + it++; } } - // remove all timedout requests - auto itrm = std::remove_if( - pending_RequestUserStats.begin(), pending_RequestUserStats.end(), - [](const struct RequestAllStats &item) { return item.timeout; } - ); - pending_RequestUserStats.erase(itrm, pending_RequestUserStats.end()); + // send all pending BGetUserAchievementStatus requests + for (auto it = pending_RequestAchievement.begin(); it != pending_RequestAchievement.end();) { + if (check_timedout(it->created, PENDING_RequestUserStats_TIMEOUT)) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = false; + data.m_SteamID = it->steamIDUser.ConvertToUint64(); + it->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestAchievement.erase(it); + } else { + it++; + } + } } void Steam_GameServerStats::collect_and_send_updated_user_stats() @@ -491,9 +538,6 @@ void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg) PRINT_DEBUG("error got all player stats but pending request timedout/removed (doesn't exist)"); return; } - - // remove this pending request - pending_RequestUserStats.erase(it); // copy new stats auto ¤t_stats = all_users_data[user_steamid].stats; @@ -515,12 +559,13 @@ void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg) callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + // remove this pending request + pending_RequestUserStats.erase(it); PRINT_DEBUG("server got all player stats %llu: %zu stats, %zu achievements", user_steamid, all_users_data[user_steamid].stats.size(), all_users_data[user_steamid].achievements.size() ); - - } // user has updated/new stats @@ -556,6 +601,50 @@ void Steam_GameServerStats::network_callback_updated_stats(Common_Message *msg) ); } +// user responded with achievement status +void Steam_GameServerStats::network_callback_achievement_response(Common_Message *msg) +{ + uint64 user_steamid = msg->source_id(); + + PRINT_DEBUG("player returned achievement %llu", user_steamid); + if (!msg->gameserver_stats_messages().has_achievement_response()) { + PRINT_DEBUG("error empty msg"); + return; + } + + const auto &ach_response_msg = msg->gameserver_stats_messages().achievement_response(); + std::string ach_name = ach_response_msg.name(); + const auto &ach_info = ach_response_msg.info(); + + // find this pending request + auto it = std::find_if( + pending_RequestAchievement.begin(), pending_RequestAchievement.end(), + [=](const RequestAchievement &item) { + return item.steamIDUser == user_steamid && + item.achievement_name == ach_name; + } + ); + if (pending_RequestAchievement.end() == it) { // timeout and already removed + PRINT_DEBUG("error got player achievement but pending request timedout/removed (doesn't exist)"); + return; + } + + all_users_data[user_steamid].achievements[ach_name].ach = ach_info; + + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = ach_info.achieved(); + data.m_SteamID = user_steamid; + ach_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + // remove this pending request + pending_RequestAchievement.erase(it); + + PRINT_DEBUG("server got player achievement %llu: '%s' %s", + user_steamid, ach_name.c_str(), ach_info.achieved() ? "earned" : "unearned" + ); +} + // only triggered when we have a message void Steam_GameServerStats::network_callback(Common_Message *msg) { @@ -573,6 +662,11 @@ void Steam_GameServerStats::network_callback(Common_Message *msg) case GameServerStats_Messages::UpdateUserStatsFromUser: network_callback_updated_stats(msg); break; + + // user responded with achievement status + case GameServerStats_Messages::Response_UserAchievement: + network_callback_achievement_response(msg); + break; default: PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); @@ -596,20 +690,36 @@ void Steam_GameServerStats::network_callback_low_level(Common_Message *msg) case Low_Level::DISCONNECT: { all_users_data.erase(steamid); - auto it_rm = std::remove_if( - pending_RequestUserStats.begin(), pending_RequestUserStats.end(), - [=](const RequestAllStats &item) { return item.steamIDUser.ConvertToUint64() == steamid; } - ); - while (pending_RequestUserStats.end() != it_rm) { - GSStatsReceived_t data{}; - data.m_eResult = k_EResultTimeout; - data.m_steamIDUser = it_rm->steamIDUser; - - callback_results->addCallResult(it_rm->steamAPICall, data.k_iCallback, &data, sizeof(data)); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - - PRINT_DEBUG("RequestUserStats timeout, %llu", it_rm->steamIDUser.ConvertToUint64()); - it_rm = pending_RequestUserStats.erase(it_rm); + + for (auto it = pending_RequestUserStats.begin(); it != pending_RequestUserStats.end();) { + if (steamid == it->steamIDUser.ConvertToUint64()) { + GSStatsReceived_t data{}; + data.m_eResult = k_EResultTimeout; + data.m_steamIDUser = it->steamIDUser; + + callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data)); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("RequestUserStats timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestUserStats.erase(it); + } else { + it++; + } + } + + for (auto it = pending_RequestAchievement.begin(); it != pending_RequestAchievement.end();) { + if (steamid == it->steamIDUser.ConvertToUint64()) { + GSClientAchievementStatus_t data{}; + data.m_bUnlocked = false; + data.m_SteamID = it->steamIDUser.ConvertToUint64(); + it->achievement_name.copy(data.m_pchAchievement, sizeof(data.m_pchAchievement) - 1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + + PRINT_DEBUG("BGetUserAchievementStatus timeout, %llu", it->steamIDUser.ConvertToUint64()); + it = pending_RequestAchievement.erase(it); + } else { + it++; + } } // PRINT_DEBUG("removed user %llu", steamid); diff --git a/dll/steam_gamestats.cpp b/dll/steam_gamestats.cpp index eb796e4ca..55d047ba7 100644 --- a/dll/steam_gamestats.cpp +++ b/dll/steam_gamestats.cpp @@ -22,7 +22,6 @@ */ #include "dll/steam_gamestats.h" -#include Steam_GameStats::Attribute_t::Attribute_t(AttributeType_t type) diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp index fcdbbe494..9e87b58a2 100644 --- a/dll/steam_inventory.cpp +++ b/dll/steam_inventory.cpp @@ -160,15 +160,13 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, // We end if we reached the end of items or the end of buffer for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) { - pOutItemsArray->m_iDefinition = std::stoi(i.key()); - pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition; - try - { - pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(0)); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; + pOutItemsArray->m_itemId = std::stoi(i.key()); + try { + pOutItemsArray->m_iDefinition = i->value("definition", static_cast(pOutItemsArray->m_itemId)); + pOutItemsArray->m_unQuantity = i->value("quantity", static_cast(1)); + } catch (...) { + pOutItemsArray->m_iDefinition = static_cast(pOutItemsArray->m_itemId); + pOutItemsArray->m_unQuantity = 1; } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; @@ -178,16 +176,13 @@ bool Steam_Inventory::GetResultItems( SteamInventoryResult_t resultHandle, if (!max_items) break; auto it = user_items.find(std::to_string(itemid)); if (it != user_items.end()) { - pOutItemsArray->m_iDefinition = static_cast(itemid); pOutItemsArray->m_itemId = itemid; - - try - { - pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(0)); - } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; + try { + pOutItemsArray->m_iDefinition = it->value("definition", static_cast(pOutItemsArray->m_itemId)); + pOutItemsArray->m_unQuantity = it->value("quantity", static_cast(1)); + } catch (...) { + pOutItemsArray->m_iDefinition = static_cast(pOutItemsArray->m_itemId); + pOutItemsArray->m_unQuantity = 1; } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; diff --git a/dll/steam_masterserver_updater.cpp b/dll/steam_masterserver_updater.cpp index b63e77d4c..1ffddb5f7 100644 --- a/dll/steam_masterserver_updater.cpp +++ b/dll/steam_masterserver_updater.cpp @@ -136,7 +136,8 @@ void Steam_Masterserver_Updater::SetBasicServerData( { PRINT_DEBUG_TODO(); std::lock_guard lock(global_mutex); - + + gameserver->set_protocol_version(nProtocolVersion); gameserver->SetDedicatedServer(bDedicatedServer); gameserver->SetRegion(pRegionName); gameserver->SetProduct(pProductName); diff --git a/dll/steam_matchmaking.cpp b/dll/steam_matchmaking.cpp index 0dc8689dd..766020639 100644 --- a/dll/steam_matchmaking.cpp +++ b/dll/steam_matchmaking.cpp @@ -23,14 +23,11 @@ #define REQUEST_LOBBY_DATA_TIMEOUT 6.0 #define LOBBY_DELETED_TIMEOUT 2 -// appid 353090 takes ~100ms to create the lobby -#define LOBBY_CREATE_DELAY 0.2 +#define LOBBY_CREATE_DELAY 0.07 //artificial delay for lobby creation #define FILTER_MAX_DEFAULT 4096 -// https://partner.steamgames.com/doc/api/ISteamMatchmaking#RequestLobbyList -// "this call can take from 300ms to 5 seconds to complete, and has a timeout of 20 seconds." -#define LOBBY_SEARCH_TIMEOUT 1.0 +#define LOBBY_SEARCH_TIMEOUT 0.2 //Tested on real steam google::protobuf::Map::const_iterator Steam_Matchmaking::caseinsensitive_find(const ::google::protobuf::Map< ::std::string, ::std::string >& map, std::string key) diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index 1277c1e29..76401bb98 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -15,7 +15,7 @@ License along with the Goldberg Emulator; if not, see . */ -#include "dll/dll.h" +#include "dll/steam_matchmaking_servers.h" #define SERVER_TIMEOUT 10.0 #define DIRECT_IP_DELAY 0.05 @@ -606,7 +606,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * int latency = MIN_LATENCY; uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } @@ -711,7 +711,7 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) { uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } @@ -751,13 +751,12 @@ void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matc } else if (!settings->matchmaking_server_details_via_source_query) { // original behavior uint32_t number_players = g->num_players(); PRINT_DEBUG(" players: %u", number_players); - const auto &players = get_steam_client()->steam_gameserver->get_players(); - auto player = players->cbegin(); - for (uint32_t i = 0; i < number_players && player != players->end(); ++i, ++player) { - auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - player->second.join_time); - float playtime = static_cast(duration.count()); - PRINT_DEBUG(" PLAYER [%u] '%s' %u %f", i, player->second.name.c_str(), player->second.score, playtime); - r->players_response->AddPlayerToList(player->second.name.c_str(), player->second.score, playtime); + + int player_num = 1; + + for (const auto &player : g->players()) { + PRINT_DEBUG(" PLAYER [%u] '%s' %u %.2f", player_num++, player.name().c_str(), player.score(), player.playtime()); + r->players_response->AddPlayerToList(player.name().c_str(), player.score(), player.playtime()); } } @@ -767,7 +766,7 @@ void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matc void Steam_Matchmaking_Servers::server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) { uint16 query_port = g->query_port(); - if (g->query_port() == 0xFFFF) { + if (g->query_port() == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g->port(); } @@ -915,7 +914,7 @@ void Steam_Matchmaking_Servers::RunCallbacks() for (auto &g : gameservers) { PRINT_DEBUG("%u:%u", g.server.ip(), g.server.query_port()); uint16 query_port = g.server.query_port(); - if (query_port == 0xFFFF) { + if (query_port == STEAMGAMESERVER_QUERY_PORT_SHARED) { query_port = g.server.port(); } @@ -953,56 +952,57 @@ void Steam_Matchmaking_Servers::Callback(Common_Message *msg) { if (msg->has_gameserver() && msg->gameserver().type() != eFriendsServer) { PRINT_DEBUG("got SERVER " "%" PRIu64 ", offline:%u", msg->gameserver().id(), msg->gameserver().offline()); + + auto it = std::find_if(gameservers.begin(), gameservers.end(), + [msg](const Steam_Matchmaking_Servers_Gameserver &item) { + return (msg->gameserver().id() == item.server.id()); + } + ); + if (msg->gameserver().offline()) { - for (auto &g : gameservers) { - if (g.server.id() == msg->gameserver().id()) { - g.last_recv = std::chrono::high_resolution_clock::time_point(); - g.type = eLANServer; - } + if (it != gameservers.end()) { + it->last_recv = std::chrono::high_resolution_clock::time_point(); + it->type = eLANServer; } } else { - bool already = false; - for (auto &g : gameservers) { - if (g.server.id() == msg->gameserver().id()) { - g.last_recv = std::chrono::high_resolution_clock::now(); - g.server = msg->gameserver(); - g.server.set_ip(msg->source_ip()); - g.type = eLANServer; - already = true; - } + if (it == gameservers.end()) { + it = gameservers.emplace(it); + PRINT_DEBUG(" eLANServer SERVER ADDED"); } - if (!already) { - struct Steam_Matchmaking_Servers_Gameserver g{}; - g.last_recv = std::chrono::high_resolution_clock::now(); - g.server = msg->gameserver(); - g.server.set_ip(msg->source_ip()); - g.type = eLANServer; - gameservers.push_back(g); - PRINT_DEBUG(" eLANServer SERVER ADDED"); + it->last_recv = std::chrono::high_resolution_clock::now(); + it->server = msg->gameserver(); + it->server.set_ip(msg->source_ip()); + + // TODO: This is obsolete, remove game_dir field eventually. + if (it->server.mod_dir().empty()) { + it->server.set_mod_dir(it->server.game_dir()); } + + it->type = eLANServer; } + + return; } if (msg->has_gameserver() && msg->gameserver().type() == eFriendsServer) { PRINT_DEBUG("got eFriendsServer SERVER " "%" PRIu64 "", msg->gameserver().id()); - bool addserver = true; - for (auto &g : gameservers_friends) { - if (g.source_id == msg->source_id()) { - g.ip = msg->gameserver().ip(); - g.port = msg->gameserver().port(); - g.last_recv = std::chrono::high_resolution_clock::now(); - addserver = false; + + auto it = std::find_if(gameservers_friends.begin(), gameservers_friends.end(), + [msg](const Steam_Matchmaking_Servers_Gameserver_Friends &item) { + return (msg->source_id() == item.source_id); } - } + ); - if (addserver) { - struct Steam_Matchmaking_Servers_Gameserver_Friends gameserver_friend; - gameserver_friend.source_id = msg->source_id(); - gameserver_friend.ip = msg->gameserver().ip(); - gameserver_friend.port = msg->gameserver().port(); - gameserver_friend.last_recv = std::chrono::high_resolution_clock::now(); - gameservers_friends.push_back(gameserver_friend); + if (it == gameservers_friends.end()) { + it = gameservers_friends.emplace(it); + it->source_id = msg->source_id(); } + + it->ip = msg->gameserver().ip(); + it->port = msg->gameserver().port(); + it->last_recv = std::chrono::high_resolution_clock::now(); + + return; } } diff --git a/dll/steam_networking.cpp b/dll/steam_networking.cpp index 7ae6abd76..d2185c4e6 100644 --- a/dll/steam_networking.cpp +++ b/dll/steam_networking.cpp @@ -16,13 +16,79 @@ . */ #include "dll/steam_networking.h" -#include "dll/dll.h" +//packet timeout in seconds for non connections +#define ORPHANED_PACKET_TIMEOUT (20) +#define NEW_CONNECTION_TIMEOUT (20.0) + +//kingdom 2 crowns doesn't work with a 0.3 delay or lower +#define NEW_CONNECTION_DELAY (0.4) #define OLD_CHANNEL_NUMBER 1 +bool Steam_Networking::connection_exists(CSteamID id) +{ + std::lock_guard lock(connections_edit_mutex); + return std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}) != connections.end(); +} + +struct Steam_Networking_Connection* Steam_Networking::get_or_create_connection(CSteamID id) +{ + std::lock_guard lock(connections_edit_mutex); + auto conn = std::find_if(connections.begin(), connections.end(), [&id](struct Steam_Networking_Connection const& conn) { return conn.remote == id;}); + + if (connections.end() == conn) { + struct Steam_Networking_Connection connection; + connection.remote = id; + connections.push_back(connection); + return &(connections[connections.size() - 1]); + } else { + return &(*conn); + } +} + +void Steam_Networking::remove_connection(CSteamID id) +{ + { + std::lock_guard lock(connections_edit_mutex); + auto conn = std::begin(connections); + while (conn != std::end(connections)) { + if (conn->remote == id) { + + conn = connections.erase(conn); + } else { + ++conn; + } + } + } + + //pretty sure steam also clears the entire queue of messages for that connection + { + std::lock_guard lock(messages_mutex); + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + if (msg->source_id() == id.ConvertToUint64()) { + msg = messages.erase(msg); + } else { + ++msg; + } + } + } + + { + auto msg = std::begin(unprocessed_messages); + while (msg != std::end(unprocessed_messages)) { + if (msg->source_id() == id.ConvertToUint64()) { + msg = unprocessed_messages.erase(msg); + } else { + ++msg; + } + } + } +} + SNetSocket_t Steam_Networking::create_connection_socket(CSteamID target, int nVirtualPort, uint32 nIP, uint16 nPort, SNetListenSocket_t id, enum steam_socket_connection_status status, SNetSocket_t other_id) { static SNetSocket_t socket_number = 0; @@ -107,7 +173,7 @@ void Steam_Networking::steam_networking_callback(void *object, Common_Message *m steam_networking->Callback(msg); } -void Steam_Networking::steam_run_every_runcb(void *object) +void Steam_Networking::steam_networking_run_every_runcp(void *object) { // PRINT_DEBUG_ENTRY(); @@ -115,26 +181,25 @@ void Steam_Networking::steam_run_every_runcb(void *object) steam_networking->RunCallbacks(); } -Steam_Networking::Steam_Networking(class Settings *settings, class Networking *network, class P2p_Manager *p2p_manager, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +Steam_Networking::Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) { this->settings = settings; this->network = network; - this->p2p_manager = p2p_manager; this->callbacks = callbacks; this->run_every_runcb = run_every_runcb; this->network->setCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->run_every_runcb->add(&Steam_Networking::steam_run_every_runcb, this); + this->run_every_runcb->add(&Steam_Networking::steam_networking_run_every_runcp, this); - PRINT_DEBUG("user id %llu", settings->get_local_steam_id().ConvertToUint64()); + PRINT_DEBUG("user id %llu messages: %p", settings->get_local_steam_id().ConvertToUint64(), &messages); } Steam_Networking::~Steam_Networking() { this->network->rmCallback(CALLBACK_ID_NETWORKING, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking::steam_networking_callback, this); - this->run_every_runcb->remove(&Steam_Networking::steam_run_every_runcb, this); + this->run_every_runcb->remove(&Steam_Networking::steam_networking_run_every_runcp, this); } //////////////////////////////////////////////////////////////////////////////////////////// @@ -152,15 +217,31 @@ Steam_Networking::~Steam_Networking() // using different channels to talk to the same user will still use the same underlying p2p connection, saving on resources bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel) { - PRINT_DEBUG( - "size=[%u] sendtype: <%u> channel: [%u] from=[%llu] to=[%llu]", - cubData, eP2PSendType, nChannel, settings->get_local_steam_id().ConvertToUint64(), steamIDRemote.ConvertToUint64() - ); + PRINT_DEBUG("len %u sendtype: %u channel: %u to: %llu", cubData, eP2PSendType, nChannel, steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + bool reliable = false; + if (eP2PSendType == k_EP2PSendReliable || eP2PSendType == k_EP2PSendReliableWithBuffering) reliable = true; + Common_Message msg; + msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + msg.set_dest_id(steamIDRemote.ConvertToUint64()); + msg.set_allocated_network(new Network_pb); + + if (!connection_exists(steamIDRemote)) { + msg.mutable_network()->set_type(Network_pb::NEW_CONNECTION); + network->sendTo(&msg, true); + } - return p2p_manager->send_packet( - settings->get_local_steam_id(), steamIDRemote, - pubData, cubData, eP2PSendType, nChannel - ); + msg.mutable_network()->set_channel(nChannel); + msg.mutable_network()->set_data(pubData, cubData); + msg.mutable_network()->set_type(Network_pb::DATA); + + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + new_connection_times.erase(steamIDRemote); + + conn->open_channels.insert(nChannel); + bool ret = network->sendTo(&msg, reliable); + PRINT_DEBUG("Sent message with size: %zu %u", msg.network().data().size(), ret); + return ret; } bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType ) @@ -172,20 +253,26 @@ bool Steam_Networking::SendP2PPacket( CSteamID steamIDRemote, const void *pubDat // returns true if any data is available for read, and the amount of data that will need to be read bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize, int nChannel) { - PRINT_DEBUG( - "channel=[%i], my steam id=%llu (is server=%u)", - nChannel, settings->get_local_steam_id().ConvertToUint64(), settings->get_local_steam_id().BGameServerAccount() - ); - + PRINT_DEBUG("channel: %i", nChannel); + std::lock_guard lock(messages_mutex); //Not sure if this should be here because it slightly screws up games that don't like such low "pings" //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop //this->network->Run(); //RunCallbacks(); - return p2p_manager->is_packet_available( - settings->get_local_steam_id(), - pcubMsgSize, nChannel - ); + PRINT_DEBUG("Messages %zu %p", messages.size(), &messages); + for (auto &msg : messages) { + if (connection_exists((uint64)msg.source_id()) && msg.mutable_network()->channel() == nChannel && msg.network().processed()) { + uint32 size = static_cast(msg.mutable_network()->data().size()); + if (pcubMsgSize) *pcubMsgSize = size; + PRINT_DEBUG("available with size: %u", size); + return true; + } + } + + PRINT_DEBUG("(not available)"); + if (pcubMsgSize) *pcubMsgSize = 0; + return false; } bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize) @@ -200,17 +287,38 @@ bool Steam_Networking::IsP2PPacketAvailable( uint32 *pcubMsgSize) // this call is not blocking, and will return false if no data is available bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel) { - PRINT_DEBUG("%u %i %p", cubDest, nChannel, pubDest); - + PRINT_DEBUG("%u %i", cubDest, nChannel); + std::lock_guard lock(messages_mutex); //Not sure if this should be here because it slightly screws up games that don't like such low "pings" //Commenting it out for now because it looks like it causes a bug where 20xx gets stuck in an infinite receive packet loop //this->network->Run(); //RunCallbacks(); - return p2p_manager->read_packet( - settings->get_local_steam_id(), - pubDest, cubDest, pcubMsgSize, psteamIDRemote, nChannel - ); + bool read = false; + PRINT_DEBUG("Number messages %zu", messages.size()); + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + if (connection_exists((uint64)msg->source_id()) && msg->network().channel() == nChannel && msg->network().processed()) { + uint32 msg_size = static_cast(msg->network().data().size()); + if (msg_size > cubDest) msg_size = cubDest; + if (pcubMsgSize) *pcubMsgSize = msg_size; + memcpy(pubDest, msg->network().data().data(), msg_size); + + PRINT_DEBUG("%s", + common_helpers::uint8_vector_to_hex_string(std::vector((uint8_t*)pubDest, (uint8_t*)pubDest + msg_size)).c_str()); + + *psteamIDRemote = CSteamID((uint64)msg->source_id()); + PRINT_DEBUG("len %u channel: %u from: " "%" PRIu64 "", msg_size, nChannel, msg->source_id()); + msg = messages.erase(msg); + return true; + } + + ++msg; + } + + if (pcubMsgSize) *pcubMsgSize = 0; + if (psteamIDRemote) *psteamIDRemote = k_steamIDNil; + return false; } bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote) @@ -227,15 +335,11 @@ bool Steam_Networking::ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcu // (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request) bool Steam_Networking::AcceptP2PSessionWithUser( CSteamID steamIDRemote ) { - PRINT_DEBUG("from [%llu], I am=[%llu]", steamIDRemote.ConvertToUint64(), settings->get_local_steam_id().ConvertToUint64()); - - if (p2p_manager->accept_session(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote)) { - PRINT_DEBUG(" connection accepted"); - return true; - } - - PRINT_DEBUG(" [X] connection NOT accepted"); - return false; + PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + if (conn) new_connection_times.erase(steamIDRemote); + return !!conn; } @@ -244,8 +348,15 @@ bool Steam_Networking::AcceptP2PSessionWithUser( CSteamID steamIDRemote ) bool Steam_Networking::CloseP2PSessionWithUser( CSteamID steamIDRemote ) { PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + + return false; + } - return p2p_manager->close_session(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote); + remove_connection(steamIDRemote); + + return true; } @@ -254,9 +365,20 @@ bool Steam_Networking::CloseP2PSessionWithUser( CSteamID steamIDRemote ) // user will trigger a P2PSessionRequest_t callback bool Steam_Networking::CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel ) { - PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote)) { + return false; + } - return p2p_manager->close_channel(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote, nChannel); + struct Steam_Networking_Connection *conn = get_or_create_connection(steamIDRemote); + + conn->open_channels.erase(nChannel); + if (conn->open_channels.size() == 0) { + remove_connection(steamIDRemote); + } + + return true; } @@ -266,8 +388,36 @@ bool Steam_Networking::CloseP2PChannelWithUser( CSteamID steamIDRemote, int nCha bool Steam_Networking::GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState ) { PRINT_DEBUG("%llu", steamIDRemote.ConvertToUint64()); - - return p2p_manager->get_session_state(settings->get_local_steam_id().ConvertToUint64(), steamIDRemote, pConnectionState); + std::lock_guard lock(global_mutex); + if (!connection_exists(steamIDRemote) && (steamIDRemote != settings->get_local_steam_id())) { + if (pConnectionState) { + pConnectionState->m_bConnectionActive = false; + pConnectionState->m_bConnecting = false; + pConnectionState->m_eP2PSessionError = 0; + pConnectionState->m_bUsingRelay = false; + pConnectionState->m_nBytesQueuedForSend = 0; + pConnectionState->m_nPacketsQueuedForSend = 0; + pConnectionState->m_nRemoteIP = 0; + pConnectionState->m_nRemotePort = 0; + } + + PRINT_DEBUG("No Connection"); + return false; + } + + if (pConnectionState) { + pConnectionState->m_bConnectionActive = true; + pConnectionState->m_bConnecting = false; + pConnectionState->m_eP2PSessionError = 0; + pConnectionState->m_bUsingRelay = false; + pConnectionState->m_nBytesQueuedForSend = 0; + pConnectionState->m_nPacketsQueuedForSend = 0; + pConnectionState->m_nRemoteIP = network->getIP(steamIDRemote); + pConnectionState->m_nRemotePort = 12345; + } + + PRINT_DEBUG("Connection"); + return true; } @@ -278,7 +428,6 @@ bool Steam_Networking::GetP2PSessionState( CSteamID steamIDRemote, P2PSessionSta // P2P packet relay is allowed by default bool Steam_Networking::AllowP2PPacketRelay( bool bAllow ) { - PRINT_DEBUG_TODO(); PRINT_DEBUG("%u", bAllow); return true; } @@ -620,18 +769,115 @@ int Steam_Networking::GetMaxPacketSize( SNetSocket_t hSocket ) return 1500; } - - void Steam_Networking::RunCallbacks() { + uint64 current_time = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + + { + std::lock_guard lock(messages_mutex); + + { + auto msg = std::begin(unprocessed_messages); + while (msg != std::end(unprocessed_messages)) { + CSteamID source_id((uint64)msg->source_id()); + if (!connection_exists(source_id)) { + if (new_connection_times.find(source_id) == new_connection_times.end()) { + new_connections_to_call_cb.push(source_id); + new_connection_times[source_id] = std::chrono::high_resolution_clock::now(); + } + } else { + struct Steam_Networking_Connection *conn = get_or_create_connection(source_id); + conn->open_channels.insert(msg->network().channel()); + } + + msg->mutable_network()->set_processed(true); + msg->mutable_network()->set_time_processed(current_time); + messages.push_back(*msg); + msg = unprocessed_messages.erase(msg); + } + } + + auto msg = std::begin(messages); + while (msg != std::end(messages)) { + bool deleted = false; + if (msg->network().processed()) { + if (!connection_exists((uint64)msg->source_id())) { + if (msg->network().time_processed() + ORPHANED_PACKET_TIMEOUT < current_time) { + deleted = true; + } + } + } + + if (deleted) { + msg = messages.erase(msg); + } else { + ++msg; + } + } + + } + + while (!new_connections_to_call_cb.empty()) { + CSteamID source_id = new_connections_to_call_cb.front(); + auto t = new_connection_times.find(source_id); + if (t == new_connection_times.end()) { + new_connections_to_call_cb.pop(); + continue; + } + + if (!check_timedout(t->second, NEW_CONNECTION_DELAY)) { + break; + } + + P2PSessionRequest_t data; + memset(&data, 0, sizeof(data)); + data.m_steamIDRemote = source_id; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + new_connections_to_call_cb.pop(); + } + //TODO: not sure if sockets should be wiped right away remove_killed_connection_sockets(); + + for(auto it = new_connection_times.begin(); it != new_connection_times.end(); ) { + if (std::chrono::duration_cast>(std::chrono::high_resolution_clock::now() - it->second).count() > NEW_CONNECTION_TIMEOUT) { + it = new_connection_times.erase(it); + //TODO send packet to other side to tell them connection has "failed". + } else { + ++it; + } + } } void Steam_Networking::Callback(Common_Message *msg) { + if (msg->has_network()) { + PRINT_DEBUG("got msg from: " "%" PRIu64 " to: " "%" PRIu64 " size %zu type %u | messages %p: %zu", + msg->source_id(), msg->dest_id(), msg->network().data().size(), msg->network().type(), &messages, messages.size() + ); + PRINT_DEBUG("msg data: '%s'", + common_helpers::uint8_vector_to_hex_string(std::vector(msg->network().data().begin(), msg->network().data().end())).c_str()); + + if (msg->network().type() == Network_pb::DATA) { + unprocessed_messages.push_back(Common_Message(*msg)); + } + + if (msg->network().type() == Network_pb::NEW_CONNECTION) { + std::lock_guard lock(messages_mutex); + auto msg_temp = std::begin(messages); + while (msg_temp != std::end(messages)) { + //only delete processed to handle unreliable message arriving at the same time. + if (msg_temp->source_id() == msg->source_id() && msg_temp->network().processed()) { + msg_temp = messages.erase(msg_temp); + } else { + ++msg_temp; + } + } + } + } + if (msg->has_network_old()) { - PRINT_DEBUG("got old network socket msg %u", msg->network_old().type()); + PRINT_DEBUG("got network socket msg %u", msg->network_old().type()); if (msg->network_old().type() == Network_Old::CONNECTION_REQUEST_IP) { for (auto & listen : listen_sockets) { if (listen.nPort == msg->network_old().port()) { @@ -698,9 +944,17 @@ void Steam_Networking::Callback(Common_Message *msg) if (msg->has_low_level()) { if (msg->low_level().type() == Low_Level::DISCONNECT) { + CSteamID source_id((uint64)msg->source_id()); + if (connection_exists(source_id)) { + P2PSessionConnectFail_t data; + data.m_steamIDRemote = source_id; + data.m_eP2PSessionError = k_EP2PSessionErrorDestinationNotLoggedIn; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } + for (auto & socket : connection_sockets) { if (socket.target.ConvertToUint64() == msg->source_id()) { - struct SocketStatusCallback_t data{}; + struct SocketStatusCallback_t data; socket.status = SOCKET_DISCONNECTED; data.m_hSocket = socket.id; data.m_hListenSocket = socket.listen_id; @@ -709,7 +963,9 @@ void Steam_Networking::Callback(Common_Message *msg) callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } } - } else if (msg->low_level().type() == Low_Level::CONNECT) { + } else + + if (msg->low_level().type() == Low_Level::CONNECT) { } } diff --git a/dll/steam_networking_sockets.cpp b/dll/steam_networking_sockets.cpp index c2bf3504a..395014f6f 100644 --- a/dll/steam_networking_sockets.cpp +++ b/dll/steam_networking_sockets.cpp @@ -54,6 +54,7 @@ SteamNetworkingMessage_t* Steam_Networking_Sockets::get_steam_message_connection pMsg->m_pfnFreeData = &free_steam_message_data; pMsg->m_pfnRelease = &delete_steam_message; pMsg->m_nChannel = 0; + pMsg->m_idxLane = static_cast(connect_socket->second.data.top().lane()); connect_socket->second.data.pop(); PRINT_DEBUG("get_steam_message_connection %u %lu, %llu", hConn, size, pMsg->m_nMessageNumber); return pMsg; @@ -135,7 +136,10 @@ bool Steam_Networking_Sockets::send_packet_new_connection(HSteamNetConnection m_ uint64_t steam_id = connect_socket->second.remote_identity.GetSteamID64(); if (steam_id) { msg.set_dest_id(steam_id); - return network->sendTo(&msg, true); + // sendToAllWithID: a peer Steam account may map to several local + // connections (bootstrap + shipping exe). Route the REQUEST/ACCEPTED to + // all of them so the process owning the socket actually receives it. + return network->sendToAllWithID(&msg, true); } const SteamNetworkingIPAddr *ip_addr = connect_socket->second.remote_identity.GetIPAddr(); @@ -187,7 +191,7 @@ void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::mapm_hListenSocket = connect_socket->second.listen_socket_id; pInfo->m_addrRemote.Clear(); //TODO if (connect_socket->second.real_port != SNS_DISABLED_PORT) { - pInfo->m_addrRemote.SetIPv4(network->getIP(connect_socket->second.remote_identity.GetSteamID()), connect_socket->first); + pInfo->m_addrRemote.SetIPv4(network->getIP(connect_socket->second.remote_identity.GetSteamID()), connect_socket->second.real_port); } pInfo->m_idPOPRemote = 0; @@ -201,6 +205,25 @@ void Steam_Networking_Sockets::set_steamnetconnectioninfo(std::map::iterator connect_socket, SteamNetConnectionInfo001_t* pInfo) +{ + pInfo->m_steamIDRemote = connect_socket->second.remote_identity.GetSteamID(); + pInfo->m_nUserData = connect_socket->second.user_data; + pInfo->m_hListenSocket = connect_socket->second.listen_socket_id; + if (connect_socket->second.real_port != SNS_DISABLED_PORT) { + pInfo->m_unIPRemote = network->getIP(connect_socket->second.remote_identity.GetSteamID()); + pInfo->m_unPortRemote = connect_socket->second.real_port; + } + + pInfo->m_idPOPRemote = 0; + pInfo->m_idPOPRelay = 0; + pInfo->m_eState = convert_status(connect_socket->second.status); + pInfo->m_eEndReason = 0; //TODO + pInfo->m_szEndDebug[0] = 0; + //Note some games might not allocate a struct the whole size of SteamNetConnectionInfo_t when calling GetConnectionInfo + //keep this in mind in future interface updates +} + void Steam_Networking_Sockets::launch_callback(HSteamNetConnection m_hConn, enum connect_socket_status old_status) { auto connect_socket = sbcs->connect_sockets.find(m_hConn); @@ -555,7 +578,7 @@ bool Steam_Networking_Sockets::CloseConnection( HSteamNetConnection hPeer, int n msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); - network->sendTo(&msg, true); + network->sendToAllWithID(&msg, true); } sbcs->connect_sockets.erase(connect_socket); @@ -576,7 +599,7 @@ bool Steam_Networking_Sockets::CloseListenSocket( HSteamListenSocket hSocket, co { PRINT_DEBUG("old"); std::lock_guard lock(global_mutex); - return false; + return CloseListenSocket(hSocket); } /// Destroy a listen socket. All the connections that were accepting on the listen @@ -739,13 +762,14 @@ EResult Steam_Networking_Sockets::SendMessageToConnection( HSteamNetConnection h msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); msg.mutable_networking_sockets()->set_data(pData, cbData); + msg.mutable_networking_sockets()->set_lane(0); uint64 message_number = connect_socket->second.packet_send_counter; msg.mutable_networking_sockets()->set_message_number(message_number); connect_socket->second.packet_send_counter += 1; bool reliable = false; if (nSendFlags & k_nSteamNetworkingSend_Reliable) reliable = true; - if (network->sendTo(&msg, reliable)) { + if (network->sendToAllWithID(&msg, reliable)) { if (pOutMessageNumber) *pOutMessageNumber = message_number; return k_EResultOK; } @@ -797,7 +821,44 @@ void Steam_Networking_Sockets::SendMessages( int nMessages, SteamNetworkingMessa std::lock_guard lock(global_mutex); for (int i = 0; i < nMessages; ++i) { int64 out_number = 0; - int result = SendMessageToConnection(pMessages[i]->m_conn, pMessages[i]->m_pData, pMessages[i]->m_cbSize, pMessages[i]->m_nFlags, &out_number); + int result = k_EResultInvalidParam; + + if (pMessages[i]) { + auto connect_socket = sbcs->connect_sockets.find(pMessages[i]->m_conn); + if (connect_socket == sbcs->connect_sockets.end()) { + result = k_EResultInvalidParam; + } else if (connect_socket->second.status == CONNECT_SOCKET_CLOSED || connect_socket->second.status == CONNECT_SOCKET_TIMEDOUT) { + result = k_EResultNoConnection; + } else if (connect_socket->second.status != CONNECT_SOCKET_CONNECTED && connect_socket->second.status != CONNECT_SOCKET_CONNECTING) { + result = k_EResultInvalidState; + } else { + Common_Message msg; + msg.set_source_id(connect_socket->second.created_by.ConvertToUint64()); + msg.set_dest_id(connect_socket->second.remote_identity.GetSteamID64()); + msg.set_allocated_networking_sockets(new Networking_Sockets); + msg.mutable_networking_sockets()->set_type(Networking_Sockets::DATA); + msg.mutable_networking_sockets()->set_virtual_port(connect_socket->second.virtual_port); + msg.mutable_networking_sockets()->set_real_port(connect_socket->second.real_port); + msg.mutable_networking_sockets()->set_connection_id_from(connect_socket->first); + msg.mutable_networking_sockets()->set_connection_id(connect_socket->second.remote_id); + msg.mutable_networking_sockets()->set_data(pMessages[i]->m_pData, pMessages[i]->m_cbSize); + msg.mutable_networking_sockets()->set_lane(pMessages[i]->m_idxLane); + + uint64 message_number = connect_socket->second.packet_send_counter; + msg.mutable_networking_sockets()->set_message_number(message_number); + connect_socket->second.packet_send_counter += 1; + + bool reliable = false; + if (pMessages[i]->m_nFlags & k_nSteamNetworkingSend_Reliable) reliable = true; + if (network->sendToAllWithID(&msg, reliable)) { + out_number = message_number; + result = k_EResultOK; + } else { + result = k_EResultFail; + } + } + } + if (pOutMessageNumberOrResult) { if (result == k_EResultOK) { pOutMessageNumberOrResult[i] = out_number; @@ -892,7 +953,7 @@ int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket /// Returns basic information about the high-level state of the connection. bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo_t *pInfo ) { - PRINT_DEBUG_ENTRY(); + PRINT_DEBUG("%u %i", hConn, pInfo == NULL); std::lock_guard lock(global_mutex); if (!pInfo) return false; @@ -993,9 +1054,16 @@ int Steam_Networking_Sockets::ReceiveMessagesOnListenSocket( HSteamListenSocket /// Returns information about the specified connection. bool Steam_Networking_Sockets::GetConnectionInfo( HSteamNetConnection hConn, SteamNetConnectionInfo001_t *pInfo ) { - PRINT_DEBUG_TODO(); + PRINT_DEBUG("001 %u %i", hConn, pInfo == NULL); std::lock_guard lock(global_mutex); - return false; + + if (!pInfo) return false; + + auto connect_socket = sbcs->connect_sockets.find(hConn); + if (connect_socket == sbcs->connect_sockets.end()) return false; + + set_steamnetconnectioninfo_001(connect_socket, pInfo); + return true; } @@ -1340,9 +1408,9 @@ bool Steam_Networking_Sockets::SetConnectionPollGroup( HSteamNetConnection hConn HSteamNetPollGroup old_poll_group = connect_socket->second.poll_group; if (old_poll_group != k_HSteamNetPollGroup_Invalid) { - auto group = sbcs->poll_groups.find(hPollGroup); - if (group != sbcs->poll_groups.end()) { - group->second.remove(hConn); + auto old_group = sbcs->poll_groups.find(old_poll_group); // bugfix: should be old_poll_group + if (old_group != sbcs->poll_groups.end()) { + old_group->second.remove(hConn); } } diff --git a/dll/steam_remoteplay.cpp b/dll/steam_remoteplay.cpp index 67a34dfb1..949912334 100644 --- a/dll/steam_remoteplay.cpp +++ b/dll/steam_remoteplay.cpp @@ -67,6 +67,14 @@ uint32 Steam_RemotePlay::GetSessionID( int iSessionIndex ) return 0; } +// Return true if the session has joined using a Remote Play Together invitation +bool Steam_RemotePlay::BSessionRemotePlayTogether(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return false; +} + // Get the SteamID of the connected user CSteamID Steam_RemotePlay::GetSessionSteamID( uint32 unSessionID ) { @@ -75,6 +83,42 @@ CSteamID Steam_RemotePlay::GetSessionSteamID( uint32 unSessionID ) return k_steamIDNil; } +// Get the guest ID of the connected user if they are a Remote Play Together guest +// This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest +uint32 Steam_RemotePlay::GetSessionGuestID(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetSmallSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetMediumSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + +// gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid +// returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again +int Steam_RemotePlay::GetLargeSessionAvatar(RemotePlaySessionID_t unSessionID) +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return 0; +} + // Get the name of the session client device // This returns NULL if the sessionID is not valid const char* Steam_RemotePlay::GetSessionClientName( uint32 unSessionID ) diff --git a/dll/steam_ugc.cpp b/dll/steam_ugc.cpp index 1b7584ff7..a0687f429 100644 --- a/dll/steam_ugc.cpp +++ b/dll/steam_ugc.cpp @@ -16,6 +16,7 @@ . */ #include "dll/steam_ugc.h" +#include "dll/dll.h" UGCQueryHandle_t Steam_UGC::new_ugc_query(EQueryType query_type, bool return_all_subscribed, uint32 page, bool next_cursor, const std::set &return_only) { @@ -1808,3 +1809,54 @@ bool Steam_UGC::SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileI return true; } + +// Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine +bool Steam_UGC::MarkDownloadedItemAsUnused(PublishedFileId_t nPublishedFileID) +{ + PRINT_DEBUG("%llu", nPublishedFileID); + // we don't really have to do anything here, leaving this TODO + // in case we need to keep track of these marked items later + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + return true; +} + +// Returns the number of items actually downloaded locally +uint32 Steam_UGC::GetNumDownloadedItems() +{ + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + // https://partner.steamgames.com/doc/api/ISteamUGC#GetDownloadedItems + // "Returns 0 if called from a game server" + if (get_steam_client()->settings_server == settings) { + return 0; + } + + return (uint32)settings->modSet().size(); // not sure if returning all mods is correct +} + +// Returns the ids of the items downloaded +uint32 Steam_UGC::GetDownloadedItems(PublishedFileId_t* pvecPublishedFileIDs, uint32 cMaxEntries) +{ + PRINT_DEBUG("%p [%u]", pvecPublishedFileIDs, cMaxEntries); + PRINT_DEBUG_TODO(); + std::lock_guard lock(global_mutex); + + if (!pvecPublishedFileIDs || cMaxEntries == 0) { + return 0; + } + + // https://partner.steamgames.com/doc/api/ISteamUGC#GetDownloadedItems + // "Returns 0 if called from a game server" + if (get_steam_client()->settings_server == settings) { + return 0; + } + + const auto all_mods = settings->modSet(); // not sure if using all mods is correct + uint32 count = std::min((uint32)all_mods.size(), cMaxEntries); + std::copy_n(all_mods.cbegin(), count, pvecPublishedFileIDs); + + PRINT_DEBUG(" copied count = %u", count); + return count; +} diff --git a/dll/steam_user.cpp b/dll/steam_user.cpp index 36fe56054..acafc732a 100644 --- a/dll/steam_user.cpp +++ b/dll/steam_user.cpp @@ -21,13 +21,14 @@ #include "dll/base64.h" #include "dll/dll.h" -Steam_User::Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) +Steam_User::Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, bool is_server) { this->settings = settings; this->local_storage = local_storage; this->network = network; this->callbacks = callbacks; this->callback_results = callback_results; + this->is_server = is_server; auth_manager = new Auth_Manager(settings, network, callbacks); voicechat = new VoiceChat(); @@ -44,18 +45,19 @@ Steam_User::~Steam_User() HSteamUser Steam_User::GetHSteamUser() { PRINT_DEBUG_ENTRY(); - return (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + return is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; } void Steam_User::LogOn( CSteamID steamID ) { PRINT_DEBUG_ENTRY(); - settings->set_offline(false); - logon_time = std::chrono::high_resolution_clock::now(); + std::lock_guard lock(global_mutex); + call_logged_on = true; call_logged_off = false; + logon_time = std::chrono::high_resolution_clock::now(); - if (settings == get_steam_client()->settings_server) { + if (is_server) { get_steam_client()->steam_gameserver->LogOnAnonymous(); } } @@ -63,13 +65,16 @@ void Steam_User::LogOn( CSteamID steamID ) void Steam_User::LogOff() { PRINT_DEBUG_ENTRY(); - settings->set_offline(true); - logoff_time = std::chrono::high_resolution_clock::now(); + std::lock_guard lock(global_mutex); + call_logged_on = false; call_logged_off = true; + logoff_time = std::chrono::high_resolution_clock::now(); + + settings->set_offline(true); player_auths.clear(); - if (settings == get_steam_client()->settings_server) { + if (is_server) { get_steam_client()->steam_gameserver->LogOff(); } } @@ -80,21 +85,34 @@ void Steam_User::LogOff() bool Steam_User::BLoggedOn() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return !settings->is_offline(); } ELogonState Steam_User::GetLogonState() { PRINT_DEBUG_ENTRY(); - if(settings->is_offline()) - return (ELogonState)0; - else - return (ELogonState)4; // tested on real steam, undocumented return value + std::lock_guard lock(global_mutex); + + if (settings->is_offline()) { + if (call_logged_on) { + return k_ELogonStateLoggingOn; + } else if (call_logged_off) { + return k_ELogonStateLoggingOff; + } else { + return k_ELogonStateNotLoggedOn; + } + } else { + return k_ELogonStateLoggedOn; + } } bool Steam_User::BConnected() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + return !settings->is_offline(); } @@ -103,6 +121,8 @@ bool Steam_User::BConnected() CSteamID Steam_User::GetSteamID() { PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + CSteamID id = settings->get_current_steam_id(); PRINT_DEBUG("GetSteamID() call #%u, returning %llu", settings->global_steamid_call_count, id.ConvertToUint64()); @@ -113,6 +133,10 @@ CSteamID Steam_User::GetSteamID() bool Steam_User::IsVACBanned( int nGameID ) { PRINT_DEBUG_ENTRY(); + // EVACBan to appid map: + // k_EVACBanGoldsrc -> 10 + // k_EVACBanSource -> 240 + // k_EVACBanDayOfDefeatSource -> 300 return false; } @@ -177,7 +201,7 @@ int Steam_User::GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ) return out_size; } -bool Steam_User::SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) +bool Steam_User::SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) { PRINT_DEBUG_TODO(); if (!pchValue) @@ -197,7 +221,7 @@ bool Steam_User::SetRegistryString( EConfigSubTree eRegistrySubTree, const char return true; } -bool Steam_User::GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) +bool Steam_User::GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) { PRINT_DEBUG_TODO(); // TODO: read data on disk, because real steam can get the string when app restarts @@ -223,7 +247,7 @@ bool Steam_User::GetRegistryString( EConfigSubTree eRegistrySubTree, const char return true; } -bool Steam_User::SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) +bool Steam_User::SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) { PRINT_DEBUG_TODO(); if (!pchKey) // tested on real steam @@ -240,7 +264,7 @@ bool Steam_User::SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pc return true; } -bool Steam_User::GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) +bool Steam_User::GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) { PRINT_DEBUG_TODO(); // TODO: read data on disk, because real steam can get the string when app restarts @@ -906,58 +930,55 @@ bool Steam_User::BSetDurationControlOnlineState( EDurationControlOnlineState eNe } // older sdk ----------------------------------------------- +// SteamUser001 -------------------------------------------- void Steam_User::Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) { PRINT_DEBUG_ENTRY(); callbacks_old1 = cmcallback; } -void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) -{ - PRINT_DEBUG_ENTRY(); - callbacks_old2 = cmcallback; -} - int Steam_User::ProcessCall( int unk ) { PRINT_DEBUG_TODO(); return 0; } -void Steam_User::LogOn( CSteamID *steamID ) +void Steam_User::LogOn_old( CSteamID &steamID ) { PRINT_DEBUG_ENTRY(); - LogOn(*steamID); + LogOn(steamID); } -int Steam_User::CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) +int Steam_User::CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) { PRINT_DEBUG_TODO(); return 0; } -bool Steam_User::GSSendLogonRequest( CSteamID *steamID ) +bool Steam_User::GSSendLogonRequest( CSteamID &steamID ) { - PRINT_DEBUG("%llu", (*steamID).ConvertToUint64()); + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); - // Note that SteamID passed into this comes from Steam.dll so it won't match the client's Goldberg SteamID. - std::pair entry(*steamID, std::chrono::high_resolution_clock::now()); + // At this point, client auth is still primarily done via Steam2 (Steam.dll), Steam3 is supplementary. + // As such, there's not much really needed from us besides firing approve callback. + // Since SteamID passed into this comes from Steam.dll, it won't match the client's Goldberg SteamID. + std::pair entry(steamID, std::chrono::high_resolution_clock::now()); player_auths.push_back(entry); - get_steam_client()->steam_gameserver->add_player(*steamID); + get_steam_client()->steam_gameserver->add_player(steamID); return true; } -bool Steam_User::GSSendDisconnect( CSteamID *steamID ) +bool Steam_User::GSSendDisconnect( CSteamID &steamID ) { - PRINT_DEBUG("%llu", (*steamID).ConvertToUint64()); + PRINT_DEBUG("%llu", steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); - get_steam_client()->steam_gameserver->remove_player(*steamID); + get_steam_client()->steam_gameserver->remove_player(steamID); return true; } -bool Steam_User::GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) +bool Steam_User::GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) { PRINT_DEBUG_TODO(); return false; @@ -968,17 +989,30 @@ bool Steam_User::GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPla PRINT_DEBUG_TODO(); return true; } +// SteamUser001 -------------------------------------------- +// SteamUser002 (old) -------------------------------------- bool Steam_User::GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) { PRINT_DEBUG_ENTRY(); return get_steam_client()->steam_gameserver->Obsolete_GSSetStatus(nAppIdServed, unServerFlags, cPlayers, cPlayersMax, cBotPlayers, unGamePort, pchServerName, pchGameDir, pchMapName, pchVersion); } +// SteamUser002 (old) -------------------------------------- + +// SteamUser002 (new) -------------------------------------- +void Steam_User::Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) +{ + PRINT_DEBUG_ENTRY(); + callbacks_old2 = cmcallback; +} +// SteamUser002 (new) -------------------------------------- +// SteamUser004 (old) -------------------------------------- bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) { PRINT_DEBUG_ENTRY(); - HSteamUser user = (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + // HACK: Use the first available pipe. + HSteamUser user = is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; HSteamPipe pipe = get_steam_client()->get_pipe_for_user(user); if (!pipe) return false; @@ -995,7 +1029,7 @@ bool Steam_User::BGetCallback( int *piCallback, uint8 **ppubParam, int *unk ) void Steam_User::FreeLastCallback() { PRINT_DEBUG_ENTRY(); - HSteamUser user = (settings == get_steam_client()->settings_server) ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; + HSteamUser user = is_server ? SERVER_HSTEAMUSER : CLIENT_HSTEAMUSER; HSteamPipe pipe = get_steam_client()->get_pipe_for_user(user); if (!pipe) return; @@ -1003,22 +1037,6 @@ void Steam_User::FreeLastCallback() steamclient_free_callback(pipe); } -int Steam_User::GetSteamTicket( void *pBlob, int cbMaxBlob ) -{ - PRINT_DEBUG_ENTRY(); - std::lock_guard lock(global_mutex); - - if (cbMaxBlob < STEAM_TICKET_MIN_SIZE) return 0; - if (!pBlob) return 0; - - uint32 out_size = STEAM_AUTH_TICKET_SIZE; - auth_manager->getTicketData(pBlob, cbMaxBlob, &out_size); - - if (out_size > STEAM_AUTH_TICKET_SIZE) - return 0; - return out_size; -} - const char *Steam_User::GetPlayerName() { PRINT_DEBUG_ENTRY(); @@ -1090,6 +1108,23 @@ int32 Steam_User::AddFriendByName( const char *pchEmailOrAccountName ) PRINT_DEBUG_ENTRY(); return get_steam_client()->steam_friends->AddFriendByName(pchEmailOrAccountName); } + +int Steam_User::GetSteamTicket( void *pBlob, int cbMaxBlob ) +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + if (cbMaxBlob < STEAM_TICKET_MIN_SIZE) return 0; + if (!pBlob) return 0; + + uint32 out_size = STEAM_AUTH_TICKET_SIZE; + auth_manager->getTicketData(pBlob, cbMaxBlob, &out_size); + + if (out_size > STEAM_AUTH_TICKET_SIZE) + return 0; + return out_size; +} +// SteamUser004 (old) -------------------------------------- // older sdk ----------------------------------------------- void Steam_User::RunCallbacks() @@ -1097,20 +1132,21 @@ void Steam_User::RunCallbacks() if (callbacks_old1) { if (call_logged_on && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLogonSuccess"); + settings->set_offline(false); callbacks_old1->OnLogonSuccess(); call_logged_on = false; } if (call_logged_off && check_timedout(logoff_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLoggedOff"); - callbacks_old1->OnLoggedOff(); + callbacks_old1->OnLoggedOff(k_EResultOK); call_logged_off = false; } for (auto it = player_auths.begin(); it != player_auths.end();) { if (check_timedout(it->second, 0.1)) { PRINT_DEBUG("ICMCallback -> GSHandleClientApprove %llu", it->first.ConvertToUint64()); - callbacks_old1->GSHandleClientApprove(&(it->first)); + callbacks_old1->GSHandleClientApprove(it->first); it = player_auths.erase(it); } else { it++; @@ -1119,20 +1155,21 @@ void Steam_User::RunCallbacks() } else if (callbacks_old2) { if (call_logged_on && check_timedout(logon_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLogonSuccess"); + settings->set_offline(false); callbacks_old2->OnLogonSuccess(); call_logged_on = false; } if (call_logged_off && check_timedout(logoff_time, 0.1)) { PRINT_DEBUG("ICMCallback -> OnLoggedOff"); - callbacks_old2->OnLoggedOff(); + callbacks_old2->OnLoggedOff(k_EResultOK); call_logged_off = false; } for (auto it = player_auths.begin(); it != player_auths.end();) { if (check_timedout(it->second, 0.1)) { PRINT_DEBUG("ICMCallback -> GSHandleClientApprove %llu", it->first.ConvertToUint64()); - callbacks_old2->GSHandleClientApprove(&(it->first)); + callbacks_old2->GSHandleClientApprove(it->first); it = player_auths.erase(it); } else { it++; diff --git a/dll/steam_user_items.cpp b/dll/steam_user_items.cpp new file mode 100644 index 000000000..64484513c --- /dev/null +++ b/dll/steam_user_items.cpp @@ -0,0 +1,232 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "dll/steam_user_items.h" +#include "dll/dll.h" + +Steam_Game_Coordinator *Steam_User_Items::gc() +{ + return get_steam_client()->steam_game_coordinator; +} + +Steam_User_Items::Steam_User_Items(class Settings *settings, class SteamCallBacks *callbacks, class SteamCallResults *callback_results) +{ + this->settings = settings; + this->callbacks = callbacks; + this->callback_results = callback_results; +} + +Steam_User_Items::~Steam_User_Items() +{ +} + +SteamAPICall_t Steam_User_Items::LoadItems() +{ + PRINT_DEBUG_ENTRY(); + std::lock_guard lock(global_mutex); + + const auto &items = gc()->load_items_from_file(); + + UserItemCount_t data{}; + data.m_gameID = settings->get_local_game_id(); + data.m_eResult = k_EItemRequestResultOK; + data.m_unCount = static_cast(items.size()); + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::LoadItems_old() +{ + PRINT_DEBUG_ENTRY(); + LoadItems(); +} + +SteamAPICall_t Steam_User_Items::GetItemCount() +{ + PRINT_DEBUG_ENTRY(); + return LoadItems(); +} + +void Steam_User_Items::GetItemCount_old() +{ + PRINT_DEBUG_ENTRY(); + GetItemCount(); +} + +bool Steam_User_Items::GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%u", iIndex); + std::lock_guard lock(global_mutex); + + const auto &items = gc()->get_items(); + + if (iIndex >= items.size()) + return false; + + const Econ_Item &item = items[iIndex]; + *pulItemID = item.id; + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; +} + +bool Steam_User_Items::GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemIterative(iIndex, pulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_User_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + const auto &items = gc()->get_items(); + + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + *punItemDefIndex = item.def; + *punItemLevel = item.level; + *peQuality = item.quality; + *punInventoryPos = item.inv_pos; + *punQuantity = item.quantity; + *punAttributeCount = static_cast(item.attributes.size()); + + return true; + } + + return false; +} + +bool Steam_User_Items::GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) +{ + PRINT_DEBUG_ENTRY(); + uint32 quantity; + return GetItemByID(ulItemID, punItemDefIndex, punItemLevel, peQuality, punInventoryPos, &quantity, punAttributeCount); +} + +bool Steam_User_Items::GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) +{ + PRINT_DEBUG("%llu %u", ulItemID, unAttributeIndex); + std::lock_guard lock(global_mutex); + + const auto &items = gc()->get_items(); + + for (const Econ_Item &item : items) { + if (item.id != ulItemID) + continue; + + if (unAttributeIndex >= item.attributes.size()) + return false; + + *punAttributeDefIndex = item.attributes[unAttributeIndex].def; + *pflAttributeValue = item.attributes[unAttributeIndex].value; + + return true; + } + + return false; +} + +SteamAPICall_t Steam_User_Items::UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) +{ + PRINT_DEBUG("%llu 0x%08X", ulItemID, unNewInventoryPos); + std::lock_guard lock(global_mutex); + + if (gc()->set_item_pos(ulItemID, unNewInventoryPos, false)) { + UpdateInventoryPosResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultOK; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + UpdateInventoryPosResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultNoMatch; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) +{ + PRINT_DEBUG_ENTRY(); + UpdateInventoryPos(ulItemID, unNewInventoryPos); +} + +SteamAPICall_t Steam_User_Items::DeleteItem( uint64 ulItemID ) +{ + PRINT_DEBUG("%llu", ulItemID); + std::lock_guard lock(global_mutex); + + if (gc()->delete_item(ulItemID, false)) { + DeleteItemResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultOK; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; + } + + DeleteItemResponse_t data{}; + data.m_ulItemID = ulItemID; + data.m_eResult = k_EItemRequestResultNoMatch; + SteamAPICall_t ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return ret; +} + +void Steam_User_Items::DropItem_old( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + DeleteItem(ulItemID); +} + +SteamAPICall_t Steam_User_Items::DropItem_old2( uint64 ulItemID ) +{ + PRINT_DEBUG_ENTRY(); + return DeleteItem(ulItemID); +} + +SteamAPICall_t Steam_User_Items::GetItemBlob( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_User_Items::SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} + +SteamAPICall_t Steam_User_Items::DropItem( uint64 ulItemID ) +{ + PRINT_DEBUG_TODO(); + return k_uAPICallInvalid; +} diff --git a/dll/steam_user_stats.cpp b/dll/steam_user_stats.cpp index 237dbb5fb..1a35a3e73 100644 --- a/dll/steam_user_stats.cpp +++ b/dll/steam_user_stats.cpp @@ -70,8 +70,16 @@ Steam_User_Stats::Steam_User_Stats(Settings *settings, class Networking *network trig.name = name; trig.value_operation = static_cast(it["progress"]["value"]["operation"]); std::string stat_name = common_helpers::to_lower(static_cast(it["progress"]["value"]["operand1"])); - trig.min_value = static_cast(it["progress"]["min_val"]); - trig.max_value = static_cast(it["progress"]["max_val"]); + const auto &min_val_obj = it["progress"]["min_val"]; + std::string min_val = min_val_obj.is_number() + ? std::to_string(static_cast(min_val_obj)) + : static_cast(min_val_obj); + const auto &max_val_obj = it["progress"]["max_val"]; + std::string max_val = max_val_obj.is_number() + ? std::to_string(static_cast(max_val_obj)) + : static_cast(max_val_obj); + trig.min_value = min_val; + trig.max_value = max_val; achievement_stat_trigger[stat_name].push_back(trig); } catch(...) {} diff --git a/dll/steam_user_stats_stats.cpp b/dll/steam_user_stats_stats.cpp index fc1ba63e0..e24079be8 100644 --- a/dll/steam_user_stats_stats.cpp +++ b/dll/steam_user_stats_stats.cpp @@ -899,6 +899,40 @@ void Steam_User_Stats::network_stats_updated(Common_Message *msg) ); } +// server requested achievement status +void Steam_User_Stats::network_achievement_request(Common_Message *msg) +{ + if (!msg->gameserver_stats_messages().has_achievement_request()) { + PRINT_DEBUG("error empty msg"); + return; + } + + uint64 server_steamid = msg->source_id(); + + const auto &ach_request_msg = msg->gameserver_stats_messages().achievement_request(); + std::string ach_name = ach_request_msg.name(); + bool achieved = false; + GetAchievement(ach_name.c_str(), &achieved); + + auto ach_response_msg = new GameServerStats_Messages::AchievementResponse(); + ach_response_msg->set_name(ach_name); + auto ach_info = new GameServerStats_Messages::AchievementInfo(); + ach_info->set_achieved(achieved); + ach_response_msg->set_allocated_info(ach_info); + + auto gameserverstats_msg = new GameServerStats_Messages(); + gameserverstats_msg->set_type(GameServerStats_Messages::Response_UserAchievement); + gameserverstats_msg->set_allocated_achievement_response(ach_response_msg); + + Common_Message new_msg{}; + new_msg.set_allocated_gameserver_stats_messages(gameserverstats_msg); + new_msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); + new_msg.set_dest_id(server_steamid); + network->sendTo(&new_msg, true); + + PRINT_DEBUG("server requested achievement '%s'", ach_name.c_str()); +} + void Steam_User_Stats::send_pending_user_stats_requests() { if (!pending_user_stats_requests.empty()) { @@ -1007,6 +1041,11 @@ void Steam_User_Stats::network_callback_stats(Common_Message *msg) case GameServerStats_Messages::UpdateUserStatsFromUser: // nothing break; + + // server requested achievement status + case GameServerStats_Messages::Request_UserAchievement: + network_achievement_request(msg); + break; default: PRINT_DEBUG("unhandled type %i", (int)msg->gameserver_stats_messages().type()); diff --git a/dll/ugc_remote_storage_bridge.cpp b/dll/ugc_remote_storage_bridge.cpp index 89a74ea11..7cc3bfde0 100644 --- a/dll/ugc_remote_storage_bridge.cpp +++ b/dll/ugc_remote_storage_bridge.cpp @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #include "dll/ugc_remote_storage_bridge.h" diff --git a/dll/voicechat.cpp b/dll/voicechat.cpp index dee4ae124..958d17181 100644 --- a/dll/voicechat.cpp +++ b/dll/voicechat.cpp @@ -1,3 +1,20 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + #include "dll/voicechat.h" diff --git a/helpers/common_helpers.cpp b/helpers/common_helpers.cpp index f46cd5be3..0fdbbd8c1 100644 --- a/helpers/common_helpers.cpp +++ b/helpers/common_helpers.cpp @@ -570,3 +570,16 @@ std::vector common_helpers::str_split(std::string_view str, std::st return out; } + +std::string common_helpers::str_strip(std::string_view str) +{ + if (str.empty()) return {}; + + constexpr static const char whitespaces[] = { ' ', '\t', '\r', '\n', 0 }; // null terminator at the end is needed + + const auto start = str.find_first_not_of(whitespaces); + if (std::string::npos == start) return {}; + + const auto end = str.find_last_not_of(whitespaces); + return std::string(str.substr(start, end - start + 1)); +} diff --git a/helpers/common_helpers/common_helpers.hpp b/helpers/common_helpers/common_helpers.hpp index ab0cc433e..15c0992a1 100644 --- a/helpers/common_helpers/common_helpers.hpp +++ b/helpers/common_helpers/common_helpers.hpp @@ -111,5 +111,6 @@ std::string to_str(std::wstring_view wstr); std::string str_replace_all(std::string_view source, std::string_view substr, std::string_view replace, bool case_insensitive = true); size_t str_find(std::string_view str_src, std::string_view str_query, size_t start = 0, bool case_insensitive = true); std::vector str_split(std::string_view str, std::string_view splitter, bool ignore_empty = true, bool case_insensitive = true); +std::string str_strip(std::string_view str); } diff --git a/libs/detours/SOURCE.txt b/libs/detours/SOURCE.txt index ed35d8d0b..dfb0fec70 100644 --- a/libs/detours/SOURCE.txt +++ b/libs/detours/SOURCE.txt @@ -2,7 +2,7 @@ https://github.com/microsoft/Detours -VERSION: https://github.com/microsoft/Detours/tree/9764cebcb1a75940e68fa83d6730ffaf0f669401 +VERSION: https://github.com/microsoft/Detours/tree/d644ce94e8c7f7f5a31591577c78134ea3ac1fae #### LICENSE diff --git a/libs/detours/disasm.cpp b/libs/detours/disasm.cpp index 3f84c9d4e..b791e2fe0 100644 --- a/libs/detours/disasm.cpp +++ b/libs/detours/disasm.cpp @@ -244,10 +244,12 @@ class CDetourDis #define ENTRY_CopyF6 { ENTRY_DataIgnored &CDetourDis::CopyF6 } #define ENTRY_CopyF7 { ENTRY_DataIgnored &CDetourDis::CopyF7 } #define ENTRY_CopyFF { ENTRY_DataIgnored &CDetourDis::CopyFF } +#define ENTRY_CopyC7 { ENTRY_DataIgnored &CDetourDis::CopyC7 } #define ENTRY_CopyVex2 { ENTRY_DataIgnored &CDetourDis::CopyVex2 } #define ENTRY_CopyVex3 { ENTRY_DataIgnored &CDetourDis::CopyVex3 } #define ENTRY_CopyEvex { ENTRY_DataIgnored &CDetourDis::CopyEvex } // 62, 3 byte payload, then normal with implied prefixes like vex #define ENTRY_CopyXop { ENTRY_DataIgnored &CDetourDis::CopyXop } // 0x8F ... POP /0 or AMD XOP +#define ENTRY_CopyRex2 { ENTRY_DataIgnored &CDetourDis::CopyRex2 } // 0xD5 Intel APX REX2 (x64 only) #define ENTRY_CopyBytesXop { 5, 5, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm #define ENTRY_CopyBytesXop1 { 6, 6, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm ... imm8 #define ENTRY_CopyBytesXop4 { 9, 9, 4, 0, 0, &CDetourDis::CopyBytes } // 0x8F xop1 xop2 opcode modrm ... imm32 @@ -276,12 +278,14 @@ class CDetourDis PBYTE CopyF6(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyF7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyFF(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); + PBYTE CopyC7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVex3(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BYTE fp16 = 0); PBYTE CopyEvex(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); PBYTE CopyXop(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); + PBYTE CopyRex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc); // AMD64 only, Intel APX REX2 protected: static const COPYENTRY s_rceCopyTable[]; @@ -362,12 +366,6 @@ PBYTE CDetourDis::CopyBytes(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { UINT nBytesFixed; - if (m_bVex || m_bEvex) - { - ASSERT(pEntry->nFlagBits == 0); - ASSERT(pEntry->nFixedSize == pEntry->nFixedSize16); - } - UINT const nModOffset = pEntry->nModOffset; UINT const nFlagBits = pEntry->nFlagBits; UINT const nFixedSize = pEntry->nFixedSize; @@ -382,6 +380,12 @@ PBYTE CDetourDis::CopyBytes(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) nBytesFixed = nFixedSize + ((nFlagBits & RAX) ? 4 : 0); } #endif + else if (m_bVex || m_bEvex) { + // VEX/EVEX pp field does not shrink immediates to 16-bit. + // This matters for EVEX MAP4 (APX) where entries like opcode 81 + // (Group 1 imm32) have nFixedSize=6 but nFixedSize16=4. + nBytesFixed = nFixedSize; + } else { nBytesFixed = m_bOperandOverride ? nFixedSize16 : nFixedSize; } @@ -658,8 +662,10 @@ PBYTE CDetourDis::CopyF6(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { (void)pEntry; - // TEST BYTE /0 - if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 + // TEST BYTE /0 and /1 (both encodings are TEST per Intel SDM Vol 2A Group 3 + // and AMD APM Vol 3; /1 is an undocumented alias of /0 historically but is + // now documented in both manuals). + if (0x00 == (0x30 & pbSrc[1])) { // reg(bits 543) of ModR/M == 000 or 001 static const COPYENTRY ce = /* f6 */ ENTRY_CopyBytes2Mod1; return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); } @@ -678,8 +684,8 @@ PBYTE CDetourDis::CopyF7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) { (void)pEntry; - // TEST WORD /0 - if (0x00 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 0 + // TEST WORD /0 and /1 (see CopyF6 for /1 rationale). + if (0x00 == (0x30 & pbSrc[1])) { // reg(bits 543) of ModR/M == 000 or 001 static const COPYENTRY ce = /* f7 */ ENTRY_CopyBytes2ModOperand; return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); } @@ -745,6 +751,21 @@ PBYTE CDetourDis::CopyFF(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) return pbOut; } +PBYTE CDetourDis::CopyC7(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) +{ + (void)pEntry; + + // C7 /7 is XBEGIN rel32 (or rel16 with 66 prefix). + // It has a relative displacement that must be relocated like CALL/JMP. + if (0x38 == (0x38 & pbSrc[1])) { // reg(bits 543) of ModR/M == 111 + static const COPYENTRY ce = /* c7 /7 */ { 6, 4, 0, 2, 0, &CDetourDis::CopyBytes }; + return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); + } + // MOV /0 r/m, imm16/32 + static const COPYENTRY ce = /* c7 /0 */ ENTRY_CopyBytes2ModOperand; + return (this->*ce.pfCopy)(&ce, pbDst, pbSrc); +} + PBYTE CDetourDis::CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BYTE fp16) // m is first instead of last in the hopes of pbDst/pbSrc being // passed along efficiently in the registers they were already in. @@ -763,14 +784,23 @@ PBYTE CDetourDis::CopyVexEvexCommon(BYTE m, PBYTE pbDst, PBYTE pbSrc, BYTE p, BY REFCOPYENTRY pEntry; // see https://software.intel.com/content/www/us/en/develop/download/intel-avx512-fp16-architecture-specification.html + // EVEX maps (with FP16 and APX mmm-bit extension): + // mmm=001 (MAP1) and mmm=101 (MAP5) share the legacy 0F opcode structure, + // so per-opcode sizing must come from s_rceCopyTable0F (e.g. MAP5 C2 /r ib = VCMPPH/VCMPSH). + // mmm=010 (MAP2) and mmm=110 (MAP6) share the legacy 0F 38 structure (ModR/M, no imm). + // mmm=011 (MAP3) has the 0F 3A structure (ModR/M + imm8). + // mmm=100 (MAP4, APX) mirrors legacy MAP0 opcode structure (per-opcode sizing). switch (m | fp16) { default: return Invalid(&ceInvalid, pbDst, pbSrc); + case 5: // MAP5 (FP16) - opcode structure mirrors 0F (MAP1). case 1: pEntry = &s_rceCopyTable0F[pbSrc[0]]; return (this->*pEntry->pfCopy)(pEntry, pbDst, pbSrc); - case 5: // fallthrough - case 6: // fallthrough + case 6: // MAP6 (FP16) - opcode structure mirrors 0F 38 (MAP2). case 2: return CopyBytes(&ceF38, pbDst, pbSrc); case 3: return CopyBytes(&ceF3A, pbDst, pbSrc); + case 4: // MAP4 (APX) - promoted legacy MAP0 instructions. + pEntry = &s_rceCopyTable[pbSrc[0]]; + return (this->*pEntry->pfCopy)(pEntry, pbDst, pbSrc); } } @@ -862,11 +892,6 @@ PBYTE CDetourDis::CopyEvex(REFCOPYENTRY, PBYTE pbDst, PBYTE pbSrc) static const COPYENTRY ceInvalid = /* 62 */ ENTRY_Invalid; - // This could also be handled by default in CopyVexEvexCommon - // if 4u changed to 4|8. - if (p0 & 8u) - return Invalid(&ceInvalid, pbDst, pbSrc); - BYTE const p1 = pbSrc[2]; if ((p1 & 0x04) != 0x04) @@ -881,6 +906,10 @@ PBYTE CDetourDis::CopyEvex(REFCOPYENTRY, PBYTE pbDst, PBYTE pbSrc) m_bRaxOverride |= !!(p1 & 0x80); // w #endif + // P0 layout: R'(7) X(6) B3(5) R'4(4) B4(3) m(2) m(1) m(0) + // Bits [2:0] = map (1-7). Bit 3 = B4 (APX register extension, not a map bit). + // For FP16: bit 2 extends map (MAP5=101, MAP6=110). + // For APX: map=4 (100) uses bit 2 as part of mmm field. return CopyVexEvexCommon(p0 & 3u, pbDst + 4, pbSrc + 4, p1 & 3u, p0 & 4u); } @@ -918,6 +947,43 @@ pp is like VEX but only instructions with 0 are defined } } +PBYTE CDetourDis::CopyRex2(REFCOPYENTRY pEntry, PBYTE pbDst, PBYTE pbSrc) +// Intel APX REX2 prefix 0xD5 (64-bit mode only) +// Byte 0: D5 +// Byte 1: M(7) R4(6) X4(5) B4(4) W(3) R3(2) X3(1) B3(0) +// M: 0 = opcode from MAP0, 1 = opcode from MAP1 (no 0F escape needed) +// W: operand size override to 64-bit (same as REX.W) +{ + (void)pEntry; + + BYTE const payload = pbSrc[1]; + + if (payload & 0x08) { // W bit (bit 3) + m_bRaxOverride = TRUE; + } + + pbDst[0] = pbSrc[0]; + pbDst[1] = pbSrc[1]; + + PBYTE pbOut; + if (payload & 0x80) { // M bit (bit 7) - MAP1 + REFCOPYENTRY pEntry2 = &s_rceCopyTable0F[pbSrc[2]]; + pbOut = (this->*pEntry2->pfCopy)(pEntry2, pbDst + 2, pbSrc + 2); + } + else { // MAP0 + REFCOPYENTRY pEntry2 = &s_rceCopyTable[pbSrc[2]]; + pbOut = (this->*pEntry2->pfCopy)(pEntry2, pbDst + 2, pbSrc + 2); + } + + // JMPABS: REX2 with payload=0x00 (M=0, W=0, all ext bits 0) and opcode A1. + // This is an absolute 64-bit jump whose target is the 8-byte immediate. + if (payload == 0x00 && pbSrc[2] == 0xA1) { + *m_ppbTarget = *(UNALIGNED PBYTE*)&pbSrc[3]; + } + + return pbOut; +} + ////////////////////////////////////////////////////////////////////////////// // PBYTE CDetourDis::s_pbModuleBeg = NULL; @@ -1227,7 +1293,7 @@ const CDetourDis::COPYENTRY CDetourDis::s_rceCopyTable[] = /* C4 */ ENTRY_CopyVex3, // LES, VEX 3-byte opcodes. /* C5 */ ENTRY_CopyVex2, // LDS, VEX 2-byte opcodes. /* C6 */ ENTRY_CopyBytes2Mod1, // MOV - /* C7 */ ENTRY_CopyBytes2ModOperand, // MOV/0 XBEGIN/7 + /* C7 */ ENTRY_CopyC7, // MOV/0 XBEGIN/7 /* C8 */ ENTRY_CopyBytes4, // ENTER /* C9 */ ENTRY_CopyBytes1, // LEAVE /* CA */ ENTRY_CopyBytes3Dynamic, // RET @@ -1246,7 +1312,7 @@ const CDetourDis::COPYENTRY CDetourDis::s_rceCopyTable[] = /* D3 */ ENTRY_CopyBytes2Mod, // RCL/2, etc. #ifdef DETOURS_X64 /* D4 */ ENTRY_Invalid, // Invalid - /* D5 */ ENTRY_Invalid, // Invalid + /* D5 */ ENTRY_CopyRex2, // REX2 (Intel APX) #else /* D4 */ ENTRY_CopyBytes2, // AAM /* D5 */ ENTRY_CopyBytes2, // AAD diff --git a/libs/gamepad/gamepad.c b/libs/gamepad/gamepad.c index e7a325d22..369bbec03 100644 --- a/libs/gamepad/gamepad.c +++ b/libs/gamepad/gamepad.c @@ -27,7 +27,7 @@ # include # include # include -# include +# include #else # error "Unknown platform in gamepad.c" #endif @@ -73,6 +73,13 @@ static GAMEPAD_STATE STATE[4]; /* Note whether a gamepad is currently connected */ #define FLAG_CONNECTED (1<<0) #define FLAG_RUMBLE (1<<1) +#if defined(__linux__) +static int GAMEPAD_WATCH_FD = -1; +static GAMEPAD_BOOL GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; +#ifdef GAMEPAD_TESTING +static unsigned int GAMEPAD_TEST_DETECT_COUNT = 0; +#endif +#endif /* Prototypes for utility functions */ static void GamepadResetState (GAMEPAD_DEVICE gamepad); @@ -80,6 +87,11 @@ static void GamepadUpdateCommon (void); static void GamepadUpdateDevice (GAMEPAD_DEVICE gamepad); static void GamepadUpdateStick (GAMEPAD_AXIS* axis, float deadzone); static void GamepadUpdateTrigger (GAMEPAD_TRIGINFO* trig); +#if defined(__linux__) +static void GamepadPollDeviceChanges(void); +static void GamepadRequestDeviceScan(void); +static void GamepadInitDeviceWatch(void); +#endif /* Various values of PI */ #define PI_1_4 0.78539816339744f @@ -209,6 +221,9 @@ static void GamepadRemoveDevice(const char* devPath); static void GamepadDetect() { +#ifdef GAMEPAD_TESTING + ++GAMEPAD_TEST_DETECT_COUNT; +#endif DIR *folder; struct dirent *dent; @@ -237,6 +252,41 @@ static void GamepadDetect() } } +#if defined(__linux__) +static void GamepadRequestDeviceScan(void) +{ + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_TRUE; +} + +static void GamepadInitDeviceWatch(void) +{ + GAMEPAD_WATCH_FD = inotify_init(); + if (GAMEPAD_WATCH_FD < 0) { + return; + } + + fcntl(GAMEPAD_WATCH_FD, F_SETFL, O_NONBLOCK); + fcntl(GAMEPAD_WATCH_FD, F_SETFD, FD_CLOEXEC); + + if (inotify_add_watch(GAMEPAD_WATCH_FD, "/dev/input", + IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF) < 0) { + close(GAMEPAD_WATCH_FD); + GAMEPAD_WATCH_FD = -1; + } +} + +static void GamepadPollDeviceChanges(void) +{ + if (GAMEPAD_WATCH_FD < 0) { + return; + } + + char buffer[sizeof(struct inotify_event) * 16]; + while (read(GAMEPAD_WATCH_FD, buffer, sizeof(buffer)) > 0) { + GamepadRequestDeviceScan(); + } +} +#endif /* Helper to add a new device */ @@ -377,9 +427,6 @@ static void GamepadRemoveDevice(const char* devPath) { } void GamepadInit(void) { - struct udev_list_entry* devices; - struct udev_list_entry* item; - struct udev_enumerate* enu; int i; /* initialize connection state */ @@ -388,17 +435,22 @@ void GamepadInit(void) { STATE[i].fd = STATE[i].effect = -1; } +#if defined(__linux__) + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; + GamepadInitDeviceWatch(); +#endif + GamepadDetect(); } void GamepadUpdate(void) { - static unsigned long last = 0; - unsigned long cur = time(NULL); - - if (last + 2 < cur) { +#if defined(__linux__) + GamepadPollDeviceChanges(); + if (GAMEPAD_DEVICE_SCAN_PENDING) { + GAMEPAD_DEVICE_SCAN_PENDING = GAMEPAD_FALSE; GamepadDetect(); - last = cur; } +#endif GamepadUpdateCommon(); } @@ -544,8 +596,25 @@ void GamepadShutdown(void) { close(STATE[i].fd); } } + +#if defined(__linux__) + if (GAMEPAD_WATCH_FD != -1) { + close(GAMEPAD_WATCH_FD); + GAMEPAD_WATCH_FD = -1; + } +#endif } +#if defined(__linux__) && defined(GAMEPAD_TESTING) +unsigned int GamepadTestDetectCount(void) { + return GAMEPAD_TEST_DETECT_COUNT; +} + +void GamepadTestSignalDeviceChange(void) { + GamepadRequestDeviceScan(); +} +#endif + void GamepadSetRumble(GAMEPAD_DEVICE gamepad, float left, float right, unsigned int rumble_length_ms) { if (STATE[gamepad].fd != -1) { struct input_event play; diff --git a/libs/gamepad/gamepad.h b/libs/gamepad/gamepad.h index 0b101432b..16a862319 100644 --- a/libs/gamepad/gamepad.h +++ b/libs/gamepad/gamepad.h @@ -318,6 +318,11 @@ GAMEPAD_API GAMEPAD_STICKDIR GamepadStickDir(GAMEPAD_DEVICE device, GAMEPAD_STIC */ GAMEPAD_API GAMEPAD_BOOL GamepadStickDirTriggered(GAMEPAD_DEVICE device, GAMEPAD_STICK stick, GAMEPAD_STICKDIR dir); +#if defined(GAMEPAD_TESTING) && defined(__linux__) +GAMEPAD_API unsigned int GamepadTestDetectCount(void); +GAMEPAD_API void GamepadTestSignalDeviceChange(void); +#endif + #if defined(__cplusplus) } /* extern "C" */ #endif diff --git a/libs/imgui/SOURCE.txt b/libs/imgui/SOURCE.txt deleted file mode 100644 index f9c06b621..000000000 --- a/libs/imgui/SOURCE.txt +++ /dev/null @@ -1,29 +0,0 @@ -#### INFO - -https://github.com/ocornut/imgui - -VERSION: ???? - -#### LICENSE - -The MIT License (MIT) - -Copyright (c) 2014-2024 Omar Cornut - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libs/simpleini/ConvertUTF.c b/libs/simpleini/ConvertUTF.c index 9eaf933a6..de6bf13ee 100644 --- a/libs/simpleini/ConvertUTF.c +++ b/libs/simpleini/ConvertUTF.c @@ -1,8 +1,10 @@ /* + * https://web.archive.org/web/20090529064329/http://www.unicode.org:80/Public/PROGRAMS/CVTUTF/ + * * Copyright 2001-2004 Unicode, Inc. - * + * * Disclaimer - * + * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine @@ -10,9 +12,9 @@ * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. - * + * * Limitations on Rights to Redistribute This Code - * + * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form @@ -20,23 +22,23 @@ * remains attached. */ -/* --------------------------------------------------------------------- + /* --------------------------------------------------------------------- - Conversions between UTF32, UTF-16, and UTF-8. Source code file. - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Sept 2001: fixed const & error conditions per - mods suggested by S. Parent & A. Lillich. - June 2002: Tim Dodd added detection and handling of incomplete - source sequences, enhanced error detection, added casts - to eliminate compiler warnings. - July 2003: slight mods to back out aggressive FFFE detection. - Jan 2004: updated switches in from-UTF8 conversions. - Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. - See the header file "ConvertUTF.h" for complete documentation. + See the header file "ConvertUTF.h" for complete documentation. ------------------------------------------------------------------------- */ + ------------------------------------------------------------------------ */ #include "ConvertUTF.h" @@ -44,7 +46,7 @@ #include #endif -static const int halfShift = 10; /* used for shifting by 10 bits */ +static const int halfShift = 10; /* used for shifting by 10 bits */ static const UTF32 halfBase = 0x0010000UL; static const UTF32 halfMask = 0x3FFUL; @@ -58,108 +60,116 @@ static const UTF32 halfMask = 0x3FFUL; /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, +ConversionResult ConvertUTF32toUTF16( + const UTF32** sourceStart, const UTF32* sourceEnd, UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - if (target >= targetEnd) { - result = targetExhausted; break; - } - ch = *source++; - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_LEGAL_UTF32) { - if (flags == strictConversion) { - result = sourceIllegal; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - --source; /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = (UTF16)ch; /* normal case */ + } + } + else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, +ConversionResult ConvertUTF16toUTF32( + const UTF16** sourceStart, const UTF16* sourceEnd, UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF32* target = *targetStart; - UTF32 ch, ch2; - while (source < sourceEnd) { - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - if (target >= targetEnd) { - source = oldSource; /* Back up source pointer! */ - result = targetExhausted; break; + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } + else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } + else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; } - *target++ = ch; - } - *sourceStart = source; - *targetStart = target; + *sourceStart = source; + *targetStart = target; #ifdef CVTUTF_DEBUG -if (result == sourceIllegal) { - fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); - fflush(stderr); -} + if (result == sourceIllegal) { + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); + } #endif - return result; + return result; } /* --------------------------------------------------------------------- */ @@ -172,14 +182,14 @@ if (result == sourceIllegal) { * allowed in earlier algorithms. */ static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /* @@ -187,14 +197,14 @@ static const char trailingBytesForUTF8[256] = { * This table contains as many values as there might be trailing bytes * in a UTF-8 sequence. */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; /* * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed * into the first byte, depending on how many bytes follow. There are * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... etc.). Remember that sequences + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs * for *legal* UTF-8 will be 4 or fewer bytes total. */ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; @@ -209,74 +219,86 @@ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC * into an inline function. */ -/* --------------------------------------------------------------------- */ + /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, +ConversionResult ConvertUTF16toUTF8( + const UTF16** sourceStart, const UTF16* sourceEnd, UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - UTF32 ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - } + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } + else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } + else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { + bytesToWrite = 1; + } + else if (ch < (UTF32)0x800) { + bytesToWrite = 2; + } + else if (ch < (UTF32)0x10000) { + bytesToWrite = 3; + } + else if (ch < (UTF32)0x110000) { + bytesToWrite = 4; + } + else { + bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ @@ -292,29 +314,29 @@ ConversionResult ConvertUTF16toUTF8 ( * definition of UTF-8 goes up to 4-byte sequences. */ -static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xED: if (a > 0x9F) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } +static Boolean isLegalUTF8(const UTF8* source, int length) { + UTF8 a; + const UTF8* srcptr = source + length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; + case 2: if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - } - if (*source > 0xF4) return false; - return true; + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + if (*source > 0xF4) return false; + return true; } /* --------------------------------------------------------------------- */ @@ -323,217 +345,234 @@ static Boolean isLegalUTF8(const UTF8 *source, int length) { * Exported function to return whether a UTF-8 sequence is legal or not. * This is not used here; it's just exported. */ -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); +Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd) { + int length = trailingBytesForUTF8[*source] + 1; + if (source + length > sourceEnd) { + return false; + } + return isLegalUTF8(source, length); } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, +ConversionResult ConvertUTF8toUTF16( + const UTF8** sourceStart, const UTF8* sourceEnd, UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (!isLegalUTF8(source, extraBytesToRead + 1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + if (target >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead + 1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = (UTF16)ch; /* normal case */ + } + } + else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead + 1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, +ConversionResult ConvertUTF32toUTF8( + const UTF32** sourceStart, const UTF32* sourceEnd, UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - ch = *source++; - if (flags == strictConversion ) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* - * Figure out how many bytes the result will require. Turn any - * illegally large UTF32 things (> Plane 17) into replacement chars. - */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - result = sourceIllegal; - } - - target += bytesToWrite; - if (target > targetEnd) { - --source; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { + bytesToWrite = 1; + } + else if (ch < (UTF32)0x800) { + bytesToWrite = 2; + } + else if (ch < (UTF32)0x10000) { + bytesToWrite = 3; + } + else if (ch <= UNI_MAX_LEGAL_UTF32) { + bytesToWrite = 4; + } + else { + bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ -ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, +ConversionResult ConvertUTF8toUTF32( + const UTF8** sourceStart, const UTF8* sourceEnd, UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF32* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (!isLegalUTF8(source, extraBytesToRead + 1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up the source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_LEGAL_UTF32) { - /* - * UTF-16 surrogate values are illegal in UTF-32, and anything - * over Plane 17 (> 0x10FFFF) is illegal. - */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; + if (target >= targetEnd) { + source -= (extraBytesToRead + 1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead + 1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } + else { + *target++ = ch; + } + } + else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; } - } else { - *target++ = ch; - } - } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ - result = sourceIllegal; - *target++ = UNI_REPLACEMENT_CHAR; } - } - *sourceStart = source; - *targetStart = target; - return result; + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: { - int tmpBytesToRead = extraBytesToRead+1; - do { + int tmpBytesToRead = extraBytesToRead+1; + do { ch += *source++; --tmpBytesToRead; if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); + } while (tmpBytesToRead > 0); } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. - --------------------------------------------------------------------- */ + --------------------------------------------------------------------- */ \ No newline at end of file diff --git a/libs/simpleini/ConvertUTF.h b/libs/simpleini/ConvertUTF.h index 6ecc819cb..528c96398 100644 --- a/libs/simpleini/ConvertUTF.h +++ b/libs/simpleini/ConvertUTF.h @@ -1,8 +1,10 @@ /* + * https://web.archive.org/web/20090529064329/http://www.unicode.org:80/Public/PROGRAMS/CVTUTF/ + * * Copyright 2001-2004 Unicode, Inc. - * + * * Disclaimer - * + * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine @@ -10,9 +12,9 @@ * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. - * + * * Limitations on Rights to Redistribute This Code - * + * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form @@ -20,74 +22,74 @@ * remains attached. */ -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several functions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: , , - or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -/* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. ------------------------------------------------------------------------- */ - -typedef unsigned int UTF32; /* at least 32 bits */ + /* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + + ------------------------------------------------------------------------ */ + + /* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. + ------------------------------------------------------------------------ */ + +typedef unsigned long UTF32; /* at least 32 bits */ typedef unsigned short UTF16; /* at least 16 bits */ typedef unsigned char UTF8; /* typically 8 bits */ typedef unsigned char Boolean; /* 0 or 1 */ @@ -100,15 +102,15 @@ typedef unsigned char Boolean; /* 0 or 1 */ #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insufficient room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ } ConversionResult; typedef enum { - strictConversion = 0, - lenientConversion + strictConversion = 0, + lenientConversion } ConversionFlags; /* This is for C++ and does no harm in C */ @@ -116,34 +118,34 @@ typedef enum { extern "C" { #endif -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF8toUTF16( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF16toUTF8( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF8toUTF32( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); -ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF32toUTF8( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); + ConversionResult ConvertUTF16toUTF32( + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + + ConversionResult ConvertUTF32toUTF16( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + + Boolean isLegalUTF8Sequence(const UTF8* source, const UTF8* sourceEnd); #ifdef __cplusplus } #endif -/* --------------------------------------------------------------------- */ +/* --------------------------------------------------------------------- */ \ No newline at end of file diff --git a/libs/simpleini/SOURCE.txt b/libs/simpleini/SOURCE.txt index b0139b522..d643c72d3 100644 --- a/libs/simpleini/SOURCE.txt +++ b/libs/simpleini/SOURCE.txt @@ -2,13 +2,13 @@ https://github.com/brofield/simpleini -VERSION: https://github.com/brofield/simpleini/releases/tag/v4.22 +VERSION: https://github.com/brofield/simpleini/tree/87f8e1b5021140727e75804224f6b1c305791378 #### LICENSE The MIT License (MIT) -Copyright (c) 2006-2022 Brodie Thiesfield +Copyright (c) 2006-2024 Brodie Thiesfield Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/libs/simpleini/SimpleIni.h b/libs/simpleini/SimpleIni.h index 1f0c0ae38..240f393f4 100644 --- a/libs/simpleini/SimpleIni.h +++ b/libs/simpleini/SimpleIni.h @@ -5,7 +5,7 @@ File SimpleIni.h Author Brodie Thiesfield Source https://github.com/brofield/simpleini - Version 4.22 + Version 4.25 Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. @@ -53,7 +53,7 @@ -# If you will only be using straight utf8 files and access the data via the char interface, then you do not need any conversion library and could define SI_NO_CONVERSION. Note that no conversion also means no validation of the data. - If no converter is specified then the default converter is SI_CONVERT_GENERIC + If no converter is specified then the default converter is SI_NO_CONVERSION on Mac/Linux and SI_CONVERT_WIN32 on Windows. If you need widechar support on Mac/Linux then use either SI_CONVERT_GENERIC or SI_CONVERT_ICU. These are also supported on all platforms. @@ -161,6 +161,9 @@ @section notes NOTES + - The maximum supported file size is 1 GiB (SI_MAX_FILE_SIZE). Files larger + than this will be rejected with SI_FILE error to prevent excessive memory + allocation and potential denial of service attacks. - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU. - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked. @@ -261,6 +264,10 @@ constexpr int SI_FAIL = -1; //!< Generic failure constexpr int SI_NOMEM = -2; //!< Out of memory error constexpr int SI_FILE = -3; //!< File error (see errno for detail error) +//! Maximum supported file size (1 GiB). Files larger than this will be rejected +//! to prevent excessive memory allocation and potential denial of service. +constexpr size_t SI_MAX_FILE_SIZE = 1024ULL * 1024ULL * 1024ULL; + #define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" #ifdef _WIN32 @@ -357,7 +364,7 @@ class CSimpleIniTempl if (lhs.nOrder != rhs.nOrder) { return lhs.nOrder < rhs.nOrder; } - return KeyOrder()(lhs.pItem, rhs.pItem); + return KeyOrder()(lhs, rhs); } }; }; @@ -1457,9 +1464,14 @@ CSimpleIniTempl::LoadFile( if (lSize == 0) { return SI_OK; } - + + // check file size is within supported limits (SI_MAX_FILE_SIZE) + if (static_cast(lSize) > SI_MAX_FILE_SIZE) { + return SI_FILE; + } + // allocate and ensure NULL terminated - char * pData = new(std::nothrow) char[lSize+static_cast(1)]; + char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; if (!pData) { return SI_NOMEM; } @@ -1511,13 +1523,18 @@ CSimpleIniTempl::LoadData( return SI_FAIL; } + // check converted data size is within supported limits (SI_MAX_FILE_SIZE) + if (uLen >= (SI_MAX_FILE_SIZE / sizeof(SI_CHAR))) { + return SI_FILE; + } + // allocate memory for the data, ensure that there is a NULL // terminator wherever the converted data ends - SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; + SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen + 1]; if (!pData) { return SI_NOMEM; } - memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); + memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1)); // convert the data if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { @@ -1795,6 +1812,7 @@ CSimpleIniTempl::IsMultiLineData( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return true; @@ -1802,8 +1820,8 @@ CSimpleIniTempl::IsMultiLineData( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -1816,7 +1834,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( const SI_CHAR* a_pData ) const { - // data needs quoting if it starts or ends with whitespace + // data needs quoting if it starts or ends with whitespace // and doesn't have embedded newlines // empty string @@ -1830,6 +1848,7 @@ CSimpleIniTempl::IsSingleLineQuotedValue( } // embedded newlines + const SI_CHAR * pStart = a_pData; while (*a_pData) { if (IsNewLineChar(*a_pData)) { return false; @@ -1837,8 +1856,8 @@ CSimpleIniTempl::IsSingleLineQuotedValue( ++a_pData; } - // check for suffix - if (IsSpace(*--a_pData)) { + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { return true; } @@ -2083,7 +2102,8 @@ CSimpleIniTempl::AddEntry( if (pComment) { DeleteString(a_pComment); a_pComment = pComment; - CopyString(a_pComment); + rc = CopyString(a_pComment); + if (rc < 0) return rc; } Delete(a_pSection, a_pKey); iKey = keyval.end(); @@ -2252,12 +2272,13 @@ CSimpleIniTempl::GetDoubleValue( return a_nDefault; } - char * pszSuffix = NULL; + char * pszSuffix = szValue; double nValue = strtod(szValue, &pszSuffix); // any invalid strings will return the default value - if (!pszSuffix || *pszSuffix) { - return a_nDefault; + // check if no conversion was performed or if there are trailing characters + if (pszSuffix == szValue || *pszSuffix) { + return a_nDefault; } return nValue; @@ -2416,7 +2437,7 @@ CSimpleIniTempl::GetSectionSize( int nCount = 0; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { ++nCount; pLastKey = iKeyVal->first.pItem; @@ -2448,7 +2469,7 @@ CSimpleIniTempl::GetAllSections( { a_names.clear(); typename TSection::const_iterator i = m_data.begin(); - for (int n = 0; i != m_data.end(); ++i, ++n ) { + for (; i != m_data.end(); ++i) { a_names.push_back(i->first); } } @@ -2474,7 +2495,7 @@ CSimpleIniTempl::GetAllKeys( const TKeyVal & section = iSection->second; const SI_CHAR * pLastKey = NULL; typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { + for (; iKeyVal != section.end(); ++iKeyVal) { if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { a_names.push_back(iKeyVal->first); pLastKey = iKeyVal->first.pItem; @@ -2812,7 +2833,7 @@ CSimpleIniTempl::DeleteString( // strings may exist either inside the data block, or they will be // individually allocated and stored in m_strings. We only physically // delete those stored in m_strings. - if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { typename TNamesDepend::iterator i = m_strings.begin(); for (;i != m_strings.end(); ++i) { if (a_pString == i->pItem) { @@ -2834,17 +2855,19 @@ CSimpleIniTempl::DeleteString( // // SI_NO_CONVERSION Do not make the "W" wide character version of the // library available. Only CSimpleIniA etc is defined. +// Default on Linux/MacOS/etc. +// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. +// Default on Windows. // SI_CONVERT_GENERIC Use the Unicode reference conversion library in // the accompanying files ConvertUTF.h/c // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires // ICU headers on include path and icuuc.lib -// SI_CONVERT_WIN32 Use the Win32 API functions for conversion. #if !defined(SI_NO_CONVERSION) && !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) # ifdef _WIN32 # define SI_CONVERT_WIN32 # else -# define SI_CONVERT_GENERIC +# define SI_NO_CONVERSION # endif #endif @@ -3063,14 +3086,18 @@ class SI_ConvertW { return a_uInputDataLen; } -#if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux)) + // get the required buffer size +#if defined(_MSC_VER) + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, NULL, 0, a_pInputData, a_uInputDataLen); + return (e == 0) ? uBufSiz : (size_t) -1; +#elif !defined(SI_NO_MBSTOWCS_NULL) + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); +#else // fall back processing for platforms that don't support a NULL dest to mbstowcs // worst case scenario is 1:1, this will be a sufficient buffer size (void)a_pInputData; return a_uInputDataLen; -#else - // get the actual required buffer size - return mbstowcs(NULL, a_pInputData, a_uInputDataLen); #endif } @@ -3119,9 +3146,18 @@ class SI_ConvertW { } // convert to wchar_t +#if defined(_MSC_VER) + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, + a_pOutputData, a_uOutputDataSize, + a_pInputData, a_uInputDataLen); + (void)uBufSiz; + return (e == 0); +#else size_t retval = mbstowcs(a_pOutputData, a_pInputData, a_uOutputDataSize); return retval != (size_t)(-1); +#endif } /** Calculate the number of char required by the storage format of this @@ -3626,5 +3662,4 @@ typedef CSimpleIniTempl= 8 ) // is the overlap more than 8 away? + if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away { char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); - STBIR_NO_UNROLL_LOOP_START - do + + if ( ( ( ((ptrdiff_t)dest)|((ptrdiff_t)src) ) & 7 ) == 0 ) // is it 8byte aligned? + { + STBIR_NO_UNROLL_LOOP_START + do + { + STBIR_NO_UNROLL(sd); + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + sd += 8; + } while ( sd < s_end8 ); + } + else { - STBIR_NO_UNROLL(sd); - *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; - sd += 8; - } while ( sd < s_end8 ); + STBIR_NO_UNROLL_LOOP_START + do + { + int a,b; + STBIR_NO_UNROLL(sd); + a = ((int*)sd)[0]; + b = ((int*)sd)[1]; + ((int*)( sd + ofs_to_dest ))[0] = a; + ((int*)( sd + ofs_to_dest ))[1] = b; + sd += 8; + } while ( sd < s_end8 ); + } if ( sd == s_end ) return; @@ -3233,7 +3263,7 @@ static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline newspan->n1 = ( max_left - min_left ) - left_margin; scanline_extents->edge_sizes[0] = 0; // don't need to copy the left margin, since we are directly decoding into the margin } - // if we can't merge the min_left range, add it as a second range + // if we can't merge the min_right range, add it as a second range else if ( ( right_margin ) && ( min_right != 0x7fffffff ) ) { @@ -3349,23 +3379,29 @@ static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_ static void stbir__insert_coeff( stbir__contributors * contribs, float * coeffs, int new_pixel, float new_coeff, int max_width ) { - if ( new_pixel <= contribs->n1 ) // before the end + if ( contribs->n1 < contribs->n0 ) // this first clause should never happen, but handle in case + { + contribs->n0 = contribs->n1 = new_pixel; + coeffs[0] = new_coeff; + } + else if ( new_pixel <= contribs->n1 ) // before the end { if ( new_pixel < contribs->n0 ) // before the front? { if ( ( contribs->n1 - new_pixel + 1 ) <= max_width ) { int j, o = contribs->n0 - new_pixel; - for ( j = contribs->n1 - contribs->n0 ; j <= 0 ; j-- ) + for ( j = contribs->n1 - contribs->n0 ; j >= 0 ; j-- ) coeffs[ j + o ] = coeffs[ j ]; - for ( j = 1 ; j < o ; j-- ) - coeffs[ j ] = coeffs[ 0 ]; + for ( j = 1 ; j < o ; j++ ) + coeffs[ j ] = 0; coeffs[ 0 ] = new_coeff; contribs->n0 = new_pixel; } } else { + // add new weight to existing coeff if already there coeffs[ new_pixel - contribs->n0 ] += new_coeff; } } @@ -3812,7 +3848,7 @@ static int stbir__pack_coefficients( int num_contributors, stbir__contributors* } } - // some horizontal routines read one float off the end (which is then masked off), so put in a sentinal so we don't read an snan or denormal + // some horizontal routines read one float off the end (which is then masked off), so put in a sentinel so we don't read an snan or denormal coefficents[ widest * num_contributors ] = 8888.0f; // the minimum we might read for unrolled filters widths is 12. So, we need to @@ -4610,7 +4646,7 @@ static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float if ( stbir_info->in_pixels_cb ) { // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself - input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ) + sizeof(float)*STBIR_INPUT_CALLBACK_PADDING, input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); + input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ) + ( ( stbir_info->input_type != STBIR_TYPE_FLOAT ) ? ( sizeof(float)*STBIR_INPUT_CALLBACK_PADDING ) : 0 ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); } STBIR_PROFILE_START( decode ); @@ -6708,7 +6744,7 @@ static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contr } } -static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height ) +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height, int is_gather, stbir__contributors * contribs ) { int i, cur; int left = output_height; @@ -6717,9 +6753,58 @@ static void stbir__get_split_info( stbir__per_split_info* split_info, int splits for( i = 0 ; i < splits ; i++ ) { int each; + split_info[i].start_output_y = cur; each = left / ( splits - i ); split_info[i].end_output_y = cur + each; + + // ok, when we are gathering, we need to make sure we are starting on a y offset that doesn't have + // a "special" set of coefficients. Basically, with exactly the right filter at exactly the right + // resize at exactly the right phase, some of the coefficents can be zero. When they are zero, we + // don't process them at all. But this leads to a tricky thing with the thread splits, where we + // might have a set of two coeffs like this for example: (4,4) and (3,6). The 4,4 means there was + // just one single coeff because things worked out perfectly (normally, they all have 4 coeffs + // like the range 3,6. The problem is that if we start right on the (4,4) on a brand new thread, + // then when we get to (3,6), we don't have the "3" sample in memory (because we didn't load + // it on the initial (4,4) range because it didn't have a 3 (we only add new samples that are + // larger than our existing samples - it's just how the eviction works). So, our solution here + // is pretty simple, if we start right on a range that has samples that start earlier, then we + // simply bump up our previous thread split range to include it, and then start this threads + // range with the smaller sample. It just moves one scanline from one thread split to another, + // so that we end with the unusual one, instead of start with it. To do this, we check 2-4 + // sample at each thread split start and then occassionally move them. + + if ( ( is_gather ) && ( i ) ) + { + stbir__contributors * small_contribs; + int j, smallest, stop, start_n0; + stbir__contributors * split_contribs = contribs + cur; + + // scan for a max of 3x the filter width or until the next thread split + stop = vertical_pixel_margin * 3; + if ( each < stop ) + stop = each; + + // loops a few times before early out + smallest = 0; + small_contribs = split_contribs; + start_n0 = small_contribs->n0; + for( j = 1 ; j <= stop ; j++ ) + { + ++split_contribs; + if ( split_contribs->n0 > start_n0 ) + break; + if ( split_contribs->n0 < small_contribs->n0 ) + { + small_contribs = split_contribs; + smallest = j; + } + } + + split_info[i-1].end_output_y += smallest; + split_info[i].start_output_y += smallest; + } + cur += each; left -= each; @@ -6815,45 +6900,45 @@ static float stbir__compute_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4]= // 5 = { 0.56250f, 0.59375f, 0.00000f, 0.96875f }, { 1.00000f, 0.06250f, 0.00000f, 1.00000f }, { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.03125f }, }, { { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, { 0.09375f, 0.93750f, 0.00000f, 0.78125f }, { 0.87500f, 0.21875f, 0.00000f, 0.96875f }, { 0.09375f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.53125f }, }, { { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, { 0.06250f, 0.96875f, 0.00000f, 0.53125f }, { 0.87500f, 0.18750f, 0.00000f, 0.93750f }, { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.00000f, 0.56250f }, }, { { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, { 0.06250f, 0.84375f, 0.00000f, 0.87500f }, { 1.00000f, 0.50000f, 0.50000f, 0.96875f }, { 1.00000f, 0.09375f, 0.31250f, 0.50000f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, { 1.00000f, 0.03125f, 0.03125f, 0.53125f }, - { 0.18750f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.03125f, 0.18750f }, }, { { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, { 0.06250f, 0.81250f, 0.06250f, 0.59375f }, { 0.75000f, 0.43750f, 0.12500f, 0.96875f }, { 0.87500f, 0.06250f, 0.18750f, 0.43750f }, - { 1.00000f, 1.00000f, 1.00000f, 1.00000f }, + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, { 0.15625f, 0.12500f, 1.00000f, 1.00000f }, - { 0.06250f, 0.12500f, 0.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, { 0.00000f, 1.00000f, 0.03125f, 0.34375f }, } }; @@ -6907,16 +6992,16 @@ static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLA // categorize the resize into buckets if ( ( vertical_output_size <= 4 ) || ( horizontal_output_size <= 4 ) ) v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; + else if ( ( !is_gather ) && ( ( vertical_output_size <= 16 ) || ( horizontal_output_size <= 16 ) ) ) + v_classification = 4; else if ( vertical_scale <= 1.0f ) v_classification = ( is_gather ) ? 1 : 0; else if ( vertical_scale <= 2.0f) v_classification = 2; else if ( vertical_scale <= 3.0f) v_classification = 3; - else if ( vertical_scale <= 4.0f) - v_classification = 5; - else - v_classification = 6; + else + v_classification = 5; // everything bigger than 3x // use the right weights weights = weights_table[ v_classification ]; @@ -7057,7 +7142,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample #ifdef STBIR__SEPARATE_ALLOCATIONS #define STBIR__NEXT_PTR( ptr, size, ntype ) if ( alloced ) { void * p = STBIR_MALLOC( size, user_data); if ( p == 0 ) { stbir__free_internal_mem( info ); return 0; } (ptr) = (ntype*)p; } #else - #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = ((char*)advance_mem) + (size); + #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = (char*)(((size_t)advance_mem) + (size)); #endif STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); @@ -7266,7 +7351,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample } // setup the vertical split ranges - stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size ); + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size, info->vertical.is_gather, info->vertical.contributors ); // now we know precisely how many entries we need info->ring_buffer_num_entries = info->vertical.extent_info.widest; @@ -7540,7 +7625,7 @@ static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 numer_estimate = temp; } - // we didn't fine anything good enough for float, use a full range estimate + // we didn't find anything good enough for float, use a full range estimate if ( limit_denom ) { numer_estimate= (stbir_uint64)( f * (double)limit + 0.5 ); @@ -7830,7 +7915,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); - stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->vertical_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) { @@ -7861,7 +7946,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) return 0; } -void stbir_free_samplers( STBIR_RESIZE * resize ) +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ) { if ( resize->samplers ) { @@ -7955,93 +8040,99 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start return stbir__perform_resize( resize->samplers, split_start, split_count ); } -static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * output_pixels, int type_size, int output_w, int output_h, int output_stride_in_bytes, stbir_internal_pixel_layout pixel_layout ) + +static void * stbir_quick_resize_helper( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, stbir_edge edge, stbir_filter filter ) { - size_t size; - int pitch; - void * ptr; + STBIR_RESIZE resize; + int scanline_output_in_bytes; + int positive_output_stride_in_bytes; + void * start_ptr; + void * free_ptr; - pitch = output_w * type_size * stbir__pixel_channels[ pixel_layout ]; - if ( pitch == 0 ) + scanline_output_in_bytes = output_w * stbir__type_size[ data_type ] * stbir__pixel_channels[ stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ]; + if ( scanline_output_in_bytes == 0 ) return 0; + // if zero stride, use scanline output if ( output_stride_in_bytes == 0 ) - output_stride_in_bytes = pitch; + output_stride_in_bytes = scanline_output_in_bytes; - if ( output_stride_in_bytes < pitch ) - return 0; + // abs value for inverted images (negative pitches) + positive_output_stride_in_bytes = output_stride_in_bytes; + if ( positive_output_stride_in_bytes < 0 ) + positive_output_stride_in_bytes = -positive_output_stride_in_bytes; - size = (size_t)output_stride_in_bytes * (size_t)output_h; - if ( size == 0 ) + // is the requested stride smaller than the scanline output? if so, just fail + if ( positive_output_stride_in_bytes < scanline_output_in_bytes ) return 0; - *ret_ptr = 0; - *ret_pitch = output_stride_in_bytes; + start_ptr = output_pixels; + free_ptr = 0; // no free pointer, since they passed buffer to use + // did they pass a zero for the dest? if so, allocate the buffer if ( output_pixels == 0 ) { - ptr = STBIR_MALLOC( size, 0 ); - if ( ptr == 0 ) + size_t size; + char * ptr; + + size = (size_t)positive_output_stride_in_bytes * (size_t)output_h; + if ( size == 0 ) return 0; - *ret_ptr = ptr; - *ret_pitch = pitch; - } - - return 1; -} - + ptr = (char*) STBIR_MALLOC( size, 0 ); + if ( ptr == 0 ) + return 0; -STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout ) -{ - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; + free_ptr = ptr; - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; + // point at the last scanline, if they requested a flipped image + if ( output_stride_in_bytes < 0 ) + start_ptr = ptr + ( (size_t)positive_output_stride_in_bytes * (size_t)( output_h - 1 ) ); + else + start_ptr = ptr; + } + // ok, now do the resize stbir_resize_init( &resize, input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8 ); + start_ptr, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type ); + + resize.horizontal_edge = edge; + resize.vertical_edge = edge; + resize.horizontal_filter = filter; + resize.vertical_filter = filter; if ( !stbir_resize_extended( &resize ) ) { - if ( optr ) - STBIR_FREE( optr, 0 ); + if ( free_ptr ) + STBIR_FREE( free_ptr, 0 ); return 0; } - return (optr) ? optr : output_pixels; + return (free_ptr) ? free_ptr : start_ptr; +} + + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, stbir_pixel_layout pixel_layout ) { - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8_SRGB ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8_SRGB, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } @@ -8049,66 +8140,27 @@ STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int inpu float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, stbir_pixel_layout pixel_layout ) { - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_FLOAT ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (float *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_FLOAT, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); } STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout, stbir_datatype data_type, - stbir_edge edge, stbir_filter filter ) + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) { - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, - pixel_layout, data_type ); - - resize.horizontal_edge = edge; - resize.vertical_edge = edge; - resize.horizontal_filter = filter; - resize.vertical_filter = filter; - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; + return (void *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type, edge, filter ); } #ifdef STBIR_PROFILE STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) { - static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient piovot" } ; + static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient pivot" } ; stbir__info* samp = resize->samplers; int i; @@ -8159,7 +8211,7 @@ STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * info, STBIR_ info->clocks[i] = sum; } - info->total_clocks = split_info->profile.named.total; + info->total_clocks = split_info->profile.named.total; info->descriptions = descriptions; info->count = STBIR__ARRAY_SIZE( descriptions ); } @@ -8707,7 +8759,7 @@ static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int { \ stbir__simdi temp; \ stbir__simdi_32shr( temp, stbir_simdi_castf( f ), 12 ) ; \ - stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mastissa_mask) ); \ + stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mantissa_mask) ); \ stbir__simdi_or( temp, temp, STBIR__CONSTI(STBIR_topscale) ); \ stbir__simdi_16madd( i, i, temp ); \ stbir__simdi_32shr( i, i, 16 ); \ @@ -8948,7 +9000,7 @@ static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * decode -= 4; if( decode < decode_end ) { - decode[0] = stbir__srgb_uchar_to_linear_float[ stbir__decode_order0 ]; + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; } return decode_end; @@ -9684,7 +9736,7 @@ static float * STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, i static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int width_times_channels, float const * encode ) { - #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LO_CLAMP) && !defined(stbir__decode_swizzle) + #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LOW_CLAMP) && !defined(stbir__decode_swizzle) if ( (void*)outputp != (void*) encode ) STBIR_MEMCPY( outputp, encode, width_times_channels * sizeof( float ) ); @@ -9739,7 +9791,7 @@ static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); encode += stbir__simdfX_float_count * 2; output += stbir__simdfX_float_count * 2; - if ( output < end_output ) + if ( output <= end_output ) continue; if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) break; diff --git a/libs/utfcpp/SOURCE.txt b/libs/utfcpp/SOURCE.txt index cca45d206..d2b411964 100644 --- a/libs/utfcpp/SOURCE.txt +++ b/libs/utfcpp/SOURCE.txt @@ -2,7 +2,7 @@ https://github.com/nemtrif/utfcpp -VERSION: https://github.com/nemtrif/utfcpp/releases/tag/v4.0.6 +VERSION: https://github.com/nemtrif/utfcpp/releases/tag/v4.1.1 #### LICENSE diff --git a/libs/utfcpp/utf8/checked.h b/libs/utfcpp/utf8/checked.h index 98949f8bc..96ceb4d50 100644 --- a/libs/utfcpp/utf8/checked.h +++ b/libs/utfcpp/utf8/checked.h @@ -129,7 +129,7 @@ namespace utf8 template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); return utf8::replace_invalid(start, end, out, replacement_marker); } @@ -227,11 +227,11 @@ namespace utf8 octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { - utfchar32_t cp = utf8::internal::mask16(*start++); + utfchar32_t cp = static_cast(utf8::internal::mask16(*start++)); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { if (start != end) { - const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); + const utfchar32_t trail_surrogate = static_cast(utf8::internal::mask16(*start++)); if (utf8::internal::is_trail_surrogate(trail_surrogate)) cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; else diff --git a/libs/utfcpp/utf8/core.h b/libs/utfcpp/utf8/core.h index 627133c61..3ace403e0 100644 --- a/libs/utfcpp/utf8/core.h +++ b/libs/utfcpp/utf8/core.h @@ -43,9 +43,12 @@ DEALINGS IN THE SOFTWARE. #if UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #define UTF_CPP_OVERRIDE override #define UTF_CPP_NOEXCEPT noexcept + #define UTF_CPP_STATIC_ASSERT(condition) static_assert(condition, "UTFCPP static assert"); #else // C++ 98/03 #define UTF_CPP_OVERRIDE #define UTF_CPP_NOEXCEPT throw() + // Not worth simulating static_assert: + #define UTF_CPP_STATIC_ASSERT(condition) (void)(condition); #endif // C++ 11 or later @@ -87,6 +90,7 @@ namespace internal { return static_cast(0xff & oc); } + template inline utfchar16_t mask16(u16_type oc) { @@ -101,17 +105,17 @@ namespace internal inline bool is_lead_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(LEAD_SURROGATE_MAX)); } inline bool is_trail_surrogate(utfchar32_t cp) { - return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(TRAIL_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_surrogate(utfchar32_t cp) { - return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + return (cp >= static_cast(LEAD_SURROGATE_MIN) && cp <= static_cast(TRAIL_SURROGATE_MAX)); } inline bool is_code_point_valid(utfchar32_t cp) @@ -143,15 +147,15 @@ namespace internal inline bool is_overlong_sequence(utfchar32_t cp, int length) { if (cp < 0x80) { - if (length != 1) + if (length != 1) return true; } else if (cp < 0x800) { - if (length != 2) + if (length != 2) return true; } else if (cp < 0x10000) { - if (length != 3) + if (length != 3) return true; } return false; @@ -181,7 +185,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); return UTF8_OK; } @@ -189,10 +193,10 @@ namespace internal template utf_error get_sequence_2(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { - if (it == end) + if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -206,8 +210,8 @@ namespace internal { if (it == end) return NOT_ENOUGH_ROOM; - - code_point = utf8::internal::mask8(*it); + + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -226,7 +230,7 @@ namespace internal if (it == end) return NOT_ENOUGH_ROOM; - code_point = utf8::internal::mask8(*it); + code_point = static_cast(utf8::internal::mask8(*it)); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) @@ -290,7 +294,7 @@ namespace internal else err = OVERLONG_SEQUENCE; } - else + else err = INVALID_CODE_POINT; } @@ -301,13 +305,118 @@ namespace internal template inline utf_error validate_next(octet_iterator& it, octet_iterator end) { - utfchar32_t ignored; - return utf8::internal::validate_next(it, end, ignored); - } + if (it == end) + return NOT_ENOUGH_ROOM; + + octet_iterator original_it = it; + const utfchar8_t lead = utf8::internal::mask8(*it); + + if (lead < 0x80) { + ++it; + return UTF8_OK; + } else if ((lead & 0xE0) == 0xC0) { + // two-byte sequence + if (lead == 0xC0 || lead == 0xC1) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + ++it; + return UTF8_OK; + } else if ((lead & 0xF0) == 0xE0) { + // three-byte sequence + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail2 = utf8::internal::mask8(*it); + if ((trail2 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (lead == 0xE0 && trail1 < 0xA0) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (lead == 0xED && trail1 > 0x9F) { + it = original_it; + return INVALID_CODE_POINT; + } + ++it; + return UTF8_OK; + } else if ((lead & 0xF8) == 0xF0) { + // four-byte sequence + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail1 = utf8::internal::mask8(*it); + if ((trail1 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail2 = utf8::internal::mask8(*it); + if ((trail2 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (++it == end) { + it = original_it; + return NOT_ENOUGH_ROOM; + } + const utfchar8_t trail3 = utf8::internal::mask8(*it); + if ((trail3 & 0xC0) != 0x80) { + it = original_it; + return INCOMPLETE_SEQUENCE; + } + if (lead == 0xF0 && trail1 < 0x90) { + it = original_it; + return OVERLONG_SEQUENCE; + } + if (lead == 0xF4 && trail1 > 0x8F) { + it = original_it; + return INVALID_CODE_POINT; + } + if (lead >= 0xF5) { + it = original_it; + return INVALID_CODE_POINT; + } + ++it; + return UTF8_OK; + } else { + it = original_it; + return INVALID_LEAD; + } + } template utf_error validate_next16(word_iterator& it, word_iterator end, utfchar32_t& code_point) { + // Make sure the iterator dereferences a large enough type + typedef typename std::iterator_traits::value_type word_type; + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); + // Check the edge case: if (it == end) return NOT_ENOUGH_ROOM; // Save the original value of it so we can go back in case of failure @@ -326,14 +435,14 @@ namespace internal err = NOT_ENOUGH_ROOM; else if (is_lead_surrogate(first_word)) { const utfchar16_t second_word = *it++; - if (is_trail_surrogate(second_word)) { - code_point = static_cast(first_word << 10) + second_word + SURROGATE_OFFSET; + if (is_trail_surrogate(static_cast(second_word))) { + code_point = static_cast(first_word << 10) + static_cast(second_word) + SURROGATE_OFFSET; return UTF8_OK; - } else - err = INCOMPLETE_SEQUENCE; - + } else + err = INCOMPLETE_SEQUENCE; + } else { - err = INVALID_LEAD; + err = INVALID_LEAD; } } // error branch @@ -365,7 +474,7 @@ namespace internal } return result; } - + // One of the following overloads will be invoked from the API calls // A simple (but dangerous) case: the caller appends byte(s) to a char array @@ -395,6 +504,7 @@ namespace internal // the word_type. template word_iterator append16(utfchar32_t cp, word_iterator result) { + UTF_CPP_STATIC_ASSERT(sizeof(word_type) >= sizeof(utfchar16_t)); if (is_in_bmp(cp)) *(result++) = static_cast(cp); else { @@ -444,7 +554,7 @@ namespace internal inline const char* find_invalid(const char* str) { const char* end = str + std::strlen(str); - return find_invalid(str, end); + return find_invalid(str, end); } inline std::size_t find_invalid(const std::string& s) @@ -484,7 +594,7 @@ namespace internal inline bool starts_with_bom(const std::string& s) { return starts_with_bom(s.begin(), s.end()); - } + } } // namespace utf8 #endif // header guard diff --git a/libs/utfcpp/utf8/cpp17.h b/libs/utfcpp/utf8/cpp17.h index 6e2fcc238..075873003 100644 --- a/libs/utfcpp/utf8/cpp17.h +++ b/libs/utfcpp/utf8/cpp17.h @@ -89,7 +89,7 @@ namespace utf8 { return starts_with_bom(s.begin(), s.end()); } - + } // namespace utf8 #endif // header guard diff --git a/libs/utfcpp/utf8/unchecked.h b/libs/utfcpp/utf8/unchecked.h index bf2917893..173d0302e 100644 --- a/libs/utfcpp/utf8/unchecked.h +++ b/libs/utfcpp/utf8/unchecked.h @@ -82,7 +82,7 @@ namespace utf8 template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { - static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); + static const utfchar32_t replacement_marker = static_cast(utf8::internal::mask16(0xfffd)); return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); } @@ -108,22 +108,22 @@ namespace utf8 case 1: break; case 2: - it++; + ++it; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); break; case 3: - ++it; + ++it; cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); ++it; cp = static_cast(cp + ((*it) & 0x3f)); break; case 4: ++it; - cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); ++it; cp = static_cast(cp + ((utf8::internal::mask8(*it) << 6) & 0xfff)); ++it; - cp = static_cast(cp + ((*it) & 0x3f)); + cp = static_cast(cp + ((*it) & 0x3f)); break; } ++it; @@ -173,7 +173,7 @@ namespace utf8 distance(octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; - for (dist = 0; first < last; ++dist) + for (dist = 0; first < last; ++dist) utf8::unchecked::next(first); return dist; } @@ -247,15 +247,15 @@ namespace utf8 octet_iterator temp = it; return utf8::unchecked::next(temp); } - bool operator == (const iterator& rhs) const - { + bool operator == (const iterator& rhs) const + { return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } - iterator& operator ++ () + iterator& operator ++ () { ::std::advance(it, utf8::internal::sequence_length(it)); return *this; @@ -265,7 +265,7 @@ namespace utf8 iterator temp = *this; ::std::advance(it, utf8::internal::sequence_length(it)); return temp; - } + } iterator& operator -- () { utf8::unchecked::prior(it); @@ -280,8 +280,7 @@ namespace utf8 }; // class iterator } // namespace utf8::unchecked -} // namespace utf8 - +} // namespace utf8 #endif // header guard diff --git a/overlay_experimental/overlay/steam_overlay.h b/overlay_experimental/overlay/steam_overlay.h index 7fcaca8a5..af79b1a07 100644 --- a/overlay_experimental/overlay/steam_overlay.h +++ b/overlay_experimental/overlay/steam_overlay.h @@ -4,6 +4,7 @@ #include "dll/base.h" #include #include +#include #ifdef EMU_OVERLAY @@ -92,6 +93,14 @@ struct Notification std::optional ach{}; }; +// Lightweight archive entry -- no pointers, no GPU resources, safe to store long-term +struct NotificationHistoryEntry +{ + std::chrono::milliseconds timestamp{}; + uint8 type{}; + std::string message{}; +}; + // notification coordinates { x, y } struct NotificationsCoords { @@ -145,9 +154,25 @@ class Steam_Overlay // Callback infos std::queue has_friend_action{}; std::vector notifications{}; + static constexpr size_t MAX_NOTIFICATION_HISTORY = 50; + std::deque notification_history{}; + bool show_notification_history = false; + // Cache for pre-formatted history lines — avoids rebuilding every frame + std::vector notification_history_cache{}; + bool notification_history_cache_dirty = false; // used when the button "Invite all" is clicked std::atomic invite_all_friends_clicked = false; + // Rate-limiting queue for achievement notifications + struct ScheduledAchievement { + Overlay_Achievement ach; + bool for_progress; + std::chrono::milliseconds trigger_time; // when the achievement was triggered + std::chrono::milliseconds scheduled_show_time; // when the notification should be shown + }; + std::deque achievement_queue{}; + std::chrono::milliseconds last_scheduled_show_time{}; // tracks the last scheduled show time for spacing + bool overlay_state_changed = false; std::atomic i_have_lobby = false; @@ -167,10 +192,15 @@ class Steam_Overlay common_helpers::KillableWorker renderer_hook_init_thread{}; int renderer_hook_timeout_ctr{}; - // font stuff + std::vector toggle_keys{}; + + // font stuff - now supporting independent font sizes ImFontAtlas fonts_atlas{}; ImFont *font_default{}; ImFont *font_notif{}; + ImFont *font_fps{}; // separate font for FPS display + ImFont *font_ach_title{}; // separate font for achievement title + ImFont *font_ach_desc{}; // separate font for achievement description ImFontConfig font_cfg{}; ImFontGlyphRangesBuilder font_builder{}; ImVector ranges{}; @@ -188,6 +218,7 @@ class Steam_Overlay Steam_Overlay& operator=(Steam_Overlay const&) = delete; Steam_Overlay& operator=(Steam_Overlay&&) = delete; + void parse_key_combo(); bool submit_notification( notification_type type, const std::string &msg, @@ -284,6 +315,9 @@ class Steam_Overlay void FriendDisconnect(Friend _friend); void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress); + + // Rate-limiting queue functions + void process_achievement_queue(); }; #else // EMU_OVERLAY @@ -318,6 +352,7 @@ class Steam_Overlay void FriendDisconnect(Friend _friend) {} void AddAchievementNotification(const std::string &ach_name, nlohmann::json const& ach, bool for_progress) {} + void process_achievement_queue() {} }; #endif // EMU_OVERLAY diff --git a/overlay_experimental/overlay/steam_overlay_translations.h b/overlay_experimental/overlay/steam_overlay_translations.h index de9e65627..86147e07d 100644 --- a/overlay_experimental/overlay/steam_overlay_translations.h +++ b/overlay_experimental/overlay/steam_overlay_translations.h @@ -767,7 +767,7 @@ const char translationAccept[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER u8"ยอมรับ", // 26 - Turkish - u8"Kabul Et", + u8"Kabul et", // 27 - Ukrainian u8"Прийняти", @@ -1461,6 +1461,102 @@ const char translationSettings[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFF }; +const char translationHistory[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"History", + + // 1 - Arabic + u8"History", + + // 2 - Bulgarian + u8"History", + + // 3 - Simplified Chinese + u8"历史记录", + + // 4 - Traditional Chinese + u8"History", + + // 5 - Czech + u8"History", + + // 6 - Danish + u8"History", + + // 7 - Dutch + u8"History", + + // 8 - Finnish + u8"History", + + // 9 - French + u8"History", + + // 10 - German + u8"History", + + // 11 - Greek + u8"History", + + // 12 - Hungarian + u8"History", + + // 13 - Italian + u8"History", + + // 14 - Japanese + u8"History", + + // 15 - Korean + u8"History", + + // 16 - Norwegian + u8"History", + + // 17 - Polish + u8"History", + + // 18 - Portuguese + u8"History", + + // 19 - Brazilian Portuguese + u8"History", + + // 20 - Romanian + u8"History", + + // 21 - Russian + u8"History", + + // 22 - Spanish + u8"History", + + // 23 - Latin American + u8"History", + + // 24 - Swedish + u8"History", + + // 25 - Thai + u8"History", + + // 26 - Turkish + u8"Geçmiş", + + // 27 - Ukrainian + u8"History", + + // 28 - Vietnamese + u8"History", + + // 29 - Croatian + u8"History", + + // 30 - Indonesian + u8"History", + +}; + // C:\Program Files (x86)\Steam\resource\overlay_*.txt // steam_menu_friends_view const char translationFriends[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { @@ -1559,6 +1655,678 @@ const char translationFriends[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFE }; +const char translationNoNotification[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"No notifications yet", + + // 1 - Arabic + u8"No notifications yet", + + // 2 - Bulgarian + u8"No notifications yet", + + // 3 - Simplified Chinese + u8"暂无通知", + + // 4 - Traditional Chinese + u8"No notifications yet", + + // 5 - Czech + u8"No notifications yet", + + // 6 - Danish + u8"No notifications yet", + + // 7 - Dutch + u8"No notifications yet", + + // 8 - Finnish + u8"No notifications yet", + + // 9 - French + u8"No notifications yet", + + // 10 - German + u8"No notifications yet", + + // 11 - Greek + u8"No notifications yet", + + // 12 - Hungarian + u8"No notifications yet", + + // 13 - Italian + u8"No notifications yet", + + // 14 - Japanese + u8"No notifications yet", + + // 15 - Korean + u8"No notifications yet", + + // 16 - Norwegian + u8"No notifications yet", + + // 17 - Polish + u8"No notifications yet", + + // 18 - Portuguese + u8"No notifications yet", + + // 19 - Brazilian Portuguese + u8"No notifications yet", + + // 20 - Romanian + u8"No notifications yet", + + // 21 - Russian + u8"No notifications yet", + + // 22 - Spanish + u8"No notifications yet", + + // 23 - Latin American + u8"No notifications yet", + + // 24 - Swedish + u8"No notifications yet", + + // 25 - Thai + u8"No notifications yet", + + // 26 - Turkish + u8"Henüz bildirim yok", + + // 27 - Ukrainian + u8"No notifications yet", + + // 28 - Vietnamese + u8"No notifications yet", + + // 29 - Croatian + u8"No notifications yet", + + // 30 - Indonesian + u8"No notifications yet", + +}; + +const char translationClearAll[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Clear All", + + // 1 - Arabic + u8"Clear All", + + // 2 - Bulgarian + u8"Clear All", + + // 3 - Simplified Chinese + u8"清除所有通知", + + // 4 - Traditional Chinese + u8"Clear All", + + // 5 - Czech + u8"Clear All", + + // 6 - Danish + u8"Clear All", + + // 7 - Dutch + u8"Clear All", + + // 8 - Finnish + u8"Clear All", + + // 9 - French + u8"Clear All", + + // 10 - German + u8"Clear All", + + // 11 - Greek + u8"Clear All", + + // 12 - Hungarian + u8"Clear All", + + // 13 - Italian + u8"Clear All", + + // 14 - Japanese + u8"Clear All", + + // 15 - Korean + u8"Clear All", + + // 16 - Norwegian + u8"Clear All", + + // 17 - Polish + u8"Clear All", + + // 18 - Portuguese + u8"Clear All", + + // 19 - Brazilian Portuguese + u8"Clear All", + + // 20 - Romanian + u8"Clear All", + + // 21 - Russian + u8"Clear All", + + // 22 - Spanish + u8"Clear All", + + // 23 - Latin American + u8"Clear All", + + // 24 - Swedish + u8"Clear All", + + // 25 - Thai + u8"Clear All", + + // 26 - Turkish + u8"Hepsini Temizle", + + // 27 - Ukrainian + u8"Clear All", + + // 28 - Vietnamese + u8"Clear All", + + // 29 - Croatian + u8"Clear All", + + // 30 - Indonesian + u8"Clear All", + +}; + +const char translationShow[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Show", + + // 1 - Arabic + u8"Show", + + // 2 - Bulgarian + u8"Show", + + // 3 - Simplified Chinese + u8"显示", + + // 4 - Traditional Chinese + u8"Show", + + // 5 - Czech + u8"Show", + + // 6 - Danish + u8"Show", + + // 7 - Dutch + u8"Show", + + // 8 - Finnish + u8"Show", + + // 9 - French + u8"Show", + + // 10 - German + u8"Show", + + // 11 - Greek + u8"Show", + + // 12 - Hungarian + u8"Show", + + // 13 - Italian + u8"Show", + + // 14 - Japanese + u8"Show", + + // 15 - Korean + u8"Show", + + // 16 - Norwegian + u8"Show", + + // 17 - Polish + u8"Show", + + // 18 - Portuguese + u8"Show", + + // 19 - Brazilian Portuguese + u8"Show", + + // 20 - Romanian + u8"Show", + + // 21 - Russian + u8"Show", + + // 22 - Spanish + u8"Show", + + // 23 - Latin American + u8"Show", + + // 24 - Swedish + u8"Show", + + // 25 - Thai + u8"Show", + + // 26 - Turkish + u8"Göster", + + // 27 - Ukrainian + u8"Show", + + // 28 - Vietnamese + u8"Show", + + // 29 - Croatian + u8"Show", + + // 30 - Indonesian + u8"Show", + +}; + +const char translationUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Unlocked", + + // 1 - Arabic + u8"Unlocked", + + // 2 - Bulgarian + u8"Unlocked", + + // 3 - Simplified Chinese + u8"已解锁", + + // 4 - Traditional Chinese + u8"Unlocked", + + // 5 - Czech + u8"Unlocked", + + // 6 - Danish + u8"Unlocked", + + // 7 - Dutch + u8"Unlocked", + + // 8 - Finnish + u8"Unlocked", + + // 9 - French + u8"Unlocked", + + // 10 - German + u8"Unlocked", + + // 11 - Greek + u8"Unlocked", + + // 12 - Hungarian + u8"Unlocked", + + // 13 - Italian + u8"Unlocked", + + // 14 - Japanese + u8"Unlocked", + + // 15 - Korean + u8"Unlocked", + + // 16 - Norwegian + u8"Unlocked", + + // 17 - Polish + u8"Unlocked", + + // 18 - Portuguese + u8"Unlocked", + + // 19 - Brazilian Portuguese + u8"Unlocked", + + // 20 - Romanian + u8"Unlocked", + + // 21 - Russian + u8"Unlocked", + + // 22 - Spanish + u8"Unlocked", + + // 23 - Latin American + u8"Unlocked", + + // 24 - Swedish + u8"Unlocked", + + // 25 - Thai + u8"Unlocked", + + // 26 - Turkish + u8"Kazanılmış Başarımlar", + + // 27 - Ukrainian + u8"Unlocked", + + // 28 - Vietnamese + u8"Unlocked", + + // 29 - Croatian + u8"Unlocked", + + // 30 - Indonesian + u8"Unlocked", + +}; + +const char translationNoUnlockedAchievements[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"No achievements unlocked yet", + + // 1 - Arabic + u8"No achievements unlocked yet", + + // 2 - Bulgarian + u8"No achievements unlocked yet", + + // 3 - Simplified Chinese + u8"暂无已解锁成就", + + // 4 - Traditional Chinese + u8"No achievements unlocked yet", + + // 5 - Czech + u8"No achievements unlocked yet", + + // 6 - Danish + u8"No achievements unlocked yet", + + // 7 - Dutch + u8"No achievements unlocked yet", + + // 8 - Finnish + u8"No achievements unlocked yet", + + // 9 - French + u8"No achievements unlocked yet", + + // 10 - German + u8"No achievements unlocked yet", + + // 11 - Greek + u8"No achievements unlocked yet", + + // 12 - Hungarian + u8"No achievements unlocked yet", + + // 13 - Italian + u8"No achievements unlocked yet", + + // 14 - Japanese + u8"No achievements unlocked yet", + + // 15 - Korean + u8"No achievements unlocked yet", + + // 16 - Norwegian + u8"No achievements unlocked yet", + + // 17 - Polish + u8"No achievements unlocked yet", + + // 18 - Portuguese + u8"No achievements unlocked yet", + + // 19 - Brazilian Portuguese + u8"No achievements unlocked yet", + + // 20 - Romanian + u8"No achievements unlocked yet", + + // 21 - Russian + u8"No achievements unlocked yet", + + // 22 - Spanish + u8"No achievements unlocked yet", + + // 23 - Latin American + u8"No achievements unlocked yet", + + // 24 - Swedish + u8"No achievements unlocked yet", + + // 25 - Thai + u8"No achievements unlocked yet", + + // 26 - Turkish + u8"Henüz hiçbir başarım kazanılmadı", + + // 27 - Ukrainian + u8"No achievements unlocked yet", + + // 28 - Vietnamese + u8"No achievements unlocked yet", + + // 29 - Croatian + u8"No achievements unlocked yet", + + // 30 - Indonesian + u8"No achievements unlocked yet", + +}; + +const char translationLocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"Locked", + + // 1 - Arabic + u8"Locked", + + // 2 - Bulgarian + u8"Locked", + + // 3 - Simplified Chinese + u8"未解锁", + + // 4 - Traditional Chinese + u8"Locked", + + // 5 - Czech + u8"Locked", + + // 6 - Danish + u8"Locked", + + // 7 - Dutch + u8"Locked", + + // 8 - Finnish + u8"Locked", + + // 9 - French + u8"Locked", + + // 10 - German + u8"Locked", + + // 11 - Greek + u8"Locked", + + // 12 - Hungarian + u8"Locked", + + // 13 - Italian + u8"Locked", + + // 14 - Japanese + u8"Locked", + + // 15 - Korean + u8"Locked", + + // 16 - Norwegian + u8"Locked", + + // 17 - Polish + u8"Locked", + + // 18 - Portuguese + u8"Locked", + + // 19 - Brazilian Portuguese + u8"Locked", + + // 20 - Romanian + u8"Locked", + + // 21 - Russian + u8"Locked", + + // 22 - Spanish + u8"Locked", + + // 23 - Latin American + u8"Locked", + + // 24 - Swedish + u8"Locked", + + // 25 - Thai + u8"Locked", + + // 26 - Turkish + u8"Kazanılmamış Başarımlar", + + // 27 - Ukrainian + u8"Locked", + + // 28 - Vietnamese + u8"Locked", + + // 29 - Croatian + u8"Locked", + + // 30 - Indonesian + u8"Locked", + +}; + +const char translationAllAchievementsUnlocked[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { + // 0 - English + u8"All achievements unlocked!", + + // 1 - Arabic + u8"All achievements unlocked!", + + // 2 - Bulgarian + u8"All achievements unlocked!", + + // 3 - Simplified Chinese + u8"已解锁所有成就!", + + // 4 - Traditional Chinese + u8"All achievements unlocked!", + + // 5 - Czech + u8"All achievements unlocked!", + + // 6 - Danish + u8"All achievements unlocked!", + + // 7 - Dutch + u8"All achievements unlocked!", + + // 8 - Finnish + u8"All achievements unlocked!", + + // 9 - French + u8"All achievements unlocked!", + + // 10 - German + u8"All achievements unlocked!", + + // 11 - Greek + u8"All achievements unlocked!", + + // 12 - Hungarian + u8"All achievements unlocked!", + + // 13 - Italian + u8"All achievements unlocked!", + + // 14 - Japanese + u8"All achievements unlocked!", + + // 15 - Korean + u8"All achievements unlocked!", + + // 16 - Norwegian + u8"All achievements unlocked!", + + // 17 - Polish + u8"All achievements unlocked!", + + // 18 - Portuguese + u8"All achievements unlocked!", + + // 19 - Brazilian Portuguese + u8"All achievements unlocked!", + + // 20 - Romanian + u8"All achievements unlocked!", + + // 21 - Russian + u8"All achievements unlocked!", + + // 22 - Spanish + u8"All achievements unlocked!", + + // 23 - Latin American + u8"All achievements unlocked!", + + // 24 - Swedish + u8"All achievements unlocked!", + + // 25 - Thai + u8"All achievements unlocked!", + + // 26 - Turkish + u8"Tüm başarılar kazanıldı!", + + // 27 - Ukrainian + u8"All achievements unlocked!", + + // 28 - Vietnamese + u8"All achievements unlocked!", + + // 29 - Croatian + u8"All achievements unlocked!", + + // 30 - Indonesian + u8"All achievements unlocked!", + +}; + const char translationAchievementWindow[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BUFFER_SIZE] = { // 0 - English u8"Achievement Window", @@ -2023,7 +2791,7 @@ const char translationAchievedOn[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_BU u8"achieved on %s", // 26 - Turkish - u8"%s tarihinde elde edildi", + u8"%s tarihinde kazanıldı", // 27 - Ukrainian u8"achieved on %s", @@ -2119,7 +2887,7 @@ const char translationNotAchieved[TRANSLATION_NUMBER_OF_LANGUAGES][TRANSLATION_B u8"not achieved", // 26 - Turkish - u8"Başarılmadı", + u8"Kazanılmadı", // 27 - Ukrainian u8"not achieved", diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index f62947f7b..ec1597c43 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "InGameOverlay/RendererDetector.h" @@ -32,11 +34,6 @@ static constexpr int base_notif_window_id = 0 * max_window_id; static constexpr int base_friend_window_id = 1 * max_window_id; static constexpr int base_friend_item_id = 2 * max_window_id; -static InGameOverlay::ToggleKey overlay_toggle_keys[] = { - InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB -}; -static const int toggle_keys_count = 2; - // look for the column 'API language code' here: https://partner.steamgames.com/doc/store/localization/languages static constexpr const char* valid_languages[] = { "english", @@ -95,6 +92,55 @@ void Steam_Overlay::overlay_networking_callback(void* object, Common_Message* ms _this->networking_msg_received(msg); } +void Steam_Overlay::parse_key_combo() +{ + static const std::unordered_map KEYS_MAP { + { InGameOverlay::ToggleKey::SHIFT, "shift" }, + { InGameOverlay::ToggleKey::CTRL, "ctrl" }, + { InGameOverlay::ToggleKey::ALT, "alt" }, + { InGameOverlay::ToggleKey::TAB, "tab" }, + { InGameOverlay::ToggleKey::F1, "fn1" }, + { InGameOverlay::ToggleKey::F2, "fn2" }, + { InGameOverlay::ToggleKey::F3, "fn3" }, + { InGameOverlay::ToggleKey::F4, "fn4" }, + { InGameOverlay::ToggleKey::F5, "fn5" }, + { InGameOverlay::ToggleKey::F6, "fn6" }, + { InGameOverlay::ToggleKey::F7, "fn7" }, + { InGameOverlay::ToggleKey::F8, "fn8" }, + { InGameOverlay::ToggleKey::F9, "fn9" }, + { InGameOverlay::ToggleKey::F10, "fn10" }, + { InGameOverlay::ToggleKey::F11, "fn11" }, + { InGameOverlay::ToggleKey::F12, "fn12" }, + }; + + std::unordered_set keys_combo{}; + bool use_default = false; + if (settings->overlay_toggle_keys.empty()) { + use_default = true; + } else { + for (const auto &key_name : settings->overlay_toggle_keys) { + auto key_it = std::find_if(KEYS_MAP.cbegin(), KEYS_MAP.cend(), [&key_name](decltype(*KEYS_MAP.cbegin()) const &item) { + return common_helpers::str_cmp_insensitive(item.second, key_name); + }); + if (KEYS_MAP.cend() != key_it) { + keys_combo.insert(key_it->first); + } else { + use_default = true; + PRINT_DEBUG("[X] Unknown key '%s', using default key combo Shift + Tab", key_name.c_str()); + break; + } + } + } + + if (use_default) { + toggle_keys = { + InGameOverlay::ToggleKey::SHIFT, InGameOverlay::ToggleKey::TAB + }; + } else { + toggle_keys = std::vector(keys_combo.begin(), keys_combo.end()); + } +} + Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Networking* network) : settings(settings), local_storage(local_storage), @@ -113,7 +159,7 @@ Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, S std::chrono::milliseconds(renderer_detector_polling_ms), [this] { return !setup_overlay_called; } ); - + renderer_detector_delay_thread = common_helpers::KillableWorker( [this](void *){ request_renderer_detector(); @@ -125,7 +171,8 @@ Steam_Overlay::Steam_Overlay(Settings* settings, Local_Storage *local_storage, S std::chrono::milliseconds(0), [this] { return !setup_overlay_called; } ); - + + parse_key_combo(); strncpy(username_text, settings->get_local_name(), sizeof(username_text)); // we need these copies to show the warning only once, then disable the flag @@ -208,12 +255,12 @@ bool Steam_Overlay::renderer_hook_proc() return true; } PRINT_DEBUG("got renderer hook %p for '%s'", _renderer, _renderer->GetLibraryName()); - + // note: make sure to load all relevant strings before creating the font(s), otherwise some glyphs ranges will be missing load_achievements_data(); load_audio(); create_fonts(); - + // setup renderer callbacks auto overlay_toggle_callback = [this]() { open_overlay_hook(true); }; _renderer->OverlayProc = [this]() { overlay_render_proc(); }; @@ -222,9 +269,9 @@ bool Steam_Overlay::renderer_hook_proc() overlay_state_hook(state == InGameOverlay::OverlayHookState::Ready || state == InGameOverlay::OverlayHookState::Reset); }; - bool started = _renderer->StartHook(overlay_toggle_callback, overlay_toggle_keys, toggle_keys_count, &fonts_atlas); + bool started = _renderer->StartHook(overlay_toggle_callback, toggle_keys.data(), (int)toggle_keys.size(), &fonts_atlas); PRINT_DEBUG("started renderer hook (result=%i)", (int)started); - + return true; } @@ -238,6 +285,15 @@ void Steam_Overlay::create_fonts() fonts_atlas.Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; float font_size = settings->overlay_appearance.font_size; + float font_size_fps = settings->overlay_appearance.font_size_fps > 0.0f + ? settings->overlay_appearance.font_size_fps + : font_size; + float font_size_ach_title = settings->overlay_appearance.font_size_ach_title > 0.0f + ? settings->overlay_appearance.font_size_ach_title + : font_size; + float font_size_ach_desc = settings->overlay_appearance.font_size_ach_desc > 0.0f + ? settings->overlay_appearance.font_size_ach_desc + : font_size; font_cfg.FontDataOwnedByAtlas = false; // https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#loading-font-data-from-memory font_cfg.PixelSnapH = true; @@ -245,8 +301,10 @@ void Steam_Overlay::create_fonts() font_cfg.OversampleV = 1; font_cfg.SizePixels = font_size; // non-latin characters look ugly and squeezed without this horizontal spacing - font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; - font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; + + font_cfg.GlyphExtraAdvanceX = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.x = settings->overlay_appearance.font_glyph_extra_spacing_x; + // font_cfg.GlyphExtraSpacing.y = settings->overlay_appearance.font_glyph_extra_spacing_y; for (const auto &ach : achievements) { font_builder.AddText(ach.title.c_str()); @@ -267,12 +325,20 @@ void Steam_Overlay::create_fonts() font_builder.AddText(translationRenderer[i]); font_builder.AddText(translationShowAchievements[i]); font_builder.AddText(translationSettings[i]); + font_builder.AddText(translationHistory[i]); font_builder.AddText(translationFriends[i]); + font_builder.AddText(translationNoNotification[i]); + font_builder.AddText(translationClearAll[i]); font_builder.AddText(translationAchievementWindow[i]); font_builder.AddText(translationListOfAchievements[i]); font_builder.AddText(translationAchievements[i]); font_builder.AddText(translationHiddenAchievement[i]); + font_builder.AddText(translationShow[i]); font_builder.AddText(translationAchievedOn[i]); + font_builder.AddText(translationUnlocked[i]); + font_builder.AddText(translationNoUnlockedAchievements[i]); + font_builder.AddText(translationLocked[i]); + font_builder.AddText(translationAllAchievementsUnlocked[i]); font_builder.AddText(translationNotAchieved[i]); font_builder.AddText(translationGlobalSettingsWindow[i]); font_builder.AddText(translationGlobalSettingsWindowDescription[i]); @@ -301,18 +367,32 @@ void Steam_Overlay::create_fonts() font_builder.BuildRanges(&ranges); font_cfg.GlyphRanges = ranges.Data; - if (settings->overlay_appearance.font_override.size()) { - fonts_atlas.AddFontFromFileTTF(settings->overlay_appearance.font_override.c_str(), font_size, &font_cfg); - font_cfg.MergeMode = true; // merge next fonts into the first one, as if they were all just 1 font file - } + auto add_overlay_font = [this](float size, const std::string &custom_font = "") { + font_cfg.SizePixels = size; + font_cfg.MergeMode = false; + + const std::string &font_path = custom_font.empty() ? settings->overlay_appearance.font_override : custom_font; + ImFont *font = nullptr; + if (font_path.size()) { + font = fonts_atlas.AddFontFromFileTTF(font_path.c_str(), size, &font_cfg); + if (font) { + font_cfg.MergeMode = true; // merge next font into the custom font + } + } - // note: base85 compressed arrays caused a compiler heap allocation error, regular compression is more guaranteed - ImFont *font = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, font_size, &font_cfg); - font_notif = font_default = font; - stats.font = font; - - bool res = fonts_atlas.Build(); - PRINT_DEBUG("created fonts atlas (result=%i)", (int)res); + // note: base85 compressed arrays caused a compiler heap allocation error, regular compression is more guaranteed + ImFont *fallback_font = fonts_atlas.AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, size, &font_cfg); + return font ? font : fallback_font; + }; + + font_notif = font_default = add_overlay_font(font_size); + font_fps = add_overlay_font(font_size_fps); + font_ach_title = add_overlay_font(font_size_ach_title, settings->overlay_appearance.font_override_ach_title); + font_ach_desc = add_overlay_font(font_size_ach_desc, settings->overlay_appearance.font_override_ach_desc); + stats.font = font_fps; + + bool res = fonts_atlas.IsBuilt(); + PRINT_DEBUG("isbuilt fonts atlas (result=%i)", (int)res); reset_LastError(); } @@ -354,7 +434,7 @@ void Steam_Overlay::load_achievements_data() ach.name = steamUserStats->GetAchievementName(i); ach.title = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "name"); ach.description = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "desc"); - + const char *hidden = steamUserStats->GetAchievementDisplayAttribute(ach.name.c_str(), "hidden"); ach.hidden = hidden && hidden[0] == '1'; @@ -382,7 +462,7 @@ void Steam_Overlay::load_achievements_data() } achievements.emplace_back(ach); - + if (!setup_overlay_called) return; } @@ -394,7 +474,7 @@ void Steam_Overlay::load_achievements_data() void Steam_Overlay::overlay_state_hook(bool ready) { PRINT_DEBUG("%i", (int)ready); - + // NOTE usage of local objects here cause an exception when this is called with false state // the reason is that by the time this hook is called, the object may have been already destructed // this is why we use global mutex @@ -482,7 +562,7 @@ void Steam_Overlay::obscure_game_input(bool state) { io.WantCaptureMouse = state; // not necessary, just to be sure io.WantCaptureKeyboard = state; - + // restore the old cursor _renderer->HideAppInputs(false); PRINT_DEBUG("restored app input (count=%u)", new_val); @@ -556,7 +636,7 @@ int find_free_friend_id(const std::map { ids.emplace_back(i.second.id); }); - + return find_free_id(ids, base_friend_window_id); } @@ -569,7 +649,7 @@ int find_free_notification_id(std::vector const& notifications) { ids.emplace_back(i.id); }); - + return find_free_id(ids, base_friend_window_id); } @@ -583,7 +663,7 @@ bool Steam_Overlay::submit_notification( PRINT_DEBUG("%i", (int)type); std::lock_guard lock(overlay_mutex); if (!Ready()) return false; - + int id = find_free_notification_id(notifications); if (id == 0) { PRINT_DEBUG("error no free id to create a notification window"); @@ -597,7 +677,7 @@ bool Steam_Overlay::submit_notification( notif.message = msg; notif.frd = frd; if (ach) notif.ach = *ach; - + notifications.emplace_back(notif); allow_renderer_frame_processing(true); // uncomment this block to obscure cursor input and steal focus for these specific notifications @@ -661,10 +741,9 @@ void Steam_Overlay::show_test_achievement() ach.progress = progress; ach.achieved = false; } - + post_achievement_notification(ach, for_progress); - // here we always play the sound for testing - notify_sound_user_achievement(); + // sound is now played when notification is actually shown (delayed with queue) } void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_state& state) @@ -695,7 +774,7 @@ void Steam_Overlay::build_friend_context_menu(Friend const& frd, friend_window_s state.window_state |= window_state_invite; has_friend_action.push(frd); } - + // user clicked on "accept game invite" std::string translationJoin_tmp(translationJoin[current_language]); translationJoin_tmp.append("##PopupAcceptInvite"); @@ -727,7 +806,7 @@ void Steam_Overlay::build_friend_window(Friend const& frd, friend_window_state& bool send_chat_msg = false; float width = ImGui::CalcTextSize("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").x; - + if (state.window_state & window_state_need_attention && ImGui::IsWindowFocused()) { state.window_state &= ~window_state_need_attention; } @@ -790,7 +869,7 @@ void Steam_Overlay::build_friend_window(Friend const& frd, friend_window_state& send_chat_msg = true; ImGui::SetKeyboardFocusHere(-1); } - + ImGui::PopItemWidth(); ImGui::PopID(); @@ -807,7 +886,7 @@ void Steam_Overlay::build_friend_window(Friend const& frd, friend_window_state& } } } - + // User closed the friend window if (!show) { state.window_state &= ~window_state_show; @@ -823,16 +902,16 @@ std::chrono::milliseconds Steam_Overlay::get_notification_duration(notification_ { case notification_type::message: return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_chat); - + case notification_type::invite: return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_invitation); - + case notification_type::achievement: return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_achievement); - + case notification_type::achievement_progress: return std::chrono::milliseconds(settings->overlay_appearance.notification_duration_progress); - + case notification_type::auto_accept_invite: return Notification::default_show_time; } @@ -858,7 +937,7 @@ void Steam_Overlay::set_next_notification_pos(std::pair scrn_size, noti_width - padding_all_sides - global_style.ItemSpacing.x ).y; float noti_height = msg_height; - + // get the required position Overlay_Appearance::NotificationPosition pos = Overlay_Appearance::default_pos; switch ((notification_type)noti.type) { @@ -866,12 +945,26 @@ void Steam_Overlay::set_next_notification_pos(std::pair scrn_size, case notification_type::achievement: { pos = settings->overlay_appearance.ach_earned_pos; - const float new_msg_height = ImGui::CalcTextSize( - noti.message.c_str(), - noti.message.c_str() + noti.message.size(), + const auto &ach = noti.ach.value(); + const float ach_text_width = noti_width - padding_all_sides - global_style.ItemSpacing.x - settings->overlay_appearance.icon_size; + ImGui::PushFont(font_ach_title); + float new_msg_height = ImGui::CalcTextSize( + ach.title.c_str(), + ach.title.c_str() + ach.title.size(), false, - noti_width - padding_all_sides - global_style.ItemSpacing.x - settings->overlay_appearance.icon_size + ach_text_width ).y; + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + new_msg_height += global_style.ItemSpacing.y + ImGui::CalcTextSize( + ach.description.c_str(), + ach.description.c_str() + ach.description.size(), + false, + ach_text_width + ).y; + ImGui::PopFont(); + } const float new_noti_height = new_msg_height; float biggest_noti_height = settings->overlay_appearance.icon_size; @@ -984,7 +1077,7 @@ float Steam_Overlay::animate_factor(std::chrono::milliseconds elapsed, std::chro // PRINT_DEBUG("HIDE FACTOR %f", factor); } } - + return factor; } @@ -1011,7 +1104,7 @@ ImVec4 Steam_Overlay::get_notification_bg_rgba_safe() settings->overlay_appearance.notification_a ); } - + // fallback to dark-gray background return ImVec4( 0.12f, @@ -1029,7 +1122,7 @@ void Steam_Overlay::build_notifications(float width, float height) ImGui::PushFont(font_notif); // Add window rounding ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, settings->overlay_appearance.notification_rounding); - + NotificationsCoords coords{}; for (auto it = notifications.begin(); it != notifications.end(); ++it) { auto noti_duration = get_notification_duration((notification_type)it->type); @@ -1049,11 +1142,11 @@ void Steam_Overlay::build_notifications(float width, float height) float settings_noti_alpha = settings->overlay_appearance.notification_a >= 0.0f && settings->overlay_appearance.notification_a <= 1.0f ? settings->overlay_appearance.notification_a : 1.0f; - + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, settings_noti_alpha)); ImGui::PushStyleColor(ImGuiCol_WindowBg, get_notification_bg_rgba_safe()); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 255, settings_noti_alpha * 2)); - + // some extra window flags for each notification type ImGuiWindowFlags extra_flags = ImGuiWindowFlags_NoFocusOnAppearing; switch ((notification_type)it->type) { @@ -1096,11 +1189,25 @@ void Steam_Overlay::build_notifications(float width, float height) ImGui::Image(icon_rsrc->GetResourceId(), ImVec2(settings->overlay_appearance.icon_size, settings->overlay_appearance.icon_size)); ImGui::TableSetColumnIndex(1); - ImGui::TextWrapped("%s", it->message.c_str()); + ImGui::PushFont(font_ach_title); + ImGui::TextWrapped("%s", ach.title.c_str()); + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::PopFont(); + } ImGui::EndTable(); } else { - ImGui::TextWrapped("%s", it->message.c_str()); + ImGui::PushFont(font_ach_title); + ImGui::TextWrapped("%s", ach.title.c_str()); + ImGui::PopFont(); + if (ach.description.size()) { + ImGui::PushFont(font_ach_desc); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::PopFont(); + } } if ((notification_type)it->type == notification_type::achievement_progress) { @@ -1128,7 +1235,7 @@ void Steam_Overlay::build_notifications(float width, float height) case notification_type::auto_accept_invite: ImGui::TextWrapped("%s", it->message.c_str()); break; - + default: PRINT_DEBUG("error unhandled notification for type %i", (int)it->type); break; @@ -1169,9 +1276,29 @@ void Steam_Overlay::build_notifications(float width, float height) break; } + // Archive to notification history (lightweight copy, no pointers/GPU resources) + { + NotificationHistoryEntry entry{}; + // Use actual achievement unlock time when available, + // otherwise fall back to the notification display time. + if (item.ach.has_value() && item.ach->unlock_time > 0) { + entry.timestamp = std::chrono::milliseconds( + static_cast(item.ach->unlock_time) * 1000); + } else { + entry.timestamp = item.start_time; + } + entry.type = item.type; + entry.message = item.message; + if (notification_history.size() >= MAX_NOTIFICATION_HISTORY) { + notification_history.pop_front(); + } + notification_history.push_back(std::move(entry)); + notification_history_cache_dirty = true; + } + return true; } - + return false; }), notifications.end()); @@ -1188,10 +1315,10 @@ void Steam_Overlay::add_auto_accept_invite_notification() PRINT_DEBUG_ENTRY(); std::lock_guard lock(overlay_mutex); if (!Ready()) return; - + char tmp[TRANSLATION_BUFFER_SIZE]{}; snprintf(tmp, sizeof(tmp), "%s", translationAutoAcceptFriendInvite[current_language]); - + submit_notification(notification_type::auto_accept_invite, tmp); notify_sound_auto_accept_friend_invite(); } @@ -1199,16 +1326,16 @@ void Steam_Overlay::add_auto_accept_invite_notification() void Steam_Overlay::add_invite_notification(std::pair& wnd_state) { if (settings->disable_overlay_friend_notification) return; - + PRINT_DEBUG_ENTRY(); std::lock_guard lock(overlay_mutex); if (!Ready()) return; - + char tmp[TRANSLATION_BUFFER_SIZE]{}; auto &first_friend = wnd_state.first; auto &name = first_friend.name(); - snprintf(tmp, sizeof(tmp), translationInvitedYouToJoinTheGame[current_language], name.c_str(), (uint64)first_friend.appid()); - + snprintf(tmp, sizeof(tmp), translationInvitedYouToJoinTheGame[current_language], name.c_str(), (uint64)first_friend.appid()); + submit_notification(notification_type::invite, tmp, &wnd_state); } @@ -1219,16 +1346,86 @@ void Steam_Overlay::post_achievement_notification(Overlay_Achievement &ach, bool PRINT_DEBUG_ENTRY(); std::lock_guard lock(overlay_mutex); if (!Ready()) return; - - bool achieved = !for_progress; // for progress notifications we want to load the gray icon - // force upload to GPU if the pagination is request-based - try_load_ach_icon(ach, achieved, settings->paginated_achievements_icons == 0); - submit_notification( - for_progress ? notification_type::achievement_progress : notification_type::achievement, - ach.title + "\n" + ach.description, - {}, - &ach - ); +// Get current time + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // Calculate scheduled show time based on rate limiting + std::chrono::milliseconds scheduled_show_time; + int delay_ms = settings->achievement_notification_delay_ms; + + PRINT_DEBUG("Achievement delay: %d ms", delay_ms); + + if (delay_ms <= 0) { + // No delay - show immediately + scheduled_show_time = now; + } else { + // Apply rate limiting: earliest show time is last_scheduled_show_time + delay + scheduled_show_time = std::max(now, last_scheduled_show_time + std::chrono::milliseconds(delay_ms)); + } + + // Create scheduled achievement entry + ScheduledAchievement scheduled_ach; + scheduled_ach.ach = ach; + scheduled_ach.for_progress = for_progress; + scheduled_ach.trigger_time = now; + scheduled_ach.scheduled_show_time = scheduled_show_time; + + // Add to queue + achievement_queue.push_back(scheduled_ach); + + // Update last scheduled show time for next item + last_scheduled_show_time = scheduled_show_time; + + PRINT_DEBUG("Achievement queued: '%s', scheduled for %lld ms, delay=%d ms", + ach.name.c_str(), (long long)scheduled_show_time.count(), delay_ms); +} + +void Steam_Overlay::process_achievement_queue() +{ + std::lock_guard lock(overlay_mutex); + if (!Ready()) return; + if (achievement_queue.empty()) return; + + // Get current time + auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // Process all ready achievements + while (!achievement_queue.empty()) { + auto& scheduled_ach = achievement_queue.front(); + + // Check if it's time to show this notification + PRINT_DEBUG("Queue check: scheduled=%lld, now=%lld, diff=%lld", + (long long)scheduled_ach.scheduled_show_time.count(), + (long long)now.count(), + (long long)(now - scheduled_ach.scheduled_show_time).count()); + if (scheduled_ach.scheduled_show_time <= now) { + // Show the notification + bool achieved = !scheduled_ach.for_progress; + // force upload to GPU if the pagination is request-based + try_load_ach_icon(scheduled_ach.ach, achieved, settings->paginated_achievements_icons == 0); + + submit_notification( + scheduled_ach.for_progress ? notification_type::achievement_progress : notification_type::achievement, + scheduled_ach.ach.title + "\n" + scheduled_ach.ach.description, + {}, + &scheduled_ach.ach + ); + + // Play sound when notification is actually shown (delayed with queue) + notify_sound_user_achievement(); + + PRINT_DEBUG("Achievement shown: '%s' at %lld ms", + scheduled_ach.ach.name.c_str(), (long long)now.count()); + + // Remove from queue + achievement_queue.pop_front(); + } else { + // This achievement is not ready yet, and queue is ordered by scheduled time, + // so no more achievements will be ready either + break; + } + } + } bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, bool upload_new_icon_to_gpu) @@ -1250,9 +1447,9 @@ bool Steam_Overlay::try_load_ach_icon(Overlay_Achievement &ach, bool achieved, b auto image_info = settings->get_image(icon_handle); if (image_info) { int icon_size = static_cast(settings->overlay_appearance.icon_size); - icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); + //icon_rsrc->SetAutoLoad(InGameOverlay::ResourceAutoLoad_t::OnUse); icon_rsrc->AttachResource((void*)image_info->data.c_str(), icon_size, icon_size); - + PRINT_DEBUG("'%s' (result=%i)", ach.name.c_str(), (int)icon_rsrc->GetResourceId() != 0); } @@ -1266,6 +1463,9 @@ void Steam_Overlay::overlay_render_proc() if (!Ready()) return; + // Process achievement queue to show scheduled notifications + process_achievement_queue(); + if (show_overlay) { render_main_window(); } @@ -1384,7 +1584,7 @@ void Steam_Overlay::render_main_window() } ImGui::Spacing(); - + ImGui::SameLine(); // user clicked on "toggle user info" if (ImGui::Button(translationToggleUserInfo[current_language])) { @@ -1415,7 +1615,13 @@ void Steam_Overlay::render_main_window() if (ImGui::Button(translationSettings[current_language])) { show_settings = !show_settings; } - + + ImGui::SameLine(); + // user clicked on "notification history" + if (ImGui::Button(translationHistory[current_language])) { + show_notification_history = !show_notification_history; + } + ImGui::Spacing(); ImGui::Spacing(); // user clicked on "FPS" @@ -1436,6 +1642,80 @@ void Steam_Overlay::render_main_window() ImGui::Spacing(); ImGui::Spacing(); + + // --- Notification history panel --- + if (show_notification_history) { + if (ImGui::Button(translationClearAll[current_language])) { + notification_history.clear(); + notification_history_cache.clear(); + notification_history_cache_dirty = false; + } + ImGui::Separator(); + if (notification_history.empty()) { + ImGui::TextDisabled(translationNoNotification[current_language]); + } else { + ImGui::BeginChild("##history_scroll", ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10), true); + + // Rebuild cache only when history actually changes + if (notification_history_cache_dirty) { + notification_history_cache.clear(); + notification_history_cache.reserve(notification_history.size()); + + for (auto it = notification_history.rbegin(); it != notification_history.rend(); ++it) { + // Format timestamp HH:MM:SS in local timezone + const time_t total_sec = std::chrono::duration_cast(it->timestamp).count(); + struct tm local_tm_buf{}; +#ifdef _MSC_VER + localtime_s(&local_tm_buf, &total_sec); +#else + localtime_r(&total_sec, &local_tm_buf); +#endif + const auto hr = local_tm_buf.tm_hour; + const auto min = local_tm_buf.tm_min; + const auto sec = local_tm_buf.tm_sec; + + // Type label + const char *type_label = "?"; + switch ((notification_type)it->type) { + case notification_type::message: type_label = "Chat"; break; + case notification_type::invite: type_label = "Invite"; break; + case notification_type::achievement: type_label = "Achievement"; break; + case notification_type::achievement_progress: type_label = "Progress"; break; + case notification_type::auto_accept_invite: type_label = "Auto-Invite"; break; + } + + // For achievements the message contains "title\ndescription" + // Replace newline with inline separator for compact display + std::string display_msg = it->message; + if (it->type == static_cast(notification_type::achievement) || + it->type == static_cast(notification_type::achievement_progress)) { + size_t pos = display_msg.find('\n'); + if (pos != std::string::npos) { + display_msg.replace(pos, 1, " — "); + } + } + + std::string line = (std::ostringstream{} + << "[" << std::setw(2) << std::setfill('0') << hr << ":" + << std::setw(2) << std::setfill('0') << min << ":" + << std::setw(2) << std::setfill('0') << sec << "] " + << type_label << " " + << display_msg).str(); + + notification_history_cache.push_back(std::move(line)); + } + notification_history_cache_dirty = false; + } + + // Render from cache + for (const auto &line : notification_history_cache) { + ImGui::TextWrapped("%s", line.c_str()); + ImGui::Separator(); + } + ImGui::EndChild(); + } + } + ImGui::LabelText("##label", "%s", translationFriends[current_language]); if (!friends.empty()) { @@ -1472,13 +1752,30 @@ void Steam_Overlay::render_main_window() if (ImGui::Begin(translationAchievementWindow[current_language], &show_achievements)) { ImGui::Text("%s", translationListOfAchievements[current_language]); ImGui::BeginChild(translationAchievements[current_language]); - for (auto & x : achievements) { - bool achieved = x.achieved; - bool hidden = x.hidden && !achieved; - // force upload to GPU if the pagination is request-based - try_load_ach_icon(x, true, settings->paginated_achievements_icons == 0); - try_load_ach_icon(x, false, settings->paginated_achievements_icons == 0); + // Build sorted index lists: unlocked by time desc, locked in API order + std::vector unlocked_idx, locked_idx; + unlocked_idx.reserve(achievements.size()); + locked_idx.reserve(achievements.size()); + for (size_t i = 0; i < achievements.size(); ++i) { + if (achievements[i].achieved) + unlocked_idx.push_back(i); + else + locked_idx.push_back(i); + } + std::sort(unlocked_idx.begin(), unlocked_idx.end(), + [this](size_t a, size_t b) { + return achievements[a].unlock_time > achievements[b].unlock_time; + }); + + // Lambda to render a single achievement card + auto render_ach = [this](Overlay_Achievement &x) { + const bool achieved = x.achieved; + const bool hidden = x.hidden && !achieved; + + // Load only the icon matching the current state. + // The other variant is loaded by the background pagination or on state change. + try_load_ach_icon(x, achieved, settings->paginated_achievements_icons == 0); ImGui::Separator(); @@ -1504,12 +1801,21 @@ void Steam_Overlay::render_main_window() // the next column is the achievement text below } } - - // we want to display the ach text regardless the icons were displayed or not + ImGui::Text("%s", x.title.c_str()); if (hidden) { ImGui::Text("%s", translationHiddenAchievement[current_language]); + ImGui::SameLine(); + + ImGui::PushID(&x); + ImGui::SmallButton(translationShow[current_language]); + bool show = ImGui::IsItemActive(); + ImGui::PopID(); + + if (show) { + ImGui::TextWrapped("%s", x.description.c_str()); + } } else { ImGui::TextWrapped("%s", x.description.c_str()); } @@ -1517,9 +1823,15 @@ void Steam_Overlay::render_main_window() if (achieved) { char buffer[80]{}; time_t unlock_time = (time_t)x.unlock_time; - size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), std::localtime(&unlock_time)); - if (!written) { // count was reached before the entire string could be stored, keep it safe - std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", std::localtime(&unlock_time)); + struct tm unlock_tm{}; +#ifdef _MSC_VER + localtime_s(&unlock_tm, &unlock_time); +#else + localtime_r(&unlock_time, &unlock_tm); +#endif + size_t written = std::strftime(buffer, sizeof(buffer), settings->overlay_appearance.ach_unlock_datetime_format.c_str(), &unlock_tm); + if (!written) { + std::strftime(buffer, sizeof(buffer), "%Y/%m/%d - %H:%M:%S", &unlock_tm); } ImGui::TextColored(ImVec4(0, 255, 0, 255), translationAchievedOn[current_language], buffer); @@ -1531,10 +1843,33 @@ void Steam_Overlay::render_main_window() if (could_create_ach_table_entry) ImGui::EndTable(); ImGui::Separator(); + }; + + // --- Unlocked section --- + if (ImGui::CollapsingHeader(translationUnlocked[current_language], ImGuiTreeNodeFlags_DefaultOpen)) { + if (unlocked_idx.empty()) { + ImGui::TextDisabled(translationNoUnlockedAchievements[current_language]); + } else { + for (auto idx : unlocked_idx) { + render_ach(achievements[idx]); + } + } } + + // --- Locked section --- + if (ImGui::CollapsingHeader(translationLocked[current_language], ImGuiTreeNodeFlags_DefaultOpen)) { + if (locked_idx.empty()) { + ImGui::TextDisabled(translationAllAchievementsUnlocked[current_language]); + } else { + for (auto idx : locked_idx) { + render_ach(achievements[idx]); + } + } + } + ImGui::EndChild(); } - + ImGui::End(); } @@ -1580,7 +1915,7 @@ void Steam_Overlay::render_main_window() ImGui::PushItemWidth(ImGui::CalcTextSize(url.c_str()).x + 20); ImGui::InputText("##url_copy", (char *)url.data(), url.size(), ImGuiInputTextFlags_ReadOnly); ImGui::PopItemWidth(); - + ImGui::Spacing(); if (ImGui::Button(translationClose[current_language]) || !show) show_url = ""; @@ -1658,7 +1993,7 @@ void Steam_Overlay::load_next_ach_icon() auto &icon_rsrc = achieved ? ach.icon : ach.icon_gray; // always force upload to GPU in background-loading mode (pagination > 0) bool loaded = try_load_ach_icon(ach, achieved, true); - + #ifndef EMU_RELEASE_BUILD if (loaded) { auto now2 = std::chrono::high_resolution_clock::now(); @@ -1712,10 +2047,10 @@ void Steam_Overlay::UnSetupOverlay() // this hacky solution just sets it to an empty function _renderer->OverlayHookReady = [](InGameOverlay::OverlayHookState){}; _renderer->OverlayProc = [](){}; - + allow_renderer_frame_processing(false, true); obscure_game_input(false); - + PRINT_DEBUG("releasing any images resources"); for (auto &ach : achievements) { if (ach.icon->GetResourceId() != 0) { @@ -1735,7 +2070,7 @@ void Steam_Overlay::UnSetupOverlay() cleanup_renderer_hook(); } - + PRINT_DEBUG("done *********"); } @@ -1785,7 +2120,7 @@ void Steam_Overlay::OpenOverlay(const char* pchDialog) PRINT_DEBUG("TODO '%s'", pchDialog); std::lock_guard lock(overlay_mutex); if (!Ready()) return; - + // TODO: Show pages depending on pchDialog if ((strncmp(pchDialog, "Friends", sizeof("Friends") - 1) == 0) && (settings->overlayAutoAcceptInvitesCount() > 0)) { PRINT_DEBUG("won't open overlay's friends list because some friends are defined in the auto accept list"); @@ -1800,7 +2135,7 @@ void Steam_Overlay::OpenOverlayWebpage(const char* pchURL) PRINT_DEBUG("TODO '%s'", pchURL); std::lock_guard lock(overlay_mutex); if (!Ready()) return; - + show_url = pchURL; ShowOverlay(true); } @@ -1817,9 +2152,9 @@ void Steam_Overlay::ShowOverlay(bool state) show_overlay = state; overlay_state_changed = true; - + PRINT_DEBUG("%i", (int)state); - + Steam_Overlay::allow_renderer_frame_processing(state); Steam_Overlay::obscure_game_input(state); @@ -1873,7 +2208,7 @@ void Steam_Overlay::FriendConnect(Friend _friend) // players connections might happen earlier before the overlay is ready // we don't want to miss them //if (!Ready()) return; - + int id = find_free_friend_id(friends); if (id != 0) { auto& item = friends[_friend]; @@ -1893,11 +2228,11 @@ void Steam_Overlay::FriendDisconnect(Friend _friend) PRINT_DEBUG("%" PRIu64 "", _friend.id()); std::lock_guard lock(overlay_mutex); - + // players connections might happen earlier before the overlay is ready // we don't want to miss them //if (!Ready()) return; - + auto it = friends.find(_friend); if (it != friends.end()) friends.erase(it); @@ -1910,11 +2245,10 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh PRINT_DEBUG("'%s' %i", ach_name.c_str(), (int)for_progress); std::lock_guard lock(overlay_mutex); - if (!Ready()) return; - - // don't return early when disable_overlay_achievement_notification is true - // otherwise when you open the achievements list/menu you won't see the new unlock status + // Phase 1: Update achievement data regardless of Ready() state. + // This keeps the achievement list accurate even if the overlay isn't + // fully initialized yet (e.g., during the startup window before late_init_imgui). for (auto &a : achievements) { if (a.name == ach_name) { try { @@ -1927,9 +2261,12 @@ void Steam_Overlay::AddAchievementNotification(const std::string &ach_name, nloh a.max_progress = ach.value("max_progress", static_cast(0)); } catch(...) {} + // Phase 2: Only show notification if overlay is ready + if (!Ready()) return; + if (a.achieved && !for_progress) { // here we don't show the progress indications post_achievement_notification(a, for_progress); - notify_sound_user_achievement(); + // sound is now played when notification is actually shown (delayed with queue) } else if (for_progress && !settings->disable_overlay_achievement_progress) { // progress indication is shown for locked achievements only // post notification if this isn't a progress, or a progress and the user didn't disable these notifications post_achievement_notification(a, for_progress); @@ -2030,7 +2367,7 @@ void Steam_Overlay::steam_run_callback_friends_actions() // The user clicked on "Invite" (but invite all wasn't clicked) if (friend_info->second.window_state & window_state_invite) { invite_friend(friend_id, steamFriends, steamMatchmaking); - + friend_info->second.window_state &= ~window_state_invite; } // The user clicked on "Join" @@ -2051,7 +2388,7 @@ void Steam_Overlay::steam_run_callback_friends_actions() data.m_steamIDFriend.SetFromUint64(friend_id); strncpy(data.m_rgchConnect, friend_info->second.connect, k_cchMaxRichPresenceValueLength - 1); callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - + friend_info->second.window_state &= ~window_state_rich_invite; } else if (connect.length() > 0) { GameRichPresenceJoinRequested_t data = {}; @@ -2071,7 +2408,7 @@ void Steam_Overlay::steam_run_callback_friends_actions() callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } } - + friend_info->second.window_state &= ~window_state_join; } } @@ -2149,7 +2486,7 @@ void Steam_Overlay::networking_msg_received(Common_Message *msg) { if (msg->has_steam_messages()) { std::lock_guard lock(overlay_mutex); - + Friend frd; frd.set_id(msg->source_id()); auto friend_info = friends.find(frd); diff --git a/overlay_experimental/steam_overlay_stats.cpp b/overlay_experimental/steam_overlay_stats.cpp index f69a3dbf1..34b79c359 100644 --- a/overlay_experimental/steam_overlay_stats.cpp +++ b/overlay_experimental/steam_overlay_stats.cpp @@ -85,7 +85,7 @@ void Steam_Overlay_Stats::render_stats(int current_language) settings->overlay_appearance.stats_text_b, settings->overlay_appearance.stats_text_a )); - + std::stringstream stats_txt_buff{}; if (show_fps) { stats_txt_buff << translationFpsDisplay[current_language] diff --git a/package_win_debug.bat b/package_win_debug.bat index ae172a8d8..2f558965c 100644 --- a/package_win_debug.bat +++ b/package_win_debug.bat @@ -1,3 +1,3 @@ @echo off -call "package_win.bat" vs2022/debug 1 +call "package_win.bat" vs2026/debug 1 diff --git a/package_win_release.bat b/package_win_release.bat index eb49220b4..b6b396a60 100644 --- a/package_win_release.bat +++ b/package_win_release.bat @@ -1,3 +1,3 @@ @echo off -call "package_win.bat" vs2022/release +call "package_win.bat" vs2026/release diff --git a/post_build/README.release.md b/post_build/README.release.md index db5b5b370..0942896e2 100644 --- a/post_build/README.release.md +++ b/post_build/README.release.md @@ -98,6 +98,16 @@ See `steam_settings.EXAMPLE\subscribed_groups_clans.EXAMPLE.txt` for an example. --- +## Purchased Keys: +Some games request CD keys or proof-of-purchase keys for installed apps and DLCs. +You can provide these keys to the game with a `steam_settings\purchased_keys.txt` file. + +The format is `appid=KEY` with one entry per line. Comments start with `#`. + +See `steam_settings.EXAMPLE\purchased_keys.EXAMPLE.txt` for an example. + +--- + ## App paths: Some rare games might need to be provided one or more paths to app ids. For example the path to where a dlc is installed. @@ -226,9 +236,11 @@ An example that was made for payday 2 can be found in `steam_settings.EXAMPLE` Copy a `png`, or a `jpg`, or a `jpeg` image file to your `GSE Saves/settings` folder and name it `account_avatar`. You can also place this file inside the local `steam_settings` folder of the game. +You can set a default profile picture for users who are missing one by copying a similar file called `account_avatar_default`. + Players avatars are shared over the local network -You can find an example in `steam_settings.EXAMPLE` +You can find the example in `steam_settings.EXAMPLE` --- diff --git a/post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg b/post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg new file mode 100644 index 000000000..aa490cb4e Binary files /dev/null and b/post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg differ diff --git a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini index 7c21892ff..b436d4ec5 100644 --- a/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.app.EXAMPLE.ini @@ -23,6 +23,17 @@ unlock_all=0 1234=DLCNAME 56789=This is another example DLC name +[app::controller] +# 1=Enable SteamInput +# 0=Disable SteamInput +# default=0 +# SteamInput is automatically enabled when there are action sets in `steam_settings\controller\` +steam_input=0 + +# Controller type reported to SteamInput +# XBOX360 (default) | XBOXONE | PS3 | PS4 | PS5 | SWITCH +type=XBOX360 + [app::paths] # some rare games might need to be provided one or more paths to appids # for example the path to where a DLC is installed @@ -38,11 +49,11 @@ unlock_all=0 [app::cloud_save::general] # should the emu create the default directory for cloud saves on startup: # [Steam Install]/userdata/{Steam3AccountID}/{AppID}/ -# default=1 -create_default_dir=1 +# default=0 +create_default_dir=0 # should the emu create the directories specified in the cloud saves section of the current OS on startup -# default=1 -create_specific_dirs=1 +# default=0 +create_specific_dirs=0 # directories which should be created on startup, this is used for cloud saves # some games refuse to work unless these directories exist # there are reserved identifiers which are replaced at runtime diff --git a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini index 596861ad7..fd8acb53d 100644 --- a/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.main.EXAMPLE.ini @@ -125,19 +125,6 @@ disable_lobby_creation=0 # this will **not** work if the app is using native/OS web APIs # default=0 download_steamhttp_requests=0 -# sharing mode of the old P2P networking interface (ISteamNetworking). -# this defines how the gameserver and the client intances can read P2P packets received from other players. -# -# some older games require sharing the received packets, i.e if the gameserver instance received a packet, -# then the client instance should be able to detect/read it as well. -# other games won't work properly if the packets were shared, -# and the original receiver (either gameserver or client) must handle that packet -# -# 0=share packets flagged as "unreliable" (k_EP2PSendUnreliable | k_EP2PSendUnreliableNoDelay), otherwise don't share packets -# 1=always share the packets -# 2=never share the packets -# default=0 -old_p2p_packet_sharing_mode=0 # mostly workarounds for specific problems [main::misc] @@ -167,3 +154,7 @@ steam_game_stats_reports_dir=./path/relative/to/dll/ # some games may have extra bonuses/achievements when being or playing with a free-weekend player # default=0 free_weekend=0 +# very old versions of Team Fortress 2 don't properly handle 64-bit item IDs +# this workaround makes Goldberg only generate 32-bit item IDs +# default=0 +use_32bit_inventory_item_ids=0 diff --git a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini index 4831793c0..41449419e 100644 --- a/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini +++ b/post_build/steam_settings.EXAMPLE/configs.overlay.EXAMPLE.ini @@ -27,8 +27,8 @@ disable_achievement_notification=0 # default=0 disable_friend_notification=0 # 1=disable showing notifications for achievements progress -# default=0 -disable_achievement_progress=0 +# default=1 +disable_achievement_progress=1 # 1=disable any warning in the overlay # default=0 disable_warning_any=0 @@ -63,16 +63,48 @@ overlay_always_show_frametime=0 # default=0 overlay_always_show_playtime=0 +[overlay::hotkeys] +# combination of keys used to show/hide the overlay +# available values: +# shift +# ctrl +# alt +# tab +# fn1 +# fn2 +# ... +# ... +# fn12 +# +# you can use the '+' sign to combine multiple keys +# example: ctrl + shift + fn5 +# +# default=shift + tab +key_combo=shift + tab + [overlay::appearance] # load custom TrueType font from a path, it could be absolute, or relative # relative paths will be looked up inside the local folder "steam_settings/fonts" first, # if that wasn't found, it will be looked up inside the global folder "GSE Settings/settings/fonts" # default= Font_Override=Roboto-Medium.ttf +# custom font for achievement title (leave empty to use default font) +# default= +Font_Override_Achievement_Title= +# custom font for achievement description (leave empty to use default font) +# default= +Font_Override_Achievement_Description= # global font size # for built-in font, multiple of 16 is recommended. e.g. 16 32... # default=16.0 Font_Size=20.0 +# optional independent font sizes. <= 0 uses Font_Size +Font_Size_FPS=0.0 +Font_Size_Achievement_Title=0.0 +Font_Size_Achievement_Description=0.0 +# 1=bold font for achievement title +# default=0 +Font_Achievement_Title_Bold=0 # achievement icon size Icon_Size=64.0 @@ -105,6 +137,11 @@ Notification_Duration_Invitation=8.0 # duration of chat message Notification_Duration_Chat=4.0 +# minimum time interval between achievement notifications (in seconds) +# set to 0 to show all achievements immediately without delay +# default=0 +Achievement_Notification_Delay=0 + # format for the achievement unlock date/time, limited to 79 characters # if the output formatted string exceeded this limit, the builtin format will be used # look for the format here: https://en.cppreference.com/w/cpp/chrono/c/strftime diff --git a/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json b/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json index 34db32785..00ef7cd7b 100644 --- a/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json +++ b/post_build/steam_settings.EXAMPLE/default_items.EXAMPLE.json @@ -1,4 +1,10 @@ { - "2001": 1, - "2002": 1 + "1": { + "definition": 2001, + "quantity": 1 + }, + "2": { + "definition": 2002, + "quantity": 1 + } } \ No newline at end of file diff --git a/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json new file mode 100644 index 000000000..47dd9ae3c --- /dev/null +++ b/post_build/steam_settings.EXAMPLE/gc.EXAMPLE.json @@ -0,0 +1,4 @@ +{ + "gc_profile": "tf2", + "gc_version": 20091217 +} diff --git a/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt b/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt new file mode 100644 index 000000000..08b791e50 --- /dev/null +++ b/post_build/steam_settings.EXAMPLE/purchased_keys.EXAMPLE.txt @@ -0,0 +1,12 @@ +# Rename this to: purchased_keys.txt +# This file allows you to specify CD keys or proof-of-purchase keys for games and DLCs +# These keys will be returned when the game calls RequestAppProofOfPurchaseKey() or RequestAllProofOfPurchaseKeys() +# +# Format: appid=KEY +# Each line should contain one app ID and its corresponding key, separated by an equals sign +# Lines starting with # are treated as comments +# Empty lines are ignored +# +# Examples: +# 480=AAAAA-BBBBB-CCCCC-DDDDD +# 218620=XYZFJ-13370-98765 diff --git a/premake5-deps.lua b/premake5-deps.lua index 2c5c9b888..2c9a04631 100644 --- a/premake5-deps.lua +++ b/premake5-deps.lua @@ -1,11 +1,11 @@ -require("premake", ">=5.0.0-beta2") +require("premake", ">=5.0.0-beta8") -- don't forget to set env var CMAKE_GENERATOR to one of these values: -- https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#manual:cmake-generators(7) -- common ones -- ============ -- Unix Makefiles --- Visual Studio 17 2022 +-- Visual Studio 18 2026 -- MSYS Makefiles local os_iden = '' -- identifier @@ -104,6 +104,12 @@ newoption { description = "Extract portaudio", } +newoption { + category = "extract", + trigger = "ext-sdl", + description = "Extract sdl", +} + -- build newoption { category = "build", @@ -174,6 +180,12 @@ newoption { description = "Build portaudio", } +newoption { + category = "build", + trigger = "build-sdl", + description = "Build sdl", +} + local function merge_list(src, dest) local src_count = #src local res = {} @@ -340,7 +352,7 @@ local function cmake_build(dep_folder, is_32, extra_cmd_defs, c_flags_init, cxx_ -- write toolchain file local toolchain_file_content = '' if _OPTIONS["cmake-toolchain"] then - toolchain_file_content='include(' .. _OPTIONS["cmake-toolchain"] .. ')\n\n' + toolchain_file_content = 'include(' .. _OPTIONS["cmake-toolchain"] .. ')\n\n' end if #cflags_init_str > 0 then toolchain_file_content = toolchain_file_content .. 'set(CMAKE_C_FLAGS_INIT "' .. cflags_init_str .. '" )\n' @@ -465,6 +477,9 @@ end if _OPTIONS["ext-portaudio"] or _OPTIONS["all-ext"] then table.insert(deps_to_extract, { 'portaudio/portaudio.tar.gz', 'portaudio' }) end +if _OPTIONS["ext-sdl"] or _OPTIONS["all-ext"] then + table.insert(deps_to_extract, { 'sdl/sdl.tar.gz', 'sdl' }) +end -- start extraction for _, dep in pairs(deps_to_extract) do @@ -574,12 +589,12 @@ local mbedx509_name = '' -- name if _ACTION and os.target() == 'windows' then if string.match(_ACTION, 'vs.+') then - zlib_name = 'zlibstatic' + zlib_name = 'zs' mbedtls_name = 'mbedtls' mbedcrypto_name = 'mbedcrypto' mbedx509_name = 'mbedx509' elseif string.match(_ACTION, 'gmake.*') then - zlib_name = 'libzlibstatic' + zlib_name = 'libzs' mbedtls_name = 'libmbedtls' mbedcrypto_name = 'libmbedcrypto' mbedx509_name = 'libmbedx509' @@ -666,7 +681,7 @@ if _OPTIONS["build-curl"] or _OPTIONS["all-build"] then "CURL_USE_MBEDTLS=ON", -- "CURL_USE_SCHANNEL=ON", - "CURL_CA_FALLBACK=ON", + "CURL_CA_FALLBACK=OFF", -- fix building on Arch Linux "CURL_USE_LIBSSH2=OFF", @@ -824,3 +839,23 @@ if _OPTIONS["build-portaudio"] or _OPTIONS["all-build"] then cmake_build('portaudio', false, portaudio_common_defs) end end + +if _OPTIONS["build-sdl"] or _OPTIONS["all-build"] then + local sdl_common_defs = { + -- enable / disable SDL subsystems + "SDL_AUDIO=OFF", + "SDL_VIDEO=OFF", + "SDL_HIDAPI=OFF", + "SDL_SENSOR=OFF", + "SDL_DIALOG=OFF", + "SDL_TRAY=OFF", + "SDL_UNIX_CONSOLE_BUILD=ON", + } + + if _OPTIONS["32-build"] then + cmake_build('sdl', true, sdl_common_defs) + end + if _OPTIONS["64-build"] then + cmake_build('sdl', false, sdl_common_defs) + end +end \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index e70176b58..80f5bb192 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,4 +1,4 @@ -require("premake", ">=5.0.0-beta2") +require("premake", ">=5.0.0-beta8") -- add "-Wl,--whole-archive -Wl,-Bstatic -lmylib -Wl,-Bdynamic -Wl,--no-whole-archive" @@ -161,6 +161,7 @@ local function genproto() local protoc_exe = path.join(deps_dir, 'protobuf', deps_install_prefix, 'bin', 'protoc') local out_dir = 'proto_gen/' .. os_iden + local out_dir_tf2 = out_dir .. "/tf2" if os.target() == "windows" then protoc_exe = protoc_exe .. '.exe' @@ -178,6 +179,12 @@ local function genproto() return end + local ok_mk, err_mk = os.mkdir(out_dir_tf2) + if not ok_mk then + error("Error: " .. err_mk) + return + end + if os.target() == "linux" then local ok_chmod, err_chmod = os.chmod(protoc_exe, "777") if not ok_chmod then @@ -190,6 +197,12 @@ local function genproto() protoc_exe = 'wine ' .. protoc_exe end + if not os.execute(protoc_exe .. ' dll/gc_steam/steammessages.proto -I./dll/gc_steam --cpp_out=' .. out_dir ) then + return + end + if not os.execute(protoc_exe .. ' dll/gc_tf2/*.proto -I./dll/gc_steam -I./dll/gc_tf2 --cpp_out=' .. out_dir_tf2 ) then + return + end return os.execute(protoc_exe .. ' dll/net.proto -I./dll/ --cpp_out=' .. out_dir) end @@ -221,6 +234,7 @@ local x32_deps_include = { path.join(deps_dir, "mbedtls/install32/include"), path.join(deps_dir, "opus/install32/include"), path.join(deps_dir, "portaudio/install32/include"), + path.join(deps_dir, "sdl/install32/include"), } local x32_deps_overlay_include = { @@ -237,6 +251,7 @@ local x64_deps_include = { path.join(deps_dir, "mbedtls/install64/include"), path.join(deps_dir, "opus/install64/include"), path.join(deps_dir, "portaudio/install64/include"), + path.join(deps_dir, "sdl/install64/include"), } local x64_deps_overlay_include = { @@ -287,7 +302,12 @@ end local zlib_archive_name = 'z' if os.target() == 'windows' then - zlib_archive_name = 'zlibstatic' -- even on MinGw we need this name + zlib_archive_name = 'zs' -- even on MinGw we need this name +end + +local sdl_name = 'SDL3' +if os.target() == 'windows' then + sdl_name = 'SDL3-static' end local deps_link = { @@ -299,13 +319,11 @@ local deps_link = { "mbedx509" .. static_postfix, "opus" .. static_postfix, "portaudio" .. static_postfix, + sdl_name .. static_postfix, } -- add protobuf libs table_append(deps_link, { - lib_prefix .. "protobuf-lite" .. static_postfix, - "absl_bad_any_cast_impl" .. static_postfix, - "absl_bad_optional_access" .. static_postfix, - "absl_bad_variant_access" .. static_postfix, + lib_prefix .. "protobuf" .. static_postfix, "absl_base" .. static_postfix, "absl_city" .. static_postfix, "absl_civil_time" .. static_postfix, @@ -344,7 +362,6 @@ table_append(deps_link, { "absl_int128" .. static_postfix, "absl_kernel_timeout_internal" .. static_postfix, "absl_leak_check" .. static_postfix, - "absl_log_entry" .. static_postfix, "absl_log_flags" .. static_postfix, "absl_log_globals" .. static_postfix, "absl_log_initialize" .. static_postfix, @@ -367,7 +384,7 @@ table_append(deps_link, { "absl_random_distributions" .. static_postfix, "absl_random_internal_distribution_test_util" .. static_postfix, "absl_random_internal_platform" .. static_postfix, - "absl_random_internal_pool_urbg" .. static_postfix, + "absl_random_internal_entropy_pool" .. static_postfix, "absl_random_internal_randen" .. static_postfix, "absl_random_internal_randen_hwaes" .. static_postfix, "absl_random_internal_randen_hwaes_impl" .. static_postfix, @@ -407,6 +424,7 @@ local common_link_win = { "Winmm" .. static_postfix, "Bcrypt" .. static_postfix, "Dbghelp" .. static_postfix, + "ntdll" .. static_postfix, -- gamepad "Xinput" .. static_postfix, -- imgui / overlay @@ -452,6 +470,7 @@ local x32_deps_libdir = { path.join(deps_dir, "mbedtls/install32/lib"), path.join(deps_dir, "opus/install32/lib"), path.join(deps_dir, "portaudio/install32/lib"), + path.join(deps_dir, "sdl/install32/lib"), } local x32_deps_overlay_libdir = { @@ -469,6 +488,7 @@ local x64_deps_libdir = { path.join(deps_dir, "ingame_overlay/install64/lib"), path.join(deps_dir, "opus/install64/lib"), path.join(deps_dir, "portaudio/install64/lib"), + path.join(deps_dir, "sdl/install64/lib"), } local x64_deps_overlay_libdir = { @@ -508,7 +528,7 @@ end filter {} -- reset the filter and remove all active keywords configurations { "debug", "release", } -platforms { "x64", "x32", } +platforms { "x64", "x86", } language "C++" cppdialect "C++17" cdialect "C17" @@ -518,11 +538,9 @@ filter {} -- reset the filter and remove all active keywords characterset "Unicode" staticruntime "on" -- /MT or /MTd runtime "Release" -- ensure we never link with /MTd, otherwise deps linking will fail -flags { - "NoPCH", -- no precompiled header on Windows - "MultiProcessorCompile", -- /MP "Enable Visual Studio to use multiple compiler processes when building" - "RelativeLinks", -} +enablepch "Off" -- no precompiled header on Windows +multiprocessorcompile "On" -- /MP "Enable Visual Studio to use multiple compiler processes when building" +userelativelinks "On" targetprefix "" -- prevent adding the prefix libxxx on linux visibility "Hidden" -- hide all symbols by default on GCC (unless they are marked visible) linkgroups "On" -- turn off the awful order dependent linking on gcc/clang, causes the linker to go back and forth to find missing symbols @@ -553,7 +571,7 @@ vpaths { -- just for visual niceness, see: https://premake.github.io/docs/vpaths -- arch --------- -filter { "platforms:x32", } +filter { "platforms:x86", } architecture "x86" filter { "platforms:x64", } architecture "x86_64" @@ -673,7 +691,7 @@ filter { 'options:incexamples', 'system:not windows', } } -- deps -filter { 'options:incdeps', "platforms:x32", } +filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_include, '/**.h'), table_postfix_items(x32_deps_include, '/**.hxx'), @@ -719,7 +737,7 @@ filter { "system:windows", "options:dosstub", } -- sign filter { "system:windows", "options:winsign", } postbuildcommands { - '"' .. signer_tool .. '" %[%{!cfg.buildtarget.abspath}]', + '"' .. signer_tool .. '" %[%{!cfg.buildtarget.directory}%{!cfg.buildtarget.name}]', } filter {} -- reset the filter and remove all active keywords end @@ -740,7 +758,7 @@ project "api_regular" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steam_api" filter { "system:windows", "platforms:x64", } targetname "steam_api64" @@ -749,7 +767,7 @@ project "api_regular" -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -776,7 +794,7 @@ project "api_regular" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/api/32/resources.rc" } @@ -805,7 +823,7 @@ project "api_regular" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } @@ -827,7 +845,7 @@ project "api_experimental" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steam_api" filter { "system:windows", "platforms:x64", } targetname "steam_api64" @@ -847,7 +865,7 @@ project "api_experimental" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, x32_deps_overlay_include, @@ -871,7 +889,7 @@ project "api_experimental" 'libs/detours/uimports.cc', } -- deps - filter { 'options:incdeps', "platforms:x32", } + filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_overlay_include, '/**.h'), table_postfix_items(x32_deps_overlay_include, '/**.hxx'), @@ -889,7 +907,7 @@ project "api_experimental" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/api/32/resources.rc" } @@ -928,7 +946,7 @@ project "api_experimental" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, x32_deps_overlay_libdir, @@ -958,7 +976,7 @@ project "steamclient_experimental" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "steamclient" filter { "system:windows", "platforms:x64", } targetname "steamclient64" @@ -978,7 +996,7 @@ project "steamclient_experimental" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, x32_deps_overlay_include, @@ -1004,7 +1022,7 @@ project "steamclient_experimental" 'dll/flat.cpp', } -- deps - filter { 'options:incdeps', "platforms:x32", } + filter { 'options:incdeps', "platforms:x86", } files { table_postfix_items(x32_deps_overlay_include, '/**.h'), table_postfix_items(x32_deps_overlay_include, '/**.hxx'), @@ -1022,7 +1040,7 @@ project "steamclient_experimental" "dll/wrap.cpp" } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1060,7 +1078,7 @@ project "steamclient_experimental" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, x32_deps_overlay_libdir, @@ -1098,7 +1116,7 @@ project "tool_lobby_connect" --------- -- common include dir -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1123,7 +1141,7 @@ project "tool_lobby_connect" 'dll/flat.cpp', } -- Windows x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/launcher/32/resources.rc" } @@ -1153,7 +1171,7 @@ project "tool_lobby_connect" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } @@ -1220,7 +1238,7 @@ project "lib_game_overlay_renderer" -- name --------- - filter { "system:windows", "platforms:x32", } + filter { "system:windows", "platforms:x86", } targetname "GameOverlayRenderer" filter { "system:windows", "platforms:x64", } targetname "GameOverlayRenderer64" @@ -1231,7 +1249,7 @@ project "lib_game_overlay_renderer" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1252,7 +1270,7 @@ project "lib_game_overlay_renderer" "common_helpers/os_detector.h", } -- x32 common source files - filter { "system:windows", "platforms:x32", "options:winrsrc", } + filter { "system:windows", "platforms:x86", "options:winrsrc", } files { "resources/win/game_overlay_renderer/32/resources.rc" } @@ -1280,7 +1298,7 @@ project "steamclient_experimental_stub" -- name --------- - filter { "platforms:x32", } + filter { "platforms:x86", } targetname "steamclient" filter { "platforms:x64", } targetname "steamclient64" @@ -1293,7 +1311,7 @@ project "steamclient_experimental_stub" "steamclient/steamclient.cpp", } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1316,7 +1334,7 @@ project "steamclient_experimental_extra" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1342,7 +1360,7 @@ project "steamclient_experimental_extra" 'libs/detours/uimports.cc', } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/client/32/resources.rc" } @@ -1434,7 +1452,7 @@ project "steamclient_experimental_loader" "libs/simpleini/**", } -- x32 common source files - filter { "platforms:x32", "options:winrsrc", } + filter { "platforms:x86", "options:winrsrc", } files { "resources/win/launcher/32/resources.rc" } @@ -1546,7 +1564,7 @@ project "steamclient_regular" -- include dir --------- -- x32 include dir - filter { "platforms:x32", } + filter { "platforms:x86", } includedirs { x32_deps_include, } @@ -1579,7 +1597,7 @@ project "steamclient_regular" -- libs search dir --------- -- x32 libs search dir - filter { "platforms:x32", } + filter { "platforms:x86", } libdirs { x32_deps_libdir, } @@ -1661,6 +1679,46 @@ project "test_crash_printer_sa_sigaction" } -- End test_crash_printer_sa_sigaction + +-- Project test_gamepad_linux +--------- +project "test_gamepad_linux" + kind "ConsoleApp" + location "%{wks.location}/%{prj.name}" + targetdir(path.join(build_dir, os_iden, _ACTION, "%{cfg.buildcfg}/tests/gamepad")) + targetname "test_gamepad_linux_%{cfg.platform}" + + + -- defines + --------- + filter {} -- reset the filter and remove all active keywords + defines { + "GAMEPAD_TESTING", + } + + + -- common source & header files + --------- + filter {} -- reset the filter and remove all active keywords + files { + 'libs/gamepad/gamepad.c', + 'libs/gamepad/gamepad.h', + 'tests/gamepad/test_gamepad_linux.cpp', + } + removefiles { + 'post_build/**', + 'build/deps/**', + } + + + -- post build + --------- + filter {} -- reset the filter and remove all active keywords + postbuildcommands { + '%[%{!cfg.buildtarget.abspath}]', + } +-- End test_gamepad_linux + end -- End LINUX ONLY TARGETS diff --git a/sdk/steam/isteamapps.h b/sdk/steam/isteamapps.h index 820b7775b..618244da9 100644 --- a/sdk/steam/isteamapps.h +++ b/sdk/steam/isteamapps.h @@ -116,13 +116,13 @@ class ISteamApps virtual int GetNumBetas( int *pnAvailable, int *pnPrivate ) = 0; // // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) - virtual bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription ) = 0; // iterate through + virtual bool GetBetaInfo( int iBetaIndex, uint32 *punFlags, uint32 *punBuildID, char *pchBetaName, int cchBetaName, char *pchDescription, int cchDescription, uint32* punLastUpdated) = 0; // iterate through // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch virtual bool SetActiveBeta( const char *pchBetaName ) = 0; }; -#define STEAMAPPS_INTERFACE_VERSION "STEAMAPPS_INTERFACE_VERSION008" +#define STEAMAPPS_INTERFACE_VERSION "STEAMAPPS_INTERFACE_VERSION009" #ifndef STEAM_API_EXPORTS // Global interface accessor diff --git a/sdk/steam/isteamapps008.h b/sdk/steam/isteamapps008.h new file mode 100644 index 000000000..17aa65e8c --- /dev/null +++ b/sdk/steam/isteamapps008.h @@ -0,0 +1,112 @@ + +#ifndef ISTEAMAPPS008_H +#define ISTEAMAPPS008_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +class ISteamApps008 +{ +public: + virtual bool BIsSubscribed() = 0; + virtual bool BIsLowViolence() = 0; + virtual bool BIsCybercafe() = 0; + virtual bool BIsVACBanned() = 0; + virtual const char* GetCurrentGameLanguage() = 0; + virtual const char* GetAvailableGameLanguages() = 0; + + // only use this member if you need to check ownership of another game related to yours, a demo for example + virtual bool BIsSubscribedApp(AppId_t appID) = 0; + + // Takes AppID of DLC and checks if the user owns the DLC & if the DLC is installed + virtual bool BIsDlcInstalled(AppId_t appID) = 0; + + // returns the Unix time of the purchase of the app + virtual uint32 GetEarliestPurchaseUnixTime(AppId_t nAppID) = 0; + + // Checks if the user is subscribed to the current app through a free weekend + // This function will return false for users who have a retail or other type of license + // Before using, please ask your Valve technical contact how to package and secure your free weekend + virtual bool BIsSubscribedFromFreeWeekend() = 0; + + // Returns the number of DLC pieces for the running app + virtual int GetDLCCount() = 0; + + // Returns metadata for DLC by index, of range [0, GetDLCCount()] + virtual bool BGetDLCDataByIndex(int iDLC, AppId_t* pAppID, bool* pbAvailable, char* pchName, int cchNameBufferSize) = 0; + + // Install/Uninstall control for optional DLC + virtual void InstallDLC(AppId_t nAppID) = 0; + virtual void UninstallDLC(AppId_t nAppID) = 0; + + // Request legacy cd-key for yourself or owned DLC. If you are interested in this + // data then make sure you provide us with a list of valid keys to be distributed + // to users when they purchase the game, before the game ships. + // You'll receive an AppProofOfPurchaseKeyResponse_t callback when + // the key is available (which may be immediately). + virtual void RequestAppProofOfPurchaseKey(AppId_t nAppID) = 0; + + virtual bool GetCurrentBetaName(char* pchName, int cchNameBufferSize) = 0; // returns current beta branch name, 'public' is the default branch + virtual bool MarkContentCorrupt(bool bMissingFilesOnly) = 0; // signal Steam that game files seems corrupt or missing + virtual uint32 GetInstalledDepots(AppId_t appID, DepotId_t* pvecDepots, uint32 cMaxDepots) = 0; // return installed depots in mount order + + // returns current app install folder for AppID, returns folder name length + virtual uint32 GetAppInstallDir(AppId_t appID, char* pchFolder, uint32 cchFolderBufferSize) = 0; + virtual bool BIsAppInstalled(AppId_t appID) = 0; // returns true if that app is installed (not necessarily owned) + + // returns the SteamID of the original owner. If this CSteamID is different from ISteamUser::GetSteamID(), + // the user has a temporary license borrowed via Family Sharing + virtual CSteamID GetAppOwner() = 0; + + // Returns the associated launch param if the game is run via steam://run///?param1=value1¶m2=value2¶m3=value3 etc. + // Parameter names starting with the character '@' are reserved for internal use and will always return and empty string. + // Parameter names starting with an underscore '_' are reserved for steam features -- they can be queried by the game, + // but it is advised that you not param names beginning with an underscore for your own features. + // Check for new launch parameters on callback NewUrlLaunchParameters_t + virtual const char* GetLaunchQueryParam(const char* pchKey) = 0; + + // get download progress for optional DLC + virtual bool GetDlcDownloadProgress(AppId_t nAppID, uint64* punBytesDownloaded, uint64* punBytesTotal) = 0; + + // return the buildid of this app, may change at any time based on backend updates to the game + virtual int GetAppBuildId() = 0; + + // Request all proof of purchase keys for the calling appid and associated DLC. + // A series of AppProofOfPurchaseKeyResponse_t callbacks will be sent with + // appropriate appid values, ending with a final callback where the m_nAppId + // member is k_uAppIdInvalid (zero). + virtual void RequestAllProofOfPurchaseKeys() = 0; + + STEAM_CALL_RESULT(FileDetailsResult_t) + virtual SteamAPICall_t GetFileDetails(const char* pszFileName) = 0; + + // Get command line if game was launched via Steam URL, e.g. steam://run////. + // This method of passing a connect string (used when joining via rich presence, accepting an + // invite, etc) is preferable to passing the connect string on the operating system command + // line, which is a security risk. In order for rich presence joins to go through this + // path and not be placed on the OS command line, you must set a value in your app's + // configuration on Steam. Ask Valve for help with this. + // + // If game was already running and launched again, the NewUrlLaunchParameters_t will be fired. + virtual int GetLaunchCommandLine(char* pszCommandLine, int cubCommandLine) = 0; + + // Check if user borrowed this game via Family Sharing, If true, call GetAppOwner() to get the lender SteamID + virtual bool BIsSubscribedFromFamilySharing() = 0; + + // check if game is a timed trial with limited playtime + virtual bool BIsTimedTrial(uint32* punSecondsAllowed, uint32* punSecondsPlayed) = 0; + + // set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions + virtual bool SetDlcContext(AppId_t nAppID) = 0; + + // returns total number of known app beta branches (including default "public" branch ) + virtual int GetNumBetas(int* pnAvailable, int* pnPrivate) = 0; // + + // return beta branch details, name, description, current BuildID and state flags (EBetaBranchFlags) + virtual bool GetBetaInfo(int iBetaIndex, uint32* punFlags, uint32* punBuildID, char* pchBetaName, int cchBetaName, char* pchDescription, int cchDescription) = 0; // iterate through + + // select this beta branch for this app as active, might need the game to restart so Steam can update to that branch + virtual bool SetActiveBeta(const char* pchBetaName) = 0; +}; + +#endif //ISTEAMAPPS008_H diff --git a/sdk/steam/isteamclient001.h b/sdk/steam/isteamclient001.h index cf421b73f..239e2065b 100644 --- a/sdk/steam/isteamclient001.h +++ b/sdk/steam/isteamclient001.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient001 { public: diff --git a/sdk/steam/isteamclient002.h b/sdk/steam/isteamclient002.h index a8bc9be46..05dd2573d 100644 --- a/sdk/steam/isteamclient002.h +++ b/sdk/steam/isteamclient002.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient002 { public: diff --git a/sdk/steam/isteamclient003.h b/sdk/steam/isteamclient003.h index 267072611..2961c911c 100644 --- a/sdk/steam/isteamclient003.h +++ b/sdk/steam/isteamclient003.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient003 { public: diff --git a/sdk/steam/isteamclient004.h b/sdk/steam/isteamclient004.h index 7a6aafb61..8cdd0b897 100644 --- a/sdk/steam/isteamclient004.h +++ b/sdk/steam/isteamclient004.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient004 { public: diff --git a/sdk/steam/isteamclient005.h b/sdk/steam/isteamclient005.h index 614537c0d..3a4c90e91 100644 --- a/sdk/steam/isteamclient005.h +++ b/sdk/steam/isteamclient005.h @@ -7,8 +7,6 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries -#include "isteammasterserverupdater.h" - class ISteamClient005 { public: diff --git a/sdk/steam/isteamcontroller.h b/sdk/steam/isteamcontroller.h index f0485dc8a..adb103eb8 100644 --- a/sdk/steam/isteamcontroller.h +++ b/sdk/steam/isteamcontroller.h @@ -506,6 +506,93 @@ enum EControllerActionOrigin k_EControllerActionOrigin_Horipad_L4, k_EControllerActionOrigin_Horipad_R4, + k_EControllerActionOrigin_LenovoLegionGo_A, + k_EControllerActionOrigin_LenovoLegionGo_B, + k_EControllerActionOrigin_LenovoLegionGo_X, + k_EControllerActionOrigin_LenovoLegionGo_Y, + k_EControllerActionOrigin_LenovoLegionGo_LB, + k_EControllerActionOrigin_LenovoLegionGo_RB, + k_EControllerActionOrigin_LenovoLegionGo_Menu, + k_EControllerActionOrigin_LenovoLegionGo_View, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Touch, // Left pad is only present on the original Legion Go + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Swipe, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_Click, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_LeftPad_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Touch, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Swipe, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_Click, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_RightPad_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_LT_SoftPull, + k_EControllerActionOrigin_LenovoLegionGo_LT, + k_EControllerActionOrigin_LenovoLegionGo_RT_SoftPull, + k_EControllerActionOrigin_LenovoLegionGo_RT, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_Move, + k_EControllerActionOrigin_LenovoLegionGo_LS, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_LeftStick_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_Move, + k_EControllerActionOrigin_LenovoLegionGo_RS, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadNorth, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadSouth, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadWest, + k_EControllerActionOrigin_LenovoLegionGo_RightStick_DPadEast, + k_EControllerActionOrigin_LenovoLegionGo_Y1, + k_EControllerActionOrigin_LenovoLegionGo_Y2, + k_EControllerActionOrigin_LenovoLegionGo_DPad_Move, + k_EControllerActionOrigin_LenovoLegionGo_DPad_North, + k_EControllerActionOrigin_LenovoLegionGo_DPad_South, + k_EControllerActionOrigin_LenovoLegionGo_DPad_West, + k_EControllerActionOrigin_LenovoLegionGo_DPad_East, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Move, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Pitch, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Yaw, + k_EControllerActionOrigin_LenovoLegionGo_Gyro_Roll, + k_EControllerActionOrigin_LenovoLegionGo_Reserved1, + k_EControllerActionOrigin_LenovoLegionGo_Reserved2, + k_EControllerActionOrigin_LenovoLegionGo_Reserved3, + k_EControllerActionOrigin_LenovoLegionGo_Reserved4, + k_EControllerActionOrigin_LenovoLegionGo_Reserved5, + k_EControllerActionOrigin_LenovoLegionGo_Reserved6, + k_EControllerActionOrigin_LenovoLegionGo_Reserved7, + k_EControllerActionOrigin_LenovoLegionGo_Reserved8, + k_EControllerActionOrigin_LenovoLegionGo_Reserved9, + k_EControllerActionOrigin_LenovoLegionGo_Reserved10, + k_EControllerActionOrigin_LenovoLegionGo_Reserved11, + k_EControllerActionOrigin_LenovoLegionGo_Reserved12, + k_EControllerActionOrigin_LenovoLegionGo_Reserved13, + k_EControllerActionOrigin_LenovoLegionGo_Reserved14, + k_EControllerActionOrigin_LenovoLegionGo_Reserved15, + k_EControllerActionOrigin_LenovoLegionGo_Reserved16, + k_EControllerActionOrigin_LenovoLegionGo_Reserved17, + k_EControllerActionOrigin_LenovoLegionGo_Reserved18, + k_EControllerActionOrigin_LenovoLegionGo_Reserved19, + k_EControllerActionOrigin_LenovoLegionGo_Reserved20, + + k_EControllerActionOrigin_Generic_L4, + k_EControllerActionOrigin_Generic_R4, + k_EControllerActionOrigin_Generic_L5, + k_EControllerActionOrigin_Generic_R5, + k_EControllerActionOrigin_Generic_PL, + k_EControllerActionOrigin_Generic_PR, + k_EControllerActionOrigin_Generic_C, + k_EControllerActionOrigin_Generic_Z, + k_EControllerActionOrigin_Generic_MISC1, + k_EControllerActionOrigin_Generic_MISC2, + k_EControllerActionOrigin_Generic_MISC3, + k_EControllerActionOrigin_Generic_MISC4, + k_EControllerActionOrigin_Generic_MISC5, + k_EControllerActionOrigin_Generic_MISC6, + k_EControllerActionOrigin_Generic_MISC7, + k_EControllerActionOrigin_Generic_MISC8, + k_EControllerActionOrigin_Count, // If Steam has added support for new controllers origins will go here. k_EControllerActionOrigin_MaximumPossibleValue = 32767, // Origins are currently a maximum of 16 bits. }; diff --git a/sdk/steam/isteamgameserveritems.h b/sdk/steam/isteamgameserveritems.h new file mode 100644 index 000000000..60c7ba96f --- /dev/null +++ b/sdk/steam/isteamgameserveritems.h @@ -0,0 +1,155 @@ + +#ifndef ISTEAMGAMESERVERITEMS_H +#define ISTEAMGAMESERVERITEMS_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +#include "isteamuseritems.h" + +enum EItemCriteriaOperator +{ + k_EOperator_String_EQ = 0, + k_EOperator_Not, + k_EOperator_String_Not_EQ = k_EOperator_Not, + k_EOperator_Float_EQ, + k_EOperator_Float_Not_EQ, + k_EOperator_Float_LT, + k_EOperator_Float_Not_LT, + k_EOperator_Float_LTE, + k_EOperator_Float_Not_LTE, + k_EOperator_Float_GT, + k_EOperator_Float_Not_GT, + k_EOperator_Float_GTE, + k_EOperator_Float_Not_GTE, + k_EOperator_Subkey_Contains, + k_EOperator_Subkey_Not_Contains, + k_EItemCriteriaOperator_Count, +}; + +typedef int32 HNewItemRequest; + +class ISteamGameServerItems +{ +public: + virtual SteamAPICall_t LoadItems( CSteamID ownerID ) = 0; + virtual SteamAPICall_t GetItemCount( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID ) = 0; + virtual bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) = 0; + virtual bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) = 0; + virtual bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) = 0; + virtual bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual SteamAPICall_t SendNewItemRequest( HNewItemRequest handle ) = 0; + virtual SteamAPICall_t GrantItemToUser( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual SteamAPICall_t DeleteTemporaryItem( uint64 ulItemID ) = 0; + virtual SteamAPICall_t DeleteAllTemporaryItems() = 0; + virtual SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#define STEAMGAMESERVERITEMS_INTERFACE_VERSION "STEAMGAMESERVERITEMS_INTERFACE_VERSION004" + +struct GSItemCount_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 0 }; + CSteamID m_OwnerID; + EItemRequestResult m_eResult; + uint32 m_unCount; +}; + +struct GSNewItemRequestResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 1 }; + CSteamID m_SteamID; + EItemRequestResult m_eResult; + uint64 m_ulItemID; + HNewItemRequest m_hRequest; +}; + +struct GSItemInventoryPosUpdated_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 2 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSItemDeleted_t // renamed from GSItemDropped_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 3 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSGrantItemToUser_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 4 }; + EItemRequestResult m_eResult; + uint64 m_ulItemIDOld; + uint64 m_ulItemIDPermanent; + CSteamID m_SteamID; +}; + +struct GSDeleteTemporaryItem_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 5 }; + EItemRequestResult m_eResult; + uint64 m_ulItemID; +}; + +struct GSDeleteAllTemporaryItems_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 6 }; + EItemRequestResult m_eResult; +}; + +struct GSItemAdded_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 7 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +struct GSGetItemBlobResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 8 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; + uint8 m_pubBlob[1024]; + int32 m_cubBlob; +}; + +struct GSSetItemBlobResponse_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 9 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct GSUpdateQuantity_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 10 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct GSItemsRefreshed_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 11 }; +}; + +struct GSItemDropped_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 12 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +#endif // ISTEAMGAMESERVERITEMS_H diff --git a/sdk/steam/isteamgameserveritems001.h b/sdk/steam/isteamgameserveritems001.h new file mode 100644 index 000000000..c727c7ff9 --- /dev/null +++ b/sdk/steam/isteamgameserveritems001.h @@ -0,0 +1,31 @@ + +#ifndef ISTEAMGAMESERVERITEMS001_H +#define ISTEAMGAMESERVERITEMS001_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems001 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; +}; + +struct GSItemDropped001_t +{ + enum { k_iCallback = k_iSteamGameServerItemsCallbacks + 3 }; + CSteamID m_SteamID; + uint64 m_ulItemID; +}; + +#endif // ISTEAMGAMESERVERITEMS001_H diff --git a/sdk/steam/isteamgameserveritems002.h b/sdk/steam/isteamgameserveritems002.h new file mode 100644 index 000000000..fcd607963 --- /dev/null +++ b/sdk/steam/isteamgameserveritems002.h @@ -0,0 +1,27 @@ + +#ifndef ISTEAMGAMESERVERITEMS002_H +#define ISTEAMGAMESERVERITEMS002_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems002 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID, uint32 unItemLevel, EItemQuality eQuality ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; + virtual void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual void DeleteTemporaryItem_old( uint64 ulItemID ) = 0; + virtual void DeleteAllTemporaryItems_old() = 0; +}; + +#endif // ISTEAMGAMESERVERITEMS002_H diff --git a/sdk/steam/isteamgameserveritems003.h b/sdk/steam/isteamgameserveritems003.h new file mode 100644 index 000000000..2cb5523ed --- /dev/null +++ b/sdk/steam/isteamgameserveritems003.h @@ -0,0 +1,34 @@ + +#ifndef ISTEAMGAMESERVERITEMS003_H +#define ISTEAMGAMESERVERITEMS003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamGameServerItems003 +{ +public: + virtual void LoadItems_old( CSteamID ownerID ) = 0; + virtual void GetItemCount_old( CSteamID ownerID ) = 0; + virtual bool GetItemIterative( CSteamID ownerID, uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, CSteamID *pOwnerID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual HNewItemRequest CreateNewItemRequest( CSteamID steamID ) = 0; + virtual bool AddNewItemLevel( HNewItemRequest handle, uint32 unItemLevel ) = 0; + virtual bool AddNewItemQuality( HNewItemRequest handle, EItemQuality eQuality ) = 0; + virtual bool SetNewItemInitialInventoryPos( HNewItemRequest handle, uint32 unInventoryPos ) = 0; + virtual bool SetNewItemInitialQuantity( HNewItemRequest handle, uint32 unQuantity ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, const char *pchValue, bool bRequired ) = 0; + virtual bool AddNewItemCriteria( HNewItemRequest handle, const char *pchField, EItemCriteriaOperator eOperator, float flValue, bool bRequired ) = 0; + virtual void SendNewItemRequest_old( HNewItemRequest handle ) = 0; + virtual void GrantItemToUser_old( uint64 ulItemID, CSteamID steamIDRecipient ) = 0; + virtual void DeleteTemporaryItem_old( uint64 ulItemID ) = 0; + virtual void DeleteAllTemporaryItems_old() = 0; + virtual SteamAPICall_t UpdateQuantity( uint64 ulItemID, uint32 unNewQuantity ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMGAMESERVERITEMS003_H diff --git a/sdk/steam/isteaminput.h b/sdk/steam/isteaminput.h index 0c1827f98..2c0470603 100644 --- a/sdk/steam/isteaminput.h +++ b/sdk/steam/isteaminput.h @@ -509,6 +509,93 @@ enum EInputActionOrigin k_EInputActionOrigin_Horipad_L4, k_EInputActionOrigin_Horipad_R4, + k_EInputActionOrigin_LenovoLegionGo_A, + k_EInputActionOrigin_LenovoLegionGo_B, + k_EInputActionOrigin_LenovoLegionGo_X, + k_EInputActionOrigin_LenovoLegionGo_Y, + k_EInputActionOrigin_LenovoLegionGo_LB, + k_EInputActionOrigin_LenovoLegionGo_RB, + k_EInputActionOrigin_LenovoLegionGo_Menu, + k_EInputActionOrigin_LenovoLegionGo_View, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Touch, // Left pad is only present on the original Legion Go + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Swipe, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_Click, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_LeftPad_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Touch, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Swipe, + k_EInputActionOrigin_LenovoLegionGo_RightPad_Click, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_RightPad_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_LT_SoftPull, + k_EInputActionOrigin_LenovoLegionGo_LT, + k_EInputActionOrigin_LenovoLegionGo_RT_SoftPull, + k_EInputActionOrigin_LenovoLegionGo_RT, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_Move, + k_EInputActionOrigin_LenovoLegionGo_LS, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_LeftStick_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_RightStick_Move, + k_EInputActionOrigin_LenovoLegionGo_RS, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadNorth, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadSouth, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadWest, + k_EInputActionOrigin_LenovoLegionGo_RightStick_DPadEast, + k_EInputActionOrigin_LenovoLegionGo_Y1, + k_EInputActionOrigin_LenovoLegionGo_Y2, + k_EInputActionOrigin_LenovoLegionGo_DPad_Move, + k_EInputActionOrigin_LenovoLegionGo_DPad_North, + k_EInputActionOrigin_LenovoLegionGo_DPad_South, + k_EInputActionOrigin_LenovoLegionGo_DPad_West, + k_EInputActionOrigin_LenovoLegionGo_DPad_East, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Move, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Pitch, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Yaw, + k_EInputActionOrigin_LenovoLegionGo_Gyro_Roll, + k_EInputActionOrigin_LenovoLegionGo_Reserved1, + k_EInputActionOrigin_LenovoLegionGo_Reserved2, + k_EInputActionOrigin_LenovoLegionGo_Reserved3, + k_EInputActionOrigin_LenovoLegionGo_Reserved4, + k_EInputActionOrigin_LenovoLegionGo_Reserved5, + k_EInputActionOrigin_LenovoLegionGo_Reserved6, + k_EInputActionOrigin_LenovoLegionGo_Reserved7, + k_EInputActionOrigin_LenovoLegionGo_Reserved8, + k_EInputActionOrigin_LenovoLegionGo_Reserved9, + k_EInputActionOrigin_LenovoLegionGo_Reserved10, + k_EInputActionOrigin_LenovoLegionGo_Reserved11, + k_EInputActionOrigin_LenovoLegionGo_Reserved12, + k_EInputActionOrigin_LenovoLegionGo_Reserved13, + k_EInputActionOrigin_LenovoLegionGo_Reserved14, + k_EInputActionOrigin_LenovoLegionGo_Reserved15, + k_EInputActionOrigin_LenovoLegionGo_Reserved16, + k_EInputActionOrigin_LenovoLegionGo_Reserved17, + k_EInputActionOrigin_LenovoLegionGo_Reserved18, + k_EInputActionOrigin_LenovoLegionGo_Reserved19, + k_EInputActionOrigin_LenovoLegionGo_Reserved20, + + k_EInputActionOrigin_Generic_L4, + k_EInputActionOrigin_Generic_R4, + k_EInputActionOrigin_Generic_L5, + k_EInputActionOrigin_Generic_R5, + k_EInputActionOrigin_Generic_PL, + k_EInputActionOrigin_Generic_PR, + k_EInputActionOrigin_Generic_C, + k_EInputActionOrigin_Generic_Z, + k_EInputActionOrigin_Generic_MISC1, + k_EInputActionOrigin_Generic_MISC2, + k_EInputActionOrigin_Generic_MISC3, + k_EInputActionOrigin_Generic_MISC4, + k_EInputActionOrigin_Generic_MISC5, + k_EInputActionOrigin_Generic_MISC6, + k_EInputActionOrigin_Generic_MISC7, + k_EInputActionOrigin_Generic_MISC8, + k_EInputActionOrigin_Count, // If Steam has added support for new controllers origins will go here. k_EInputActionOrigin_MaximumPossibleValue = 32767, // Origins are currently a maximum of 16 bits. }; diff --git a/sdk/steam/isteammatchmaking.h b/sdk/steam/isteammatchmaking.h index 9de3b5e47..13c24fc99 100644 --- a/sdk/steam/isteammatchmaking.h +++ b/sdk/steam/isteammatchmaking.h @@ -18,7 +18,7 @@ enum ELobbyType { k_ELobbyTypePrivate = 0, // only way to join the lobby is to invite to someone else - k_ELobbyTypeFriendsOnly = 1, // shows for friends or invitees, but not in lobby list + k_ELobbyTypeFriendsOnly = 1, // shows for friends or invitees, but not in public lobby list, allows those who join to invite their own friends k_ELobbyTypePublic = 2, // visible for friends and in lobby list k_ELobbyTypeInvisible = 3, // returned by search, but not visible to other friends // useful if you want a user in two lobbies, for example matching groups together diff --git a/sdk/steam/isteamparentalsettings.h b/sdk/steam/isteamparentalsettings.h index edf822bbf..7b385faf3 100644 --- a/sdk/steam/isteamparentalsettings.h +++ b/sdk/steam/isteamparentalsettings.h @@ -31,6 +31,7 @@ enum EParentalFeature k_EFeatureSiteLicense = 13, k_EFeatureKioskMode_Deprecated = 14, k_EFeatureBlockAlways = 15, + k_EFeatureDesktop = 16, k_EFeatureMax }; diff --git a/sdk/steam/isteamremoteplay.h b/sdk/steam/isteamremoteplay.h index a248595f1..2811f439c 100644 --- a/sdk/steam/isteamremoteplay.h +++ b/sdk/steam/isteamremoteplay.h @@ -161,6 +161,23 @@ enum ERemotePlayScancode k_ERemotePlayScancodeDown = 81, k_ERemotePlayScancodeUp = 82, + k_ERemotePlayScancodeKeypadDivide = 84, + k_ERemotePlayScancodeKeypadMultiply = 85, + k_ERemotePlayScancodeKeypadMinus = 86, + k_ERemotePlayScancodeKeypadPlus = 87, + k_ERemotePlayScancodeKeypadEnter = 88, + k_ERemotePlayScancodeKeypad1 = 89, + k_ERemotePlayScancodeKeypad2 = 90, + k_ERemotePlayScancodeKeypad3 = 91, + k_ERemotePlayScancodeKeypad4 = 92, + k_ERemotePlayScancodeKeypad5 = 93, + k_ERemotePlayScancodeKeypad6 = 94, + k_ERemotePlayScancodeKeypad7 = 95, + k_ERemotePlayScancodeKeypad8 = 96, + k_ERemotePlayScancodeKeypad9 = 97, + k_ERemotePlayScancodeKeypad0 = 98, + k_ERemotePlayScancodeKeypadPeriod = 99, + k_ERemotePlayScancodeLeftControl = 224, k_ERemotePlayScancodeLeftShift = 225, k_ERemotePlayScancodeLeftAlt = 226, @@ -265,9 +282,28 @@ class ISteamRemotePlay // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. virtual RemotePlaySessionID_t GetSessionID( int iSessionIndex ) = 0; + // Return true if the session has joined using a Remote Play Together invitation + virtual bool BSessionRemotePlayTogether( RemotePlaySessionID_t unSessionID ) = 0; + // Get the SteamID of the connected user virtual CSteamID GetSessionSteamID( RemotePlaySessionID_t unSessionID ) = 0; + // Get the guest ID of the connected user if they are a Remote Play Together guest + // This returns 0 if the sessionID isn't valid or the session isn't a Remote Play Together guest + virtual uint32 GetSessionGuestID( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the small (32x32) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetSmallSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the medium (64x64) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetMediumSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + + // gets the large (184x184) avatar of the connected user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if the sessionID isn't valid + // returns -1 if this image has yet to be loaded, in this case wait for a RemotePlaySessionAvatarLoaded_t callback and then call this again + virtual int GetLargeSessionAvatar( RemotePlaySessionID_t unSessionID ) = 0; + // Get the name of the session client device // This returns NULL if the sessionID is not valid virtual const char *GetSessionClientName( RemotePlaySessionID_t unSessionID ) = 0; @@ -283,8 +319,7 @@ class ISteamRemotePlay // This returns false if your game is not configured for Remote Play Together virtual bool ShowRemotePlayTogetherUI() = 0; - // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is empty - // This will automatically start Remote Play Together if it hasn't already been started + // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is CSteamID() // This returns false if the invite can't be sent or your game is not configured for Remote Play Together virtual bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) = 0; @@ -321,8 +356,8 @@ class ISteamRemotePlay // nHeight - The height of the cursor, in pixels // nHotX - The X coordinate of the cursor hot spot in pixels, offset from the left of the cursor // nHotY - The Y coordinate of the cursor hot spot in pixels, offset from the top of the cursor - // pBGRA - A pointer to the cursor pixels, with the color channels in red, green, blue, alpha order - // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 + // pBGRA - A pointer to the cursor pixels, with 8-bit channels in red, green, blue, alpha order + // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 virtual RemotePlayCursorID_t CreateMouseCursor( int nWidth, int nHeight, int nHotX, int nHotY, const void *pBGRA, int nPitch = 0 ) = 0; // Set the mouse cursor for a remote player @@ -332,7 +367,7 @@ class ISteamRemotePlay virtual void SetMouseCursor( RemotePlaySessionID_t unSessionID, RemotePlayCursorID_t unCursorID ) = 0; }; -#define STEAMREMOTEPLAY_INTERFACE_VERSION "STEAMREMOTEPLAY_INTERFACE_VERSION003" +#define STEAMREMOTEPLAY_INTERFACE_VERSION "STEAMREMOTEPLAY_INTERFACE_VERSION004" // Global interface accessor inline ISteamRemotePlay *SteamRemotePlay(); @@ -350,18 +385,25 @@ STEAM_DEFINE_USER_INTERFACE_ACCESSOR( ISteamRemotePlay *, SteamRemotePlay, STEAM STEAM_CALLBACK_BEGIN( SteamRemotePlaySessionConnected_t, k_iSteamRemotePlayCallbacks + 1 ) STEAM_CALLBACK_MEMBER( 0, RemotePlaySessionID_t, m_unSessionID ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) STEAM_CALLBACK_BEGIN( SteamRemotePlaySessionDisconnected_t, k_iSteamRemotePlayCallbacks + 2 ) STEAM_CALLBACK_MEMBER( 0, RemotePlaySessionID_t, m_unSessionID ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) STEAM_CALLBACK_BEGIN( SteamRemotePlayTogetherGuestInvite_t, k_iSteamRemotePlayCallbacks + 3 ) STEAM_CALLBACK_MEMBER_ARRAY( 0, char, m_szConnectURL, 1024 ) -STEAM_CALLBACK_END( 0 ) +STEAM_CALLBACK_END( 1 ) + +STEAM_CALLBACK_BEGIN(SteamRemotePlaySessionAvatarLoaded_t, k_iSteamRemotePlayCallbacks + 4) + STEAM_CALLBACK_MEMBER(0, RemotePlaySessionID_t, m_unSessionID) // sessionID the avatar has been loaded for + STEAM_CALLBACK_MEMBER(1, int, m_iImage) // the image index of the now loaded image + STEAM_CALLBACK_MEMBER(2, int, m_iWide) // width of the loaded image + STEAM_CALLBACK_MEMBER(3, int, m_iTall) // height of the loaded image +STEAM_CALLBACK_END(4) #pragma pack( pop ) diff --git a/sdk/steam/isteamremoteplay003.h b/sdk/steam/isteamremoteplay003.h new file mode 100644 index 000000000..06e0d8d71 --- /dev/null +++ b/sdk/steam/isteamremoteplay003.h @@ -0,0 +1,86 @@ + +#ifndef ISTEAMREMOTEPLAY003_H +#define ISTEAMREMOTEPLAY003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Functions to provide information about Steam Remote Play sessions +//----------------------------------------------------------------------------- +class ISteamRemotePlay003 +{ +public: + // Get the number of currently connected Steam Remote Play sessions + virtual uint32 GetSessionCount() = 0; + + // Get the currently connected Steam Remote Play session ID at the specified index. Returns zero if index is out of bounds. + virtual RemotePlaySessionID_t GetSessionID( int iSessionIndex ) = 0; + + // Get the SteamID of the connected user + virtual CSteamID GetSessionSteamID( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the name of the session client device + // This returns NULL if the sessionID is not valid + virtual const char *GetSessionClientName( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the form factor of the session client device + virtual ESteamDeviceFormFactor GetSessionClientFormFactor( RemotePlaySessionID_t unSessionID ) = 0; + + // Get the resolution, in pixels, of the session client device + // This is set to 0x0 if the resolution is not available + virtual bool BGetSessionClientResolution( RemotePlaySessionID_t unSessionID, int *pnResolutionX, int *pnResolutionY ) = 0; + + // Show the Remote Play Together UI in the game overlay + // This returns false if your game is not configured for Remote Play Together + virtual bool ShowRemotePlayTogetherUI() = 0; + + // Invite a friend to Remote Play Together, or create a guest invite if steamIDFriend is CSteamID() + // This returns false if the invite can't be sent or your game is not configured for Remote Play Together + virtual bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend ) = 0; + + // Make mouse and keyboard input for Remote Play Together sessions available via GetInput() instead of being merged with local input + virtual bool BEnableRemotePlayTogetherDirectInput() = 0; + + // Merge Remote Play Together mouse and keyboard input with local input + virtual void DisableRemotePlayTogetherDirectInput() = 0; + + // Get input events from Remote Play Together sessions + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // pInput is an array of input events that will be filled in by this function, up to unMaxEvents. + // This returns the number of events copied to pInput, or the number of events available if pInput is nullptr. + virtual uint32 GetInput( RemotePlayInput_t *pInput, uint32 unMaxEvents ) = 0; + + // Set the mouse cursor visibility for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + virtual void SetMouseVisibility( RemotePlaySessionID_t unSessionID, bool bVisible ) = 0; + + // Set the mouse cursor position for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // This is used to warp the cursor to a specific location and isn't needed during normal event processing. + // + // The position is normalized relative to the window, where 0,0 is the upper left, and 1,1 is the lower right. + virtual void SetMousePosition( RemotePlaySessionID_t unSessionID, float flNormalizedX, float flNormalizedY ) = 0; + + // Create a cursor that can be used with SetMouseCursor() + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // Parameters: + // nWidth - The width of the cursor, in pixels + // nHeight - The height of the cursor, in pixels + // nHotX - The X coordinate of the cursor hot spot in pixels, offset from the left of the cursor + // nHotY - The Y coordinate of the cursor hot spot in pixels, offset from the top of the cursor + // pBGRA - A pointer to the cursor pixels, with the color channels in red, green, blue, alpha order + // nPitch - The distance between pixel rows in bytes, defaults to nWidth * 4 + virtual RemotePlayCursorID_t CreateMouseCursor( int nWidth, int nHeight, int nHotX, int nHotY, const void *pBGRA, int nPitch = 0 ) = 0; + + // Set the mouse cursor for a remote player + // This is available after calling BEnableRemotePlayTogetherDirectInput() + // + // The cursor ID is a value returned by CreateMouseCursor() + virtual void SetMouseCursor( RemotePlaySessionID_t unSessionID, RemotePlayCursorID_t unCursorID ) = 0; +}; + +#endif // #define ISTEAMREMOTEPLAY003_H diff --git a/sdk/steam/isteamugc.h b/sdk/steam/isteamugc.h index cf2fbf4d5..344a6c125 100644 --- a/sdk/steam/isteamugc.h +++ b/sdk/steam/isteamugc.h @@ -400,6 +400,15 @@ class ISteamUGC // Set the local load order for these items. If there are any items not in the given list, they will sort by the time subscribed. virtual bool SetSubscriptionsLoadOrder( PublishedFileId_t *pvecPublishedFileIDs, uint32 unNumPublishedFileIDs ) = 0; + + // Tells the client to no longer try to keep the item in its local cache, unless it was subscribed to by other users on this machine + virtual bool MarkDownloadedItemAsUnused( PublishedFileId_t nPublishedFileID ) = 0; + + // Returns the number of items actually downloaded locally + virtual uint32 GetNumDownloadedItems() = 0; + + // Returns the ids of the items downloaded + virtual uint32 GetDownloadedItems( PublishedFileId_t *pvecPublishedFileIDs, uint32 cMaxEntries ) = 0; }; #define STEAMUGC_INTERFACE_VERSION "STEAMUGC_INTERFACE_VERSION021" diff --git a/sdk/steam/isteamuser001.h b/sdk/steam/isteamuser001.h index 9c44c1dde..b0c164509 100644 --- a/sdk/steam/isteamuser001.h +++ b/sdk/steam/isteamuser001.h @@ -7,18 +7,28 @@ // this interface version is not found in public SDK archives, it is based on reversing old Linux binaries +//----------------------------------------------------------------------------- +// Purpose: types of VAC bans +//----------------------------------------------------------------------------- +enum EVACBan +{ + k_EVACBanGoldsrc, + k_EVACBanSource, + k_EVACBanDayOfDefeatSource, +}; + class ICMCallback001 { public: virtual ~ICMCallback001() {} virtual void OnLogonSuccess() = 0; - virtual void OnLogonFailure( EResult result ) = 0; - virtual void OnLoggedOff() = 0; - virtual void HandleVACChallenge( int unk1, void *unk2, int unk3 ) = 0; - virtual void GSHandleClientApprove( CSteamID *steamID ) = 0; - virtual void GSHandleClientDeny( CSteamID *steamID, EDenyReason reason ) = 0; - virtual void GSHandleClientKick( CSteamID *steamID, EDenyReason reason ) = 0; + virtual void OnLogonFailure( EResult eResult ) = 0; + virtual void OnLoggedOff( EResult eResult ) = 0; + virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; + virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; + virtual void GSHandleClientDeny( CSteamID &steamID, EDenyReason eDenyReason ) = 0; + virtual void GSHandleClientKick( CSteamID &steamID, EDenyReason eDenyReason ) = 0; }; class ISteam2Auth @@ -33,29 +43,29 @@ class ISteamUser001 public: virtual void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; virtual EUniverse Test_GetEUniverse() = 0; diff --git a/sdk/steam/isteamuser002.h b/sdk/steam/isteamuser002.h index abdf6e002..d1ad0bcdd 100644 --- a/sdk/steam/isteamuser002.h +++ b/sdk/steam/isteamuser002.h @@ -9,7 +9,10 @@ enum ELogonState { - ELogonState_stub + k_ELogonStateNotLoggedOn = 0, + k_ELogonStateLoggingOn = 1, + k_ELogonStateLoggingOff = 2, + k_ELogonStateLoggedOn = 3 }; class ICMCallback @@ -18,13 +21,13 @@ class ICMCallback virtual ~ICMCallback() {} virtual void OnLogonSuccess() = 0; - virtual void OnLogonFailure( EResult result ) = 0; - virtual void OnLoggedOff() = 0; + virtual void OnLogonFailure( EResult eResult ) = 0; + virtual void OnLoggedOff( EResult eResult ) = 0; virtual void OnBeginLogonRetry() = 0; - virtual void HandleVACChallenge( int unk1, void *unk2, int unk3 ) = 0; - virtual void GSHandleClientApprove( CSteamID *steamID ) = 0; - virtual void GSHandleClientDeny( CSteamID *steamID, EDenyReason reason ) = 0; - virtual void GSHandleClientKick( CSteamID *steamID, EDenyReason reason ) = 0; + virtual void HandleVACChallenge( int nClientGameID, uint8 *pubChallenge, int cubChallenge ) = 0; + virtual void GSHandleClientApprove( CSteamID &steamID ) = 0; + virtual void GSHandleClientDeny( CSteamID &steamID, EDenyReason eDenyReason ) = 0; + virtual void GSHandleClientKick( CSteamID &steamID, EDenyReason eDenyReason ) = 0; }; class ISteamUser002 @@ -32,30 +35,30 @@ class ISteamUser002 public: virtual void Init( ICMCallback *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual ELogonState GetLogonState() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; diff --git a/sdk/steam/isteamuser002_old.h b/sdk/steam/isteamuser002_old.h index 23f1cee49..1cbab9433 100644 --- a/sdk/steam/isteamuser002_old.h +++ b/sdk/steam/isteamuser002_old.h @@ -12,29 +12,29 @@ class ISteamUser002_old public: virtual void Init( ICMCallback001 *cmcallback, ISteam2Auth *steam2auth ) = 0; virtual int ProcessCall( int unk ) = 0; - virtual void LogOn( CSteamID *steamID ) = 0; + virtual void LogOn_old( CSteamID &steamID ) = 0; virtual void LogOff() = 0; virtual bool BLoggedOn() = 0; virtual bool BConnected() = 0; - virtual int CreateAccount( const char *unk1, void *unk2, void *unk3, const char *unk4, int unk5, void *unk6 ) = 0; + virtual int CreateAccount( const char *unk1, uint8 *unk2, uint8 *unk3, const char *unk4, int unk5, uint8 *unk6 ) = 0; virtual bool IsVACBanned( int nGameID ) = 0; virtual bool RequireShowVACBannedMessage( int nGameID ) = 0; virtual void AcknowledgeVACBanning( int nGameID ) = 0; - virtual bool GSSendLogonRequest( CSteamID *steamID ) = 0; - virtual bool GSSendDisconnect( CSteamID *steamID ) = 0; - virtual bool GSSendStatusResponse( CSteamID *steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; + virtual bool GSSendLogonRequest( CSteamID &steamID ) = 0; + virtual bool GSSendDisconnect( CSteamID &steamID ) = 0; + virtual bool GSSendStatusResponse( CSteamID &steamID, int nSecondsConnected, int nSecondsSinceLast ) = 0; virtual bool GSSetStatus( int32 nAppIdServed, uint32 unServerFlags, int cPlayers, int cPlayersMax, int cBotPlayers, int unGamePort, const char *pchServerName, const char *pchGameDir, const char *pchMapName, const char *pchVersion ) = 0; virtual int NClientGameIDAdd( int nGameID ) = 0; virtual void RemoveClientGame( int nClientGameID ) = 0; virtual void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer ) = 0; virtual void Test_SuspendActivity() = 0; virtual void Test_ResumeActivity() = 0; - virtual void Test_SendVACResponse( int unk1, void *unk2, int unk3 ) = 0; - virtual void Test_SetFakePrivateIP( uint32 ip ) = 0; + virtual void Test_SendVACResponse( int nClientGameID, uint8 *pubResponse, int cubResponse ) = 0; + virtual void Test_SetFakePrivateIP( uint32 unIPPrivate ) = 0; virtual void Test_SendBigMessage() = 0; virtual bool Test_BBigMessageResponseReceived() = 0; - virtual void Test_SetPktLossPct( int unk1 ) = 0; - virtual void Test_SetForceTCP( bool unk1 ) = 0; + virtual void Test_SetPktLossPct( int nPct ) = 0; + virtual void Test_SetForceTCP( bool bForceTCP ) = 0; virtual void Test_SetMaxUDPConnectionAttempts( int unk1 ) = 0; virtual void Test_Heartbeat() = 0; virtual void Test_FakeDisconnect() = 0; diff --git a/sdk/steam/isteamuser004.h b/sdk/steam/isteamuser004.h index e40cddcab..9bceb8ae5 100644 --- a/sdk/steam/isteamuser004.h +++ b/sdk/steam/isteamuser004.h @@ -5,9 +5,14 @@ #pragma once #endif -enum EConfigSubTree +enum ERegistrySubTree { - EConfigSubTree_stub + k_ERegistrySubTreeNews = 0, + k_ERegistrySubTreeApps = 1, + k_ERegistrySubTreeSubscriptions = 2, + k_ERegistrySubTreeGameServers = 3, + k_ERegistrySubTreeFriends = 4, + k_ERegistrySubTreeSystem = 5, }; class ISteamUser004 @@ -35,10 +40,10 @@ class ISteamUser004 // logon cookie - this is obsolete and never used virtual int GetSteamGameConnectToken( void *pBlob, int cbMaxBlob ) = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; virtual int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, int nGameAppID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) = 0; virtual void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) = 0; virtual void SetSelfAsPrimaryChatDestination() = 0; diff --git a/sdk/steam/isteamuser005.h b/sdk/steam/isteamuser005.h index 6d1b0359d..b4220497b 100644 --- a/sdk/steam/isteamuser005.h +++ b/sdk/steam/isteamuser005.h @@ -25,10 +25,10 @@ class ISteamUser005 virtual void SetSteam2Ticket( uint8 *pubTicket, int cubTicket ) = 0; virtual void AddServerNetAddress( uint32 unIP, uint16 unPort ) = 0; virtual bool SetEmail( const char *pchEmail ) = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; virtual int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure ) = 0; virtual void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer ) = 0; virtual void SetSelfAsPrimaryChatDestination() = 0; diff --git a/sdk/steam/isteamuser006.h b/sdk/steam/isteamuser006.h index ed9be0965..5a0955ffd 100644 --- a/sdk/steam/isteamuser006.h +++ b/sdk/steam/isteamuser006.h @@ -28,10 +28,10 @@ class ISteamUser006 // a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API virtual CSteamID GetSteamID() = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; // Multiplayer Authentication functions diff --git a/sdk/steam/isteamuser007.h b/sdk/steam/isteamuser007.h index b7e0d5db8..c728105e4 100644 --- a/sdk/steam/isteamuser007.h +++ b/sdk/steam/isteamuser007.h @@ -28,10 +28,10 @@ class ISteamUser007 // a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API virtual CSteamID GetSteamID() = 0; - virtual bool SetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; - virtual bool GetRegistryString( EConfigSubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; - virtual bool SetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; - virtual bool GetRegistryInt( EConfigSubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; + virtual bool SetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, const char *pchValue ) = 0; + virtual bool GetRegistryString( ERegistrySubTree eRegistrySubTree, const char *pchKey, char *pchValue, int cbValue ) = 0; + virtual bool SetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int iValue ) = 0; + virtual bool GetRegistryInt( ERegistrySubTree eRegistrySubTree, const char *pchKey, int *piValue ) = 0; // Multiplayer Authentication functions diff --git a/sdk/steam/isteamuseritems.h b/sdk/steam/isteamuseritems.h new file mode 100644 index 000000000..a57aa65a2 --- /dev/null +++ b/sdk/steam/isteamuseritems.h @@ -0,0 +1,113 @@ + +#ifndef ISTEAMUSERITEMS_H +#define ISTEAMUSERITEMS_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +enum EItemQuality +{ + k_EItemQuality_Invalid = -2, + k_EItemQuality_Any = -1, + k_EItemQuality_Normal = 0, + k_EItemQuality_Common, + k_EItemQuality_Rare, + k_EItemQuality_Unique, + k_EItemQuality_Count, +}; + +enum EItemRequestResult +{ + k_EItemRequestResultOK = 0, + k_EItemRequestResultDenied, + k_EItemRequestResultServerError, + k_EItemRequestResultTimeout, + k_EItemRequestResultInvalid, + k_EItemRequestResultNoMatch, + k_EItemRequestResultUnknownError, + k_EItemRequestResultNotLoggedOn, +}; + +class ISteamUserItems +{ +public: + virtual SteamAPICall_t LoadItems() = 0; + virtual SteamAPICall_t GetItemCount() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual SteamAPICall_t DeleteItem( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; + virtual SteamAPICall_t DropItem( uint64 ulItemID ) = 0; +}; + +#define STEAMUSERITEMS_INTERFACE_VERSION "STEAMUSERITEMS_INTERFACE_VERSION004" + +struct UserItemCount_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 0 }; + CGameID m_gameID; + EItemRequestResult m_eResult; + uint32 m_unCount; +}; + +struct UpdateInventoryPosResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 1 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct DeleteItemResponse_t // renamed from DropItemResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 2 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct ItemGranted_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 3 }; + uint64 m_ulItemID; + CGameID m_gameID; +}; + +struct GetItemBlobResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 4 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; + uint8 m_pubBlob[1024]; + int32 m_cubBlob; +}; + +struct SetItemBlobResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 5 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +struct ItemQuantityUpdated_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 6 }; + uint64 m_ulItemID; +}; + +struct ItemsRefreshed_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 7 }; +}; + +struct DropItemResponse_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 8 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +#endif // ISTEAMUSERITEMS_H diff --git a/sdk/steam/isteamuseritems001.h b/sdk/steam/isteamuseritems001.h new file mode 100644 index 000000000..1e7268d5e --- /dev/null +++ b/sdk/steam/isteamuseritems001.h @@ -0,0 +1,29 @@ + +#ifndef ISTEAMUSERITEMS001_H +#define ISTEAMUSERITEMS001_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems001 +{ +public: + virtual void LoadItems_old() = 0; + virtual void GetItemCount_old() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual void DropItem_old( uint64 ulItemID ) = 0; +}; + +struct DropItemResponse001_t +{ + enum { k_iCallback = k_iClientDepotBuilderCallbacks + 2 }; + uint64 m_ulItemID; + EItemRequestResult m_eResult; +}; + +#endif // ISTEAMUSERITEMS001_H diff --git a/sdk/steam/isteamuseritems002.h b/sdk/steam/isteamuseritems002.h new file mode 100644 index 000000000..154a83b0e --- /dev/null +++ b/sdk/steam/isteamuseritems002.h @@ -0,0 +1,24 @@ + +#ifndef ISTEAMUSERITEMS002_H +#define ISTEAMUSERITEMS002_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems002 +{ +public: + virtual void LoadItems_old() = 0; + virtual void GetItemCount_old() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual void UpdateInventoryPos_old( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual void DropItem_old( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMUSERITEMS002_H diff --git a/sdk/steam/isteamuseritems003.h b/sdk/steam/isteamuseritems003.h new file mode 100644 index 000000000..6cc63ef39 --- /dev/null +++ b/sdk/steam/isteamuseritems003.h @@ -0,0 +1,24 @@ + +#ifndef ISTEAMUSERITEMS003_H +#define ISTEAMUSERITEMS003_H +#ifdef STEAM_WIN32 +#pragma once +#endif + +// this interface version is not found in public SDK archives, it is based on reversing old Linux binaries + +class ISteamUserItems003 +{ +public: + virtual SteamAPICall_t LoadItems() = 0; + virtual SteamAPICall_t GetItemCount() = 0; + virtual bool GetItemIterative( uint32 iIndex, uint64 *pulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemByID( uint64 ulItemID, uint32 *punItemDefIndex, uint32 *punItemLevel, EItemQuality *peQuality, uint32 *punInventoryPos, uint32 *punQuantity, uint32 *punAttributeCount ) = 0; + virtual bool GetItemAttribute( uint64 ulItemID, uint32 unAttributeIndex, uint32 *punAttributeDefIndex, float *pflAttributeValue ) = 0; + virtual SteamAPICall_t UpdateInventoryPos( uint64 ulItemID, uint32 unNewInventoryPos ) = 0; + virtual SteamAPICall_t DropItem_old2( uint64 ulItemID ) = 0; + virtual SteamAPICall_t GetItemBlob( uint64 ulItemID ) = 0; + virtual SteamAPICall_t SetItemBlob( uint64 ulItemID, const void *pubBlob, uint32 cubBlob ) = 0; +}; + +#endif // ISTEAMUSERITEMS003_H diff --git a/sdk/steam/steam_api.h b/sdk/steam/steam_api.h index 7584b249d..46ba98570 100644 --- a/sdk/steam/steam_api.h +++ b/sdk/steam/steam_api.h @@ -121,6 +121,7 @@ #include "isteamuserstats002.h" #include "isteamuserstats001.h" #include "isteamapps.h" +#include "isteamapps008.h" #include "isteamapps007.h" #include "isteamapps006.h" #include "isteamapps005.h" @@ -228,6 +229,7 @@ #include "isteamremoteplay.h" #include "isteamremoteplay001.h" #include "isteamremoteplay002.h" +#include "isteamremoteplay003.h" #include "isteamnetworkingmessages.h" #include "isteamnetworkingsockets.h" #include "isteamnetworkingutils.h" @@ -248,6 +250,14 @@ #include "isteamgameserver001.h" #include "isteamgameserverstats.h" #include "isteamgamestats.h" +#include "isteamuseritems.h" +#include "isteamuseritems001.h" +#include "isteamuseritems002.h" +#include "isteamuseritems003.h" +#include "isteamgameserveritems.h" +#include "isteamgameserveritems001.h" +#include "isteamgameserveritems002.h" +#include "isteamgameserveritems003.h" //----------------------------------------------------------------------------------------------------------------------------------------------------------// diff --git a/sdk/steam/steam_api_flat.h b/sdk/steam/steam_api_flat.h index fd24be3e7..121c28ed4 100644 --- a/sdk/steam/steam_api_flat.h +++ b/sdk/steam/steam_api_flat.h @@ -37,7 +37,7 @@ STEAMAPI_API ISteamApps * SteamAPI_ISteamClient_GetISteamApps( ISteamClient* sel STEAMAPI_API ISteamNetworking * SteamAPI_ISteamClient_GetISteamNetworking( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamRemoteStorage * SteamAPI_ISteamClient_GetISteamRemoteStorage( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamScreenshots * SteamAPI_ISteamClient_GetISteamScreenshots( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); -STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); +STEAMAPI_API ISteamGameSearch * SteamAPI_ISteamClient_GetISteamGameSearch( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); // removed in SDK 1.63 STEAMAPI_API uint32 SteamAPI_ISteamClient_GetIPCCallCount( ISteamClient* self ); STEAMAPI_API void SteamAPI_ISteamClient_SetWarningMessageHook( ISteamClient* self, SteamAPIWarningMessageHook_t pFunction ); STEAMAPI_API steam_bool SteamAPI_ISteamClient_BShutdownIfAllPipesClosed( ISteamClient* self ); @@ -46,7 +46,7 @@ STEAMAPI_API ISteamController * SteamAPI_ISteamClient_GetISteamController( IStea STEAMAPI_API ISteamUGC * SteamAPI_ISteamClient_GetISteamUGC( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamAppList * SteamAPI_ISteamClient_GetISteamAppList( ISteamClient* self, HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamMusic * SteamAPI_ISteamClient_GetISteamMusic( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); -STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); +STEAMAPI_API ISteamMusicRemote * SteamAPI_ISteamClient_GetISteamMusicRemote( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); // removed in SDK 1.63 STEAMAPI_API ISteamHTMLSurface * SteamAPI_ISteamClient_GetISteamHTMLSurface( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamInventory * SteamAPI_ISteamClient_GetISteamInventory( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); STEAMAPI_API ISteamVideo * SteamAPI_ISteamClient_GetISteamVideo( ISteamClient* self, HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char * pchVersion ); @@ -306,6 +306,7 @@ STEAMAPI_API HServerQuery SteamAPI_ISteamMatchmakingServers_ServerRules( ISteamM STEAMAPI_API void SteamAPI_ISteamMatchmakingServers_CancelServerQuery( ISteamMatchmakingServers* self, HServerQuery hServerQuery ); // ISteamGameSearch +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamGameSearch *SteamAPI_SteamGameSearch_v001(); STEAMAPI_API EGameSearchErrorCode_t SteamAPI_ISteamGameSearch_AddGameSearchParams( ISteamGameSearch* self, const char * pchKeyToFind, const char * pchValuesToFind ); STEAMAPI_API EGameSearchErrorCode_t SteamAPI_ISteamGameSearch_SearchForGameWithLobby( ISteamGameSearch* self, uint64_steamid steamIDLobby, int nPlayerMin, int nPlayerMax ); @@ -453,6 +454,8 @@ STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt STEAMAPI_API steam_bool SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat( ISteamUserStats* self, const char * pchName, float * pfMinProgress, float * pfMaxProgress ); // ISteamApps +STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v009(); +STEAMAPI_API ISteamApps *SteamAPI_SteamGameServerApps_v009(); STEAMAPI_API ISteamApps *SteamAPI_SteamApps_v008(); STEAMAPI_API ISteamApps *SteamAPI_SteamGameServerApps_v008(); STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsSubscribed( ISteamApps* self ); @@ -486,7 +489,7 @@ STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing( ISte STEAMAPI_API steam_bool SteamAPI_ISteamApps_BIsTimedTrial( ISteamApps* self, uint32 * punSecondsAllowed, uint32 * punSecondsPlayed ); STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetDlcContext( ISteamApps* self, AppId_t nAppID ); STEAMAPI_API int SteamAPI_ISteamApps_GetNumBetas( ISteamApps* self, int * pnAvailable, int * pnPrivate ); -STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo( ISteamApps* self, int iBetaIndex, uint32 * punFlags, uint32 * punBuildID, char * pchBetaName, int cchBetaName, char * pchDescription, int cchDescription ); +STEAMAPI_API steam_bool SteamAPI_ISteamApps_GetBetaInfo( ISteamApps* self, int iBetaIndex, uint32 * punFlags, uint32 * punBuildID, char * pchBetaName, int cchBetaName, char * pchDescription, int cchDescription, uint32 *punLastUpdated ); STEAMAPI_API steam_bool SteamAPI_ISteamApps_SetActiveBeta( ISteamApps* self, const char * pchBetaName ); @@ -541,6 +544,7 @@ STEAMAPI_API void SteamAPI_ISteamMusic_SetVolume( ISteamMusic* self, float flVol STEAMAPI_API float SteamAPI_ISteamMusic_GetVolume( ISteamMusic* self ); // ISteamMusicRemote +// whole interface removed in SDK 1.63 STEAMAPI_API ISteamMusicRemote *SteamAPI_SteamMusicRemote_v001(); STEAMAPI_API steam_bool SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote( ISteamMusicRemote* self, const char * pchName ); STEAMAPI_API steam_bool SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote( ISteamMusicRemote* self ); @@ -961,9 +965,15 @@ STEAMAPI_API steam_bool SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList( I STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v001(); STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v002(); STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v003(); +STEAMAPI_API ISteamRemotePlay *SteamAPI_SteamRemotePlay_v004(); STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionCount( ISteamRemotePlay* self ); STEAMAPI_API RemotePlaySessionID_t SteamAPI_ISteamRemotePlay_GetSessionID( ISteamRemotePlay* self, int iSessionIndex ); +STEAMAPI_API bool SteamAPI_ISteamRemotePlay_BSessionRemotePlayTogether( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API uint64_steamid SteamAPI_ISteamRemotePlay_GetSessionSteamID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API uint32 SteamAPI_ISteamRemotePlay_GetSessionGuestID( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetSmallSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetMediumSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); +STEAMAPI_API int SteamAPI_ISteamRemotePlay_GetLargeSessionAvatar( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API const char * SteamAPI_ISteamRemotePlay_GetSessionClientName( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API ESteamDeviceFormFactor SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID ); STEAMAPI_API steam_bool SteamAPI_ISteamRemotePlay_BGetSessionClientResolution( ISteamRemotePlay* self, RemotePlaySessionID_t unSessionID, int * pnResolutionX, int * pnResolutionY ); diff --git a/sdk/steam/steam_api_internal.h b/sdk/steam/steam_api_internal.h index c2e46b74d..fe5d39478 100644 --- a/sdk/steam/steam_api_internal.h +++ b/sdk/steam/steam_api_internal.h @@ -229,7 +229,7 @@ class ISteamNetworking; class ISteamRemoteStorage; class ISteamScreenshots; class ISteamMusic; -class ISteamMusicRemote; +class ISteamMusicRemote; // removed in SDK 1.63 class ISteamGameServerStats; class ISteamHTTP; class ISteamController; @@ -239,7 +239,7 @@ class ISteamHTMLSurface; class ISteamInventory; class ISteamVideo; class ISteamParentalSettings; -class ISteamGameSearch; +class ISteamGameSearch; // removed in SDK 1.63 class ISteamInput; class ISteamParties; class ISteamTV; @@ -298,7 +298,7 @@ enum { k_iClientShortcutsCallbacks = 3700 }; enum { k_iClientRemoteControlManagerCallbacks = 3800 }; enum { k_iSteamAppListCallbacks = 3900 }; enum { k_iSteamMusicCallbacks = 4000 }; -enum { k_iSteamMusicRemoteCallbacks = 4100 }; +enum { k_iSteamMusicRemoteCallbacks = 4100 }; // removed in SDK 1.63 enum { k_iClientVRCallbacks = 4200 }; enum { k_iClientGameNotificationCallbacks = 4300 }; enum { k_iSteamGameNotificationCallbacks = 4400 }; diff --git a/sdk/steam/steamclientpublic.h b/sdk/steam/steamclientpublic.h index 5af797c58..3fed12470 100644 --- a/sdk/steam/steamclientpublic.h +++ b/sdk/steam/steamclientpublic.h @@ -156,6 +156,7 @@ enum EResult k_EResultNotSupported = 128, // The data being accessed is not supported by this API k_EResultFamilySizeLimitExceeded = 129, // Reached the maximum size of the family k_EResultOfflineAppCacheInvalid = 130, // The local data for the offline mode cache is insufficient to login + k_EResultTryLater = 131, // retry the operation later }; // Error codes for use with the voice functions @@ -685,6 +686,9 @@ enum EMarketNotAllowedReasonFlags // User accepted a wallet gift that was recently purchased k_EMarketNotAllowedReason_AcceptedWalletGift = (1 << 15), + + // User did something that triggered a trade cooldown (like reversing trades) + k_EMarketNotAllowedReason_TradeCooldown = (1 << 16), }; diff --git a/sdk/steam/steamhttpenums.h b/sdk/steam/steamhttpenums.h index 004b3a93c..9ea1d9fbe 100644 --- a/sdk/steam/steamhttpenums.h +++ b/sdk/steam/steamhttpenums.h @@ -38,62 +38,76 @@ enum EHTTPMethod enum EHTTPStatusCode { // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code) - k_EHTTPStatusCodeInvalid = 0, + k_EHTTPStatusCodeInvalid = 0, // Informational codes - k_EHTTPStatusCode100Continue = 100, - k_EHTTPStatusCode101SwitchingProtocols = 101, + k_EHTTPStatusCode100Continue = 100, + k_EHTTPStatusCode101SwitchingProtocols = 101, // Success codes - k_EHTTPStatusCode200OK = 200, - k_EHTTPStatusCode201Created = 201, - k_EHTTPStatusCode202Accepted = 202, - k_EHTTPStatusCode203NonAuthoritative = 203, - k_EHTTPStatusCode204NoContent = 204, - k_EHTTPStatusCode205ResetContent = 205, - k_EHTTPStatusCode206PartialContent = 206, + k_EHTTPStatusCode200OK = 200, + k_EHTTPStatusCode201Created = 201, + k_EHTTPStatusCode202Accepted = 202, + k_EHTTPStatusCode203NonAuthoritative = 203, + k_EHTTPStatusCode204NoContent = 204, + k_EHTTPStatusCode205ResetContent = 205, + k_EHTTPStatusCode206PartialContent = 206, // Redirection codes - k_EHTTPStatusCode300MultipleChoices = 300, - k_EHTTPStatusCode301MovedPermanently = 301, - k_EHTTPStatusCode302Found = 302, - k_EHTTPStatusCode303SeeOther = 303, - k_EHTTPStatusCode304NotModified = 304, - k_EHTTPStatusCode305UseProxy = 305, + k_EHTTPStatusCode300MultipleChoices = 300, + k_EHTTPStatusCode301MovedPermanently = 301, + k_EHTTPStatusCode302Found = 302, + k_EHTTPStatusCode303SeeOther = 303, + k_EHTTPStatusCode304NotModified = 304, + k_EHTTPStatusCode305UseProxy = 305, //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1) - k_EHTTPStatusCode307TemporaryRedirect = 307, - k_EHTTPStatusCode308PermanentRedirect = 308, + k_EHTTPStatusCode307TemporaryRedirect = 307, + k_EHTTPStatusCode308PermanentRedirect = 308, // Error codes - k_EHTTPStatusCode400BadRequest = 400, - k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. - k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients - k_EHTTPStatusCode403Forbidden = 403, - k_EHTTPStatusCode404NotFound = 404, - k_EHTTPStatusCode405MethodNotAllowed = 405, - k_EHTTPStatusCode406NotAcceptable = 406, - k_EHTTPStatusCode407ProxyAuthRequired = 407, - k_EHTTPStatusCode408RequestTimeout = 408, - k_EHTTPStatusCode409Conflict = 409, - k_EHTTPStatusCode410Gone = 410, - k_EHTTPStatusCode411LengthRequired = 411, - k_EHTTPStatusCode412PreconditionFailed = 412, - k_EHTTPStatusCode413RequestEntityTooLarge = 413, - k_EHTTPStatusCode414RequestURITooLong = 414, - k_EHTTPStatusCode415UnsupportedMediaType = 415, + k_EHTTPStatusCode400BadRequest = 400, + k_EHTTPStatusCode401Unauthorized = 401, // You probably want 403 or something else. 401 implies you're sending a WWW-Authenticate header and the client can sent an Authorization header in response. + k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients + k_EHTTPStatusCode403Forbidden = 403, + k_EHTTPStatusCode404NotFound = 404, + k_EHTTPStatusCode405MethodNotAllowed = 405, + k_EHTTPStatusCode406NotAcceptable = 406, + k_EHTTPStatusCode407ProxyAuthRequired = 407, + k_EHTTPStatusCode408RequestTimeout = 408, + k_EHTTPStatusCode409Conflict = 409, + k_EHTTPStatusCode410Gone = 410, + k_EHTTPStatusCode411LengthRequired = 411, + k_EHTTPStatusCode412PreconditionFailed = 412, + k_EHTTPStatusCode413RequestEntityTooLarge = 413, + k_EHTTPStatusCode414RequestURITooLong = 414, + k_EHTTPStatusCode415UnsupportedMediaType = 415, k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416, - k_EHTTPStatusCode417ExpectationFailed = 417, - k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown - k_EHTTPStatusCode429TooManyRequests = 429, - k_EHTTPStatusCode444ConnectionClosed = 444, // nginx only? + k_EHTTPStatusCode417ExpectationFailed = 417, + k_EHTTPStatusCode4xxUnknown = 418, // 418 is reserved, so we'll use it to mean unknown + k_EHTTPStatusCode421MisdirectedRequest = 421, + k_EHTTPStatusCode422UnprocessableContent = 422, + k_EHTTPStatusCode423Locked = 423, + k_EHTTPStatusCode424FailedDependency = 424, + k_EHTTPStatusCode425TooEarly = 425, + k_EHTTPStatusCode426UpgradeRequired = 426, + k_EHTTPStatusCode428PreconditionRequired = 428, + k_EHTTPStatusCode429TooManyRequests = 429, + k_EHTTPStatusCode431RequestHeaderFieldsTooLarge = 431, + k_EHTTPStatusCode444ConnectionClosed = 444, // nginx only? + k_EHTTPStatusCode451UnavailableForLegalReasons = 451, // Server error codes - k_EHTTPStatusCode500InternalServerError = 500, - k_EHTTPStatusCode501NotImplemented = 501, - k_EHTTPStatusCode502BadGateway = 502, - k_EHTTPStatusCode503ServiceUnavailable = 503, - k_EHTTPStatusCode504GatewayTimeout = 504, + k_EHTTPStatusCode500InternalServerError = 500, + k_EHTTPStatusCode501NotImplemented = 501, + k_EHTTPStatusCode502BadGateway = 502, + k_EHTTPStatusCode503ServiceUnavailable = 503, + k_EHTTPStatusCode504GatewayTimeout = 504, k_EHTTPStatusCode505HTTPVersionNotSupported = 505, + k_EHTTPStatusCode506VariantAlsoNegotiates = 506, + k_EHTTPStatusCode507InsufficientStorage = 507, + k_EHTTPStatusCode508LoopDetected = 508, + k_EHTTPStatusCode510NotExtended = 510, + k_EHTTPStatusCode511NetworkAuthenticationRequired = 511, k_EHTTPStatusCode5xxUnknown = 599, }; diff --git a/sdk/steam/steamnetworkingtypes.h b/sdk/steam/steamnetworkingtypes.h index a1d9f53b4..02f023e53 100644 --- a/sdk/steam/steamnetworkingtypes.h +++ b/sdk/steam/steamnetworkingtypes.h @@ -807,8 +807,17 @@ struct SteamNetConnectionRealTimeStatus_t /// Nagle delay is ignored for the purposes of this calculation. SteamNetworkingMicroseconds m_usecQueueTime; + /// Highest packet jitter experienced, since the last time this information + /// was returned. (The high water mark is cleared each time you fetch the info.) + /// + /// - The units are microseconds, although the measurement precision is usually + /// not nearly this precise. + /// - A negative value means "no data available". + /// - Not all connections are able to measure jitter. + int32 m_usecMaxJitter; + // Internal stuff, room to change API easily - uint32 reserved[16]; + uint32 reserved[15]; }; #define SteamNetworkingQuickConnectionStatus SteamNetConnectionRealTimeStatus_t @@ -1696,12 +1705,24 @@ enum ESteamNetworkingConfigValue k_ESteamNetworkingConfig_LogLevel_P2PRendezvous = 17, // [connection int32] P2P rendezvous messages k_ESteamNetworkingConfig_LogLevel_SDRRelayPings = 18, // [global int32] Ping relays - // Experimental. Set the ECN header field on all outbound UDP packets - // -1 = the default, and means "don't set anything". - // 0..3 = set that value. (Even though 0 is the default UDP ECN value, a 0 here means "explicitly set a 0".) +// +// Experimental values. These are subject to be deleted or change at any time, +// do not set them, except as a result of an explicit and advanced user opt-in, +// and do not write anything that depends on them existing, or have any particular +// behaviour. +// + // [global int32] ECN value to send in every packet. + // -1 = The default, and means "auto". We will set ECN=1, if it appears that the local internet connection appears to understand it and there may be some benefit. + // 0..2 = use that value. k_ESteamNetworkingConfig_ECN = 999, - // Deleted, do not use + // [global int32] If true, send and request different TOS values in probes to relays + // to try to deduce if there is any bleaching or mutating of the TOS field in either direction + k_ESteamNetworkingConfig_SDRClient_EnableTOSProbes = 998, + +// +// Deleted, do not use +// k_ESteamNetworkingConfig_DELETED_EnumerateDevVars = 35, k_ESteamNetworkingConfigValue__Force32Bit = 0x7fffffff diff --git a/steam_old_lib/steam_old_lib.cpp b/steam_old_lib/steam_old_lib.cpp index d0515f0d1..8b1f887e7 100644 --- a/steam_old_lib/steam_old_lib.cpp +++ b/steam_old_lib/steam_old_lib.cpp @@ -40,19 +40,7 @@ constexpr const static char SOLD_ITF_NAME_FRIENDS[] = "SteamFriends017"; static ISteamClient017 *sold_steam_client = nullptr; static ISteamUtils009 *sold_steam_utils = nullptr; static ISteamUser019 *sold_steam_user = nullptr; -static ISteamApps *sold_steam_apps = nullptr; // TODO at that time ISteamApps == ISteamApps008 (update this when the interface vesion changes) - -static_assert( - sizeof(STEAMAPPS_INTERFACE_VERSION) == sizeof(SOLD_ITF_NAME_APPS) - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 1] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 1] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 2] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 2] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 3] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 3] - && STEAMAPPS_INTERFACE_VERSION[sizeof(STEAMAPPS_INTERFACE_VERSION) - 4] == SOLD_ITF_NAME_APPS[sizeof(SOLD_ITF_NAME_APPS) - 4] - , - "ISteamApps interface was updated,\n" - "change the variable definition to a specific version,\n" - "ex: ISteamApps008" -); +static ISteamApps008 *sold_steam_apps = nullptr; static sold::steamclient_loader_t *steamclient_loader = nullptr; static CreateInterface_t ptrCreateInterface = nullptr; diff --git a/tests/gamepad/test_gamepad_linux.cpp b/tests/gamepad/test_gamepad_linux.cpp new file mode 100644 index 000000000..66893acb1 --- /dev/null +++ b/tests/gamepad/test_gamepad_linux.cpp @@ -0,0 +1,43 @@ +#include "gamepad/gamepad.h" + +#include + +#ifdef GAMEPAD_TESTING +extern "C" unsigned int GamepadTestDetectCount(void); +extern "C" void GamepadTestSignalDeviceChange(void); +#endif + +int main() +{ +#ifndef GAMEPAD_TESTING + std::cerr << "GAMEPAD_TESTING must be enabled for this test" << std::endl; + return 1; +#else + GamepadInit(); + + if (GamepadTestDetectCount() != 1) { + std::cerr << "expected exactly one initial device scan" << std::endl; + return 1; + } + + for (int i = 0; i < 10; ++i) { + GamepadUpdate(); + } + + if (GamepadTestDetectCount() != 1) { + std::cerr << "unexpected rescans without hotplug" << std::endl; + return 1; + } + + GamepadTestSignalDeviceChange(); + GamepadUpdate(); + + if (GamepadTestDetectCount() != 2) { + std::cerr << "expected a rescan after a hotplug signal" << std::endl; + return 1; + } + + GamepadShutdown(); + return 0; +#endif +} diff --git a/third-party/common/linux b/third-party/common/linux index 890cb007c..1f34b918c 160000 --- a/third-party/common/linux +++ b/third-party/common/linux @@ -1 +1 @@ -Subproject commit 890cb007c168faf132ed7673a18486c1512f5720 +Subproject commit 1f34b918c37d58dffd5a9fe9fe1cf47edd5f0186 diff --git a/third-party/common/win b/third-party/common/win index c40455760..d431ffcea 160000 --- a/third-party/common/win +++ b/third-party/common/win @@ -1 +1 @@ -Subproject commit c40455760bf690550be7240b7482884250215188 +Subproject commit d431ffceaadf4e279e70bc4adea3ae42be6820ac diff --git a/third-party/deps/common b/third-party/deps/common index 127738c5f..92a4a1302 160000 --- a/third-party/deps/common +++ b/third-party/deps/common @@ -1 +1 @@ -Subproject commit 127738c5f02ae0ecb9835055bc986dffa389ef3e +Subproject commit 92a4a130262083c4e887155cbe6bcab99baf36ea diff --git a/third-party/deps/linux b/third-party/deps/linux index 0f64de912..702f658c7 160000 --- a/third-party/deps/linux +++ b/third-party/deps/linux @@ -1 +1 @@ -Subproject commit 0f64de91256ea576a6aa087a71284ec8330d78e3 +Subproject commit 702f658c7b8ed7cd87ea31d9b2676f370d07d065 diff --git a/third-party/deps/win b/third-party/deps/win index 107d27376..83308a20b 160000 --- a/third-party/deps/win +++ b/third-party/deps/win @@ -1 +1 @@ -Subproject commit 107d27376e491a1fa1dee2b18b27b9446c725d6f +Subproject commit 83308a20b093ff955fd8119726eb6d531027b2a6 diff --git a/tools/generate_interfaces/generate_interfaces.cpp b/tools/generate_interfaces/generate_interfaces.cpp index 1cc091dc5..f85aa5238 100644 --- a/tools/generate_interfaces/generate_interfaces.cpp +++ b/tools/generate_interfaces/generate_interfaces.cpp @@ -8,6 +8,7 @@ // static char old_xxx[128] = ... const static std::vector interface_patterns = { R"(STEAMAPPS_INTERFACE_VERSION\d+)", + R"(SteamApps\d+)", R"(STEAMAPPLIST_INTERFACE_VERSION\d+)", R"(STEAMAPPTICKET_INTERFACE_VERSION\d+)", R"(SteamClient\d+)", @@ -61,7 +62,7 @@ const static std::vector interface_patterns = { R"(SteamMasterServerUpdater\d+)", }; -unsigned int findinterface( +size_t findinterface( std::ofstream &out_file, const std::string &file_contents, const std::string &interface_patt) @@ -71,15 +72,32 @@ unsigned int findinterface( auto begin = std::sregex_iterator(file_contents.cbegin(), file_contents.cend(), interface_regex); auto end = std::sregex_iterator(); - unsigned int matches = 0; - + std::vector matches; for (std::sregex_iterator i = begin; i != end; ++i) { - out_file << i->str() << std::endl; - ++matches; + matches.push_back(i->str()); + } + + if (interface_patt == R"(SteamClient\d+)" && + matches.size() > 1 && + std::find(matches.begin(), matches.end(), "SteamClient017") != matches.end()) + { + // In newer SDKs, legacy steam_api.dll interface exports were removed except for SteamClient(), + // which still returns SteamClient017. + auto rm = std::remove_if(matches.begin(), matches.end(), [](const std::string &item) + { + return (item != "SteamClient017"); + } + ); + matches.erase(rm, matches.end()); + } + + for (const std::string &match : matches) + { + out_file << match << std::endl; } - return matches; + return matches.size(); } int main(int argc, char *argv[]) @@ -114,7 +132,7 @@ int main(int argc, char *argv[]) return 1; } - unsigned int total_matches = 0; + size_t total_matches = 0; for (const auto &patt : interface_patterns) { diff --git a/tools/lobby_connect/lobby_connect.cpp b/tools/lobby_connect/lobby_connect.cpp index 2bc6780e7..1d50bb150 100644 --- a/tools/lobby_connect/lobby_connect.cpp +++ b/tools/lobby_connect/lobby_connect.cpp @@ -64,41 +64,32 @@ int main() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); } + title(); + int friend_count = SteamFriends()->GetFriendCount(k_EFriendFlagAll); - /* std::cout << "People on the network: " << friend_count << "\n"; - for (int i = 0; i < friend_count; ++i) { - CSteamID id = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); - const char *name = SteamFriends()->GetFriendPersonaName(id); - - FriendGameInfo_t friend_info = {}; - SteamFriends()->GetFriendGamePlayed(id, &friend_info); - std::cout << name << " is playing: " << friend_info.m_gameID.AppID() << std::endl; - } - */ - - title(); - std::vector> arguments; for (int i = 0; i < friend_count; ++i) { CSteamID id = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); const char *name = SteamFriends()->GetFriendPersonaName(id); const char *connect = SteamFriends()->GetFriendRichPresence( id, "connect"); FriendGameInfo_t friend_info = {}; - SteamFriends()->GetFriendGamePlayed(id, &friend_info); + bool isSuccess = SteamFriends()->GetFriendGamePlayed(id, &friend_info); auto appid = friend_info.m_gameID.AppID(); if (strlen(connect) > 0) { std::cout << arguments.size() << " - " << name << " is playing " << appid << " (" << connect << ").\n"; arguments.emplace_back(connect, appid); - } else { - if (friend_info.m_steamIDLobby != k_steamIDNil) { + } else if (isSuccess && friend_info.m_steamIDLobby != k_steamIDNil) { std::string connect = "+connect_lobby " + std::to_string(friend_info.m_steamIDLobby.ConvertToUint64()); std::cout << arguments.size() << " - " << name << " is playing " << appid << " (" << connect << ").\n"; arguments.emplace_back(connect, appid); - } - } + } else if (isSuccess) { + std::cout << name << " is playing " << appid << std::endl; + } else { + std::cout << name << " is found in network " << std::endl; + } } std::cout << arguments.size() << " - Refresh.\n\n";