Compare commits

...

No commits in common. "third-party/common/win" and "dev" have entirely different histories.

781 changed files with 319746 additions and 51 deletions

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 120
[*.py]
indent_size = 4
[*.h]
indent_size = 4
[*.cpp]
indent_size = 4

View File

@ -0,0 +1,123 @@
name: "Build emu (Linux)"
on:
workflow_call:
# needed since it allows this to become a reusable workflow
workflow_dispatch:
# allows manual trigger
permissions:
contents: "write"
env:
PREMAKE_ACTION: "gmake2"
DEPS_CACHE_KEY: "emu-deps-linux"
DEPS_CACHE_DIR: "build/deps/linux"
PACKAGE_BASE_DIR: "build/package/linux"
THIRD_PARTY_BASE_DIR: "third-party"
jobs:
deps:
name: "Restore or build deps"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-deps-linux.yml"
builds-matrix-linux:
name: "build"
needs: ["deps"]
runs-on: "ubuntu-20.04"
if: ${{ !cancelled() }}
continue-on-error: true
strategy:
fail-fast: false
matrix:
prj: [
# regular api
"api_regular",
"steamclient_regular",
# api + client (experimental)
"api_experimental",
"steamclient_experimental",
# tools
"tool_lobby_connect",
"tool_generate_interfaces",
]
arch: ["x64", "x32"]
cfg: ["debug", "release"]
steps:
# clone branch
- name: "Checkout branch"
uses: actions/checkout@v4
# deps
- name: "Restore deps"
id: "emu-deps-cache-step"
uses: actions/cache@v4
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/linux)"
uses: actions/checkout@v4
with:
ref: "third-party/common/linux"
path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux"
- name: "Clone third-party build helpers (build/linux)"
uses: actions/checkout@v4
with:
ref: "third-party/build/linux"
path: "${{env.THIRD_PARTY_BASE_DIR}}/build/linux"
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# generate project files
- name: "Generate project files"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: |
sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5
./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5.lua --genproto --emubuild=${{ github.sha }} --os=linux gmake2
# mandatory Linux packages
- name: "Install required packages"
shell: "bash"
run: |
sudo apt update -y
sudo apt install -y coreutils # echo, printf, etc...
sudo apt install -y build-essential
sudo apt install -y gcc-multilib # needed for 32-bit builds
sudo apt install -y g++-multilib
# sudo apt install -y clang
sudo apt install -y libglx-dev # needed for overlay build (header files such as GL/glx.h)
sudo apt install -y libgl-dev # needed for overlay build (header files such as GL/gl.h)
# sudo apt install -y binutils # (optional) contains the tool 'readelf' mainly, and other usefull binary stuff
# build target
- name: "Build target"
shell: "bash"
working-directory: "${{ github.workspace }}/build/project/gmake2/linux"
run: |
echo "dry run..."
make -n -j 2 config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }}
echo "actual run..."
make -j 2 config=${{ matrix.cfg }}_${{ matrix.arch }} ${{ matrix.prj }}
# upload artifact/package to github Actions
- name: "Upload target package"
uses: actions/upload-artifact@v4
with:
name: "emu-linux-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}"
path: "build/linux"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

114
.github/workflows/emu-build-all-win.yml vendored Normal file
View File

@ -0,0 +1,114 @@
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"
jobs:
deps:
name: "Restore or build deps"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-deps-win.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",
# tools
"tool_lobby_connect",
"tool_generate_interfaces",
]
arch: ["x64", "Win32"]
cfg: ["debug", "release"]
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
# ensure we have msbuild
- name: "Add MSBuild to PATH"
uses: microsoft/setup-msbuild@v2
# clone branch
- name: "Checkout branch"
uses: actions/checkout@v4
# deps
- name: "Restore deps"
id: "emu-deps-cache-step"
uses: actions/cache@v4
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@v4
with:
ref: "third-party/common/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win"
- name: "Clone third-party deps (build/win)"
uses: actions/checkout@v4
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:2 /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@v4
with:
name: "emu-win-${{ matrix.prj }}-${{ matrix.cfg }}-${{ matrix.arch }}-${{ github.sha }}"
path: "build/win"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

88
.github/workflows/emu-deps-linux.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: "Emu third-party dependencies (Linux)"
on:
workflow_call:
# needed since it allows this to become a reusable workflow
workflow_dispatch:
# allows manual trigger
permissions:
contents: "write"
env:
PREMAKE_ACTION: "gmake2"
DEPS_CACHE_KEY: "emu-deps-linux"
DEPS_CACHE_DIR: "build/deps/linux"
PACKAGE_BASE_DIR: "build/package/linux"
THIRD_PARTY_BASE_DIR: "third-party"
jobs:
deps-build:
runs-on: "ubuntu-20.04"
if: ${{ !cancelled() }}
steps:
- name: "Lookup cache for deps"
id: "emu-deps-cache-step"
uses: actions/cache@v4
with:
key: "${{ env.DEPS_CACHE_KEY }}-${{ env.PREMAKE_ACTION }}"
path: "${{ env.DEPS_CACHE_DIR }}/${{ env.PREMAKE_ACTION }}"
# we need branch because it has build scripts
- name: "Checkout branch"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
- name: "Clone third-party deps (common/linux)"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
ref: "third-party/common/linux"
path: "${{env.THIRD_PARTY_BASE_DIR}}/common/linux"
- name: "Clone third-party deps (deps/linux)"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
ref: "third-party/deps/linux"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/linux"
- name: "Clone third-party deps (deps/common)"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
ref: "third-party/deps/common"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common"
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# mandatory Linux packages
- name: "Install required packages"
shell: "bash"
run: |
sudo apt update -y
sudo apt install -y coreutils # echo, printf, etc...
sudo apt install -y build-essential
sudo apt install -y gcc-multilib # needed for 32-bit builds
sudo apt install -y g++-multilib
# sudo apt install -y clang
sudo apt install -y libglx-dev # needed for overlay build (header files such as GL/glx.h)
sudo apt install -y libgl-dev # needed for overlay build (header files such as GL/gl.h)
# sudo apt install -y binutils # (optional) contains the tool 'readelf' mainly, and other usefull binary stuff
- name: "Build deps"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
shell: "bash"
working-directory: "${{ github.workspace }}"
run: |
export CMAKE_GENERATOR="Unix Makefiles"
sudo chmod 777 ./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5
./${{env.THIRD_PARTY_BASE_DIR}}/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --j=2 --verbose --clean --os=linux gmake2

82
.github/workflows/emu-deps-win.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: "Emu third-party dependencies (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"
PACKAGE_BASE_DIR: "build/package/win"
THIRD_PARTY_BASE_DIR: "third-party"
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@v4
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@v4
- name: "Clone third-party deps (common/win)"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
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@v4
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@v4
with:
ref: "third-party/deps/common"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/common"
- name: "Clone third-party deps (common/win)"
if: steps.emu-deps-cache-step.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
ref: "third-party/common/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/common/win"
- 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=2 --verbose --clean --os=windows vs2022

30
.github/workflows/emu-pull-request.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: "Emu PR"
on:
pull_request:
branches: ["dev"]
paths-ignore:
- "**/*.md"
- "dev.notes/**"
- "post_build/**"
- "z_original_repo_files/**"
- "sdk/*.txt"
- "LICENSE"
# tools
- "tools/generate_emu_config/**"
- "tools/migrate_gse/**"
- "tools/steamclient_loader/linux/**" # these are just scripts, not built
permissions:
contents: "write"
jobs:
emu-win-all:
name: "win"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-build-all-win.yml"
emu-linux-all:
name: "linux"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-build-all-linux.yml"

View File

@ -0,0 +1,52 @@
name: "Build gen_emu_config script (Linux)"
on:
workflow_call:
# needed since it allows this to become a reusable workflow
workflow_dispatch:
# allows manual trigger
permissions:
contents: "write"
env:
ARTIFACT_NAME: "generate_emu_config-linux-${{ github.sha }}"
SCRIPT_BASE_DIR: "tools/generate_emu_config"
PACKAGE_BASE_DIR: "tools/generate_emu_config/bin/linux"
jobs:
build:
runs-on: "ubuntu-20.04"
steps:
- name: "Checkout branch"
uses: actions/checkout@v4
# env
- name: "Install env"
shell: "bash"
working-directory: "${{ env.SCRIPT_BASE_DIR }}"
run: sudo chmod 777 recreate_venv_linux.sh && sudo ./recreate_venv_linux.sh
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# build
- name: "Rebuild"
shell: "bash"
working-directory: "${{ env.SCRIPT_BASE_DIR }}"
run: sudo chmod 777 rebuild_linux.sh && ./rebuild_linux.sh
# upload artifact
- name: "Upload build package"
uses: actions/upload-artifact@v4
with:
name: "${{ env.ARTIFACT_NAME }}"
path: "${{ env.PACKAGE_BASE_DIR }}/"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

View File

@ -0,0 +1,83 @@
name: "Build gen_emu_config 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: "generate_emu_config-win-${{ github.sha }}"
SCRIPT_BASE_DIR: "tools/generate_emu_config"
PACKAGE_BASE_DIR: "tools/generate_emu_config/bin/win"
THIRD_PARTY_BASE_DIR: "third-party"
jobs:
build:
runs-on: "windows-2022"
steps:
- name: "Set up Python"
uses: actions/setup-python@v5
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@v4
## extra helpers/tools, these are not built inside the deps build dir
- name: "Clone third-party deps (build/win)"
uses: actions/checkout@v4
with:
ref: "third-party/build/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win"
## clone this for 7za.exe
- name: "Clone third-party deps (deps/win)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win"
# download artifacts
- name: "Download emu build artifacts (Win)"
uses: actions/download-artifact@v4
with:
path: "build/win"
pattern: "emu-win-*"
merge-multiple: true
# 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@v4
with:
name: "${{ env.ARTIFACT_NAME }}"
path: "${{ env.PACKAGE_BASE_DIR }}/"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

View File

@ -0,0 +1,22 @@
name: "Gen emu cfg PR"
on:
pull_request:
branches: ["dev"]
paths:
- "!**/*.md"
- "tools/generate_emu_config/**"
permissions:
contents: "write"
jobs:
script-win:
name: "Gen emu config win"
if: ${{ !cancelled() }}
uses: "./.github/workflows/gen_emu_config-build-win.yml"
script-linux:
name: "Gen emu config linux"
if: ${{ !cancelled() }}
uses: "./.github/workflows/gen_emu_config-build-linux.yml"

View File

@ -0,0 +1,52 @@
name: "Build migrate_gse script (Linux)"
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-linux-${{ github.sha }}"
SCRIPT_BASE_DIR: "tools/migrate_gse"
PACKAGE_BASE_DIR: "tools/migrate_gse/bin/linux"
jobs:
build:
runs-on: "ubuntu-20.04"
steps:
- name: "Checkout branch"
uses: actions/checkout@v4
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# env
- name: "Install env"
shell: "bash"
working-directory: "${{ env.SCRIPT_BASE_DIR }}"
run: sudo chmod 777 recreate_venv_linux.sh && sudo ./recreate_venv_linux.sh
# build
- name: "Rebuild"
shell: "bash"
working-directory: "${{ env.SCRIPT_BASE_DIR }}"
run: sudo chmod 777 rebuild_linux.sh && ./rebuild_linux.sh
# upload artifact
- name: "Upload build package"
uses: actions/upload-artifact@v4
with:
name: "${{ env.ARTIFACT_NAME }}"
path: "${{ env.PACKAGE_BASE_DIR }}/"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

View File

@ -0,0 +1,83 @@
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-${{ 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@v5
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@v4
# extra helpers/tools, these are not built inside the deps build dir
- name: "Clone third-party deps (build/win)"
uses: actions/checkout@v4
with:
ref: "third-party/build/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/build/win"
## clone this for 7za.exe
- name: "Clone third-party deps (deps/win)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win"
# download artifacts
- name: "Download emu build artifacts (Win)"
uses: actions/download-artifact@v4
with:
path: "build/win"
pattern: "emu-win-*"
merge-multiple: true
# 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@v4
with:
name: "${{ env.ARTIFACT_NAME }}"
path: "${{ env.PACKAGE_BASE_DIR }}/"
if-no-files-found: "error"
compression-level: 9
retention-days: 1

View File

@ -0,0 +1,22 @@
name: "Migrate GSE PR"
on:
pull_request:
branches: ["dev"]
paths:
- "!**/*.md"
- "tools/migrate_gse/**"
permissions:
contents: "write"
jobs:
script-win:
name: "Migrate GSE win"
if: ${{ !cancelled() }}
uses: "./.github/workflows/migrate_gse-build-win.yml"
script-linux:
name: "Migrate GSE linux"
if: ${{ !cancelled() }}
uses: "./.github/workflows/migrate_gse-build-linux.yml"

415
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,415 @@
name: "Prepare release"
on:
push:
tags:
- "release-*"
workflow_dispatch:
# allows manual trigger
permissions:
contents: "write"
env:
THIRD_PARTY_BASE_DIR: "third-party"
jobs:
emu-win-all:
name: "Emu win all"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-build-all-win.yml"
emu-win-prep:
needs: ["emu-win-all"]
runs-on: "windows-2022"
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
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
- name: "Clone third-party deps (deps/win)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win"
# download artifacts
- name: "Download emu build artifacts (Win)"
uses: actions/download-artifact@v4
with:
path: "build/win"
pattern: "emu-win-*-${{ github.sha }}"
merge-multiple: true
# print files
- name: "Print files"
shell: "cmd"
working-directory: "${{ github.workspace }}"
run: |
dir /s /b /a:-d build\win
# package (release mode)
- name: "Package build (release)"
shell: "cmd"
working-directory: "${{ github.workspace }}"
run: package_win.bat vs2022\release
# package (debug mode)
- name: "Package build (debug)"
shell: "cmd"
working-directory: "${{ github.workspace }}"
run: package_win.bat vs2022\debug 1
# release (debug + release modes) if this is a tag push
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "build/package/win/**/*"
# upload artifacts/packages if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-emu-win-release-${{ github.sha }}"
path: "build/package/win/vs2022/*release*"
if-no-files-found: "error"
compression-level: 0
retention-days: 7
- name: "Upload debug package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-emu-win-debug-${{ github.sha }}"
path: "build/package/win/vs2022/*debug*"
if-no-files-found: "error"
compression-level: 0
retention-days: 7
emu-linux-all:
name: "Emu linux all"
if: ${{ !cancelled() }}
uses: "./.github/workflows/emu-build-all-linux.yml"
emu-linux-prep:
needs: ["emu-linux-all"]
runs-on: "ubuntu-20.04"
steps:
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
- name: "Clone third-party deps (deps/linux)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/linux"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/linux"
# download artifacts
- name: "Download emu build artifacts (linux)"
uses: actions/download-artifact@v4
with:
path: "build/linux"
pattern: "emu-linux-*-${{ github.sha }}"
merge-multiple: true
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}" && sudo chmod 777 package_linux.sh
# print files
- name: "Print files"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: |
ls -la build/linux/*/*
# downlaod ubuntu packages
- name: "Download required Ubuntu packages"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: |
sudo apt update || exit 1
sudo apt install tar -y || exit 1
# package (release mode)
- name: "Package build (release)"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: ./package_linux.sh gmake2/release
# package (debug mode)
- name: "Package build (debug)"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: ./package_linux.sh gmake2/debug 1
# release (debug + release modes) if this is a tag push
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "build/package/linux/**/*"
# upload artifacts/packages if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-emu-linux-release-${{ github.sha }}"
path: "build/package/linux/gmake2/*release*"
if-no-files-found: "error"
compression-level: 0
retention-days: 7
- name: "Upload debug package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-emu-linux-debug-${{ github.sha }}"
path: "build/package/linux/gmake2/*debug*"
if-no-files-found: "error"
compression-level: 0
retention-days: 7
gen_emu_script-win:
needs: ["emu-win-all"] # add emu-win-all to wait for emu build to complete, so that we include the latest dlls and tools in generate_emu_config
name: "Gen emu config win"
if: ${{ !cancelled() }}
uses: "./.github/workflows/gen_emu_config-build-win.yml"
gen_emu_script-win-prep:
needs: ["gen_emu_script-win"]
runs-on: "windows-2022"
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
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
- name: "Clone third-party deps (deps/win)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win"
# download artifacts
- name: "Download script build artifacts (Win)"
uses: actions/download-artifact@v4
with:
path: "tools/generate_emu_config/bin/win"
pattern: "generate_emu_config-win-*"
merge-multiple: true
# package
- name: "Package script"
shell: "cmd"
working-directory: "tools/generate_emu_config"
run: package_win.bat
# release tag
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "tools/generate_emu_config/bin/package/win/**/*"
# upload artifact/package if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-generate_emu_config-win-${{ github.sha }}"
path: "tools/generate_emu_config/bin/package/win/**/*"
if-no-files-found: "error"
compression-level: 9
retention-days: 7
gen_emu_script-linux:
needs: ["emu-linux-all"] # add emu-linux-all to wait for emu build to complete (not really needed but included for better build matrix visualization)
name: "Gen emu config linux"
if: ${{ !cancelled() }}
uses: "./.github/workflows/gen_emu_config-build-linux.yml"
gen_emu_script-linux-prep:
needs: ["gen_emu_script-linux"]
runs-on: "ubuntu-20.04"
steps:
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
# download artifacts
- name: "Download script build artifacts (linux)"
uses: actions/download-artifact@v4
with:
path: "tools/generate_emu_config/bin/linux"
pattern: "generate_emu_config-linux-*"
merge-multiple: true
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# package
- name: "Package script"
shell: "bash"
working-directory: "tools/generate_emu_config"
run: sudo chmod 777 package_linux.sh && sudo ./package_linux.sh
# release tag
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "tools/generate_emu_config/bin/package/linux/**/*"
# upload artifact/package if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-generate_emu_config-linux-${{ github.sha }}"
path: "tools/generate_emu_config/bin/package/linux/**/*"
if-no-files-found: "error"
compression-level: 9
retention-days: 7
migrate_gse_script-win:
needs: ["emu-win-all"] # add emu-win-all to wait for emu build to complete, so that we include the latest dlls and tools in migrate_gse
name: "Migrate GSE win"
if: ${{ !cancelled() }}
uses: "./.github/workflows/migrate_gse-build-win.yml"
migrate_gse_script-win-prep:
needs: ["migrate_gse_script-win"]
runs-on: "windows-2022"
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
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
- name: "Clone third-party deps (deps/win)"
uses: actions/checkout@v4
with:
ref: "third-party/deps/win"
path: "${{env.THIRD_PARTY_BASE_DIR}}/deps/win"
# download artifacts
- name: "Download script build artifacts (Win)"
uses: actions/download-artifact@v4
with:
path: "tools/migrate_gse/bin/win"
pattern: "migrate_gse-win-*"
merge-multiple: true
# package
- name: "Package script"
shell: "cmd"
working-directory: "tools/migrate_gse"
run: package_win.bat
# release tag
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "tools/migrate_gse/bin/package/win/**/*"
# upload artifact/package if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-migrate_gse-win-${{ github.sha }}"
path: "tools/migrate_gse/bin/package/win/**/*"
if-no-files-found: "error"
compression-level: 9
retention-days: 7
migrate_gse_script-linux:
needs: ["emu-linux-all"] # add emu-linux-all to wait for emu build to complete (not really needed but included for better build matrix visualization)
name: Migrate GSE linux
if: ${{ !cancelled() }}
uses: "./.github/workflows/migrate_gse-build-linux.yml"
migrate_gse_script-linux-prep:
needs: ["migrate_gse_script-linux"]
runs-on: "ubuntu-20.04"
steps:
# we need branch because it has package scripts
- name: "Checkout branch"
uses: actions/checkout@v4
# download artifacts
- name: "Download script build artifacts (linux)"
uses: actions/download-artifact@v4
with:
path: "tools/migrate_gse/bin/linux"
pattern: "migrate_gse-linux-*"
merge-multiple: true
# fix folder permissions! not sure why this fails
# nested subdirs "build/linux/release" cause permission problems
- name: "Give all permissions to repo folder"
shell: "bash"
working-directory: "${{ github.workspace }}"
run: sudo chmod -R 777 "${{ github.workspace }}"
# package
- name: "Package script"
shell: "bash"
working-directory: "tools/migrate_gse"
run: sudo chmod 777 package_linux.sh && sudo ./package_linux.sh
# release tag
- name: "Release"
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: "tools/migrate_gse/bin/package/linux/**/*"
# upload artifact/package if this is a manual run
- name: "Upload release package"
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: actions/upload-artifact@v4
with:
name: "release-migrate_gse-linux-${{ github.sha }}"
path: "tools/migrate_gse/bin/package/linux/**/*"
if-no-files-found: "error"
compression-level: 9
retention-days: 7

67
.gitignore vendored Normal file
View File

@ -0,0 +1,67 @@
# IDE
**/.vs
**/.vscode
# PYTHON
**/.venv
**/__pycache__
# PROTOBUF
/proto_gen
# SPECIFIC
/build
/third-party
# TOOLS
# generate_emu_config
/tools/generate_emu_config/.py*/
/tools/generate_emu_config/.env*/
/tools/generate_emu_config/bin
/tools/generate_emu_config/_DEFAULT/0/steam_api.dll
/tools/generate_emu_config/_DEFAULT/0/steam_api.7z
/tools/generate_emu_config/_DEFAULT/0/steam_api64.dll
/tools/generate_emu_config/_DEFAULT/0/steam_api64.7z
/tools/generate_emu_config/_DEFAULT/0/steam_api.7z
/tools/generate_emu_config/_DEFAULT/0/steam_api64.dll
/tools/generate_emu_config/_DEFAULT/0/steam_api64.7z
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces.7z
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces.exe
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces64.exe
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect.7z
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect.exe
/tools/generate_emu_config/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect64.exe
/tools/generate_emu_config/_OUTPUT/
/tools/generate_emu_config/login_temp/
/tools/generate_emu_config/**/my_login.txt
# migrate_gse
/tools/migrate_gse/.py*/
/tools/migrate_gse/.env*/
/tools/migrate_gse/bin
/tools/migrate_gse/_DEFAULT/0/steam_api.dll
/tools/migrate_gse/_DEFAULT/0/steam_api.7z
/tools/migrate_gse/_DEFAULT/0/steam_api64.dll
/tools/migrate_gse/_DEFAULT/0/steam_api64.7z
/tools/migrate_gse/_DEFAULT/0/steam_api.7z
/tools/migrate_gse/_DEFAULT/0/steam_api64.dll
/tools/migrate_gse/_DEFAULT/0/steam_api64.7z
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces.7z
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces.exe
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/generate_interfaces/generate_interfaces64.exe
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect.7z
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect.exe
/tools/migrate_gse/_DEFAULT/0/steam_misc/tools/lobby_connect/lobby_connect64.exe
/tools/migrate_gse/_OUTPUT/

34
.gitmodules vendored Normal file
View File

@ -0,0 +1,34 @@
[submodule "third-party/build/win"]
path = third-party/build/win
url = ./
branch = third-party/build/win
[submodule "third-party/build/linux"]
path = third-party/build/linux
url = ./
branch = third-party/build/linux
[submodule "third-party/common/win"]
path = third-party/common/win
url = ./
branch = third-party/common/win
[submodule "third-party/common/linux"]
path = third-party/common/linux
url = ./
branch = third-party/common/linux
[submodule "third-party/deps/win"]
path = third-party/deps/win
url = ./
branch = third-party/deps/win
[submodule "third-party/deps/linux"]
path = third-party/deps/linux
url = ./
branch = third-party/deps/linux
[submodule "third-party/deps/common"]
path = third-party/deps/common
url = ./
branch = third-party/deps/common

1036
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

2556
CREDITS.md Normal file

File diff suppressed because it is too large Load Diff

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

365
README.md Normal file
View File

@ -0,0 +1,365 @@
## :large_orange_diamond: **Goldberg Steam Emu**
Fork of https://gitlab.com/Mr_Goldberg/goldberg_emulator with a lot of fixes, improvements, additional features and a completely reworked file structure.
Fork originally made by wizark952. This is latest version of it.
### Feel free to make a PR.
---
:red_circle:
**This fork is not a takeover, not a resurrection of the original project, and not a replacement.**
**You are highly encouraged to fork/clone it and do whatever you want with it.**
:red_circle:
---
## **Compatibility**
This fork is incompatible with the original repo, lots of things has changed and might be even broken.
If something doesn't work, feel free to create a pull request with the appropriate fix, otherwise ignore this fork and use the original emu.
---
## **Credits**
Thanks to everyone contributing to this project in any way possible, we try to keep the [CHANGELOG.md](./CHANGELOG.md) updated with all the changes and their authors.
This project depends on many third-party libraries and tools, credits to them for their amazing work, you can find their listing here in [CREDITS.md](./CREDITS.md).
---
# How to use the emu
* **Always generate the interfaces file using the `find_interfaces` tool.**
* **Generate the proper app configuration using the `generate_emu_config` tool.**
* **If things don't work, try the `ColdClientLoader` setup.**
You can find some guides, helper tools and scripts here:
**These guides, tools and scripts are maintained by their authors.
Before using them, it's always a good idea to first make sure they are updated and designed to support all features of this fork of the emulator.**
* **[How to use Goldberg Emulator](https://rentry.co/goldberg_emulator)**
* **[Steam Emu Utility](https://github.com/turusudiro/SteamEmuUtility)**
* **[GSE-Generator](https://github.com/brunolee-GIT/GSE-Generator)**
You can also find instructions here in [README.release.md](./post_build/README.release.md)
---
# **Compiling**
## One time setup
### **Cloning the repo**
Disable automatic CRLF handling:
*Locally*
```shell
git config --local core.autocrlf false
```
*Or globally/system wide*
```shell
git config --system core.autocrlf false
git config --global core.autocrlf false
```
Clone the repo and its submodules **recursively**
```shell
git clone --recurse-submodules -j8 https://github.com/otavepto/gbe_fork.git
```
The switch `-j8` is optional, it allows Git to fetch up to 8 submodules
It is adviseable to always checkout submodules every now and then, to make sure they're up to date
```shell
git submodule update --init --recursive --remote
```
### For Windows:
* You need Windows 10 or 8.1 + WDK
* Using Visual Studio, install `Visual Studio 2022 Community`: https://visualstudio.microsoft.com/vs/community/
* Select the Workload `Desktop development with C++`
* In the `Individual componenets` scroll to the buttom and select the **latest** version of `Windows XX SDK (XX.X...)`
For example `Windows 11 SDK (10.0.22621.0)`
* Using `MSYS2` **this is currently experimental and will not work due to ABI differences**: https://www.msys2.org/
<details>
<summary>steps</summary>
* To build 64-bit binaries use either the [environment](https://www.msys2.org/docs/environments/) `UCRT64` or `MINGW64` then install the GCC toolchain
`UCRT64`
```shell
pacman -S mingw-w64-ucrt-x86_64-gcc
```
`MINGW64`
```shell
pacman -S mingw-w64-i686-gcc
```
* To build 32-bit binaries use the environment `MINGW32` then install the GCC toolchain
```shell
pacman -S mingw-w64-i686-gcc
```
</details>
* Python 3.10 or above: https://www.python.org/downloads/windows/
After installation, make sure it works
```batch
python --version
```
* *(Optional)* Install a GUI for Git like [GitHub Desktop](https://desktop.github.com/), or [Sourcetree](https://www.sourcetreeapp.com/)
### For Linux:
* Ubuntu 20.04 LTS: https://ubuntu.com/download/desktop
* Ubuntu required packages:
```shell
sudo apt update -y
sudo apt install -y coreutils # echo, printf, etc...
sudo apt install -y build-essential
sudo apt install -y gcc-multilib # needed for 32-bit builds
sudo apt install -y g++-multilib
sudo apt install -y libglx-dev # needed for overlay build (header files such as GL/glx.h)
sudo apt install -y libgl-dev # needed for overlay build (header files such as GL/gl.h)
```
*(Optional)* Additional packages
```shell
sudo apt install -y clang # clang compiler
sudo apt install -y binutils # contains the tool 'readelf' mainly, and other usefull binary stuff
```
* Python 3.10 or above
```shell
sudo apt update -y
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update -y
sudo apt install -y "python3.12"
sudo apt install -y "python3.12-dev"
sudo apt install -y "python3.12-venv"
sudo apt install -y python3-dev
# make sure it works
python3.12 --version
```
### **Building dependencies**
These are third party libraries needed to build the emu later, they are linked with the emu during its build process.
You don't need to build these dependencies every time, they rarely get updated.
The only times you'll need to rebuild them is either when their separete build folder was accedentally deleted, or when the dependencies were updated.
<br/>
#### On Windows:
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
```
* To build using `MSYS2` **this is currently experimental and will not work due to ABI differences**
<details>
<summary>steps</summary>
*(Optional)* In both cases below, you can use `Clang` compiler instead of `GCC` by running these 2 commands in the same terminal instance
```shell
export CC="clang"
export CXX="clang++"
```
* To build 64-bit binaries (`UCRT64` or `MINGW64`)
```shell
export CMAKE_GENERATOR="MSYS Makefiles"
./third-party/common/win/premake/premake5.exe --file=premake5-deps.lua --64-build --all-ext --all-build --verbose --os=windows gmake2
```
* To build 32-bit binaries (`MINGW32`)
```shell
export CMAKE_GENERATOR="MSYS Makefiles"
./third-party/common/win/premake/premake5.exe --file=premake5-deps.lua --32-build --all-ext --all-build --verbose --os=windows gmake2
```
</details>
This will:
* Extract all third party dependencies from the folder `third-party` into the folder `build\deps\win`
* Build all dependencies
#### On Linux:
Open a terminal in the repo folder
*(Optional)* You can use `Clang` compiler instead of `GCC` by running these 2 commands in the current terminal instance
```shell
export CC="clang"
export CXX="clang++"
```
Then run the following
```shell
export CMAKE_GENERATOR="Unix Makefiles"
./third-party/common/linux/premake/premake5 --file=premake5-deps.lua --64-build --32-build --all-ext --all-build --verbose --os=linux gmake2
```
This will:
* Extract all third party dependencies from the folder `third-party` into the folder `build/deps/linux`
* Build all dependencies (32-bit and 64-bit)
---
## **Building the emu**
### On Windows:
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
```
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:
```batch
msbuild /nologo /v:n /p:Configuration=release,Platform=Win32 gbe.sln
msbuild /nologo /v:n /p:Configuration=release,Platform=x64 gbe.sln
```
* For `MSYS2` **this is currently experimental and will not work due to ABI differences**
<details>
<summary>steps</summary>
```shell
./third-party/common/win/premake/premake5.exe --file=premake5.lua --genproto --os=windows gmake2
cd ./build/project/gmake2/win
```
*(Optional)* You can use `Clang` compiler instead of `GCC` by running these 2 commands in the current terminal instance
```shell
export CC="clang"
export CXX="clang++"
```
* 64-bit build (`UCRT64` or `MINGW64`)
```shell
make config=release_x64 -j 8 all
```
* 32-bit build (`MINGW32`)
```shell
make config=release_x32 -j 8 all
```
To see all possible build targets
```shell
make help
```
</details>
This will build a release version of the emu in the folder `build\win\<toolchain>\release`
An example script `build_win_premake.bat` is available, check it out
<br/>
### On Linux:
Open a terminal in the repo folder, then run the following
```shell
./third-party/common/linux/premake/premake5 --file=premake5.lua --genproto --os=linux gmake2
cd ./build/project/gmake2/linux
```
*(Optional)* You can use `Clang` compiler instead of `GCC` by running these 2 commands in the current terminal instance
```shell
export CC="clang"
export CXX="clang++"
```
Then run the following
```shell
make config=release_x32 -j 8 all
make config=release_x64 -j 8 all
```
To see all possible build targets
```shell
make help
```
This will build a release version of the emu in the folder `build/linux/<toolchain>/release`
An example script `build_linux_premake.sh` is available, check it out
---
## **Building the tool `generate_emu_config`**
Navigate to the folder `tools/generate_emu_config/` then
### On Windows:
Open CMD then:
1. Create python virtual environemnt and install the required packages/dependencies
```batch
recreate_venv_win.bat
```
2. Build the tool using `pyinstaller`
```batch
rebuild_win.bat
```
This will build the tool inside `bin\win`
### On Linux:
Open bash terminal then:
1. Create python virtual environemnt and install the required packages/dependencies
```shell
sudo ./recreate_venv_linux.sh
```
You might need to edit this script to use a different python version.
Find this line and change it:
```shell
python_package="python3.12"
```
2. Build the tool using `pyinstaller`
```shell
./rebuild_linux.sh
```
This will build the tool inside `bin/linux`
---
## **Using Github CI as a builder**
This is really slow and mainly intended for the CI Workflow scripts, but you can use it as another outlet if you can't build locally.
**You have to fork the repo first**.
### Initial setup
In your fork, open the `Settings` tab from the top, then:
* From the left side panel select `Actions` -> `General`
* In the section `Actions permissions` select `Allow all actions and reusable workflows`
* Scroll down, and in the section `Workflow permissions` select `Read and write permissions`
* *(Optional)* In the section `Artifact and log retention`, you can specify the amount of days to keep the build artifacts/archives.
It is recommended to set a reasonable number like 3-4 days, otherwise you may consume your packages storage if you use Github as a builder frequently, more details here: https://docs.github.com/en/get-started/learning-about-github/githubs-plans
### Manual trigger
1. Go to the `Actions` tab in your fork
2. Select the emu dependencies Workflow (ex: `Emu third-party dependencies (Windows) `) and run it on the **main** branch (ex: `dev`).
Dependencies not created on the main branch won't be recognized by other branches or subsequent runs
3. Select one of the Workflow scripts from the left side panel, for example `Build all emu variants (Windows)`
3. On the top-right, select `Run workflow` -> select the desired branch (for example `dev`) -> press the button `Run workflow`
4. When it's done, many packages (called build artifacts) will be created for that workflow.
Make sure to select the workflow again to view its history, then select the last run at the very top to view its artifacts
<br/>
Important note:
---
When you build the dependencies workflows, they will be cached to decrease the build times of the next triggers and avoid unnecessary/wasteful build process.
This will cause a problem if at any time the third-party dependencies were updated, in that case you need to manually delete the cache, in your fork:
1. Go to the `Actions` tab at the top
2. Select `Caches` from the left side panel
3. Delete the corresponding cache
<br/>
---
## ***(Optional)* Packaging**
This step is intended for Github CI/Workflow, but you can create a package locally.
### On Windows:
Open CMD in the repos's directory, then run this script
```batch
package_win.bat <build_folder>
```
`build_folder` is any folder inside `build\win`, for example: `vs2022\release`
The above example will create a `.7z` archive inside `build\package\win\`
### On Linux:
Open bash terminal in the repos's directory, then run this script
```shell
package_linux.sh <build_folder>
```
`build_folder` is any folder inside `build/linux`, for example: `gmake2/release`
The above example will create a compressed `.tar` archive inside `build/package/linux/`

71
build_linux_premake.sh Normal file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env bash
function help_page () {
echo "./$(basename "$0") [switches]"
echo "switches:"
echo " --deps: rebuild third-party dependencies"
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
for (( i=1; i<=$#; ++i )); do
arg="${!i}"
if [[ "$arg" = "--deps" ]]; then
BUILD_DEPS=1
elif [[ "$arg" = "--help" ]]; then
help_page
exit 0
else
echo "invalid arg $arg" 1>&2
exit 1
fi
done
premake_exe=./"third-party/common/linux/premake/premake5"
if [[ ! -f "$premake_exe" ]]; then
echo "preamke wasn't found" 1>&2
exit 1
fi
chmod 777 "$premake_exe"
# build deps
if [[ $BUILD_DEPS = 1 ]]; then
export CMAKE_GENERATOR="Unix Makefiles"
"$premake_exe" --file="premake5-deps.lua" --all-ext --all-build --64-build --32-build --verbose --clean --j=$build_threads --os=linux gmake2 || {
exit 1;
}
fi
"$premake_exe" --genproto --os=linux gmake2 || {
exit 1;
}
pushd ./"build/project/gmake2/linux"
# you can select individual or all
echo; echo building debug x64
make -j $build_threads config=debug_x64 || {
exit 1;
}
echo; echo building debug x32
make -j $build_threads config=debug_x32 || {
exit 1;
}
echo; echo building release x64
make -j $build_threads config=release_x64 || {
exit 1;
}
echo; echo building release x32
make -j $build_threads config=release_x32 || {
exit 1;
}
popd

112
build_win_premake.bat Normal file
View File

@ -0,0 +1,112 @@
@echo off
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"
:args_loop
if "%~1" equ "" (
goto :args_loop_end
) else if "%~1" equ "--deps" (
set /a "BUILD_DEPS=1"
) else if "%~1" equ "--help" (
goto :help_page
) else (
1>&2 echo:invalid arg %~1
goto :end_script_with_err
)
shift /1
goto :args_loop
:args_loop_end
:: check premake
set "PREMAKE_EXE=third-party\common\win\premake\premake5.exe"
if not exist "%PREMAKE_EXE%" (
1>&2 echo:premake wasn't found
goto :end_script_with_err
)
:: 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 || (
goto :end_script_with_err
)
goto :end_script
)
:: check vswhere
set "VSWHERE_EXE=third-party\common\win\vswhere\vswhere.exe"
if not exist "%VSWHERE_EXE%" (
1>&2 echo:vswhere wasn't found
goto :end_script_with_err
)
:: check msbuild
set "MSBUILD_EXE="
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"
)
if not exist "%MSBUILD_EXE%" (
1>&2 echo:MSBuild wasn't found
goto :end_script_with_err
)
:: create .sln
call "%PREMAKE_EXE%" --file="premake5.lua" --genproto --dosstub --winrsrc --winsign --os=windows vs2022 || (
goto :end_script_with_err
)
:: check .sln
set "SLN_FILE=build\project\vs2022\win\gbe.sln"
if not exist "%SLN_FILE%" (
1>&2 echo:.sln file wasn't found
goto :end_script_with_err
)
:: build .sln
set "BUILD_TYPES=release debug"
set "BUILD_PLATFORMS=x64 Win32"
set "BUILD_TARGETS=api_regular api_experimental steamclient_experimental_stub steamclient_experimental steamclient_experimental_loader steamclient_experimental_extra lib_game_overlay_renderer tool_lobby_connect tool_generate_interfaces"
for %%A in (%BUILD_TYPES%) do (
set "BUILD_TYPE=%%A"
for %%B in (%BUILD_PLATFORMS%) do (
set "BUILD_PLATFORM=%%B"
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%" || (
goto :end_script_with_err
)
)
)
)
goto :end_script
:end_script
endlocal
exit /b 0
:end_script_with_err
endlocal
exit /b 1
:: show help page
:help_page
echo:"%~nx0" [switches]
echo:switches:
echo: --deps: rebuild third-party dependencies
echo: --help: show this page
goto :end_script

View File

@ -0,0 +1,3 @@
@echo off
call "build_win_premake.bat" --deps

View File

@ -0,0 +1,3 @@
@echo off
call "build_win_premake.bat"

View File

@ -0,0 +1,15 @@
#ifndef _CRASH_PRINTER_LINUX
#define _CRASH_PRINTER_LINUX
#include <string>
namespace crash_printer {
bool init(const std::string &log_file);
void deinit();
}
#endif // _CRASH_PRINTER_LINUX

View File

@ -0,0 +1,15 @@
#ifndef _CRASH_PRINTER_WIN
#define _CRASH_PRINTER_WIN
#include <string>
namespace crash_printer {
bool init(const std::string &log_file);
void deinit();
}
#endif // _CRASH_PRINTER_WIN

165
crash_printer/linux.cpp Normal file
View File

@ -0,0 +1,165 @@
// https://stackoverflow.com/a/1925461
#include "common_helpers/common_helpers.hpp"
#include "crash_printer/linux.hpp"
#include <sstream>
#include <chrono>
#include <ctime>
#include <ucontext.h>
#include <signal.h> // SIGBUS + SA_SIGINFO ...
#include <string.h> // strsignal
#include <execinfo.h> // backtrace + backtrace_symbols
constexpr static const int max_stack_frames = 50;
static bool old_SIGILL = false;
static struct sigaction oldact_SIGILL{};
static bool old_SIGSEGV = false;
static struct sigaction oldact_SIGSEGV{};
static bool old_SIGBUS = false;
static struct sigaction oldact_SIGBUS{};
static std::string logs_filepath{};
static void restore_handlers()
{
if (old_SIGILL) {
old_SIGILL = false;
sigaction(SIGILL, &oldact_SIGILL, nullptr);
}
if (old_SIGSEGV) {
old_SIGSEGV = false;
sigaction(SIGSEGV, &oldact_SIGSEGV, nullptr);
}
if (old_SIGBUS) {
old_SIGBUS = false;
sigaction(SIGBUS, &oldact_SIGBUS, nullptr);
}
}
static void exception_handler(int signal, siginfo_t *info, void *context, struct sigaction *oldact)
{
if (!common_helpers::create_dir(logs_filepath)) {
return;
}
std::ofstream file(std::filesystem::u8path(logs_filepath), std::ios::app);
std::string time(common_helpers::get_utc_time());
common_helpers::write(file, "[" + time + "]");
{
std::stringstream ss{};
ss << "Unhandled exception:" << std::endl
<< " code: " << std::dec << signal << " (" << strsignal(signal) << ")" << std::endl;
common_helpers::write(file, ss.str());
}
void* stack_frames[max_stack_frames];
int stack_size = backtrace(stack_frames, max_stack_frames);
char** stack_symbols = backtrace_symbols(stack_frames, stack_size);
if (stack_symbols != nullptr) {
// fprintf(stderr, "Stack trace:\n");
common_helpers::write(file, "*********** Stack trace ***********");
for (int i = 1; i < stack_size; ++i) {
char *symbol = stack_symbols[i];
std::stringstream ss{};
ss << "[frame " << std::dec << (stack_size - i - 1) << "]: "
<< std::hex << stack_frames[i] << " | "
<< symbol;
common_helpers::write(file, ss.str());
}
free(stack_symbols);
}
common_helpers::write(file, "**********************************\n");
file.close();
}
// Register the signal handler for illegal instruction (SIGILL)
static void exception_handler_SIGILL(int signal, siginfo_t *info, void *context) {
exception_handler(signal, info, context, &oldact_SIGILL);
sigaction(SIGILL, &oldact_SIGILL, nullptr);
}
static void exception_handler_SIGSEGV(int signal, siginfo_t *info, void *context) {
exception_handler(signal, info, context, &oldact_SIGSEGV);
sigaction(SIGSEGV, &oldact_SIGSEGV, nullptr);
}
static void exception_handler_SIGBUS(int signal, siginfo_t *info, void *context) {
exception_handler(signal, info, context, &oldact_SIGBUS);
sigaction(SIGBUS, &oldact_SIGBUS, nullptr);
}
bool crash_printer::init(const std::string &log_file)
{
logs_filepath = log_file;
// save old handlers
// https://linux.die.net/man/2/sigaction
if (
sigaction(SIGILL, nullptr, &oldact_SIGILL) != 0 ||
sigaction(SIGSEGV, nullptr, &oldact_SIGSEGV) != 0 ||
sigaction(SIGBUS, nullptr, &oldact_SIGBUS) != 0) {
return false;
}
constexpr int sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK | SA_RESETHAND | SA_NOCLDSTOP;
// https://linux.die.net/man/2/sigaction
// register handler for illegal instruction (SIGILL)
struct sigaction sa_SIGILL{};
sa_SIGILL.sa_sigaction = exception_handler_SIGILL;
sa_SIGILL.sa_flags = sa_flags;
sigemptyset(&sa_SIGILL.sa_mask); // all signals unblocked
if (sigaction(SIGILL, &sa_SIGILL, nullptr) != 0) {
restore_handlers();
return false;
}
old_SIGILL = true;
// register handler for segmentation fault (SIGSEGV)
struct sigaction sa_SIGSEGV{};
sa_SIGSEGV.sa_sigaction = exception_handler_SIGSEGV;
sa_SIGSEGV.sa_flags = sa_flags;
sigemptyset(&sa_SIGSEGV.sa_mask); // all signals unblocked
if (sigaction(SIGSEGV, &sa_SIGSEGV, nullptr) != 0) {
restore_handlers();
return false;
}
old_SIGSEGV = true;
// register handler for bus error (SIGBUS)
struct sigaction sa_SIGBUS{};
sa_SIGBUS.sa_sigaction = exception_handler_SIGBUS;
sa_SIGBUS.sa_flags = sa_flags;
sigemptyset(&sa_SIGBUS.sa_mask); // all signals unblocked
if (sigaction(SIGBUS, &sa_SIGBUS, nullptr) != 0) {
restore_handlers();
return false;
}
old_SIGBUS = true;
// // register handler for floating-point exception (SIGFPE)
// if (sigaction(SIGFPE, &sa, nullptr) != 0) {
// perror("Error setting up signal handler");
// return EXIT_FAILURE;
// }
return true;
}
void crash_printer::deinit()
{
restore_handlers();
}

View File

@ -0,0 +1,59 @@
#include "crash_printer/linux.hpp"
#include "common_helpers/common_helpers.hpp"
#include <iostream>
#include <fstream>
#include <ucontext.h>
#include <signal.h> // SIGBUS + SA_SIGINFO ...
#include <string.h> // strsignal
#include <execinfo.h> // backtrace + backtrace_symbols
std::string logs_filepath = "./crash_test/asd.txt";
// sa_sigaction handler for illegal instruction (SIGILL)
void exception_handler_SIGILL(int signal)
{
std::ifstream file(std::filesystem::u8path(logs_filepath));
if (file.is_open()) {
std::string line{};
std::getline(file, line);
file.close();
if (line.size()) {
std::cout << "Success!" << std::endl;
exit(0);
}
}
std::cerr << "Failed!" << std::endl;
exit(1);
}
int main()
{
// simulate the existence of previous handler
struct sigaction sa_SIGSEGV_prev{};
sa_SIGSEGV_prev.sa_handler = exception_handler_SIGILL;
sa_SIGSEGV_prev.sa_flags = SA_RESETHAND;
sigemptyset(&sa_SIGSEGV_prev.sa_mask); // all signals unblocked
sigaction(SIGSEGV, &sa_SIGSEGV_prev, nullptr);
if (!common_helpers::remove_file(logs_filepath)) {
std::cerr << "failed to remove log" << std::endl;
return 1;
}
if (!crash_printer::init(logs_filepath)) {
std::cerr << "failed to init" << std::endl;
return 1;
}
// simulate a crash
volatile int * volatile ptr = nullptr;
*ptr = 42;
return 0;
}

View File

@ -0,0 +1,59 @@
#include "crash_printer/linux.hpp"
#include "common_helpers/common_helpers.hpp"
#include <iostream>
#include <fstream>
#include <ucontext.h>
#include <signal.h> // SIGBUS + SA_SIGINFO ...
#include <string.h> // strsignal
#include <execinfo.h> // backtrace + backtrace_symbols
std::string logs_filepath = "./crash_test/asd.txt";
// sa_handler handler for illegal instruction (SIGILL)
void exception_handler_SIGILL(int signal, siginfo_t *info, void *context)
{
std::ifstream file(std::filesystem::u8path(logs_filepath));
if (file.is_open()) {
std::string line{};
std::getline(file, line);
file.close();
if (line.size()) {
std::cout << "Success!" << std::endl;
exit(0);
}
}
std::cerr << "Failed!" << std::endl;
exit(1);
}
int main()
{
// simulate the existence of previous handler
struct sigaction sa_SIGSEGV_prev{};
sa_SIGSEGV_prev.sa_sigaction = exception_handler_SIGILL;
sa_SIGSEGV_prev.sa_flags = SA_SIGINFO | SA_RESETHAND;
sigemptyset(&sa_SIGSEGV_prev.sa_mask); // all signals unblocked
sigaction(SIGSEGV, &sa_SIGSEGV_prev, nullptr);
if (!common_helpers::remove_file(logs_filepath)) {
std::cerr << "failed to remove log" << std::endl;
return 1;
}
if (!crash_printer::init(logs_filepath)) {
std::cerr << "failed to init" << std::endl;
return 1;
}
// simulate a crash
volatile int * volatile ptr = nullptr;
*ptr = 42;
return 0;
}

View File

@ -0,0 +1,57 @@
#include "crash_printer/win.hpp"
#include "common_helpers/common_helpers.hpp"
#include <iostream>
#include <fstream>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
std::string logs_filepath = "./crash_test/zxc.txt";
static LONG WINAPI exception_handler(LPEXCEPTION_POINTERS ex_pointers)
{
std::ifstream file(std::filesystem::u8path(logs_filepath));
if (file.is_open()) {
std::string line{};
std::getline(file, line);
file.close();
if (line.size()) {
std::cout << "Success!" << std::endl;
exit(0);
}
}
std::cerr << "Failed!" << std::endl;
exit(1);
}
int* get_ptr()
{
return nullptr;
}
int main()
{
// simulate the existence of previous handler
SetUnhandledExceptionFilter(exception_handler);
if (!common_helpers::remove_file(logs_filepath)) {
std::cerr << "failed to remove log" << std::endl;
return 1;
}
if (!crash_printer::init(logs_filepath)) {
std::cerr << "failed to init" << std::endl;
return 1;
}
// simulate a crash
volatile int * volatile ptr = get_ptr();
*ptr = 42;
return 0;
}

148
crash_printer/win.cpp Normal file
View File

@ -0,0 +1,148 @@
#include "common_helpers/common_helpers.hpp"
#include "crash_printer/win.hpp"
#include <sstream>
#include <chrono>
#include <ctime>
#include <vector>
#include <time.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <DbgHelp.h>
static LPTOP_LEVEL_EXCEPTION_FILTER originalExceptionFilter = nullptr;
static std::string logs_filepath{};
static void print_stacktrace(std::ofstream &file, CONTEXT* context) {
auto this_proc = GetCurrentProcess();
auto this_thread = GetCurrentThread();
if (!SymInitialize(this_proc, NULL, TRUE)) {
return;
}
STACKFRAME stack_frame{};
#ifdef _WIN64
constexpr DWORD machine_type = IMAGE_FILE_MACHINE_AMD64;
DWORD64 symbol_displacement = 0;
stack_frame.AddrPC.Offset = context->Rip;
stack_frame.AddrFrame.Offset = context->Rsp;
stack_frame.AddrStack.Offset = context->Rsp;
#else
constexpr DWORD machine_type = IMAGE_FILE_MACHINE_I386;
DWORD symbol_displacement = 0;
stack_frame.AddrPC.Offset = context->Eip;
stack_frame.AddrFrame.Offset = context->Esp; // EBP may not be used by module
stack_frame.AddrStack.Offset = context->Esp;
#endif
stack_frame.AddrPC.Mode = ADDRESS_MODE::AddrModeFlat;
stack_frame.AddrFrame.Mode = ADDRESS_MODE::AddrModeFlat;
stack_frame.AddrStack.Mode = ADDRESS_MODE::AddrModeFlat;
common_helpers::write(file, "*********** Stack trace ***********");
std::vector<std::string> symbols{};
while (true) {
BOOL res = StackWalk(
machine_type,
this_proc, this_thread,
&stack_frame,
context,
NULL, SymFunctionTableAccess, SymGetModuleBase, NULL);
if (!res) {
break;
}
bool print_symbol = false;
IMAGEHLP_SYMBOL *symbol = (IMAGEHLP_SYMBOL *)_malloca(sizeof(IMAGEHLP_SYMBOL) + MAX_PATH);
if (symbol) {
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
symbol->MaxNameLength = MAX_PATH - 1; // "in characters, not including the null-terminating character"
symbol_displacement = 0;
if (SymGetSymFromAddr(this_proc, stack_frame.AddrPC.Offset, &symbol_displacement, symbol)) {
print_symbol = true;
}
}
std::stringstream ss{};
ss << "0x" << std::hex << (void *)stack_frame.AddrPC.Offset << " | ";
if (print_symbol) {
ss << symbol->Name;
} else {
ss << "<unknown function>";
}
symbols.push_back(ss.str());
if (symbol) {
_freea(symbol);
}
}
SymCleanup(this_proc);
auto idx = symbols.size();
for (const auto &s : symbols) {
std::stringstream ss{};
ss << "[frame " << std::dec << (idx - 1) << "]: "
<< s;
common_helpers::write(file, ss.str());
idx--;
}
}
static void log_exception(LPEXCEPTION_POINTERS ex_pointers)
{
if (!common_helpers::create_dir(logs_filepath)) {
return;
}
std::ofstream file(std::filesystem::u8path(logs_filepath), std::ios::out | std::ios::app);
std::string time(common_helpers::get_utc_time());
common_helpers::write(file, "[" + time + "]");
{
std::stringstream ss{};
ss << "Unhandled exception:" << std::endl
<< " code: 0x" << std::hex << ex_pointers->ExceptionRecord->ExceptionCode << std::endl
<< " @address = 0x" << std::hex << ex_pointers->ExceptionRecord->ExceptionAddress;
common_helpers::write(file, ss.str());
}
print_stacktrace(file, ex_pointers->ContextRecord);
common_helpers::write(file, "**********************************\n");
file.close();
}
static LONG WINAPI exception_handler(LPEXCEPTION_POINTERS ex_pointers)
{
log_exception(ex_pointers);
if (originalExceptionFilter) {
return originalExceptionFilter(ex_pointers);
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool crash_printer::init(const std::string &log_file)
{
logs_filepath = log_file;
originalExceptionFilter = SetUnhandledExceptionFilter(exception_handler);
return true;
}
void crash_printer::deinit()
{
if (originalExceptionFilter) {
SetUnhandledExceptionFilter(originalExceptionFilter);
}
}

2
dev.notes/README.md Normal file
View File

@ -0,0 +1,2 @@
Explanations for random thoughts and ideas.

View File

@ -0,0 +1,682 @@
# How different versions/implementations of 1 interface are maintained and dispatched
This is done by ~~ab~~using the inheritance of C++ and how virtual functions tables (`vftables`) are implemented by compilers.
The point of this article is explaining this somewhat cryptic code in [steam_client.cpp](../dll/steam_client.cpp)
```c++
if (strcmp(pchVersion, "SteamNetworking001") == 0) {
return (ISteamNetworking *)(void *)(ISteamNetworking001 *)steam_networking_temp;
} else if (strcmp(pchVersion, "SteamNetworking002") == 0) {
return (ISteamNetworking *)(void *)(ISteamNetworking002 *)steam_networking_temp;
}
```
And this other cryptic code in [flat.cpp](../dll/flat.cpp)
```c++
STEAMAPI_API uint32 SteamAPI_ISteamUtils_GetSecondsSinceAppActive( ISteamUtils* self )
{
long long client_vftable_distance = ((char *)self - (char*)get_steam_client()->steam_utils);
long long server_vftable_distance = ((char *)self - (char*)get_steam_client()->steam_gameserver_utils);
auto ptr = get_steam_client()->steam_gameserver_utils;
if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) {
ptr = get_steam_client()->steam_utils;
}
return (ptr)->GetSecondsSinceAppActive();
}
```
Let's say you have 2 versions of an interface `INetworking_1` and `INetworking_2`, both provide 3 functions, but the newer one has subtle differences and maybe provides a new 4th function.
During runtime the game will ask for `"net_1"` or `"net_2"` to get a pointer to some class implementing the corresponding interface, how should we do this?
One way is to implement each interface separately in a standalone class
```c++
class NetImpl_1 { ... }
```
```c++
class NetImpl_2 { ... }
```
And if both of them have some common functionality, we can compose a 3rd class implementing the common stuff
```c++
// concrete class implementing the common functionality
class NetCommon { ... }
// back in these 2
class NetImpl_1 : public INetworking_1 {
private:
class NetCommon *common;
}
class NetImpl_2 : public INetworking_2 {
private:
class NetCommon *common;
}
```
Then return a pointer to either `NetImpl_1` or `NetImpl_2` whenever the game asks for `"net_1"` or `"net_2"` respectively.
The obvious downside is the boilerplate code that has to be written for common functions:
```c++
// common provided function in each interface
class INetworking_1 {
public:
virtual void common_func() = 0;
...
}
class INetworking_2 {
public:
virtual void common_func() = 0;
...
}
class NetImpl_1 : public INetworking_1 {
private:
class NetCommon *common;
public:
void common_func() {
common->do_whatever();
}
}
class NetImpl_2 : public INetworking_2 {
private:
class NetCommon *common;
public:
void common_func() {
common->do_whatever();
}
}
```
Both versions of the interface offer/provide `common_func()`, hence both implementations has to implement it, so the function *body* must be written twice.
Another way, and this is how it's done for now, is benefitting from the multiple inheritance feature, in this case we only need to write the common function body once, also we need only one concrete implementation!
```c++
// common provided function in each interface
class INetworking_1 {
public:
virtual void common_func() = 0;
...
}
class INetworking_2 {
public:
virtual void common_func() = 0;
...
}
// notice we no longer need 2 classes with a common composite between them!
class NetImpl_all :
public INetworking_1,
public INetworking_2
{
public:
void common_func() {
/* do_whatever directly here! */
}
}
```
That looks much better, isn't it?
Well, yes it looks much better, especially if the functionality is the same anyway. But how the memory layout would look like when the 2 interfaces are different
```c++
class INetworking_1 {
public:
virtual void common_func() = 0;
virtual int func_aa() = 0;
virtual float func_bb() = 0;
}
class INetworking_2 {
public:
virtual void common_func() = 0;
virtual int func_aa() = 0;
// new!
virtual const char* func_new() = 0;
virtual float func_bb() = 0;
...
}
```
In C++ this actually makes a difference!, if the game asked for `"net_1"` expecting a pointer to an implementation for `INetworking_1`, and later it tried to call `func_bb()`, it will access the **3rd** member function.
Imagine if we returned a wrong pointer to an implementation for `INetworking_2`, the **3rd** member function will be `func_new()`, by the way this is why failing to generate old interfaces will eventually cause a crash.
# Exploring a simple layout
To understand this deeper, we'll create a console app, using `Visual Studio` on `Windows` OS, and the compilation type is `x64`.
First let's check the layout of some structs, common includes:
```c++
#include <cstdint> // uint64_t
#include <iostream> // std::cout
```
Then simple structs to explore
```c++
struct My_Struct_1 {
uint64_t x1 = 1111; // 8 bytes
uint64_t y1 = 1111111; // 8 bytes
};
struct My_Struct_2 {
uint64_t x2 = 2222; // 8 bytes
uint64_t y2 = 22222222; // 8 bytes
virtual void MyFn_2()
{
std::cout << "I'm My_Struct_2::MyFn_2()" << std::endl;
}
};
struct My_Struct_3 {
uint64_t x3 = 3333;
uint64_t y3 = 33333333;
virtual void MyFn_3()
{
std::cout << "I'm My_Struct_3::MyFn_3()" << std::endl;
}
virtual void MyOtherFn_3()
{
std::cout << "I'm My_Struct_3::MyOtherFn_3()" << std::endl;
}
};
```
Let's print the size of each struct
```c++
int main(int argc, char* argv[])
{
// create
My_Struct_1 ss1;
My_Struct_2 ss2;
My_Struct_3 ss3;
// check size
std::cout << "My_Struct_1 size " << sizeof(ss1) << std::endl;
std::cout << "My_Struct_2 size " << sizeof(ss2) << std::endl;
std::cout << "My_Struct_3 size " << sizeof(ss3) << std::endl;
std::cout << "=========" << std::endl;
return 0;
}
```
We know for sure `My_Struct_1` will be 8+8=16 bytes, but let's look at the n others
```shell
My_Struct_1 size 16
My_Struct_2 size 24
My_Struct_3 size 24
=========
```
This looks quite unexpected, both `My_Struct_2` and `My_Struct_3` have an extra 8 bytes, why is that?
# `reinterpret_cast<...>` aka "trust me bro I know what I'm doing"
We need to look at the memory layout of each struct to understand what's going on and why we have extra 8 bytes.
We would like to take the address of each variable and print each 8 bytes alone, but we'll take a tangent since this won't work
```c++
uint64_t* ptr_ss = &ss1;
// error C2440: 'initializing': cannot convert from 'My_Struct_1 *' to 'uint64_t *'
// Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or parenthesized function-style cast
```
The error and it's solution are obvious, let's do a simple `C-style cast`, but in C++ this actually performs multiple casts (during compilation) until the first one succeeds, check the order of casting here: https://en.cppreference.com/w/cpp/language/explicit_cast
To explicitly tell the compiler *"hey do it as I want, I know what I'm doing"* there are 2 common options:
* `reinterpret_cast<uint64_t*>(&ss1)` which does what we want because the compiler will just pretend that it's a `uint64_t*` without a second thought or any checking, this is the usual way
* This hacky way: `uint64_t* ptr_ss = (uint64_t*)(void*)&ss1` this exploits the fact the casting from any pointer to `void*` will always succeed (not really but mostly will), and casting from `void*` back to any other pointer type will always be accepted (again not really but pretend it always succeeds).
We'll use the second way since it's used a lot in the code anyway to get used to it.
# Printing the layout of the structs
The idea is to get the address of each object, *pretend* each will be multiple of 8, and print each 8-bytes alone.
Given that we knew from the above code that
```shell
My_Struct_1 size = 16 bytes (2 * 8)
My_Struct_2 size = 24 bytes (3 * 8)
My_Struct_3 size = 24 bytes (3 * 8)
```
We can just loop over
```c++
uint64_t* layout_1 = (uint64_t*)(void*)&ss1;
uint64_t* layout_2 = (uint64_t*)(void*)&ss2;
uint64_t* layout_3 = (uint64_t*)(void*)&ss3;
// print the layout of My_Struct_1
for (int i = 0; i < 2; ++i) {
std::cout << "layout My_Struct_1 [" << i << "] = " << layout_1[i] << std::endl;
}
std::cout << "=========" << std::endl;
// print the layout of My_Struct_2
for (int i = 0; i < 3; ++i) {
std::cout << "layout My_Struct_2 [" << i << "] = " << layout_2[i] << std::endl;
}
std::cout << "=========" << std::endl;
// print the layout of My_Struct_3
for (int i = 0; i < 3; ++i) {
std::cout << "layout My_Struct_3 [" << i << "] = " << layout_3[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
This is the output
```shell
layout My_Struct_1 [0] = 1111
layout My_Struct_1 [1] = 1111111
=========
layout My_Struct_2 [0] = 140695735040816
layout My_Struct_2 [1] = 2222
layout My_Struct_2 [2] = 22222222
=========
layout My_Struct_3 [0] = 140695735040872
layout My_Struct_3 [1] = 3333
layout My_Struct_3 [2] = 33333333
=========
```
Ok, this looks mostly fine, we can see the numbers for each variable in its own struct, but what is the first value of the layout from `My_Struct_2` and `My_Struct_3`?
Because both structs have `virtual` functions, the compiler has added a new member for us, which holds the address of a compiler-generated array/list/table, and each value in this list is simply the address of the virtual function!
This table is called the virtual functions table `vftable`, here's the layout in poor ASCII art
```
layout of <My_Struct_3>
|-----------------|
| 140695735040872 | // [0] memory address of the vftable
|-----------------|
| 3333 | // [1] our guy!
|-----------------|
| 33333333 | // [2] also our guy!
|-----------------|
```
```
layout of <140695735040872> (aka vftable)
|-----------------|
| &MyFn_3 | // [0] address of first virtual function
|-----------------|
| &MyOtherFn_3 | // [1] address of second virtual function
|-----------------|
```
Let's proove that, we'll grab the address of each virtual function, print it, and even invoke it.
The 1st element of the struct layout was a pointer to the table, so let's take that 1st element and grab the address of that `vftable`
```c++
uint64_t* vtable_3 = (uint64_t*)layout_3[0]; // casting numbers to pointers is fine when you know what you're doing
```
Next, since we know we have 2 virtual functions, we'll print their addresses
```c++
for (int i = 0; i < 2; ++i) {
std::cout << "vtable3 [" << i << "] = " << vtable_3[i] << std::endl;
}
```
Here's the output
```shell
vtable3 [0] = 140695734809281
vtable3 [1] = 140695734811996
```
Ok but how can we be sure that these are the actual functions addresses? Let's call them.
In C++ class/struct member functions are simply regular C-functions with a *hidden* first argument, which is a pointer to the object, aka `this`
```c++
// a function which takes a ponter and returns void
typedef void (*vtable_member_t)(void* _this_obj);
```
We'll take each element in the vftable (address of `MyFn_3()` and address of `MyOtherFn_3()`) and call them
```c++
vtable_member_t vt_mem_item0 = (vtable_member_t)vtable_3[0];
std::cout << "calling original vtable_3[0]:" << std::endl;
vt_mem_item0(&ss3);
std::cout << "=========" << std::endl;
vtable_member_t vt_mem_item1 = (vtable_member_t)vtable_3[1];
std::cout << "calling original vtable_3[1]:" << std::endl;
vt_mem_item1(&ss3);
std::cout << "=========" << std::endl;
```
And here's the output!
```
calling original vtable_3[0]:
I'm My_Struct_3::MyFn_3()
=========
calling original vtable_3[1]:
I'm My_Struct_3::MyOtherFn_3()
=========
```
Nice, this prooves that structs/classes with `virtual` functions will have a *bonus hidden* member at the top, holding the address of a compiler-generated table called `vftable` (virtual functions table).
If we grab the 1st element in that table, it will be the address of the 1st virtual function, the 2nd element in that table will be the address of the 2nd virtual function, etc...
# Inheritance from multiple interfaces
This is how all steam interfaces are implemented, it's always 1 class implementing all interfaces and providing all the implementations.
But remember, the order of the virtual functions in source code impacts the order of the `vftable` members, go ahead and swap the functions in the source code
```c++
struct My_Struct_3 {
uint64_t x3 = 3333;
uint64_t y3 = 33333333;
// this is now at the top
virtual void MyOtherFn_3()
{
std::cout << "I'm My_Struct_3::MyOtherFn_3()" << std::endl;
}
// and this is placed at the bottom
virtual void MyFn_3()
{
std::cout << "I'm My_Struct_3::MyFn_3()" << std::endl;
}
};
```
Then, run the code which invokes the functions from the `vftable`, here's the output
```
calling original vtable_3[0]:
I'm My_Struct_3::MyOtherFn_3()
=========
calling original vtable_3[1]:
I'm My_Struct_3::MyFn_3()
=========
```
Also remember, games will get a pointer to that class based on the interface version they send to us, and will invoke the corresponding member function according to its order in the header `.h` file.
So, what if 2 interfaces share the same set of functions, but the order is different? And what if a newer interface version removed or added a member function?
Actually the answer is the same for all of the scenarios, let's first define a new struct which inherits from the previous ones
```c++
struct My_Struct_final :
public My_Struct_3,
public My_Struct_2,
public My_Struct_1
{
uint64_t x_final = 9999;
uint64_t y_final = 99999999;
virtual void MyOtherFn_final()
{
std::cout << "I'm My_Struct_final::MyOtherFn_final()" << std::endl;
}
};
```
Let's also do all the previous steps we did on this new struct, create an object
```c++
// create
My_Struct_final ss_final;
```
Print the size
```c++
std::cout << "My_Struct_final size " << sizeof(ss_final) << std::endl;
std::cout << "=========" << std::endl;
```
Output
```
My_Struct_final size 80
=========
```
That is 80 bytes / 8 bytes (each element) = 10 elements, so let's print the layout
```c++
uint64_t* layout_final = (uint64_t*)(void*)&ss_final;
for (int i = 0; i < 10; ++i) {
std::cout << "layout My_Struct_final [" << i << "] = " << layout_final[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
And the output
```
layout My_Struct_final [0] = 140695735040976
layout My_Struct_final [1] = 3333
layout My_Struct_final [2] = 33333333
layout My_Struct_final [3] = 140695735041008
layout My_Struct_final [4] = 2222
layout My_Struct_final [5] = 22222222
layout My_Struct_final [6] = 1111
layout My_Struct_final [7] = 1111111
layout My_Struct_final [8] = 9999
layout My_Struct_final [9] = 99999999
=========
```
Let's analyze what we have from bottom to top, the last 2 elements are `9999` and `99999999` which are the members of the struct itself, not inherited.
Next we have `1111` and `1111111` which are inherted from `My_Struct_1`.
Next we have a trio { `140695735041008`, `2222`, and `22222222` }, and another similar trio { `140695735040976`, `3333`, and `33333333` }.
It looks like the compiler just combined toghether all the layouts from above and put them in some sequential order!
To verify this, let's *assume* that the large number above `3333` (`140695735040976`) is the address of the `vftable` for `My_Struct_3`, and because our new struct `My_Struct_final` didn't actually override anything, so the addresses of the virtual functions should be the same, let's test that.
Get the first element from the layout
```c++
uint64_t* vtable_3_inheritance = (uint64_t*)layout_final[0]; // casting numbers to pointers is fine when you know what you're doing
```
Pretend that value is the address of the `vftable` for the inherited `My_Struct_3`, so grab the 1st item in the vftable (address of 1st virtual function)
```c++
vtable_member_t vt_mem_inheritance = (vtable_member_t)vtable_3_inheritance[0];
```
Invoke the first element in the `vftable`
```c++
std::cout << "calling vtable_3[0] from inheritance:" << std::endl;
vt_mem_inheritance(&ss_final);
std::cout << "=========" << std::endl;
```
Output!
```
calling vtable_3[0] from inheritance:
I'm My_Struct_3::MyFn_3()
=========
```
Ok that actually worked, it turns out our assumption was correct, so the compiler indeed combined all layouts into one, but the order in weird.
And notice how the compiler created **a new vftable for each layout inherited**, so we don't have to worry about different versions of an interface, because the compiler will create a new vftable even if all versions share a set of similar functions. In our example we could even pretend `My_Struct_1`, `My_Struct_2`, and `My_Struct_3` are different versions of some arbitrary interface from the point of view of `My_Struct_final`, even though they are completely unrelated, the compiler doesn't care anyway.
The Microsoft compiler vaguely implements the following layout from bottom to top:
```
2. layout of non-trivial classes/structs (with any virtual functions), in the order they're defined in source code
1. layout of trivial classes/structs without any virtual functions, in the order they're defined in source code
```
Go ahead and swap the order and print the layout again
```c++
struct My_Struct_final :
public My_Struct_2, // now at the top
public My_Struct_3,
public My_Struct_1
{
...
};
```
Print it (notice how the layout containing `2222` is now above the one containing `3333`)
```
layout My_Struct_final [0] = 140699306621904
layout My_Struct_final [1] = 2222
layout My_Struct_final [2] = 22222222
layout My_Struct_final [3] = 140699306621936
layout My_Struct_final [4] = 3333
layout My_Struct_final [5] = 33333333
layout My_Struct_final [6] = 1111
layout My_Struct_final [7] = 1111111
layout My_Struct_final [8] = 9999
layout My_Struct_final [9] = 99999999
=========
```
# Casting pointers to classes with inheritance (answering the 1st riddle)
Casting an object of type `My_Struct_final*` to `My_Struct_1*` will make the compiler simply offset the pointer to point at the layout at the very bottom, let's proove it
```c++
My_Struct_1* ptr_adjusted_by_compiler = (My_Struct_1*)&ss_final; // notice how we're just casting, nothing crazy
uint64_t* layout_ptr = (uint64_t*)(void*)ptr_adjusted_by_compiler;
for (int i = 0; i < 2; ++i) {
std::cout << "layout ptr [" << i << "] = " << layout_ptr[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
Output
```
layout ptr [0] = 1111
layout ptr [1] = 1111111
=========
```
Casting an object of type `My_Struct_final*` to `My_Struct_1*` will simply offset the pointer to get the corresponding layout.
etc...
So, the key point here is that **even C-style casts aren't for free!** they definitely have an impact when the pointer target is some class/struct with inheritance.
The above code is similar to this C++ style casts
```c++
My_Struct_1* ptr_adjusted_by_compiler = static_cast<My_Struct_1*>(&ss_final); // don't use "reinterpret_cast", we want the compiler to do its magic
uint64_t* layout_ptr = reinterpret_cast<uint64_t*>(ptr_adjusted_by_compiler); // trust me bro I know what I'm doing!
for (int i = 0; i < 2; ++i) {
std::cout << "layout ptr [" << i << "] = " << layout_ptr[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
Output (same)
```
layout ptr [0] = 1111
layout ptr [1] = 1111111
=========
```
Now you have a full picture what this evil line is doing
```c++
if (strcmp(pchVersion, "SteamNetworking001") == 0) {
return (ISteamNetworking *)(void *)(ISteamNetworking001 *)steam_networking_temp;
}
```
If the game is asking for `v001` of the `networking` interface, from right to left these are the steps
1. We first cast the pointer of the implementation class `steam_networking_temp` to `ISteamNetworking001 *` via a `C-Style` cast, this allows the compiler to do its **magic** and adjust/offset the pointer to the relevant/correct part of the huge layout
2. Cast that to `void *` so the compiler will lose the type information and will not do any more adjustments
3. Cast the result back to `ISteamNetworking *`, but this won't do any magic, the compiler already lost type info
That whole sentence could be replaced with
```c++
return reinterpret_cast<ISteamNetworking *>(static_cast<ISteamNetworking001 *>(steam_networking_temp));
```
Whether this is better or worse is up to you, both are ugly and took an article to explain!
But can't we just do this
```c++
return (ISteamNetworking *)(ISteamNetworking001 *)steam_networking_temp;
```
Remember C-Style cast in C++ isn't deterministic, casting a pointer of derived class to its parent will adjust the pointer again!, let's prove it
```c++
My_Struct_final* what_ptr_is_this = (My_Struct_final*)(My_Struct_1*)&ss_final; // hmmm
uint64_t* what_layout_is_this = reinterpret_cast<uint64_t*>(what_ptr_is_this); // trust me bro I know what I'm doing!
for (int i = 0; i < 3; ++i) {
std::cout << "what_layout_is_this [" << i << "] = " << what_layout_is_this[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
Output
```
what_layout_is_this [0] = 140700555742240
what_layout_is_this [1] = 3333
what_layout_is_this [2] = 33333333
=========
```
We got back the layout of the original/big class, not the small one, so we can't really ommit the `(void *)` cast in the middle, let's do it again with the `(void *)` cast
```c++
My_Struct_final* what_ptr_is_this = (My_Struct_final*)(void*)(My_Struct_1*)&ss_final; // all we did was literally add (void*)
uint64_t* what_layout_is_this = reinterpret_cast<uint64_t*>(what_ptr_is_this); // trust me bro I know what I'm doing!
for (int i = 0; i < 3; ++i) {
std::cout << "what_layout_is_this [" << i << "] = " << what_layout_is_this[i] << std::endl;
}
std::cout << "=========" << std::endl;
```
Output
```
what_layout_is_this [0] = 1111
what_layout_is_this [1] = 1111111
what_layout_is_this [2] = 9999
=========
```
Pure evil in my opinion
# Detecting the base of the layout from a given address (answering the 2nd riddle)
In this code
```c++
STEAMAPI_API uint32 SteamAPI_ISteamUtils_GetSecondsSinceAppActive( ISteamUtils* self )
{
long long client_vftable_distance = ((char *)self - (char*)get_steam_client()->steam_utils);
long long server_vftable_distance = ((char *)self - (char*)get_steam_client()->steam_gameserver_utils);
auto ptr = get_steam_client()->steam_gameserver_utils;
if (client_vftable_distance >= 0 && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) {
ptr = get_steam_client()->steam_utils;
}
return (ptr)->GetSecondsSinceAppActive();
}
```
At the first line
```c++
long long client_vftable_distance = ((char *)self - (char*)get_steam_client()->steam_utils);
```
1. We get the address of `steam_utils` and use a `C-Style` cast to convert it to `(char *)`
If we tried
```c++
static_cast<char*>(get_steam_client()->steam_utils)
```
We'll get a compiler error `invalid type conversion`, but remember in C++, a C-Style cast will force the compiler to try different other casts: https://en.cppreference.com/w/cpp/language/explicit_cast
The one that will work here is the `reinterpret_cast` (aka *"don't do any magic, I'm aware of what I'm doing"*)
2. Subtract `given address - base layout address`
3. Do the same for the `gameserver` instance (`steam_gameserver_utils`) layout
We end up with 2 results from the subtraction
* If the given address is somewhere within the layout of the client instance (`steam_utils`),
then the distnce `client_vftable_distance` will be a positive value
and the other distance `server_vftable_distance` will be a negative value
* If the given address is somewhere within the layout of the gameserver instance (`steam_gameserver_utils`),
then the distnce `server_vftable_distance` will be a positive value
and the other distance `client_vftable_distance` will be a negative value
This is why we have that condition
```c++
if (client_vftable_distance >= 0 ... ) {
}
```
But what if we're unlucky and we got 2 memory blocks under each other like this:
```
|------------------------|
| .................. |
| .................. |
| .................. |
| steam_utils | // client instance layout
| .................. |
| .................. |
| .................. |
|------------------------|
| .................. |
| .................. |
| .................. |
| steam_gameserver_utils | // gameserver instance layout
| .................. |
| .................. |
| .................. |
|------------------------|
```
In that case the distance `client_vftable_distance` will be positive if the given pointer was somewhere inside the layout of the gameserver instance, and for that reason we have the other condition
```c++
if ( ... && (server_vftable_distance < 0 || client_vftable_distance < server_vftable_distance)) {
}
```
If `server_vftable_distance < 0` then the answer is obvious, but in the unlucky case `client_vftable_distance < server_vftable_distance` will be our savior.

View File

@ -0,0 +1,60 @@
1. create an orphan branch
```shell
git checkout --orphan 'third-party/my-branch'
```
2. make sure no files are staged yet
```shell
git rm -r -f --cached .
```
3. copy some new files (or add ones that already exist)
```shell
cp ~/myfile.txt ./
```
4. stage the required files
```shell
git add myfile.txt
```
you can also stage all files in the current directory
```shell
git add .
```
5. commit the files
```shell
git commit -m 'my commit msg'
```
6. add the branch as submodule
```shell
git -c protocol.file.allow=always submodule add -f -b 'third-party/my-branch' file://"$(pwd)" 'my-relative-dir/without/dot/at/beginning'
```
git by default disallow local repos, this option `protocol.file.allow=always` forces git to allow it
this will:
- look for a **local** repo in the directory shown by `pwd` (current folder),
notice how we don't simply use `./` because if we did that git will try to use the `origin` of the repo,
and since the origin (github/gitlab/etc...) doesn't have this branch yet it will fail, using the file protocol (`file://absolute_path`) forces git to use the local repo files
you can of course push the branch to origin before doing this step
- look for a branch named `third-party/my-branch`
- create a submodule pointing at this branch inside a new folder `my-relative-dir/without/dot/at/beginning`
notice that the new folder does **not** start with `./` as usual
7. fix the submodule path
after the last command, the file `.gitmodules` will point at the absolute path of the repo on disk, fix it to be relative
```shell
git -c protocol.file.allow=always submodule add -f -b 'third-party/my-branch' ./ 'my-relative-dir/without/dot/at/beginning'
```
this time git won't try to grab the data from origin, it will just edit `.gitmodules`
8. new git management objects/files will be staged, you can view them
```shell
git status
```
possible output
```shell
On branch third-party/my-branch
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: .gitmodules
new file: third-party/my-branch
```
9. commit these 2 files
```shell
git commit -m 'add branch third-party/my-branch as submodule'
```

View File

@ -0,0 +1,100 @@
# Interesting things about the new auth ticket
Firstly, why do you need to use the new auth ticket?
Well, thats because some Emulators, or servers checking inside the tickets. (Example is Nemirtingas Epic Emu)\
Old version of the ticket not gonna work with them.
## How does the old ticket look vs the new?
Old Ticket looks like this:
```
4 byte (header) | 4 byte | 8 byte
0x14 (AKA 20) 00 00 00 | [TicketNumber] | [SteamId]
```
As you see the ticket doesnt contains any information when its made, what DLC you have and appid you started.
### Before seeing how the new ticket looks, what does the "HasGC" means?
GC means Game Coordinator.\
It helps with IP address, better matchmake, and other things.
Why do we use it?\
Well simple because I researched for it and everything usually sending that data back.\
You can write a simple Application and edit steam_api.txt for any appid you own and gather the ticket from it.
GC contains these infromation:
```c++
uint32_t STEAM_APPTICKET_GCLen = 20; // Magic header 20
uint64_t GCToken{}; // A unique token for this, can be random or sequential
CSteamID id{}; // our steamId
uint32_t ticketGenDate{}; //epoch time when generated
uint32_t STEAM_APPTICKET_SESSIONLEN = 24; // Magic Header 24
uint32_t one = 1; // dont know yet
uint32_t two = 2; // dont know yet
uint32_t ExternalIP{}; // External ip (Steam usually encrypting these)
uint32_t InternalIP{}; // Internal ip (Steam usually encrypting these)
uint32_t TimeSinceStartup{}; // Seconds since Steam Startup
uint32_t TicketGeneratedCount{}; // how many ticket did you generated since startup
uint32_t FullSizeOfGC = 56; // GC size (52) + 4
```
If you add those together you get 52
```
8 = uint64_t
4 = uint32_t
4 + 8 + 8 + 4 = 24 (4 without the header is 20 so the lenght of the Next section)
4 + 4 + 4 + 4 + 4 + 4 + 4 = 28 (4 without the header is 24 so the lenght of the Next section)
```
Yes, we could separate these but since only GC doing this, that is not much
### The rest of the Ticket
As you see in the auth.h file the ticket is contains these infromation:
```c++
uint32_t TheTicketLenght; // Full lenght of the ticket exluding the padding and the Singature
uint32_t Version{}; // Latest version is 4 so we keep that way
CSteamID id{}; // our steamId
uint32_t AppId{}; // Current AppId that we playing
uint32_t ExternalIP{}; // External ip (Steam usually encrypting these)
uint32_t InternalIP{}; // Internal ip (Steam usually encrypting these)
uint32_t AlwaysZero = 0; //OwnershipFlags? or Might be VAC Banned?
uint32_t TicketGeneratedDate{}; // Epoch Seconds when the Ticket generated
uint32_t TicketGeneratedExpireDate{}; // Epoch Seconds when the Ticket will expire
std::vector<uint32_t> Licenses{}; // our licenses (Usually is 0 or if you own a locked beta that will be it)
std::vector<DLC> DLCs{}; // what DLC we own
```
The DLC data inside:
```c++
struct DLC {
uint32_t AppId{}; // AppId of the DLC
std::vector<uint32_t> Licenses{}; // Again license what you own, usually 0 or nothing inside
};
```
The Licenses:\
All app if not relesed to public is behind a license, steam usually set (or returns) 0 as if you own it or doesnt have any license to it.\
IT DOES not mean the app is free, even if you bought it still shown as 0!
### Signature and padding.
I dont know why steam has a 2 byte for a padding but that could be something or a random value.\
OR that could be if we got banend by VAC? I dont know yet.
Steam has a signature, as I seen its a 128 lenght one. I choosen RSA1 and PKCS1 since it giving me that one.\
I generated a key (You can get yourself here: https://github.com/Detanup01/stmsrv/blob/main/Cert/AppTicket.key) or from Auth.cpp/h file.
It is just we get the ticket data as bytes and we sign it with our key, and vola we have a ticket!
Thats why the NEW size is Minimum 170 because 128 + 42 (Minimum Ticket Data without any DLC, License, and GC)
## Interesting things
The Ticket can exceed 1024 byte if user own soo many DLC. Steam recommend setting as 1024 but I recommend everyone using 2048 if you have a Game that has many DLC. (PayDay 2)
Old ticket is similar to the start of our GC ticket.
Currently SendUserConnectAndAuthenticate, beginAuth "does not" have code for supporting NEW AuthTicket. But because the Old ticket header is similar to GC which we do send data with my steamId and a random Id. It doesnt need to Deserialize anything from the ticket.

1356
dev.notes/interfaces.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,345 @@
# What is the interface ISteamGameServerStats
This interface is relevant for game servers only, and responsible for sharing stats and achievements with the client. Some games (like appid 541300) might not handle user stats on their own, but rather expect the server to send the updated/changed user data.
While playing a game, the game client will send the player's current progress to the server, example: *the player ate an apple*, or *the player killed 5 enemies*, etc...
The server keeps track of the player progress, and once the player hits a certain condition (ex: eating an apple for the first time), the server will share an achievement with the game client, or send the current progress as a user statistics metric (ex: killing 5/10/50 enemies).
# Overview of the interface functions
* On startup, the server will ask Valve servers to *prepare* the user data by calling `RequestUserStats()` and wait for the response
* The server then, at any point, can ask for specific user stat/achievement
- `GetUserStat()` will return the current value for a given stat name, ex: amount of killed enemies
- `GetUserAchievement()` will return whether the user has unlocked a given achievement or not
* The server can also change/update this data
- `SetUserStat()` and `UpdateUserAvgRateStat()` will update the stat value
- `SetUserAchievement()` and `ClearUserAchievement()` will grant or remove from the user a given achievement
* Finally, the server should upload the changed data back to Valve servers by calling `StoreUserStats()`
# How it is implemented in the emu
For starters, the emu doesn't offer a mechanism to emulate a central server, also all user stats and achievements are saved locally on the user's computer.
Let's say we have 3 people currently playing a game on the same network which has a dedicated server, and the server is utilizing this interface to share stats.
We'll implement the interface in a way such that each person is their own central server, and each will either broadcast their data, or only send it to the dedicated server:
# Implementation of `RequestUserStats()`
When the server asks for the user data via `RequestUserStats()` we'll send a request to that user, wait for their response, and finally trigger a callresult + a callback.
We'll also store each user data, in a hash map (dictionary) for later so when we change that data we know whose data are we changing.
It is fairly straightforward, one request from the server, with its corresponding response from the player/user. It is a directed one-to-one message.
```
|-------------| ====>> |-------------|
| server | | game client |
|-------------| <<==== |-------------|
```
- Send a `protobuf` message to the user asking for all their stats
```proto
enum Types {
...
Request_AllUserStats = 0;
...
}
// sent from server as a request, response sent by the user
message InitialAllStats {
...
uint64 steam_api_call = 1;
...
}
Types type = 1;
oneof data_messages {
InitialAllStats initial_user_stats = 2;
...
}
```
```c++
// SteamAPICall_t Steam_GameServerStats::RequestUserStats( CSteamID steamIDUser )
auto initial_stats_msg = new GameServerStats_Messages::InitialAllStats();
initial_stats_msg->set_steam_api_call(new_request.steamAPICall);
auto gameserverstats_messages = new GameServerStats_Messages();
gameserverstats_messages->set_typ(GameServerStats_Messages::Request_AllUserStats);
gameserverstats_messages->set_allocated_initial_user_stats(initial_stats_msg);
Common_Message msg{};
// https://protobuf.dev/reference/cpp/cpp-generated/#string
// set_allocated_xxx() takes ownership of the allocated object, no need to delete
msg.set_allocated_gameserver_stats_messages(gameserverstats_messages);
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(new_request.steamIDUser.ConvertToUint64());
network->sendTo(&msg, true);
```
- The user will send back a `protobuf` message containing all the data, this is the user response with the enum `Type` set to `Response_AllUserStats`
```proto
enum Types {
...
Response_AllUserStats = 1;
...
}
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;
}
// this is used when updating stats, from server or user, bi-directional
message AllStats {
map<string, StatInfo> user_stats = 1;
map<string, AchievementInfo> user_achievements = 2;
}
message StatInfo {
enum Stat_Type {
STAT_TYPE_INT = 0;
STAT_TYPE_FLOAT = 1;
STAT_TYPE_AVGRATE = 2;
}
message AvgStatInfo {
float count_this_session = 1;
double session_length = 2;
}
Stat_Type stat_type = 1;
oneof stat_value {
float value_float = 2;
int32 value_int = 3;
}
...
}
message AchievementInfo {
bool achieved = 1;
}
Types type = 1;
oneof data_messages {
...
InitialAllStats initial_user_stats = 2;
...
}
```
```c++
// void Steam_User_Stats::network_stats_initial(Common_Message *msg)
auto initial_stats_msg = new GameServerStats_Messages::InitialAllStats();
// send back same api call id
initial_stats_msg->set_steam_api_call(msg->gameserver_stats_messages() initial_user_stats().steam_api_call());
initial_stats_msg->set_allocated_all_data(all_stats_msg);
auto gameserverstats_msg = new GameServerStats_Messages();
gameserverstats_msg->set_type(GameServerStats_Messages::Response_AllUserStats);
gameserverstats_msg->set_allocated_initial_user_stats(initial_stats_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);
```
- When the user returns a response, we'll trigger a callback + a callresult
```c++
// void Steam_GameServerStats::network_callback_initial_stats(Common_Message *msg)
GSStatsReceived_t data{};
data.m_eResult = EResult::k_EResultOK;
data.m_steamIDUser = user_steamid;
callback_results->addCallResult(it->steamAPICall, data.k_iCallback, &data, sizeof(data));
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
```
# Implementation of `SetUserStat()`, `SetUserAchievement()`, and `ClearUserAchievement()`
The emu already asked the user earlier via `RequestUserStats()` for their data and stored the result in a map/dictionary, so whenver the server calls any of these functions we can easily update the dictionary.
But when we send the updated data to the user we don't want to send the entire dictionary, it is wasteful but more importantly, if we keep sending the same **unchanged** data each time over and over, the Steam functions on the player's side will trigger a notification each time, meaning that the player might keep unlocking an achievement over and over or updating the same stats with the exact same values.
To solve this, the emu keeps a dictionary of *cached* data, each piece of data has a an accompanied boolean flag called `dirty`, this flag is set to `true` only when the server updates the data, and when it's time to send the new data to the user, we only collect those whose `dirty` flag is set.
```c++
struct CachedStat {
bool dirty = false; // true means it was changed on the server and should be sent to the user
GameServerStats_Messages::StatInfo stat{};
};
struct CachedAchievement {
bool dirty = false; // true means it was changed on the server and should be sent to the user
GameServerStats_Messages::AchievementInfo ach{};
};
struct UserData {
std::map<std::string, CachedStat> stats{};
std::map<std::string, CachedAchievement> achievements{};
};
// dictionary of <user id, user data>
std::map<uint64, UserData> all_users_data{};
```
An example from `SetUserAchievement()`
```c++
auto ach = find_ach(steamIDUser, pchName);
if (!ach) return false;
if (ach->ach.achieved() == true) return true; // don't waste time
ach->dirty = true; // set the dirty flag
```
Another optimization made here is that the data is not sent immediately, game servers and game clients utilizing the Steam networking will always call `Steam_Client::RunCallbacks()` periodically, so we can just for that periodic call and send any *dirty* data all at once, or nothing if everything is clean! (unchanged).
Here's the `protobuf` message, and notice how it's exactly the same as the *user/player response* for `RequestUserStats()`, with these exceptions:
1. This is sent from the server, not the user/player
2. The enum `Type` is set to `UpdateUserStatsFromServer`
3. The active member in the `oneof data_messages` is `update_user_stats`
But the overall message has the same shape, and now contains **only** the changed data
```proto
enum Types {
...
UpdateUserStatsFromServer = 2; // sent by Steam_GameServerStats
...
}
// this is used when updating stats, from server or user, bi-directional
message AllStats {
map<string, StatInfo> user_stats = 1;
map<string, AchievementInfo> user_achievements = 2;
}
message StatInfo {
enum Stat_Type {
STAT_TYPE_INT = 0;
STAT_TYPE_FLOAT = 1;
STAT_TYPE_AVGRATE = 2;
}
message AvgStatInfo {
float count_this_session = 1;
double session_length = 2;
}
Stat_Type stat_type = 1;
oneof stat_value {
float value_float = 2;
int32 value_int = 3;
}
optional AvgStatInfo value_avg = 4; // only set when type != INT
}
message AchievementInfo {
bool achieved = 1;
}
Types type = 1;
oneof data_messages {
...
AllStats update_user_stats = 3;
...
}
```
```c++
// void Steam_GameServerStats::collect_and_send_updated_user_stats()
// collect all dirty stats
// collect all dirty achievements
// then for each user, send the dirty data at once as a single packet
auto gameserverstats_msg = new GameServerStats_Messages();
gameserverstats_msg->set_type(GameServerStats_Messages::UpdateUserStatsFromServer);
gameserverstats_msg->set_allocated_update_user_stats(updated_stats_msg);
Common_Message msg{};
// https://protobuf.dev/reference/cpp/cpp-generated/#string
// set_allocated_xxx() takes ownership of the allocated object, no need to delete
msg.set_allocated_gameserver_stats_messages(gameserverstats_msg);
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(user_steamid);
network->sendTo(&msg, true);
```
Back on the user/client side, they will receive this message and update their data, as if the game itself has updated this data.
# How data is shared with game servers if the game client updated its data
This more or less the same, with these changes
* The `protobuf` enum `Type` is set to `UpdateUserStatsFromUser`
* Since the game client doesn't know the server ID, it will broadcast the message to all game servers
```c++
// void Steam_User_Stats::send_updated_stats()
auto gameserverstats_msg = new GameServerStats_Messages();
gameserverstats_msg->set_type(GameServerStats_Messages::UpdateUserStatsFromUser);
gameserverstats_msg->set_allocated_update_user_stats(new_updates_msg);
Common_Message msg{};
// https://protobuf.dev/reference/cpp/cpp-generated/#string
// set_allocated_xxx() takes ownership of the allocated object, no need to delete
msg.set_allocated_gameserver_stats_messages(gameserverstats_msg);
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
// here we send to all gameservers on the network because we don't know the server steamid
network->sendToAllGameservers(&msg, true);
```
# Changes made to the networking
The networking implementation works like this
* Store a list of all functions to trigger once a certain message is received, messages are tagged with some ID
```c++
enum Callback_Ids {
CALLBACK_ID_USER_STATUS,
...
CALLBACK_ID_GAMESERVER_STATS, // this is our new member
...
CALLBACK_IDS_MAX
};
```
Internally this is implemented as a static array, not a map/dictionary as one might expect
```c++
struct Network_Callback_Container callbacks[CALLBACK_IDS_MAX];
```
No need for a map here since the *keys* are static numbers known during compilation (the enum above), hence an array is equivalent to a map/dictionary here.
Each element of the array is just a collection of functions to be called/invoked later
```c++
struct Network_Callback_Container {
std::vector<struct Network_Callback> callbacks{};
};
```
```
---------------------------
CALLBACK_ID_USER_STATUS ---> | &func_1 | &func_2 | ... |
---------------------------
-----------------------------------
CALLBACK_ID_GAMESERVER_STATS ---> | &other_fn_1 | &other_fn_2 | ... |
-----------------------------------
```
* You can subscribe/listen to messages of that type or unsubscribe using these functions
```c++
// subscribe
Networking::setCallback(...)
// unsubscribe
Networking::rmCallback(...)
```
* The networking class has an event-based function, called whenever a network message is available, which will check for the message type, and trigger/call each subscriber
```c++
void Networking::do_callbacks_message(Common_Message *msg) {
...
if (msg->has_gameserver_stats_messages()) {
PRINT_DEBUG("has_gameserver_stats");
run_callbacks(CALLBACK_ID_GAMESERVER_STATS, msg);
}
...
}
```

266
dll/appticket.cpp Normal file
View File

@ -0,0 +1,266 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "dll/appticket.h"
void AppTicketV1::Reset()
{
TicketSize = 0;
TicketVersion = 0;
Unk2 = 0;
UserData.clear();
}
std::vector<uint8_t> AppTicketV1::Serialize() const
{
std::vector<uint8_t> buffer{};
uint8_t* pBuffer{};
buffer.resize(16 + UserData.size());
pBuffer = buffer.data();
*reinterpret_cast<uint32_t*>(pBuffer) = TicketSize; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = TicketVersion; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = static_cast<uint32_t>(UserData.size()); pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = Unk2; pBuffer += 4;
memcpy(pBuffer, UserData.data(), UserData.size());
return buffer;
}
bool AppTicketV1::Deserialize(const uint8_t* pBuffer, size_t size)
{
if (size < 16)
return false;
uint32_t user_data_size;
TicketSize = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
TicketVersion = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
user_data_size = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
if (size < (user_data_size + 16))
return false;
Unk2 = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
UserData.resize(user_data_size);
memcpy(UserData.data(), pBuffer, user_data_size);
return true;
}
void AppTicketV2::Reset()
{
TicketSize = 0;
TicketVersion = 0;
SteamID = 0;
AppID = 0;
Unk1 = 0;
Unk2 = 0;
TicketFlags = 0;
TicketIssueTime = 0;
TicketValidityEnd = 0;
}
std::vector<uint8_t> AppTicketV2::Serialize() const
{
std::vector<uint8_t> buffer{};
uint8_t* pBuffer{};
buffer.resize(40);
pBuffer = buffer.data();
*reinterpret_cast<uint32_t*>(pBuffer) = TicketSize; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = TicketVersion; pBuffer += 4;
*reinterpret_cast<uint64_t*>(pBuffer) = SteamID; pBuffer += 8;
*reinterpret_cast<uint32_t*>(pBuffer) = AppID; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = Unk1; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = Unk2; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = TicketFlags; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = TicketIssueTime; pBuffer += 4;
*reinterpret_cast<uint32_t*>(pBuffer) = TicketValidityEnd;
return buffer;
}
bool AppTicketV2::Deserialize(const uint8_t* pBuffer, size_t size)
{
if (size < 40)
return false;
TicketSize = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
TicketVersion = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
SteamID = *reinterpret_cast<const uint64_t*>(pBuffer); pBuffer += 8;
AppID = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
Unk1 = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
Unk2 = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
TicketFlags = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
TicketIssueTime = *reinterpret_cast<const uint32_t*>(pBuffer); pBuffer += 4;
TicketValidityEnd = *reinterpret_cast<const uint32_t*>(pBuffer);
return true;
}
void AppTicketV4::Reset()
{
AppIDs.clear();
HasVACStatus = false;
HasAppValue = false;
}
std::vector<uint8_t> AppTicketV4::Serialize()
{
std::vector<uint32_t> appids = AppIDs;
if (appids.size() == 0) {
appids.emplace_back(0);
}
uint16_t appid_count = static_cast<uint16_t>(static_cast<uint16_t>(appids.size()) > 140 ? 140 : static_cast<uint16_t>(appids.size()));
size_t buffer_size = static_cast<uint32_t>(appid_count) * 4ul + 2ul;
std::vector<uint8_t> buffer{};
uint8_t* pBuffer{};
if (HasAppValue) {// VACStatus + AppValue
buffer_size += 4;
if (!HasVACStatus) {
HasVACStatus = true;
VACStatus = 0;
}
}
if (HasVACStatus) {// VACStatus only
buffer_size += 4;
}
buffer.resize(buffer_size);
pBuffer = buffer.data();
*reinterpret_cast<uint16_t*>(pBuffer) = appid_count;
pBuffer += 2;
for (int i = 0; i < appid_count && i < 140; ++i) {
*reinterpret_cast<uint32_t*>(pBuffer) = appids[i];
pBuffer += 4;
}
if (HasVACStatus) {
*reinterpret_cast<uint32_t*>(pBuffer) = VACStatus;
pBuffer += 4;
}
if (HasAppValue) {
*reinterpret_cast<uint32_t*>(pBuffer) = AppValue;
}
return buffer;
}
bool AppTicketV4::Deserialize(const uint8_t* pBuffer, size_t size)
{
if (size < 2)
return false;
uint16_t appid_count = *reinterpret_cast<const uint16_t*>(pBuffer);
if (size < static_cast<size_t>(appid_count * 4 + 2) || appid_count >= 140u)
return false;
AppIDs.resize(appid_count);
pBuffer += 2;
size -= 2;
for (int i = 0; i < appid_count; ++i) {
AppIDs[i] = *reinterpret_cast<const uint32_t*>(pBuffer);
pBuffer += 4;
size -= 4;
}
HasVACStatus = false;
HasAppValue = false;
if (size < 4)
return true;
HasVACStatus = true;
VACStatus = *reinterpret_cast<const uint32_t*>(pBuffer);
pBuffer += 4;
size -= 4;
if (size < 4)
return true;
HasAppValue = true;
AppValue = *reinterpret_cast<const uint32_t*>(pBuffer);
return true;
}
bool DecryptedAppTicket::DeserializeTicket(const uint8_t* pBuffer, size_t buf_size)
{
if (!TicketV1.Deserialize(pBuffer, buf_size))
return false;
pBuffer += 16 + TicketV1.UserData.size();
buf_size -= 16 + TicketV1.UserData.size();
if (!TicketV2.Deserialize(pBuffer, buf_size))
return false;
if (TicketV2.TicketVersion > 2) {
pBuffer += 40;
buf_size -= 40;
if (!TicketV4.Deserialize(pBuffer, buf_size))
return false;
}
return true;
}
std::vector<uint8_t> DecryptedAppTicket::SerializeTicket()
{
std::vector<uint8_t> buffer{};
TicketV1.TicketSize = static_cast<uint32_t>(TicketV1.UserData.size()) + 40 + 2 + ((static_cast<uint32_t>(TicketV4.AppIDs.size()) == 0 ? 1 : static_cast<uint32_t>(TicketV4.AppIDs.size())) * 4) + (TicketV4.HasVACStatus ? 4 : 0) + (TicketV4.HasAppValue ? 4 : 0);
TicketV2.TicketSize = TicketV1.TicketSize - static_cast<uint32_t>(TicketV1.UserData.size());
buffer = TicketV1.Serialize();
auto v = TicketV2.Serialize();
buffer.insert(buffer.end(), v.begin(), v.end());
v = TicketV4.Serialize();
buffer.insert(buffer.end(), v.begin(), v.end());
return buffer;
}
Steam_AppTicket::Steam_AppTicket(class Settings *settings) :
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);
std::lock_guard<std::recursive_mutex> lock(global_mutex);
return 0;
}

909
dll/auth.cpp Normal file
View File

@ -0,0 +1,909 @@
#include "dll/auth.h"
#define STEAM_ID_OFFSET_TICKET (4 + 8)
#define STEAM_TICKET_MIN_SIZE (4 + 8 + 8)
#define STEAM_TICKET_MIN_SIZE_NEW 170
#define STEAM_TICKET_PROCESS_TIME 0.03
//Conan Exiles doesn't work with 512 or 128, 256 seems to be the good size
// Usually steam send as 1024 (or recommend sending as that)
//Steam returns 234
#define STEAM_AUTH_TICKET_SIZE 256 //234
static inline int generate_random_int() {
int a;
randombytes((char *)&a, sizeof(a));
return a;
}
static uint32 generate_steam_ticket_id() {
/* not random starts with 2? */
static uint32 a = 1;
++a;
// this must never return 0, it is reserved for "k_HAuthTicketInvalid" when the auth APIs fail
if (a == 0) ++a;
return a;
}
static uint32_t get_ticket_count() {
static uint32_t a = 0;
++a;
// this must never return 0, on overflow just go back to 1 again
if (a == 0) a = 1;
return a;
}
// source: https://github.com/Detanup01/stmsrv/blob/main/Cert/AppTicket.key
// thanks Detanup01
const static std::string app_ticket_key =
"-----BEGIN PRIVATE KEY-----\n"
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMITHOY6pfsvaGTI\n"
"llmilPa1+ev4BsUV0IW3+F/3pQlZ+o57CO1HbepSh2a37cbGUSehOVQ7lREPVXP3\n"
"UdyF5tU5IMytJef5N7euM5z2IG9IszeOReO87h2AmtlwGqnRj7qd0MeVxSAuUq7P\n"
"C/Ir1VyOg58+wAKxaPL18upylnGJAgMBAAECgYEAnKQQj0KG9VYuTCoaL/6pfPcj\n"
"4PEvhaM1yrfSIKMg8YtOT/G+IsWkUZyK7L1HjUhD+FiIjRQKHNrjfdYAnJz20Xom\n"
"k6iVt7ugihIne1Q3pGYG8TY9P1DPdN7zEnAVY1Bh2PAlqJWrif3v8v1dUGE/dYr2\n"
"U3M0JhvzO7VL1B/chIECQQDqW9G5azGMA/cL4jOg0pbj9GfxjJZeT7M2rBoIaRWP\n"
"C3ROndyb+BNahlKk6tbvqillvvMQQiSFGw/PbmCwtLL3AkEA0/79W0q9d3YCXQGW\n"
"k3hQvR8HEbxLmRaRF2gU4MOa5C0JqwsmxzdK4mKoJCpVAiu1gmFonLjn2hm8i+vK\n"
"b7hffwJAEiMpCACTxRJJfFH1TOz/YIT5xmfq+0GPzRtkqGH5mSh5x9vPxwJb/RWI\n"
"L9s85y90JLuyc/+qc+K0Rol0Ujip4QJAGLXVJEn+8ajAt8SSn5fbmV+/fDK9gRef\n"
"S+Im5NgH+ubBBL3lBD2Orfqf7K8+f2VG3+6oufPXmpV7Y7fVPdZ40wJALDujJXgi\n"
"XiCBSht1YScYjfmJh2/xZWh8/w+vs5ZTtrnW2FQvfvVDG9c1hrChhpvmA0QxdgWB\n"
"zSsAno/utcuB9w==\n"
"-----END PRIVATE KEY-----\n";
static std::vector<uint8_t> sign_auth_data(const std::string &private_key_content, const std::vector<uint8_t> &data, size_t effective_data_len)
{
std::vector<uint8_t> signature{};
// Hash the data using SHA-1
constexpr static int SHA1_DIGEST_LENGTH = 20;
uint8_t hash[SHA1_DIGEST_LENGTH]{};
int result = mbedtls_sha1(data.data(), effective_data_len, hash);
if (result != 0) {
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
std::string err_msg(256, 0);
mbedtls_strerror(result, &err_msg[0], err_msg.size());
PRINT_DEBUG("failed to hash the data via SHA1: %s", err_msg.c_str());
#endif
return signature;
}
mbedtls_entropy_context entropy_ctx; // entropy context for random number generation
mbedtls_entropy_init(&entropy_ctx);
mbedtls_ctr_drbg_context ctr_drbg_ctx; // CTR-DRBG context for deterministic random number generation
mbedtls_ctr_drbg_init(&ctr_drbg_ctx);
// seed the CTR-DRBG context with random numbers
result = mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0);
if (mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func, &entropy_ctx, nullptr, 0) != 0) {
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
mbedtls_entropy_free(&entropy_ctx);
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
std::string err_msg(256, 0);
mbedtls_strerror(result, &err_msg[0], err_msg.size());
PRINT_DEBUG("failed to seed the CTR-DRBG context: %s", err_msg.c_str());
#endif
return signature;
}
mbedtls_pk_context private_key_ctx; // holds the parsed private key
mbedtls_pk_init(&private_key_ctx);
result = mbedtls_pk_parse_key(
&private_key_ctx, // will hold the parsed private key
(const unsigned char *)private_key_content.c_str(),
private_key_content.size() + 1, // we MUST include the null terminator, otherwise this API returns an error!
nullptr, 0, // no password stuff, private key isn't protected
mbedtls_ctr_drbg_random, &ctr_drbg_ctx // random number generation function + the CTR-DRBG context it requires as an input
);
if (result != 0) {
mbedtls_pk_free(&private_key_ctx);
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
mbedtls_entropy_free(&entropy_ctx);
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
std::string err_msg(256, 0);
mbedtls_strerror(result, &err_msg[0], err_msg.size());
PRINT_DEBUG("failed to parse private key: %s", err_msg.c_str());
#endif
return signature;
}
// private key must be valid RSA key
if (mbedtls_pk_get_type(&private_key_ctx) != MBEDTLS_PK_RSA || // invalid type
mbedtls_pk_can_do(&private_key_ctx, MBEDTLS_PK_RSA) == 0) { // or initialized but not properly setup (maybe freed?)
mbedtls_pk_free(&private_key_ctx);
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
mbedtls_entropy_free(&entropy_ctx);
PRINT_DEBUG("parsed key is not a valid RSA private key");
return signature;
}
// get the underlying RSA context from the parsed private key
mbedtls_rsa_context* rsa_ctx = mbedtls_pk_rsa(private_key_ctx);
// resize the output buffer to accomodate the size of the private key
const size_t private_key_len = mbedtls_pk_get_len(&private_key_ctx);
if (private_key_len == 0) { // TODO must be 128 siglen
mbedtls_pk_free(&private_key_ctx);
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
mbedtls_entropy_free(&entropy_ctx);
PRINT_DEBUG("failed to get private key (final buffer) length");
return signature;
}
PRINT_DEBUG("computed private key (final buffer) length = %zu", private_key_len);
signature.resize(private_key_len);
// finally sign the computed hash using RSA and PKCS#1 padding
result = mbedtls_rsa_pkcs1_sign(
rsa_ctx,
mbedtls_ctr_drbg_random, &ctr_drbg_ctx,
MBEDTLS_MD_SHA1, // we used SHA1 to hash the data
sizeof(hash), hash,
signature.data() // output
);
mbedtls_pk_free(&private_key_ctx);
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
mbedtls_entropy_free(&entropy_ctx);
if (result != 0) {
signature.clear();
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
std::string err_msg(256, 0);
mbedtls_strerror(result, &err_msg[0], err_msg.size());
PRINT_DEBUG("RSA signing failed: %s", err_msg.c_str());
#endif
}
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(signature);
PRINT_DEBUG("final signature [%zu bytes]:\n %s", signature.size(), str.c_str());
#endif
return signature;
}
std::vector<uint8_t> DLC::Serialize() const
{
PRINT_DEBUG("AppId = %u, Licenses count = %zu", AppId, Licenses.size());
// we need this variable because we depend on the sizeof, must be 2 bytes
const uint16_t dlcs_licenses_count = (uint16_t)Licenses.size();
const size_t dlcs_licenses_total_size =
Licenses.size() * sizeof(Licenses[0]); // count * element size
const size_t total_size =
sizeof(AppId) +
sizeof(dlcs_licenses_count) +
dlcs_licenses_total_size;
std::vector<uint8_t> buffer{};
buffer.resize(total_size);
uint8_t* pBuffer = &buffer[0];
#define SER_VAR(v) \
*reinterpret_cast<std::remove_const<decltype(v)>::type *>(pBuffer) = v; \
pBuffer += sizeof(v)
SER_VAR(AppId);
SER_VAR(dlcs_licenses_count);
for(uint32_t dlc_license : Licenses) {
SER_VAR(dlc_license);
}
#undef SER_VAR
PRINT_DEBUG("final size = %zu", buffer.size());
return buffer;
}
std::vector<uint8_t> AppTicketGC::Serialize() const
{
const uint64_t steam_id = id.ConvertToUint64();
// must be 52
constexpr size_t total_size =
sizeof(STEAM_APPTICKET_GCLen) +
sizeof(GCToken) +
sizeof(steam_id) +
sizeof(ticketGenDate) +
sizeof(STEAM_APPTICKET_SESSIONLEN) +
sizeof(one) +
sizeof(two) +
sizeof(ExternalIP) +
sizeof(InternalIP) +
sizeof(TimeSinceStartup) +
sizeof(TicketGeneratedCount);
// check the size at compile time, we must ensure the correct size
#ifndef EMU_RELEASE_BUILD
static_assert(
total_size == 52,
"AUTH::AppTicketGC::SER calculated size of serialized data != 52 bytes, your compiler has some incorrect sizes"
);
#endif
PRINT_DEBUG(
"\n"
" GCToken: " "%" PRIu64 "\n"
" user steam_id: " "%" PRIu64 "\n"
" ticketGenDate: %u\n"
" ExternalIP: 0x%08X, InternalIP: 0x%08X\n"
" TimeSinceStartup: %u, TicketGeneratedCount: %u\n"
" SER size = %zu",
GCToken,
steam_id,
ticketGenDate,
ExternalIP, InternalIP,
TimeSinceStartup, TicketGeneratedCount,
total_size
);
std::vector<uint8_t> buffer{};
buffer.resize(total_size);
uint8_t* pBuffer = &buffer[0];
#define SER_VAR(v) \
*reinterpret_cast<std::remove_const<decltype(v)>::type *>(pBuffer) = v; \
pBuffer += sizeof(v)
SER_VAR(STEAM_APPTICKET_GCLen);
SER_VAR(GCToken);
SER_VAR(steam_id);
SER_VAR(ticketGenDate);
SER_VAR(STEAM_APPTICKET_SESSIONLEN);
SER_VAR(one);
SER_VAR(two);
SER_VAR(ExternalIP);
SER_VAR(InternalIP);
SER_VAR(TimeSinceStartup);
SER_VAR(TicketGeneratedCount);
#undef SER_VAR
#ifndef EMU_RELEASE_BUILD
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(buffer);
PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str());
#endif
return buffer;
}
std::vector<uint8_t> AppTicket::Serialize() const
{
const uint64_t steam_id = id.ConvertToUint64();
PRINT_DEBUG(
"\n"
" Version: %u\n"
" user steam_id: " "%" PRIu64 "\n"
" AppId: %u\n"
" ExternalIP: 0x%08X, InternalIP: 0x%08X\n"
" TicketGeneratedDate: %u, TicketGeneratedExpireDate: %u\n"
" Licenses count: %zu, DLCs count: %zu",
Version,
steam_id,
AppId,
ExternalIP, InternalIP,
TicketGeneratedDate, TicketGeneratedExpireDate,
Licenses.size(), DLCs.size()
);
// we need this variable because we depend on the sizeof, must be 2 bytes
const uint16_t licenses_count = (uint16_t)Licenses.size();
const size_t licenses_total_size =
Licenses.size() * sizeof(Licenses[0]); // total count * element size
// we need this variable because we depend on the sizeof, must be 2 bytes
const uint16_t dlcs_count = (uint16_t)DLCs.size();
size_t dlcs_total_size = 0;
std::vector<std::vector<uint8_t>> serialized_dlcs{};
for (const DLC &dlc : DLCs) {
auto dlc_ser = dlc.Serialize();
dlcs_total_size += dlc_ser.size();
serialized_dlcs.push_back(dlc_ser);
}
//padding
constexpr uint16_t padding = (uint16_t)0;
// must be 42
constexpr size_t static_fields_size =
sizeof(Version) +
sizeof(steam_id) +
sizeof(AppId) +
sizeof(ExternalIP) +
sizeof(InternalIP) +
sizeof(AlwaysZero) +
sizeof(TicketGeneratedDate) +
sizeof(TicketGeneratedExpireDate) +
sizeof(licenses_count) +
sizeof(dlcs_count) +
sizeof(padding);
// check the size at compile time, we must ensure the correct size
#ifndef EMU_RELEASE_BUILD
static_assert(
static_fields_size == 42,
"AUTH::AppTicket::SER calculated size of serialized data != 42 bytes, your compiler has some incorrect sizes"
);
#endif
const size_t total_size =
static_fields_size +
licenses_total_size +
dlcs_total_size;
PRINT_DEBUG("final size = %zu", total_size);
std::vector<uint8_t> buffer{};
buffer.resize(total_size);
uint8_t* pBuffer = &buffer[0];
#define SER_VAR(v) \
*reinterpret_cast<std::remove_const<decltype(v)>::type *>(pBuffer) = v; \
pBuffer += sizeof(v)
SER_VAR(Version);
SER_VAR(steam_id);
SER_VAR(AppId);
SER_VAR(ExternalIP);
SER_VAR(InternalIP);
SER_VAR(AlwaysZero);
SER_VAR(TicketGeneratedDate);
SER_VAR(TicketGeneratedExpireDate);
#ifndef EMU_RELEASE_BUILD
{
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(buffer);
PRINT_DEBUG("(before licenses + DLCs):\n %s", str.c_str());
}
#endif
/*
* layout of licenses:
* ------------------------
* 2 bytes: count of licenses
* ------------------------
* [
* ------------------------
* | 4 bytes: license element
* ------------------------
* ]
*/
SER_VAR(licenses_count);
for(uint32_t license : Licenses) {
SER_VAR(license);
}
/*
* layout of DLCs:
* ------------------------
* | 2 bytes: count of DLCs
* ------------------------
* [
* ------------------------
* | 4 bytes: app id
* ------------------------
* | 2 bytes: DLC licenses count
* ------------------------
* [
* 4 bytes: DLC license element
* ]
* ]
*/
SER_VAR(dlcs_count);
for (const auto &dlc_ser : serialized_dlcs){
memcpy(pBuffer, dlc_ser.data(), dlc_ser.size());
pBuffer += dlc_ser.size();
}
//padding
SER_VAR(padding);
#undef SER_VAR
#ifndef EMU_RELEASE_BUILD
{
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(buffer);
PRINT_DEBUG("final data [%zu bytes]:\n %s", buffer.size(), str.c_str());
}
#endif
return buffer;
}
std::vector<uint8_t> Auth_Data::Serialize() const
{
/*
* layout of Auth_Data with GC:
* ------------------------
* X bytes: GC data blob (currently 52 bytes)
* ------------------------
* 4 bytes: remaining Auth_Data blob size (4 + Y + Z)
* ------------------------
* 4 bytes: size of ticket data layout (not blob!, hence blob + 4)
* ------------------------
* Y bytes: ticket data blob
* ------------------------
* Z bytes: App Ticket signature
* ------------------------
*
* total layout length = X + 4 + 4 + Y + Z
*/
/*
* layout of Auth_Data without GC:
* ------------------------
* 4 bytes: size of ticket data layout (not blob!, hence blob + 4)
* ------------------------
* Y bytes: ticket data blob
* ------------------------
* Z bytes: App Ticket signature
* ------------------------
*
* total layout length = 4 + Y + Z
*/
const uint64_t steam_id = id.ConvertToUint64();
PRINT_DEBUG(
"\n"
" HasGC: %u\n"
" user steam_id: " "%" PRIu64 "\n"
" number: " "%" PRIu64 ,
(int)HasGC,
steam_id,
number
);
/*
* layout of ticket data:
* ------------------------
* 4 bytes: size of ticket data layout (not blob!, hence blob + 4)
* ------------------------
* Y bytes: ticket data blob
* ------------------------
*
* total layout length = 4 + Y
*/
std::vector<uint8_t> tickedData = Ticket.Serialize();
// we need this variable because we depend on the sizeof, must be 4 bytes
const uint32_t ticket_data_layout_length =
sizeof(uint32_t) + // size of this uint32_t because it is included!
(uint32_t)tickedData.size();
size_t total_size_without_siglen = ticket_data_layout_length;
std::vector<uint8_t> GCData{};
size_t gc_data_layout_length = 0;
if (HasGC) {
/*
* layout of GC data:
* ------------------------
* X bytes: GC data blob (currently 52 bytes)
* ------------------------
* 4 bytes: remaining Auth_Data blob size
* ------------------------
*
* total layout length = X + 4
*/
GCData = GC.Serialize();
gc_data_layout_length +=
GCData.size() +
sizeof(uint32_t);
total_size_without_siglen += gc_data_layout_length;
}
const size_t final_buffer_size = total_size_without_siglen + STEAM_APPTICKET_SIGLEN;
PRINT_DEBUG("size without sig len = %zu, size with sig len (final size) = %zu",
total_size_without_siglen,
final_buffer_size
);
std::vector<uint8_t> buffer;
buffer.resize(final_buffer_size);
uint8_t* pBuffer = &buffer[0];
#define SER_VAR(v) \
*reinterpret_cast<std::remove_const<decltype(v)>::type *>(pBuffer) = v; \
pBuffer += sizeof(v)
// serialize the GC data first
if (HasGC) {
memcpy(pBuffer, GCData.data(), GCData.size());
pBuffer += GCData.size();
// when GC data is written (HasGC),
// the next 4 bytes after the GCData will be the length of the remaining data in the final buffer
// i.e. final buffer size - length of GCData layout
// i.e. ticket data length + STEAM_APPTICKET_SIGLEN
//
// notice that we subtract the entire layout length, not just GCData.size(),
// otherwise these next 4 bytes will include themselves!
uint32_t remaining_length = (uint32_t)(final_buffer_size - gc_data_layout_length);
SER_VAR(remaining_length);
}
// serialize the ticket data
SER_VAR(ticket_data_layout_length);
memcpy(pBuffer, tickedData.data(), tickedData.size());
#ifndef EMU_RELEASE_BUILD
{
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(buffer);
PRINT_DEBUG("final data (before signature) [%zu bytes]:\n %s", buffer.size(), str.c_str());
}
#endif
//Todo make a signature
std::vector<uint8_t> signature = sign_auth_data(app_ticket_key, tickedData, total_size_without_siglen);
if (signature.size() == STEAM_APPTICKET_SIGLEN) {
memcpy(buffer.data() + total_size_without_siglen, signature.data(), signature.size());
#ifndef EMU_RELEASE_BUILD
{
// we nedd a live object until the printf does its job, hence this special handling
auto str = common_helpers::uint8_vector_to_hex_string(buffer);
PRINT_DEBUG("final data (after signature) [%zu bytes]:\n %s", buffer.size(), str.c_str());
}
#endif
} else {
PRINT_DEBUG("signature size [%zu] is invalid", signature.size());
}
#undef SER_VAR
return buffer;
}
void Auth_Manager::ticket_callback(void *object, Common_Message *msg)
{
// PRINT_DEBUG_ENTRY();
Auth_Manager *auth_manager = (Auth_Manager *)object;
auth_manager->Callback(msg);
}
Auth_Manager::Auth_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks)
{
this->network = network;
this->settings = settings;
this->callbacks = callbacks;
this->network->setCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this);
this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this);
}
Auth_Manager::~Auth_Manager()
{
this->network->rmCallback(CALLBACK_ID_AUTH_TICKET, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this);
this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Auth_Manager::ticket_callback, this);
}
void Auth_Manager::launch_callback(CSteamID id, EAuthSessionResponse resp, double delay)
{
ValidateAuthTicketResponse_t data{};
data.m_SteamID = id;
data.m_eAuthSessionResponse = resp;
data.m_OwnerSteamID = id;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), delay);
}
void Auth_Manager::launch_callback_gs(CSteamID id, bool approved)
{
if (approved) {
GSClientApprove_t data{};
data.m_SteamID = data.m_OwnerSteamID = id;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
} else {
GSClientDeny_t data{};
data.m_SteamID = id;
data.m_eDenyReason = k_EDenyNotLoggedOn; //TODO: other reasons?
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
}
Auth_Data Auth_Manager::getTicketData( void *pTicket, int cbMaxTicket, uint32 *pcbTicket )
{
#define IP4_AS_DWORD_LITTLE_ENDIAN(a,b,c,d) (((uint32_t)d)<<24 | ((uint32_t)c)<<16 | ((uint32_t)b)<<8 | (uint32_t)a)
Auth_Data ticket_data{};
CSteamID steam_id = settings->get_local_steam_id();
if (settings->enable_new_app_ticket)
{
ticket_data.id = steam_id;
ticket_data.number = generate_steam_ticket_id();
ticket_data.Ticket.Version = 4;
ticket_data.Ticket.id = steam_id;
ticket_data.Ticket.AppId = settings->get_local_game_id().AppID();
ticket_data.Ticket.ExternalIP = IP4_AS_DWORD_LITTLE_ENDIAN(127, 0, 0, 1); //TODO
ticket_data.Ticket.InternalIP = IP4_AS_DWORD_LITTLE_ENDIAN(127, 0, 0, 1);
ticket_data.Ticket.AlwaysZero = 0;
const auto curTime = std::chrono::system_clock::now();
const auto GenDate = std::chrono::duration_cast<std::chrono::seconds>(curTime.time_since_epoch());
ticket_data.Ticket.TicketGeneratedDate = (uint32_t)GenDate.count();
uint32_t expTime = (uint32_t)(GenDate + std::chrono::hours(24)).count();
ticket_data.Ticket.TicketGeneratedExpireDate = expTime;
ticket_data.Ticket.Licenses.resize(0);
ticket_data.Ticket.Licenses.push_back(0); //TODO
unsigned int dlcCount = settings->DLCCount();
ticket_data.Ticket.DLCs.resize(0); //currently set to 0
for (unsigned i = 0; i < dlcCount; ++i)
{
DLC dlc;
AppId_t appid;
bool available;
std::string name;
if (!settings->getDLC(i, appid, available, name)) break;
dlc.AppId = (uint32_t)appid;
dlc.Licenses.resize(0); //TODO
ticket_data.Ticket.DLCs.push_back(dlc);
}
ticket_data.HasGC = false;
if (settings->use_gc_token)
{
ticket_data.HasGC = true;
ticket_data.GC.GCToken = generate_random_int();
ticket_data.GC.id = steam_id;
ticket_data.GC.ticketGenDate = (uint32_t)GenDate.count();
ticket_data.GC.ExternalIP = IP4_AS_DWORD_LITTLE_ENDIAN(127, 0, 0, 1);
ticket_data.GC.InternalIP = IP4_AS_DWORD_LITTLE_ENDIAN(127, 0, 0, 1);
ticket_data.GC.TimeSinceStartup = (uint32_t)std::chrono::duration_cast<std::chrono::seconds>(curTime - startup_time).count();
ticket_data.GC.TicketGeneratedCount = get_ticket_count();
}
std::vector<uint8_t> ser = ticket_data.Serialize();
uint32_t ser_size = static_cast<uint32_t>(ser.size());
*pcbTicket = ser_size;
if (cbMaxTicket > 0 && static_cast<uint32_t>(cbMaxTicket) >= ser_size) {
memcpy(pTicket, ser.data(), ser_size);
}
}
else
{
memset(pTicket, 123, cbMaxTicket);
((char *)pTicket)[0] = 0x14;
((char *)pTicket)[1] = 0;
((char *)pTicket)[2] = 0;
((char *)pTicket)[3] = 0;
uint64 steam_id_buff = steam_id.ConvertToUint64();
memcpy((char *)pTicket + STEAM_ID_OFFSET_TICKET, &steam_id_buff, sizeof(steam_id_buff));
*pcbTicket = cbMaxTicket;
uint32 ttt = generate_steam_ticket_id();
ticket_data.id = steam_id;
ticket_data.number = ttt;
memcpy(((char *)pTicket) + sizeof(uint64), &ttt, sizeof(ttt));
}
#undef IP4_AS_DWORD_LITTLE_ENDIAN
return ticket_data;
}
HAuthTicket Auth_Manager::getTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket )
{
if (settings->enable_new_app_ticket)
{
if (cbMaxTicket < STEAM_TICKET_MIN_SIZE_NEW) return k_HAuthTicketInvalid;
}
else
{
if (cbMaxTicket < STEAM_TICKET_MIN_SIZE) return k_HAuthTicketInvalid;
if (cbMaxTicket > STEAM_AUTH_TICKET_SIZE) cbMaxTicket = STEAM_AUTH_TICKET_SIZE;
}
Auth_Data ticket_data = getTicketData(pTicket, cbMaxTicket, pcbTicket );
if (*pcbTicket > static_cast<uint32>(cbMaxTicket)) {
return k_HAuthTicketInvalid;
}
GetAuthSessionTicketResponse_t data{};
data.m_hAuthTicket = (HAuthTicket)ticket_data.number;
data.m_eResult = EResult::k_EResultOK;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), STEAM_TICKET_PROCESS_TIME);
outbound.push_back(ticket_data);
return data.m_hAuthTicket;
}
HAuthTicket Auth_Manager::getWebApiTicket( const char* pchIdentity )
{
// https://docs.unity.com/ugs/en-us/manual/authentication/manual/platform-signin-steam
GetTicketForWebApiResponse_t data{};
uint32 cbTicket = 0;
Auth_Data ticket_data = getTicketData(data.m_rgubTicket, sizeof(data.m_rgubTicket), &cbTicket);
if (cbTicket > sizeof(data.m_rgubTicket))
return k_HAuthTicketInvalid;
data.m_cubTicket = (int)cbTicket;
data.m_hAuthTicket = (HAuthTicket)ticket_data.number;
data.m_eResult = EResult::k_EResultOK;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), STEAM_TICKET_PROCESS_TIME);
outbound.push_back(ticket_data);
return data.m_hAuthTicket;
}
CSteamID Auth_Manager::fakeUser()
{
Auth_Data data = {};
data.id = generate_steam_anon_user();
inbound.push_back(data);
return data.id;
}
void Auth_Manager::cancelTicket(uint32 number)
{
auto ticket = std::find_if(outbound.begin(), outbound.end(), [&number](Auth_Data const& item) { return item.number == number; });
if (outbound.end() == ticket)
return;
Auth_Ticket *auth_ticket = new Auth_Ticket();
auth_ticket->set_number(number);
auth_ticket->set_type(Auth_Ticket::CANCEL);
Common_Message msg;
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_allocated_auth_ticket(auth_ticket);
network->sendToAll(&msg, true);
outbound.erase(ticket);
}
bool Auth_Manager::SendUserConnectAndAuthenticate( uint32 unIPClient, const void *pvAuthBlob, uint32 cubAuthBlobSize, CSteamID *pSteamIDUser )
{
if (cubAuthBlobSize < STEAM_TICKET_MIN_SIZE) return false;
Auth_Data data;
uint64 id;
memcpy(&id, (char *)pvAuthBlob + STEAM_ID_OFFSET_TICKET, sizeof(id));
uint32 number;
memcpy(&number, ((char *)pvAuthBlob) + sizeof(uint64), sizeof(number));
data.id = CSteamID(id);
data.number = number;
if (pSteamIDUser) *pSteamIDUser = data.id;
for (auto & t : inbound) {
if (t.id == data.id) {
//Should this return false?
launch_callback_gs(id, true);
return true;
}
}
inbound.push_back(data);
launch_callback_gs(id, true);
return true;
}
EBeginAuthSessionResult Auth_Manager::beginAuth(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID )
{
if (cbAuthTicket < STEAM_TICKET_MIN_SIZE) return k_EBeginAuthSessionResultInvalidTicket;
Auth_Data data;
uint64 id;
memcpy(&id, (char *)pAuthTicket + STEAM_ID_OFFSET_TICKET, sizeof(id));
uint32 number;
memcpy(&number, ((char *)pAuthTicket) + sizeof(uint64), sizeof(number));
data.id = CSteamID(id);
data.number = number;
data.created = std::chrono::high_resolution_clock::now();
for (auto & t : inbound) {
if (t.id == data.id && !check_timedout(t.created, STEAM_TICKET_PROCESS_TIME)) {
return k_EBeginAuthSessionResultDuplicateRequest;
}
}
inbound.push_back(data);
launch_callback(steamID, k_EAuthSessionResponseOK, STEAM_TICKET_PROCESS_TIME);
return k_EBeginAuthSessionResultOK;
}
uint32 Auth_Manager::countInboundAuth()
{
return static_cast<uint32>(inbound.size());
}
bool Auth_Manager::endAuth(CSteamID id)
{
bool erased = false;
auto t = std::begin(inbound);
while (t != std::end(inbound)) {
if (t->id == id) {
erased = true;
t = inbound.erase(t);
} else {
++t;
}
}
return erased;
}
void Auth_Manager::Callback(Common_Message *msg)
{
if (msg->has_low_level()) {
if (msg->low_level().type() == Low_Level::CONNECT) {
}
if (msg->low_level().type() == Low_Level::DISCONNECT) {
PRINT_DEBUG("TICKET DISCONNECT");
auto t = std::begin(inbound);
while (t != std::end(inbound)) {
if (t->id.ConvertToUint64() == msg->source_id()) {
launch_callback(t->id, k_EAuthSessionResponseUserNotConnectedToSteam);
t = inbound.erase(t);
} else {
++t;
}
}
}
}
if (msg->has_auth_ticket()) {
if (msg->auth_ticket().type() == Auth_Ticket::CANCEL) {
PRINT_DEBUG("TICKET CANCEL " "%" PRIu64, msg->source_id());
uint32 number = msg->auth_ticket().number();
auto t = std::begin(inbound);
while (t != std::end(inbound)) {
if (t->id.ConvertToUint64() == msg->source_id() && t->number == number) {
PRINT_DEBUG("TICKET CANCELED");
launch_callback(t->id, k_EAuthSessionResponseAuthTicketCanceled);
t = inbound.erase(t);
} else {
++t;
}
}
}
}
}

666
dll/base.cpp Normal file
View File

@ -0,0 +1,666 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "dll/base.h"
#include "dll/settings_parser.h"
#ifndef EMU_RELEASE_BUILD
#include "dbg_log/dbg_log.hpp"
#endif
std::recursive_mutex global_mutex{};
// some arbitrary counter/time for reference
const std::chrono::time_point<std::chrono::high_resolution_clock> startup_counter = std::chrono::high_resolution_clock::now();
const std::chrono::time_point<std::chrono::system_clock> startup_time = std::chrono::system_clock::now();
#ifndef EMU_RELEASE_BUILD
dbg_log dbg_logger(get_full_program_path() + "STEAM_LOG_" + std::to_string(common_helpers::rand_number(UINT32_MAX)) + ".log");
#endif
#ifdef __WINDOWS__
void randombytes(char *buf, size_t size)
{
// NT_SUCCESS is: return value >= 0, including Ntdef.h causes so many errors
while (BCryptGenRandom(NULL, (PUCHAR) buf, (ULONG) size, BCRYPT_USE_SYSTEM_PREFERRED_RNG) < 0) {
PRINT_DEBUG("ERROR");
Sleep(100);
}
}
std::string get_env_variable(const std::string &name)
{
wchar_t env_variable[1024]{};
DWORD ret = GetEnvironmentVariableW(utf8_decode(name).c_str(), env_variable, _countof(env_variable));
if (ret <= 0 || !env_variable[0]) {
return std::string();
}
env_variable[ret] = 0;
return utf8_encode(env_variable);
}
bool set_env_variable(const std::string &name, const std::string &value)
{
return SetEnvironmentVariableW(utf8_decode(name).c_str(), utf8_decode(value).c_str());
}
#else
static int fd = -1;
void randombytes(char *buf, size_t size)
{
int i;
if (fd == -1) {
for (;;) {
fd = open("/dev/urandom",O_RDONLY);
if (fd != -1) break;
sleep(1);
}
}
while (size > 0) {
if (size < 1048576) i = size; else i = 1048576;
i = read(fd,buf,i);
if (i < 1) {
sleep(1);
continue;
}
buf += i;
size -= i;
}
}
std::string get_env_variable(const std::string &name)
{
char *env = getenv(name.c_str());
if (!env) {
return std::string();
}
return std::string(env);
}
bool set_env_variable(const std::string &name, const std::string &value)
{
return setenv(name.c_str(), value.c_str(), 1) == 0;
}
#endif
unsigned generate_account_id()
{
int a;
randombytes((char *)&a, sizeof(a));
a = abs(a);
if (!a) ++a;
return a;
}
CSteamID generate_steam_anon_user()
{
return CSteamID(generate_account_id(), k_unSteamUserDefaultInstance, k_EUniversePublic, k_EAccountTypeAnonUser);
}
SteamAPICall_t generate_steam_api_call_id() {
static SteamAPICall_t a;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
randombytes((char *)&a, sizeof(a));
++a;
if (a == 0) ++a;
return a;
}
CSteamID generate_steam_id_user()
{
return CSteamID(generate_account_id(), k_unSteamUserDefaultInstance, k_EUniversePublic, k_EAccountTypeIndividual);
}
CSteamID generate_steam_id_server()
{
return CSteamID(generate_account_id(), k_unSteamUserDefaultInstance, k_EUniversePublic, k_EAccountTypeGameServer);
}
CSteamID generate_steam_id_anonserver()
{
return CSteamID(generate_account_id(), k_unSteamUserDefaultInstance, k_EUniversePublic, k_EAccountTypeAnonGameServer);
}
CSteamID generate_steam_id_lobby()
{
return CSteamID(generate_account_id(), k_EChatInstanceFlagLobby | k_EChatInstanceFlagMMSLobby, k_EUniversePublic, k_EAccountTypeChat);
}
bool check_timedout(std::chrono::high_resolution_clock::time_point old, double timeout)
{
if (timeout == 0.0) return true;
std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::duration<double>>(now - old).count() > timeout) {
return true;
}
return false;
}
#ifdef __LINUX__
std::string get_lib_path() {
std::string dir = "/proc/self/map_files";
DIR *dp;
int i = 0;
struct dirent *ep;
dp = opendir (dir.c_str());
uintptr_t p = (uintptr_t)&get_lib_path;
if (dp != NULL)
{
while ((ep = readdir (dp))) {
if (memcmp(ep->d_name, ".", 2) != 0 && memcmp(ep->d_name, "..", 3) != 0) {
char *upper = NULL;
uintptr_t lower_bound = strtoull(ep->d_name, &upper, 16);
if (lower_bound) {
++upper;
uintptr_t upper_bound = strtoull(upper, &upper, 16);
if (upper_bound && (lower_bound < p && p < upper_bound)) {
std::string path = dir + PATH_SEPARATOR + ep->d_name;
char link[PATH_MAX] = {};
if (readlink(path.c_str(), link, sizeof(link)) > 0) {
std::string lib_path = link;
(void) closedir (dp);
return link;
}
}
}
i++;
}
}
(void) closedir (dp);
}
return ".";
}
#endif
std::string get_full_lib_path()
{
std::string program_path;
#if defined(__WINDOWS__)
wchar_t DllPath[2048] = {0};
GetModuleFileNameW((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
program_path = utf8_encode(DllPath);
#else
program_path = get_lib_path();
#endif
return program_path;
}
std::string get_full_program_path()
{
std::string env_program_path = get_env_variable("GseAppPath");
if (env_program_path.length()) {
if (env_program_path.back() != PATH_SEPARATOR[0]) {
env_program_path = env_program_path.append(PATH_SEPARATOR);
}
return env_program_path;
}
std::string program_path{};
program_path = get_full_lib_path();
return program_path.substr(0, program_path.rfind(PATH_SEPARATOR)).append(PATH_SEPARATOR);
}
std::string get_current_path()
{
std::string path;
#if defined(STEAM_WIN32)
char *buffer = _getcwd( NULL, 0 );
#else
char *buffer = get_current_dir_name();
#endif
if (buffer) {
path = buffer;
path.append(PATH_SEPARATOR);
free(buffer);
}
return path;
}
std::string canonical_path(const std::string &path)
{
std::string output;
#if defined(STEAM_WIN32)
wchar_t *buffer = _wfullpath(NULL, utf8_decode(path).c_str(), 0);
if (buffer) {
output = utf8_encode(buffer);
free(buffer);
}
#else
char *buffer = canonicalize_file_name(path.c_str());
if (buffer) {
output = buffer;
free(buffer);
}
#endif
return output;
}
bool file_exists_(const std::string &full_path)
{
#if defined(STEAM_WIN32)
struct _stat buffer{};
if (_wstat(utf8_decode(full_path).c_str(), &buffer) != 0)
return false;
if ( buffer.st_mode & S_IFDIR)
return false;
#else
struct stat buffer{};
if (stat(full_path.c_str(), &buffer) != 0)
return false;
if (S_ISDIR(buffer.st_mode))
return false;
#endif
return true;
}
unsigned int file_size_(const std::string &full_path)
{
#if defined(STEAM_WIN32)
struct _stat buffer{};
if (_wstat(utf8_decode(full_path).c_str(), &buffer) != 0) return 0;
#else
struct stat buffer{};
if (stat (full_path.c_str(), &buffer) != 0) return 0;
#endif
return buffer.st_size;
}
#ifdef EMU_EXPERIMENTAL_BUILD
#ifdef __WINDOWS__
struct ips_test {
uint32_t ip_from;
uint32_t ip_to;
};
static std::vector<struct ips_test> whitelist_ips;
void set_whitelist_ips(uint32_t *from, uint32_t *to, unsigned num_ips)
{
whitelist_ips.clear();
for (unsigned i = 0; i < num_ips; ++i) {
struct ips_test ip_a;
PRINT_DEBUG("from: %hhu.%hhu.%hhu.%hhu", ((unsigned char *)&from[i])[0], ((unsigned char *)&from[i])[1], ((unsigned char *)&from[i])[2], ((unsigned char *)&from[i])[3]);
PRINT_DEBUG("to: %hhu.%hhu.%hhu.%hhu", ((unsigned char *)&to[i])[0], ((unsigned char *)&to[i])[1], ((unsigned char *)&to[i])[2], ((unsigned char *)&to[i])[3]);
ip_a.ip_from = ntohl(from[i]);
ip_a.ip_to = ntohl(to[i]);
if (ip_a.ip_to < ip_a.ip_from) continue;
if ((ip_a.ip_to - ip_a.ip_from) > (1 << 25)) continue;
PRINT_DEBUG("added ip to whitelist");
whitelist_ips.push_back(ip_a);
}
}
static bool is_whitelist_ip(unsigned char *ip)
{
uint32_t ip_temp = 0;
memcpy(&ip_temp, ip, sizeof(ip_temp));
ip_temp = ntohl(ip_temp);
for (auto &i : whitelist_ips) {
if (i.ip_from <= ip_temp && ip_temp <= i.ip_to) {
PRINT_DEBUG("IP IS WHITELISTED %hhu.%hhu.%hhu.%hhu", ip[0], ip[1], ip[2], ip[3]);
return true;
}
}
return false;
}
static bool is_lan_ipv4(unsigned char *ip)
{
PRINT_DEBUG("CHECK LAN IP %hhu.%hhu.%hhu.%hhu", ip[0], ip[1], ip[2], ip[3]);
if (is_whitelist_ip(ip)) return true;
if (ip[0] == 127) return true;
if (ip[0] == 10) return true;
if (ip[0] == 192 && ip[1] == 168) return true;
if (ip[0] == 169 && ip[1] == 254 && ip[2] != 0) return true;
if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) return true;
if ((ip[0] == 100) && ((ip[1] & 0xC0) == 0x40)) return true;
if (ip[0] == 239) return true; //multicast
if (ip[0] == 0) return true; //Current network
if (ip[0] == 192 && (ip[1] == 18 || ip[1] == 19)) return true; //Used for benchmark testing of inter-network communications between two separate subnets.
if (ip[0] >= 224) return true; //ip multicast (224 - 239) future use (240.0.0.0 - 255.255.255.254) broadcast (255.255.255.255)
return false;
}
static bool is_lan_ip(const sockaddr *addr, int namelen)
{
if (!namelen) return false;
if (addr->sa_family == AF_INET) {
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
unsigned char ip[4];
memcpy(ip, &addr_in->sin_addr, sizeof(ip));
if (is_lan_ipv4(ip)) return true;
} else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
unsigned char ip[16];
unsigned char zeroes[16] = {};
memcpy(ip, &addr_in6->sin6_addr, sizeof(ip));
PRINT_DEBUG("CHECK LAN IP6 %hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu.%hhu", ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]);
if (((ip[0] == 0xFF) && (ip[1] < 3) && (ip[15] == 1)) ||
((ip[0] == 0xFE) && ((ip[1] & 0xC0) == 0x80))) return true;
if (memcmp(zeroes, ip, sizeof(ip)) == 0) return true;
if (memcmp(zeroes, ip, sizeof(ip) - 1) == 0 && ip[15] == 1) return true;
if (ip[0] == 0xff) return true; //multicast
if (ip[0] == 0xfc) return true; //unique local
if (ip[0] == 0xfd) return true; //unique local
unsigned char ipv4_mapped[12] = {};
ipv4_mapped[10] = 0xFF;
ipv4_mapped[11] = 0xFF;
if (memcmp(ipv4_mapped, ip, sizeof(ipv4_mapped)) == 0) {
if (is_lan_ipv4(ip + 12)) return true;
}
}
PRINT_DEBUG("NOT LAN IP");
return false;
}
int ( WINAPI *Real_SendTo )( SOCKET s, const char *buf, int len, int flags, const sockaddr *to, int tolen) = sendto;
int ( WINAPI *Real_Connect )( SOCKET s, const sockaddr *addr, int namelen ) = connect;
int ( WINAPI *Real_WSAConnect )( SOCKET s, const sockaddr *addr, int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS) = WSAConnect;
static int WINAPI Mine_SendTo( SOCKET s, const char *buf, int len, int flags, const sockaddr *to, int tolen)
{
PRINT_DEBUG_ENTRY();
if (is_lan_ip(to, tolen)) {
return Real_SendTo( s, buf, len, flags, to, tolen );
} else {
return len;
}
}
static int WINAPI Mine_Connect( SOCKET s, const sockaddr *addr, int namelen )
{
PRINT_DEBUG_ENTRY();
if (is_lan_ip(addr, namelen)) {
return Real_Connect(s, addr, namelen);
} else {
WSASetLastError(WSAECONNREFUSED);
return SOCKET_ERROR;
}
}
static int WINAPI Mine_WSAConnect( SOCKET s, const sockaddr *addr, int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS)
{
PRINT_DEBUG_ENTRY();
if (is_lan_ip(addr, namelen)) {
return Real_WSAConnect(s, addr, namelen, lpCallerData, lpCalleeData, lpSQOS, lpGQOS);
} else {
WSASetLastError(WSAECONNREFUSED);
return SOCKET_ERROR;
}
}
inline bool file_exists (const std::string& name)
{
struct stat buffer;
return (stat (name.c_str(), &buffer) == 0);
}
#ifdef DETOURS_64BIT
#define DLL_NAME "steam_api64.dll"
#else
#define DLL_NAME "steam_api.dll"
#endif
HMODULE (WINAPI *Real_GetModuleHandleA)(LPCSTR lpModuleName) = GetModuleHandleA;
HMODULE WINAPI Mine_GetModuleHandleA(LPCSTR lpModuleName)
{
PRINT_DEBUG("%s", lpModuleName);
if (!lpModuleName) return Real_GetModuleHandleA(lpModuleName);
std::string in(lpModuleName);
if (in == std::string(DLL_NAME)) {
in = std::string("crack") + in;
}
return Real_GetModuleHandleA(in.c_str());
}
static void redirect_crackdll()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourAttach( reinterpret_cast<PVOID*>(&Real_GetModuleHandleA), reinterpret_cast<PVOID>(Mine_GetModuleHandleA) );
DetourTransactionCommit();
}
static void unredirect_crackdll()
{
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourDetach( reinterpret_cast<PVOID*>(&Real_GetModuleHandleA), reinterpret_cast<PVOID>(Mine_GetModuleHandleA) );
DetourTransactionCommit();
}
HMODULE crack_dll_handle{};
static void load_crack_dll()
{
std::string path(get_full_program_path() + "crack" + DLL_NAME);
PRINT_DEBUG("searching for crack file '%s'", path.c_str());
if (file_exists(path)) {
redirect_crackdll();
crack_dll_handle = LoadLibraryW(utf8_decode(path).c_str());
unredirect_crackdll();
PRINT_DEBUG("Loaded crack file");
}
}
#include "dll/local_storage.h"
static void load_dlls()
{
std::string path(Local_Storage::get_game_settings_path() + "load_dlls" + PATH_SEPARATOR);
std::vector<std::string> paths(Local_Storage::get_filenames_path(path));
for (auto & p: paths) {
std::string full_path(path + p);
if (!common_helpers::ends_with_i(full_path, ".dll")) continue;
PRINT_DEBUG("loading '%s'", full_path.c_str());
if (LoadLibraryW(utf8_decode(full_path).c_str())) {
PRINT_DEBUG(" LOADED");
} else {
PRINT_DEBUG(" FAILED, error 0x%X", GetLastError());
}
}
}
//For some reason when this function is optimized it breaks the shogun 2 prophet (reloaded) crack.
#pragma optimize( "", off )
bool crack_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID)
{
if (crack_dll_handle) {
bool (__stdcall* restart_app)(uint32) = (bool (__stdcall *)(uint32))GetProcAddress(crack_dll_handle, "SteamAPI_RestartAppIfNecessary");
if (restart_app) {
PRINT_DEBUG("Calling crack SteamAPI_RestartAppIfNecessary");
redirect_crackdll();
bool ret = restart_app(unOwnAppID);
unredirect_crackdll();
return ret;
}
}
return false;
}
#pragma optimize( "", on )
bool crack_SteamAPI_Init()
{
if (crack_dll_handle) {
bool (__stdcall* init_app)() = (bool (__stdcall *)())GetProcAddress(crack_dll_handle, "SteamAPI_Init");
if (init_app) {
PRINT_DEBUG("Calling crack SteamAPI_Init");
redirect_crackdll();
bool ret = init_app();
unredirect_crackdll();
return ret;
}
}
return false;
}
HINTERNET (WINAPI *Real_WinHttpConnect)(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved
);
HINTERNET WINAPI Mine_WinHttpConnect(
IN HINTERNET hSession,
IN LPCWSTR pswzServerName,
IN INTERNET_PORT nServerPort,
IN DWORD dwReserved
) {
PRINT_DEBUG("%ls %u", pswzServerName, nServerPort);
struct sockaddr_in ip4;
struct sockaddr_in6 ip6;
ip4.sin_family = AF_INET;
ip6.sin6_family = AF_INET6;
if ((InetPtonW(AF_INET, pswzServerName, &(ip4.sin_addr)) && is_lan_ip((sockaddr *)&ip4, sizeof(ip4))) || (InetPtonW(AF_INET6, pswzServerName, &(ip6.sin6_addr)) && is_lan_ip((sockaddr *)&ip6, sizeof(ip6)))) {
return Real_WinHttpConnect(hSession, pswzServerName, nServerPort, dwReserved);
} else {
return Real_WinHttpConnect(hSession, L"127.1.33.7", nServerPort, dwReserved);
}
}
HINTERNET (WINAPI *Real_WinHttpOpenRequest)(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer,
IN LPCWSTR *ppwszAcceptTypes,
IN DWORD dwFlags
);
HINTERNET WINAPI Mine_WinHttpOpenRequest(
IN HINTERNET hConnect,
IN LPCWSTR pwszVerb,
IN LPCWSTR pwszObjectName,
IN LPCWSTR pwszVersion,
IN LPCWSTR pwszReferrer,
IN LPCWSTR *ppwszAcceptTypes,
IN DWORD dwFlags
) {
PRINT_DEBUG("%ls %ls %ls %ls %i", pwszVerb, pwszObjectName, pwszVersion, pwszReferrer, dwFlags);
if (dwFlags & WINHTTP_FLAG_SECURE) {
dwFlags ^= WINHTTP_FLAG_SECURE;
}
return Real_WinHttpOpenRequest(hConnect, pwszVerb, pwszObjectName, pwszVersion, pwszReferrer, ppwszAcceptTypes, dwFlags);
}
static bool network_functions_attached = false;
BOOL WINAPI DllMain( HINSTANCE, DWORD dwReason, LPVOID )
{
switch ( dwReason ) {
case DLL_PROCESS_ATTACH:
PRINT_DEBUG("experimental DLL_PROCESS_ATTACH");
if (!settings_disable_lan_only()) {
PRINT_DEBUG("Hooking lan only functions");
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourAttach( reinterpret_cast<PVOID*>(&Real_SendTo), reinterpret_cast<PVOID>(Mine_SendTo) );
DetourAttach( reinterpret_cast<PVOID*>(&Real_Connect), reinterpret_cast<PVOID>(Mine_Connect) );
DetourAttach( reinterpret_cast<PVOID*>(&Real_WSAConnect), reinterpret_cast<PVOID>(Mine_WSAConnect) );
HMODULE winhttp = GetModuleHandleA("winhttp.dll");
if (winhttp) {
Real_WinHttpConnect = (decltype(Real_WinHttpConnect))GetProcAddress(winhttp, "WinHttpConnect");
DetourAttach( reinterpret_cast<PVOID*>(&Real_WinHttpConnect), reinterpret_cast<PVOID>(Mine_WinHttpConnect) );
// Real_WinHttpOpenRequest = (decltype(Real_WinHttpOpenRequest))GetProcAddress(winhttp, "WinHttpOpenRequest");
// DetourAttach( reinterpret_cast<PVOID*>(&Real_WinHttpOpenRequest), reinterpret_cast<PVOID>(Mine_WinHttpOpenRequest) );
}
DetourTransactionCommit();
network_functions_attached = true;
}
load_crack_dll();
load_dlls();
break;
case DLL_PROCESS_DETACH:
PRINT_DEBUG("experimental DLL_PROCESS_DETACH");
if (network_functions_attached) {
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourDetach( reinterpret_cast<PVOID*>(&Real_SendTo), reinterpret_cast<PVOID>(Mine_SendTo) );
DetourDetach( reinterpret_cast<PVOID*>(&Real_Connect), reinterpret_cast<PVOID>(Mine_Connect) );
DetourDetach( reinterpret_cast<PVOID*>(&Real_WSAConnect), reinterpret_cast<PVOID>(Mine_WSAConnect) );
if (Real_WinHttpConnect) {
DetourDetach( reinterpret_cast<PVOID*>(&Real_WinHttpConnect), reinterpret_cast<PVOID>(Mine_WinHttpConnect) );
// DetourDetach( reinterpret_cast<PVOID*>(&Real_WinHttpOpenRequest), reinterpret_cast<PVOID>(Mine_WinHttpOpenRequest) );
}
DetourTransactionCommit();
}
break;
}
return TRUE;
}
#else
void set_whitelist_ips(uint32_t *from, uint32_t *to, unsigned num_ips)
{
}
#endif
#else
void set_whitelist_ips(uint32_t *from, uint32_t *to, unsigned num_ips)
{
}
#endif

419
dll/callsystem.cpp Normal file
View File

@ -0,0 +1,419 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "dll/callsystem.h"
void CCallbackMgr::SetRegister(class CCallbackBase *pCallback, int iCallback)
{
pCallback->m_nCallbackFlags |= CCallbackBase::k_ECallbackFlagsRegistered;
pCallback->m_iCallback = iCallback;
};
void CCallbackMgr::SetUnregister(class CCallbackBase *pCallback)
{
if (pCallback)
pCallback->m_nCallbackFlags &= !CCallbackBase::k_ECallbackFlagsRegistered;
};
bool CCallbackMgr::isServer(class CCallbackBase *pCallback)
{
return (pCallback->m_nCallbackFlags & CCallbackBase::k_ECallbackFlagsGameServer) != 0;
};
Steam_Call_Result::Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb)
{
api_call = a;
result.resize(s);
if (s > 0 && r != NULL) {
memcpy(&(result[0]), r, s);
}
run_in = r_in;
run_call_completed_cb = run_cc_cb;
iCallback = icb;
created = std::chrono::high_resolution_clock::now();
}
bool Steam_Call_Result::operator==(const struct Steam_Call_Result& other) const
{
return other.api_call == api_call && other.callbacks == callbacks;
}
bool Steam_Call_Result::timed_out() const
{
return check_timedout(created, STEAM_CALLRESULT_TIMEOUT);
}
bool Steam_Call_Result::call_completed() const
{
return (!reserved) && check_timedout(created, run_in);
}
bool Steam_Call_Result::can_execute() const
{
return (!to_delete) && call_completed() && (has_cb() || check_timedout(created, STEAM_CALLRESULT_WAIT_FOR_CB));
}
bool Steam_Call_Result::has_cb() const
{
return callbacks.size() > 0;
}
void SteamCallResults::addCallCompleted(class CCallbackBase *cb)
{
if (std::find(completed_callbacks.begin(), completed_callbacks.end(), cb) == completed_callbacks.end()) {
completed_callbacks.push_back(cb);
PRINT_DEBUG("new cb for call complete notification [result k_iCallback=%i] %p", cb ? (cb->GetICallback()) : -1, cb);
}
}
void SteamCallResults::rmCallCompleted(class CCallbackBase *cb)
{
auto c = std::find(completed_callbacks.begin(), completed_callbacks.end(), cb);
if (c != completed_callbacks.end()) {
completed_callbacks.erase(c);
PRINT_DEBUG("removed cb for call complete notification [result k_iCallback=%i] %p", cb ? (cb->GetICallback()) : -1, cb);
}
}
void SteamCallResults::addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb)
{
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
if (cb_result != callresults.end()) {
cb_result->callbacks.push_back(cb);
CCallbackMgr::SetRegister(cb, cb->GetICallback());
PRINT_DEBUG("new cb for call result [api id=%llu, result k_iCallback=%i] %p", api_call, cb ? (cb->GetICallback()) : -1, cb);
}
}
bool SteamCallResults::exists(SteamAPICall_t api_call) const
{
auto cr = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) {
return item.api_call == api_call;
});
if (callresults.end() == cr) return false;
if (!cr->call_completed()) return false;
return true;
}
bool SteamCallResults::callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size)
{
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) {
return item.api_call == api_call;
});
if (cb_result != callresults.end()) {
if (!cb_result->call_completed()) return false;
if (cb_result->result.size() > size) return false;
memcpy(copy_to, &(cb_result->result[0]), cb_result->result.size());
cb_result->to_delete = true;
return true;
} else {
return false;
}
}
void SteamCallResults::rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb)
{
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
if (cb_result != callresults.end()) {
auto it = std::find(cb_result->callbacks.begin(), cb_result->callbacks.end(), cb);
if (it != cb_result->callbacks.end()) {
cb_result->callbacks.erase(it);
CCallbackMgr::SetUnregister(cb);
PRINT_DEBUG("removed cb for call result [api id=%llu, result k_iCallback=%i] %p", api_call, cb ? (cb->GetICallback()) : -1, cb);
}
}
}
void SteamCallResults::rmCallBack(class CCallbackBase *cb)
{
//TODO: check if callback is callback or call result?
for (auto & cr: callresults) {
auto it = std::find(cr.callbacks.begin(), cr.callbacks.end(), cb);
if (it != cr.callbacks.end()) {
cr.callbacks.erase(it);
PRINT_DEBUG("removed cb %p, kind=%i (0=callback, 1=call result)", cb, (int)cr.run_call_completed_cb);
}
if (cr.callbacks.size() == 0) {
cr.to_delete = true;
}
}
}
SteamAPICall_t SteamCallResults::addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout, bool run_call_completed_cb)
{
PRINT_DEBUG("%i", iCallback);
auto cb_result = std::find_if(callresults.begin(), callresults.end(), [api_call](struct Steam_Call_Result const& item) { return item.api_call == api_call; });
if (cb_result != callresults.end()) {
// only change the data if this is a previously reserved callresult
if (cb_result->reserved) {
std::chrono::high_resolution_clock::time_point created = cb_result->created;
std::vector<class CCallbackBase *> temp_cbs = cb_result->callbacks;
*cb_result = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb);
cb_result->callbacks = temp_cbs;
cb_result->created = created;
return cb_result->api_call;
}
} else {
struct Steam_Call_Result res = Steam_Call_Result(api_call, iCallback, result, size, timeout, run_call_completed_cb);
callresults.push_back(res);
return callresults.back().api_call;
}
PRINT_DEBUG("ERROR");
return k_uAPICallInvalid;
}
SteamAPICall_t SteamCallResults::reserveCallResult()
{
struct Steam_Call_Result res = Steam_Call_Result(generate_steam_api_call_id(), 0, NULL, 0, 0.0, true);
res.reserved = true;
callresults.push_back(res);
return callresults.back().api_call;
}
SteamAPICall_t SteamCallResults::addCallResult(int iCallback, void *result, unsigned int size, double timeout, bool run_call_completed_cb)
{
return addCallResult(generate_steam_api_call_id(), iCallback, result, size, timeout, run_call_completed_cb);
}
void SteamCallResults::setCbAll(void (*cb_all)(std::vector<char> result, int callback))
{
this->cb_all = cb_all;
}
void SteamCallResults::runCallResults()
{
unsigned long current_size = static_cast<unsigned long>(callresults.size());
for (unsigned i = 0; i < current_size; ++i) {
unsigned index = i;
if (!callresults[index].to_delete) {
if (callresults[index].can_execute()) {
std::vector<char> result = callresults[index].result;
SteamAPICall_t api_call = callresults[index].api_call;
bool run_call_completed_cb = callresults[index].run_call_completed_cb;
int iCallback = callresults[index].iCallback;
if (run_call_completed_cb) {
callresults[index].run_call_completed_cb = false;
}
callresults[index].to_delete = true;
if (callresults[index].has_cb()) {
std::vector<class CCallbackBase *> temp_cbs = callresults[index].callbacks;
for (auto & cb : temp_cbs) {
PRINT_DEBUG("Calling callresult %p %i, kind=%i (0=callback, 1=call result)", cb, cb->GetICallback(), (int)run_call_completed_cb);
global_mutex.unlock();
//TODO: unlock relock doesn't work if mutex was locked more than once.
if (run_call_completed_cb) { //run the right function depending on if it's a callback or a call result.
cb->Run(&(result[0]), false, api_call);
} else { // if this is a callback
cb->Run(&(result[0]));
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//COULD BE DELETED SO DON'T TOUCH CB
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
global_mutex.lock();
PRINT_DEBUG("callresult done");
}
}
if (run_call_completed_cb) {
//can it happen that one is removed during the callback?
std::vector<class CCallbackBase *> callbacks = completed_callbacks;
SteamAPICallCompleted_t data{};
data.m_hAsyncCall = api_call;
data.m_iCallback = iCallback;
data.m_cubParam = (uint32)result.size();
for (auto & cb: callbacks) {
PRINT_DEBUG("Calling complete cb %p %i %llu", cb, iCallback, api_call);
//TODO: check if this is a problem or not.
SteamAPICallCompleted_t temp = data;
global_mutex.unlock();
cb->Run(&temp);
global_mutex.lock();
}
if (cb_all) {
std::vector<char> res{};
res.resize(sizeof(data));
memcpy(&(res[0]), &data, sizeof(data));
cb_all(res, data.k_iCallback);
}
} else {
if (cb_all) {
cb_all(result, iCallback);
}
}
} else {
if (callresults[index].timed_out()) {
callresults[index].to_delete = true;
}
}
}
}
PRINT_DEBUG("erase to_delete");
auto c = std::begin(callresults);
while (c != std::end(callresults)) {
if (c->to_delete) {
if (c->timed_out()) {
PRINT_DEBUG("removed callresult %i", c->iCallback);
c = callresults.erase(c);
} else {
++c;
}
} else {
++c;
}
}
}
SteamCallBacks::SteamCallBacks(SteamCallResults *results)
{
this->results = results;
}
void SteamCallBacks::addCallBack(int iCallback, class CCallbackBase *cb)
{
PRINT_DEBUG("%i", iCallback);
if (iCallback == SteamAPICallCompleted_t::k_iCallback) { // if this is a call result "call completed cb"
results->addCallCompleted(cb);
CCallbackMgr::SetRegister(cb, iCallback);
return;
}
if (std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb) == callbacks[iCallback].callbacks.end()) {
callbacks[iCallback].callbacks.push_back(cb);
PRINT_DEBUG("new cb for callback [result k_iCallback=%i] %p", iCallback, cb);
CCallbackMgr::SetRegister(cb, iCallback);
for (auto & res: callbacks[iCallback].results) {
//TODO: timeout?
SteamAPICall_t api_id = results->addCallResult(iCallback, &(res[0]), static_cast<unsigned long>(res.size()), 0.0, false);
results->addCallBack(api_id, cb);
}
}
}
void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already)
{
if (dont_post_if_already) {
for (auto & r : callbacks[iCallback].results) {
if (r.size() == size) {
if (memcmp(&(r[0]), result, size) == 0) {
//cb already posted
return;
}
}
}
}
std::vector<char> temp{};
temp.resize(size);
memcpy(&(temp[0]), result, size);
callbacks[iCallback].results.push_back(temp);
for (auto cb: callbacks[iCallback].callbacks) {
SteamAPICall_t api_id = results->addCallResult(iCallback, result, size, timeout, false);
results->addCallBack(api_id, cb);
}
if (callbacks[iCallback].callbacks.empty()) {
results->addCallResult(iCallback, result, size, timeout, false);
}
}
void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size)
{
addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, false);
}
void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already)
{
addCBResult(iCallback, result, size, DEFAULT_CB_TIMEOUT, dont_post_if_already);
}
void SteamCallBacks::addCBResult(int iCallback, void *result, unsigned int size, double timeout)
{
addCBResult(iCallback, result, size, timeout, false);
}
void SteamCallBacks::rmCallBack(int iCallback, class CCallbackBase *cb)
{
if (iCallback == SteamAPICallCompleted_t::k_iCallback) {
results->rmCallCompleted(cb);
CCallbackMgr::SetUnregister(cb);
return;
}
auto c = std::find(callbacks[iCallback].callbacks.begin(), callbacks[iCallback].callbacks.end(), cb);
if (c != callbacks[iCallback].callbacks.end()) {
callbacks[iCallback].callbacks.erase(c);
CCallbackMgr::SetUnregister(cb);
PRINT_DEBUG("removed cb for callback [result k_iCallback=%i] %p", iCallback, cb);
results->rmCallBack(cb);
}
}
void SteamCallBacks::runCallBacks()
{
for (auto & c : callbacks) {
c.second.results.clear();
}
}
void RunEveryRunCB::add(void (*cb)(void *object), void *object)
{
remove(cb, object);
RunCBs rcb{};
rcb.function = cb;
rcb.object = object;
cbs.push_back(rcb);
}
void RunEveryRunCB::remove(void (*cb)(void *object), void *object)
{
auto c = std::begin(cbs);
while (c != std::end(cbs)) {
if (c->function == cb && c->object == object) {
c = cbs.erase(c);
} else {
++c;
}
}
}
void RunEveryRunCB::run() const
{
std::vector<struct RunCBs> temp_cbs = cbs;
for (auto c : temp_cbs) {
c.function(c.object);
}
}

View File

@ -0,0 +1,228 @@
#include "dll/client_known_interfaces.h"
/*
the client function Steam_IsKnownInterface() accesses a structure which has this layout:
typedef struct struct_known_interfaces {
void *unknown_function_ptr;
const char *name; // ex: "STEAMAPPLIST_INTERFACE_VERSION001"
const char *family; // ex: "AppList"
struct_known_interfaces *previous_node;
};
this is a dump of the `name` field when running this function from a debugger
*/
extern const std::unordered_set<std::string> client_known_interfaces = {
"SteamAppDisableUpdate001",
"STEAMAPPLIST_INTERFACE_VERSION001",
"SteamApps001",
"STEAMAPPS_INTERFACE_VERSION001",
"STEAMAPPS_INTERFACE_VERSION002",
"STEAMAPPS_INTERFACE_VERSION003",
"STEAMAPPS_INTERFACE_VERSION004",
"STEAMAPPS_INTERFACE_VERSION005",
"STEAMAPPS_INTERFACE_VERSION006",
"STEAMAPPS_INTERFACE_VERSION007",
"STEAMAPPS_INTERFACE_VERSION008",
"STEAMAPPTICKET_INTERFACE_VERSION001",
"SteamBilling002",
"STEAMCHAT_INTERFACE_VERSION003",
"SteamController003",
"SteamController004",
"SteamController005",
"SteamController006",
"SteamController007",
"SteamController008",
"STEAMCONTROLLER_INTERFACE_VERSION",
"SteamFriends001",
"SteamFriends002",
"SteamFriends003",
"SteamFriends004",
"SteamFriends005",
"SteamFriends006",
"SteamFriends007",
"SteamFriends008",
"SteamFriends009",
"SteamFriends010",
"SteamFriends011",
"SteamFriends012",
"SteamFriends013",
"SteamFriends014",
"SteamFriends015",
"SteamFriends016",
"SteamFriends017",
"SteamGameCoordinator001",
"SteamGameServer002",
"SteamGameServer003",
"SteamGameServer004",
"SteamGameServer005",
"SteamGameServer006",
"SteamGameServer007",
"SteamGameServer008",
"SteamGameServer009",
"SteamGameServer010",
"SteamGameServer011",
"SteamGameServer012",
"SteamGameServer013",
"SteamGameServer014",
"SteamGameServer015",
"SteamGameServerStats001",
"SteamGameStats001",
"STEAMHTMLSURFACE_INTERFACE_VERSION_001",
"STEAMHTMLSURFACE_INTERFACE_VERSION_002",
"STEAMHTMLSURFACE_INTERFACE_VERSION_003",
"STEAMHTMLSURFACE_INTERFACE_VERSION_004",
"STEAMHTMLSURFACE_INTERFACE_VERSION_005",
"STEAMHTTP_INTERFACE_VERSION001",
"STEAMHTTP_INTERFACE_VERSION002",
"STEAMHTTP_INTERFACE_VERSION003",
"SteamInput001",
"SteamInput002",
"SteamInput003",
"SteamInput004",
"SteamInput005",
"SteamInput006",
"STEAMINVENTORY_INTERFACE_V001",
"STEAMINVENTORY_INTERFACE_V002",
"STEAMINVENTORY_INTERFACE_V003",
"SteamMasterServerUpdater001",
"SteamMatchGameSearch001",
"SteamMatchMaking001",
"SteamMatchMaking002",
"SteamMatchMaking003",
"SteamMatchMaking004",
"SteamMatchMaking005",
"SteamMatchMaking006",
"SteamMatchMaking007",
"SteamMatchMaking008",
"SteamMatchMaking009",
"SteamMatchMakingServers001",
"SteamMatchMakingServers002",
"STEAMMUSIC_INTERFACE_VERSION001",
"STEAMMUSICREMOTE_INTERFACE_VERSION001",
"SteamNetworking001",
"SteamNetworking002",
"SteamNetworking003",
"SteamNetworking004",
"SteamNetworking005",
"SteamNetworking006",
"SteamNetworkingMessages002",
"SteamNetworkingSockets002",
"SteamNetworkingSockets003",
"SteamNetworkingSockets004",
"SteamNetworkingSockets005",
"SteamNetworkingSockets006",
"SteamNetworkingSockets008",
"SteamNetworkingSockets009",
"SteamNetworkingSockets010",
"SteamNetworkingSockets011",
"SteamNetworkingSockets012",
"SteamNetworkingSocketsSerialized001",
"SteamNetworkingSocketsSerialized002",
"SteamNetworkingSocketsSerialized003",
"SteamNetworkingSocketsSerialized004",
"SteamNetworkingSocketsSerialized005",
"SteamNetworkingUtils001",
"SteamNetworkingUtils002",
"SteamNetworkingUtils003",
"SteamNetworkingUtils004",
"STEAMPARENTALSETTINGS_INTERFACE_VERSION001",
"SteamParties001",
"SteamParties002",
"STEAMREMOTEPLAY_INTERFACE_VERSION001",
"STEAMREMOTEPLAY_INTERFACE_VERSION002",
"STEAMREMOTESTORAGE_INTERFACE_VERSION001",
"STEAMREMOTESTORAGE_INTERFACE_VERSION002",
"STEAMREMOTESTORAGE_INTERFACE_VERSION003",
"STEAMREMOTESTORAGE_INTERFACE_VERSION004",
"STEAMREMOTESTORAGE_INTERFACE_VERSION005",
"STEAMREMOTESTORAGE_INTERFACE_VERSION006",
"STEAMREMOTESTORAGE_INTERFACE_VERSION007",
"STEAMREMOTESTORAGE_INTERFACE_VERSION008",
"STEAMREMOTESTORAGE_INTERFACE_VERSION009",
"STEAMREMOTESTORAGE_INTERFACE_VERSION010",
"STEAMREMOTESTORAGE_INTERFACE_VERSION011",
"STEAMREMOTESTORAGE_INTERFACE_VERSION012",
"STEAMREMOTESTORAGE_INTERFACE_VERSION013",
"STEAMREMOTESTORAGE_INTERFACE_VERSION014",
"STEAMREMOTESTORAGE_INTERFACE_VERSION015",
"STEAMREMOTESTORAGE_INTERFACE_VERSION016",
"STEAMSCREENSHOTS_INTERFACE_VERSION001",
"STEAMSCREENSHOTS_INTERFACE_VERSION002",
"STEAMSCREENSHOTS_INTERFACE_VERSION003",
"SteamStreamLauncher001",
"STEAMTIMELINE_INTERFACE_V001",
"STEAMTV_INTERFACE_V001",
"STEAMTV_INTERFACE_V002",
"STEAMUGC_INTERFACE_VERSION001",
"STEAMUGC_INTERFACE_VERSION002",
"STEAMUGC_INTERFACE_VERSION003",
"STEAMUGC_INTERFACE_VERSION004",
"STEAMUGC_INTERFACE_VERSION005",
"STEAMUGC_INTERFACE_VERSION006",
"STEAMUGC_INTERFACE_VERSION007",
"STEAMUGC_INTERFACE_VERSION008",
"STEAMUGC_INTERFACE_VERSION009",
"STEAMUGC_INTERFACE_VERSION010",
"STEAMUGC_INTERFACE_VERSION011",
"STEAMUGC_INTERFACE_VERSION012",
"STEAMUGC_INTERFACE_VERSION013",
"STEAMUGC_INTERFACE_VERSION014",
"STEAMUGC_INTERFACE_VERSION015",
"STEAMUGC_INTERFACE_VERSION016",
"STEAMUGC_INTERFACE_VERSION017",
"STEAMUGC_INTERFACE_VERSION018",
"STEAMUGC_INTERFACE_VERSION019",
"STEAMUGC_INTERFACE_VERSION020",
"STEAMUNIFIEDMESSAGES_INTERFACE_VERSION001",
"SteamUser004",
"SteamUser005",
"SteamUser006",
"SteamUser007",
"SteamUser008",
"SteamUser009",
"SteamUser010",
"SteamUser011",
"SteamUser012",
"SteamUser013",
"SteamUser014",
"SteamUser015",
"SteamUser016",
"SteamUser017",
"SteamUser018",
"SteamUser019",
"SteamUser020",
"SteamUser021",
"SteamUser022",
"SteamUser023",
"STEAMUSERSTATS_INTERFACE_VERSION001",
"STEAMUSERSTATS_INTERFACE_VERSION002",
"STEAMUSERSTATS_INTERFACE_VERSION003",
"STEAMUSERSTATS_INTERFACE_VERSION004",
"STEAMUSERSTATS_INTERFACE_VERSION005",
"STEAMUSERSTATS_INTERFACE_VERSION006",
"STEAMUSERSTATS_INTERFACE_VERSION007",
"STEAMUSERSTATS_INTERFACE_VERSION008",
"STEAMUSERSTATS_INTERFACE_VERSION009",
"STEAMUSERSTATS_INTERFACE_VERSION010",
"STEAMUSERSTATS_INTERFACE_VERSION011",
"STEAMUSERSTATS_INTERFACE_VERSION012",
"SteamUtils001",
"SteamUtils002",
"SteamUtils003",
"SteamUtils004",
"SteamUtils005",
"SteamUtils006",
"SteamUtils007",
"SteamUtils008",
"SteamUtils009",
"SteamUtils010",
"STEAMVIDEO_INTERFACE_V001",
"STEAMVIDEO_INTERFACE_V002",
"STEAMVIDEO_INTERFACE_V003",
"STEAMVIDEO_INTERFACE_V004",
"STEAMVIDEO_INTERFACE_V005",
"STEAMVIDEO_INTERFACE_V006",
"STEAMVIDEO_INTERFACE_V007",
};

1566
dll/dll.cpp Normal file

File diff suppressed because it is too large Load Diff

128
dll/dll/appticket.h Normal file
View File

@ -0,0 +1,128 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_APP_TICKET_H__
#define __INCLUDED_STEAM_APP_TICKET_H__
#include "base.h"
#include "steam/isteamappticket.h"
struct AppTicketV1
{
// Total ticket size - 16
uint32_t TicketSize{};
uint32_t TicketVersion{};
uint32_t Unk2{};
std::vector<uint8_t> UserData{};
void Reset();
std::vector<uint8_t> Serialize() const;
bool Deserialize(const uint8_t* pBuffer, size_t size);
//inline uint32_t TicketSize() { return *reinterpret_cast<uint32_t*>(_Buffer); }
//inline uint32_t TicketVersion(){ return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 4); }
//inline uint32_t UserDataSize() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 8); }
//inline uint32_t Unk2() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 12); }
//inline uint8_t* UserData() { return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 16); }
};
struct AppTicketV2
{
// Totals ticket size - 16 - TicketV1::UserData.size()
uint32_t TicketSize{};
uint32_t TicketVersion{};
uint64_t SteamID{};
uint32_t AppID{};
uint32_t Unk1{};
uint32_t Unk2{};
uint32_t TicketFlags{};
uint32_t TicketIssueTime{};
uint32_t TicketValidityEnd{};
static constexpr const uint32_t LicenseBorrowed = 0x00000002; // Bit 1: IsLicenseBorrowed
static constexpr const uint32_t LicenseTemporary = 0x00000004; // Bit 2: IsLicenseTemporary
void Reset();
std::vector<uint8_t> Serialize() const;
bool Deserialize(const uint8_t* pBuffer, size_t size);
//inline uint32_t TicketSize() { return *reinterpret_cast<uint32_t*>(_Buffer); }
//inline uint32_t TicketVersion() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 4); }
//inline uint64_t SteamID() { return *reinterpret_cast<uint64_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 8); };
//inline uint32_t AppID() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 16); }
//inline uint32_t Unk1() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 20); }
//inline uint32_t Unk2() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 24); }
//inline uint32_t TicketFlags() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 28); }
//inline uint32_t TicketIssueTime() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 32); }
//inline uint32_t TicketValidityEnd() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 36); }
};
struct AppTicketV4
{
std::vector<uint32_t> AppIDs{};
bool HasVACStatus = false;
uint32_t VACStatus{};
bool HasAppValue = false;
uint32_t AppValue{};
void Reset();
std::vector<uint8_t> Serialize();
bool Deserialize(const uint8_t* pBuffer, size_t size);
// Often 1 with empty appid
//inline uint16_t AppIDCount() { return *reinterpret_cast<uint16_t*>(_Buffer); }
//inline uint32_t* AppIDs() { return reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 2); }
// Optional
//inline uint32_t VACStatus() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 2 + AppIDCount() * 4); }
// Optional
//inline uint32_t AppValue() { return *reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(_Buffer) + 2 + AppIDCount() * 4 + 4); }
};
class DecryptedAppTicket
{
public:
AppTicketV1 TicketV1{};
AppTicketV2 TicketV2{};
AppTicketV4 TicketV4{};
bool DeserializeTicket(const uint8_t* pBuffer, size_t buf_size);
std::vector<uint8_t> SerializeTicket();
};
class Steam_AppTicket :
public ISteamAppTicket
{
private:
class Settings *settings{};
public:
Steam_AppTicket(class Settings *settings);
virtual uint32 GetAppOwnershipTicketData( uint32 nAppID, void *pvBuffer, uint32 cbBufferLength, uint32 *piAppId, uint32 *piSteamId, uint32 *piSignature, uint32 *pcbSignature );
};
#endif // __INCLUDED_STEAM_APP_TICKET_H__

104
dll/dll/auth.h Normal file
View File

@ -0,0 +1,104 @@
// source: https://github.com/Detanup01/stmsrv/blob/main/Steam3Server/Others/AppTickets.cs
// thanks Detanup01
#ifndef AUTH_INCLUDE_H
#define AUTH_INCLUDE_H
#include "base.h"
#include "include.wrap.mbedtls.h"
// the data type is important, we depend on sizeof() for each one of them
constexpr const static uint32_t STEAM_APPTICKET_SIGLEN = 128;
constexpr const static uint32_t STEAM_APPTICKET_GCLen = 20;
constexpr const static uint32_t STEAM_APPTICKET_SESSIONLEN = 24;
struct DLC {
uint32_t AppId{};
std::vector<uint32_t> Licenses{};
std::vector<uint8_t> Serialize() const;
};
struct AppTicketGC {
uint64_t GCToken{};
CSteamID id{};
uint32_t ticketGenDate{}; //epoch
uint32_t ExternalIP{};
uint32_t InternalIP{};
uint32_t TimeSinceStartup{};
uint32_t TicketGeneratedCount{};
private:
uint32_t one = 1;
uint32_t two = 2;
public:
std::vector<uint8_t> Serialize() const;
};
struct AppTicket {
uint32_t Version{};
CSteamID id{};
uint32_t AppId{};
uint32_t ExternalIP{};
uint32_t InternalIP{};
uint32_t AlwaysZero = 0; //OwnershipFlags?
uint32_t TicketGeneratedDate{};
uint32_t TicketGeneratedExpireDate{};
std::vector<uint32_t> Licenses{};
std::vector<DLC> DLCs{};
std::vector<uint8_t> Serialize() const;
};
struct Auth_Data {
bool HasGC{};
AppTicketGC GC{};
AppTicket Ticket{};
//old data
CSteamID id{};
uint64_t number{};
std::chrono::high_resolution_clock::time_point created{};
std::vector<uint8_t> Serialize() const;
};
class Auth_Manager {
class Settings *settings{};
class Networking *network{};
class SteamCallBacks *callbacks{};
std::vector<struct Auth_Data> inbound{};
std::vector<struct Auth_Data> outbound{};
void launch_callback(CSteamID id, EAuthSessionResponse resp, double delay=0);
void launch_callback_gs(CSteamID id, bool approved);
static void ticket_callback(void *object, Common_Message *msg);
void Callback(Common_Message *msg);
public:
Auth_Manager(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks);
~Auth_Manager();
HAuthTicket getTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
HAuthTicket getWebApiTicket( const char *pchIdentity );
void cancelTicket(uint32 number);
EBeginAuthSessionResult beginAuth(const void *pAuthTicket, int cbAuthTicket, CSteamID steamID);
bool endAuth(CSteamID id);
uint32 countInboundAuth();
bool SendUserConnectAndAuthenticate( uint32 unIPClient, const void *pvAuthBlob, uint32 cubAuthBlobSize, CSteamID *pSteamIDUser );
CSteamID fakeUser();
Auth_Data getTicketData( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
};
#endif // AUTH_INCLUDE_H

64
dll/dll/base.h Normal file
View File

@ -0,0 +1,64 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef BASE_INCLUDE_H
#define BASE_INCLUDE_H
#include "common_includes.h"
#include "callsystem.h"
#define PUSH_BACK_IF_NOT_IN(vector, element) { if(std::find(vector.begin(), vector.end(), element) == vector.end()) vector.push_back(element); }
extern std::recursive_mutex global_mutex;
extern const std::chrono::time_point<std::chrono::high_resolution_clock> startup_counter;
extern const std::chrono::time_point<std::chrono::system_clock> startup_time;
void randombytes(char *buf, size_t size);
std::string get_env_variable(const std::string &name);
bool set_env_variable(const std::string &name, const std::string &value);
/// @brief Check for a timeout given some initial timepoint and a timeout in sec.
/// @param old The initial timepoint which will be compared against current time
/// @param timeout The max allowed time in seconds
/// @return true if the timepoint has exceeded the max allowed timeout, false otherwise
bool check_timedout(std::chrono::high_resolution_clock::time_point old, double timeout);
unsigned generate_account_id();
CSteamID generate_steam_anon_user();
SteamAPICall_t generate_steam_api_call_id();
CSteamID generate_steam_id_user();
CSteamID generate_steam_id_server();
CSteamID generate_steam_id_anonserver();
CSteamID generate_steam_id_lobby();
std::string get_full_lib_path();
std::string get_full_program_path();
std::string get_current_path();
std::string canonical_path(const std::string &path);
bool file_exists_(const std::string &full_path);
unsigned int file_size_(const std::string &full_path);
void set_whitelist_ips(uint32_t *from, uint32_t *to, unsigned num_ips);
#ifdef EMU_EXPERIMENTAL_BUILD
bool crack_SteamAPI_RestartAppIfNecessary(uint32 unOwnAppID);
bool crack_SteamAPI_Init();
#endif
#endif // BASE_INCLUDE_H

132
dll/dll/callsystem.h Normal file
View File

@ -0,0 +1,132 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_CALLSYSTEM_H__
#define __INCLUDED_CALLSYSTEM_H__
#include "common_includes.h"
#define DEFAULT_CB_TIMEOUT 0.002
#define STEAM_CALLRESULT_TIMEOUT 120.0
#define STEAM_CALLRESULT_WAIT_FOR_CB 0.01
class CCallbackMgr
{
public:
static void SetRegister(class CCallbackBase *pCallback, int iCallback);
static void SetUnregister(class CCallbackBase *pCallback);
static bool isServer(class CCallbackBase *pCallback);
};
struct Steam_Call_Result {
SteamAPICall_t api_call{};
std::vector<class CCallbackBase *> callbacks{};
std::vector<char> result{};
bool to_delete = false;
bool reserved = false;
std::chrono::high_resolution_clock::time_point created{};
double run_in{};
bool run_call_completed_cb{};
int iCallback{};
Steam_Call_Result(SteamAPICall_t a, int icb, void *r, unsigned int s, double r_in, bool run_cc_cb);
bool operator==(const struct Steam_Call_Result& other) const;
bool timed_out() const;
bool call_completed() const;
bool can_execute() const;
bool has_cb() const;
};
class SteamCallResults {
std::vector<struct Steam_Call_Result> callresults{};
std::vector<class CCallbackBase *> completed_callbacks{};
void (*cb_all)(std::vector<char> result, int callback) = nullptr;
public:
void addCallCompleted(class CCallbackBase *cb);
void rmCallCompleted(class CCallbackBase *cb);
void addCallBack(SteamAPICall_t api_call, class CCallbackBase *cb);
bool exists(SteamAPICall_t api_call) const;
bool callback_result(SteamAPICall_t api_call, void *copy_to, unsigned int size);
void rmCallBack(SteamAPICall_t api_call, class CCallbackBase *cb);
void rmCallBack(class CCallbackBase *cb);
SteamAPICall_t addCallResult(SteamAPICall_t api_call, int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true);
SteamAPICall_t reserveCallResult();
SteamAPICall_t addCallResult(int iCallback, void *result, unsigned int size, double timeout=DEFAULT_CB_TIMEOUT, bool run_call_completed_cb=true);
void setCbAll(void (*cb_all)(std::vector<char> result, int callback));
void runCallResults();
};
struct Steam_Call_Back {
std::vector<class CCallbackBase *> callbacks{};
std::vector<std::vector<char>> results{};
};
class SteamCallBacks {
std::map<int, struct Steam_Call_Back> callbacks{};
SteamCallResults *results{};
public:
SteamCallBacks(SteamCallResults *results);
void addCallBack(int iCallback, class CCallbackBase *cb);
void addCBResult(int iCallback, void *result, unsigned int size, double timeout, bool dont_post_if_already);
void addCBResult(int iCallback, void *result, unsigned int size);
void addCBResult(int iCallback, void *result, unsigned int size, bool dont_post_if_already);
void addCBResult(int iCallback, void *result, unsigned int size, double timeout);
void rmCallBack(int iCallback, class CCallbackBase *cb);
void runCallBacks();
};
struct RunCBs {
void (*function)(void *object) = nullptr;
void *object{};
};
class RunEveryRunCB {
std::vector<struct RunCBs> cbs{};
public:
void add(void (*cb)(void *object), void *object);
void remove(void (*cb)(void *object), void *object);
void run() const;
};
#endif // __INCLUDED_CALLSYSTEM_H__

View File

@ -0,0 +1,9 @@
#ifndef _CLIENT_KNOWN_INTERFACES_H_
#define _CLIENT_KNOWN_INTERFACES_H_
#include <unordered_set>
#include <string>
extern const std::unordered_set<std::string> client_known_interfaces;
#endif // _CLIENT_KNOWN_INTERFACES_H_

219
dll/dll/common_includes.h Normal file
View File

@ -0,0 +1,219 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_COMMON_INCLUDES__
#define __INCLUDED_COMMON_INCLUDES__
// OS detection
#include "common_helpers/os_detector.h"
#if defined(__WINDOWS__)
#define STEAM_WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#endif
// we need this otherwise 'S_API_EXPORT' will be dllimport
#define STEAM_API_EXPORTS
// C/C++ includes
#include <cstdint>
#include <algorithm>
#include <string>
#include <string_view>
#include <chrono>
#include <cctype>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <iterator>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <list>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <string.h>
#include <stdio.h>
#include <filesystem>
#include <optional>
#include <numeric>
// common includes
#include "common_helpers/common_helpers.hpp"
#include "json/json.hpp"
#include "utfcpp/utf8.h"
// OS specific includes + definitions
#if defined(__WINDOWS__)
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <processthreadsapi.h>
#include <direct.h>
#include <iphlpapi.h> // Include winsock2 before this, or winsock2 iphlpapi will be unavailable
#include <shlobj.h>
// we need this for BCryptGenRandom() in base.cpp
#include <bcrypt.h>
// we need non-standard S_IFDIR for Windows
#include <sys/stat.h>
#define MSG_NOSIGNAL 0
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define PATH_SEPARATOR "\\"
#ifdef EMU_EXPERIMENTAL_BUILD
#include <winhttp.h>
#include <intsafe.h> // MinGW cannot find DWordMult()
#include "detours/detours.h"
#endif
#include "crash_printer/win.hpp"
// Convert a wide Unicode string to an UTF8 string
static inline std::string utf8_encode(const std::wstring &wstr)
{
return common_helpers::to_str(wstr);
}
// Convert UTF8 string to a wide Unicode String
static inline std::wstring utf8_decode(const std::string &str)
{
return common_helpers::to_wstr(str);
}
static inline void reset_LastError()
{
SetLastError(0);
}
#elif defined(__LINUX__)
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif // _GNU_SOURCE
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <linux/netdevice.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <netdb.h>
#include <dlfcn.h>
#include <utime.h>
#include "crash_printer/linux.hpp"
#define PATH_MAX_STRING_SIZE 512
#define PATH_SEPARATOR "/"
#define utf8_decode(a) a
#define utf8_encode(a) a
#define reset_LastError()
#endif
// Other libs includes
#include "gamepad/gamepad.h"
// Steamsdk includes
#include "steam/steam_api.h"
#include "steam/steam_gameserver.h"
#include "steam/steamdatagram_tickets.h"
#define AS_STR(x) #x
#define EXPAND_AS_STR(x) AS_STR(x)
#if defined(__LINUX__) || defined(GNUC) || defined(__MINGW32__) || defined(__MINGW64__) // MinGw
#define EMU_FUNC_NAME __PRETTY_FUNCTION__
#else
#define EMU_FUNC_NAME __FUNCTION__##"()"
#endif
// PRINT_DEBUG definition
#ifndef EMU_RELEASE_BUILD
#include "dbg_log/dbg_log.hpp"
// we need this for printf specifiers for intptr_t such as PRIdPTR
#include <inttypes.h>
#if defined(__WINDOWS__)
#define PRINT_DEBUG_TID() (long long)GetCurrentThreadId()
#define PRINT_DEBUG_CLEANUP() WSASetLastError(0)
#elif defined(__LINUX__)
#include <sys/syscall.h> // syscall
#define PRINT_DEBUG_TID() (long long)syscall(SYS_gettid)
#define PRINT_DEBUG_CLEANUP() (void)0
#else
#warning "Unrecognized OS"
#define PRINT_DEBUG_TID() (long long)0
#define PRINT_DEBUG_CLEANUP() (void)0
#endif
extern dbg_log dbg_logger;
#define PRINT_DEBUG(a, ...) do { \
dbg_logger.write("[tid %lld] %s " a, PRINT_DEBUG_TID(), EMU_FUNC_NAME, ##__VA_ARGS__); \
PRINT_DEBUG_CLEANUP(); \
} while (0)
#else // EMU_RELEASE_BUILD
#define PRINT_DEBUG(...)
#endif // EMU_RELEASE_BUILD
// function entry
#define PRINT_DEBUG_ENTRY() PRINT_DEBUG("")
#define PRINT_DEBUG_TODO() PRINT_DEBUG("// TODO")
#define PRINT_DEBUG_GNU_WIN() PRINT_DEBUG("GNU/Win")
// Emulator includes
// add them here after the inline functions definitions
#include "include.wrap.net.pb.h"
#include "settings.h"
#include "local_storage.h"
#include "network.h"
// Emulator defines
#define CLIENT_HSTEAMUSER 1
#define SERVER_HSTEAMUSER 1
#define DEFAULT_NAME "gse orca"
#define DEFAULT_LANGUAGE "english"
#define DEFAULT_IP_COUNTRY "US"
#define LOBBY_CONNECT_APPID ((uint32)-2)
#endif //__INCLUDED_COMMON_INCLUDES__

40
dll/dll/dll.h Normal file
View File

@ -0,0 +1,40 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_DLL_H__
#define __INCLUDED_DLL_H__
#include "steam_client.h"
#ifdef STEAMCLIENT_DLL
#define STEAMAPI_API static
#define STEAMCLIENT_API S_API_EXPORT
#else
#define STEAMAPI_API S_API_EXPORT
#define STEAMCLIENT_API static
#endif
Steam_Client *get_steam_client();
bool steamclient_has_ipv6_functions();
HSteamUser flat_hsteamuser();
HSteamPipe flat_hsteampipe();
HSteamUser flat_gs_hsteamuser();
HSteamPipe flat_gs_hsteampipe();
#endif // __INCLUDED_DLL_H__

View File

@ -0,0 +1,27 @@
#if defined(__linux__) || defined(linux) || defined(GNUC) || defined(__MINGW32__) || defined(__MINGW64__)
// https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wall
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#include "mbedtls/pk.h"
#include "mbedtls/x509.h"
#include "mbedtls/error.h"
#include "mbedtls/sha1.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#pragma GCC diagnostic pop
#else
// https://learn.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-170#push-and-pop
#pragma warning(push, 0)
#include "mbedtls/pk.h"
#include "mbedtls/x509.h"
#include "mbedtls/error.h"
#include "mbedtls/sha1.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#pragma warning(pop)
#endif

View File

@ -0,0 +1,14 @@
#if defined(__linux__) || defined(linux) || defined(GNUC) || defined(__MINGW32__) || defined(__MINGW64__)
// https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html
// https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wall
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#include "net.pb.h"
#pragma GCC diagnostic pop
#else
// https://learn.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-170#push-and-pop
#pragma warning(push, 0)
#include "net.pb.h"
#pragma warning(pop)
#endif

106
dll/dll/local_storage.h Normal file
View File

@ -0,0 +1,106 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef LOCAL_STORAGE_INCLUDE_H
#define LOCAL_STORAGE_INCLUDE_H
#include "base.h"
#define MAX_FILENAME_LENGTH 300
union image_pixel_t {
uint32_t pixel;
struct pixel_channels_t {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} channels;
};
struct image_t {
size_t width{};
size_t height{};
std::vector<image_pixel_t> pix_map{};
};
class Local_Storage {
private:
static std::string saves_folder_name;
public:
static constexpr char inventory_storage_folder[] = "inventory";
static constexpr char settings_storage_folder[] = "settings";
static constexpr char remote_storage_folder[] = "remote";
static constexpr char stats_storage_folder[] = "stats";
static constexpr char leaderboard_storage_folder[] = "leaderboard";
static constexpr char user_data_storage[] = "local";
static constexpr char screenshots_folder[] = "screenshots";
static constexpr char game_settings_folder[] = "steam_settings";
static std::string get_program_path();
static std::string get_game_settings_path();
static std::string get_user_appdata_path();
static int get_file_data(const std::string &full_path, char *data, unsigned int max_length, unsigned int offset=0);
static int store_file_data(std::string folder, std::string file, const char *data, unsigned int length);
static std::vector<std::string> get_filenames_path(std::string path);
static std::vector<std::string> get_folders_path(std::string path);
static void set_saves_folder_name(std::string_view str);
static const std::string& get_saves_folder_name();
private:
std::string save_directory{};
std::string appid{}; // game appid
public:
Local_Storage(const std::string &save_directory);
const std::string& get_current_save_directory() const;
void setAppId(uint32 appid);
int store_data(std::string folder, std::string file, char *data, unsigned int length);
int store_data_settings(std::string file, const char *data, unsigned int length);
int get_data(std::string folder, std::string file, char *data, unsigned int max_length, unsigned int offset=0);
unsigned int data_settings_size(std::string file);
int get_data_settings(std::string file, char *data, unsigned int max_length);
int count_files(std::string folder);
bool iterate_file(std::string folder, int index, char *output_filename, int32 *output_size);
bool file_exists(std::string folder, std::string file);
unsigned int file_size(std::string folder, std::string file);
bool file_delete(std::string folder, std::string file);
uint64_t file_timestamp(std::string folder, std::string file);
std::string get_global_settings_path();
std::string get_path(std::string folder);
bool update_save_filenames(std::string folder);
bool load_json(const std::string &full_path, nlohmann::json& json);
bool load_json_file(std::string folder, std::string const& file, nlohmann::json& json);
bool write_json_file(std::string folder, std::string const& file, nlohmann::json const& json);
std::vector<image_pixel_t> load_image(std::string const& image_path);
static std::string load_image_resized(std::string const& image_path, std::string const& image_data, int resolution);
bool save_screenshot(std::string const& image_path, uint8_t* img_ptr, int32_t width, int32_t height, int32_t channels);
static std::string sanitize_string(std::string name);
static std::string desanitize_string(std::string name);
};
#endif // LOCAL_STORAGE_INCLUDE_H

172
dll/dll/network.h Normal file
View File

@ -0,0 +1,172 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef NETWORK_INCLUDE
#define NETWORK_INCLUDE
#include "base.h"
#include <curl/curl.h>
#define DEFAULT_PORT 47584
#if defined(STEAM_WIN32)
typedef unsigned int sock_t;
#else
typedef int sock_t;
#endif
static inline bool protobuf_message_equal(
const google::protobuf::MessageLite& msg_a,
const google::protobuf::MessageLite& msg_b)
{
return (msg_a.GetTypeName() == msg_b.GetTypeName()) &&
(msg_a.SerializeAsString() == msg_b.SerializeAsString());
}
struct IP_PORT {
uint32 ip{};
uint16 port{};
bool operator <(const IP_PORT& other) const
{
return (ip < other.ip) || (ip == other.ip && port < other.port);
}
};
struct Network_Callback {
void (*message_callback)(void *object, Common_Message *msg) = nullptr;
void *object{};
CSteamID steam_id{};
};
enum Callback_Ids {
CALLBACK_ID_USER_STATUS,
CALLBACK_ID_LOBBY,
CALLBACK_ID_NETWORKING,
CALLBACK_ID_GAMESERVER,
CALLBACK_ID_FRIEND,
CALLBACK_ID_AUTH_TICKET,
CALLBACK_ID_FRIEND_MESSAGES,
CALLBACK_ID_NETWORKING_SOCKETS,
CALLBACK_ID_STEAM_MESSAGES,
CALLBACK_ID_NETWORKING_MESSAGES,
CALLBACK_ID_GAMESERVER_STATS,
CALLBACK_ID_LEADERBOARDS_STATS,
CALLBACK_IDS_MAX
};
struct Network_Callback_Container {
std::vector<struct Network_Callback> callbacks{};
};
struct TCP_Socket {
sock_t sock = static_cast<sock_t>(~0);
bool received_data = false;
std::vector<char> recv_buffer{};
std::vector<char> send_buffer{};
std::chrono::high_resolution_clock::time_point last_heartbeat_sent{}, last_heartbeat_received{};
};
struct Connection {
struct TCP_Socket tcp_socket_outgoing{}, tcp_socket_incoming{};
bool connected = false;
IP_PORT udp_ip_port{};
bool udp_pinged = false;
IP_PORT tcp_ip_port{};
std::vector<CSteamID> ids{};
uint32 appid{};
std::chrono::high_resolution_clock::time_point last_received{};
};
class Networking
{
bool enabled = false;
bool query_alive{};
std::chrono::high_resolution_clock::time_point last_run{};
sock_t query_socket, udp_socket{}, tcp_socket{};
uint16 udp_port{}, tcp_port{};
uint32 own_ip{};
std::vector<struct Connection> connections{};
std::vector<CSteamID> ids;
uint32 appid;
std::chrono::high_resolution_clock::time_point last_broadcast;
std::vector<IP_PORT> custom_broadcasts;
std::vector<struct TCP_Socket> accepted;
std::recursive_mutex mutex;
struct Network_Callback_Container callbacks[CALLBACK_IDS_MAX];
std::vector<Common_Message> local_send;
struct Connection *find_connection(CSteamID id, uint32 appid = 0);
struct Connection *new_connection(CSteamID id, uint32 appid);
bool handle_announce(Common_Message *msg, IP_PORT ip_port);
bool handle_low_level_udp(Common_Message *msg, IP_PORT ip_port);
bool handle_tcp(Common_Message *msg, struct TCP_Socket &socket);
void send_announce_broadcasts();
bool add_id_connection(struct Connection *connection, CSteamID steam_id);
void run_callbacks(Callback_Ids id, Common_Message *msg);
void run_callback_user(CSteamID steam_id, bool online, uint32 appid);
void do_callbacks_message(Common_Message *msg);
Common_Message create_announce(bool request);
public:
Networking(CSteamID id, uint32 appid, uint16 port, std::set<IP_PORT> *custom_broadcasts, bool disable_sockets);
~Networking();
//NOTE: for all functions ips/ports are passed/returned in host byte order
//ex: 127.0.0.1 should be passed as 0x7F000001
static std::set<IP_PORT> resolve_ip(std::string dns);
void addListenId(CSteamID id);
void setAppID(uint32 appid);
void Run();
// send to a specific user, set_dest_id() must be called
bool sendTo(Common_Message *msg, bool reliable, Connection *conn = NULL);
// 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);
// send to all users whose account type is GameServer, no need to call set_dest_id(), this is done automatically
bool sendToAllGameservers(Common_Message *msg, bool reliable);
// send to all active/current connections, no need to call set_dest_id(), this is done automatically
bool sendToAll(Common_Message *msg, bool reliable);
// send to active/current connections with specific ip/port, no need to call set_dest_id(), this is done automatically
//TODO: actually send to ip/port
bool sendToIPPort(Common_Message *msg, uint32 ip, uint16 port, bool reliable);
bool setCallback(Callback_Ids id, CSteamID steam_id, void (*message_callback)(void *object, Common_Message *msg), void *object);
void rmCallback(Callback_Ids id, CSteamID steam_id, void (*message_callback)(void *object, Common_Message *msg), void *object);
uint32 getIP(CSteamID id);
uint32 getOwnIP();
void startQuery(IP_PORT ip_port);
void shutDownQuery();
bool isQueryAlive();
};
#endif // NETWORK_INCLUDE_H

412
dll/dll/settings.h Normal file
View File

@ -0,0 +1,412 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef SETTINGS_INCLUDE_H
#define SETTINGS_INCLUDE_H
#include "base.h"
struct IP_PORT;
struct DLC_entry {
AppId_t appID{};
std::string name{};
bool available{};
};
struct Mod_entry {
PublishedFileId_t id{};
std::string title{};
std::string path{};
std::string previewURL{};
EWorkshopFileType fileType{};
std::string description{};
uint64 steamIDOwner{};
uint32 timeCreated{};
uint32 timeUpdated{};
uint32 timeAddedToUserList{};
ERemoteStoragePublishedFileVisibility visibility{};
bool banned = false;
bool acceptedForUse{};
bool tagsTruncated{};
std::string tags{};
// file/url information
UGCHandle_t handleFile = generate_file_handle();
UGCHandle_t handlePreviewFile = generate_file_handle();
std::string primaryFileName{};
int32 primaryFileSize{};
std::string previewFileName{};
int32 previewFileSize{};
uint64 total_files_sizes{}; // added in sdk 1.60, "Total size of all files (non-legacy), excluding the preview file"
std::string min_game_branch{}; // added in sdk 1.60
std::string max_game_branch{}; // added in sdk 1.60
std::string workshopItemURL{};
// voting information
uint32 votesUp{};
uint32 votesDown{};
float score{};
// collection details
uint32 numChildren{}; // TODO
private:
UGCHandle_t generate_file_handle() {
static UGCHandle_t val = 0;
++val;
if (val == 0 || val == k_UGCHandleInvalid) val = 1;
return val;
}
};
struct Leaderboard_config {
enum ELeaderboardSortMethod sort_method{};
enum ELeaderboardDisplayType display_type{};
};
struct Stat_config {
GameServerStats_Messages::StatInfo::Stat_Type type{};
union {
float default_value_float;
int32 default_value_int;
};
};
struct Image_Data {
uint32 width{};
uint32 height{};
std::string data{};
};
struct Controller_Settings {
std::map<std::string, std::map<std::string, std::pair<std::set<std::string>, std::string>>> action_sets{};
std::map<std::string, std::string> action_set_layer_parents{};
std::map<std::string, std::map<std::string, std::pair<std::set<std::string>, std::string>>> action_set_layers{};
};
struct Group_Clans {
CSteamID id{};
std::string name{};
std::string tag{};
};
struct Overlay_Appearance {
enum NotificationPosition {
top_left, top_center, top_right,
bot_left, bot_center, bot_right,
};
constexpr const static NotificationPosition default_pos = NotificationPosition::top_right;
std::string font_override{}; // path to a custom user-provided TTF font
float font_size = 16.0f;
float icon_size = 64.0f;
float font_glyph_extra_spacing_x = 1.0f;
float font_glyph_extra_spacing_y = 0.0f;
float notification_r = 0.12f;
float notification_g = 0.14f;
float notification_b = 0.21f;
float notification_a = 1.0f;
float notification_rounding = 10.0f; // corners roundness for all notifications
float notification_margin_x = 5.0f; // horizontal margin
float notification_margin_y = 5.0f; // vertical margin
uint32 notification_animation = 350; // sliding animation duration (millisec)
uint32 notification_duration_progress = 6000; // achievement progress indication duration (millisec)
uint32 notification_duration_achievement = 7000; // achievement unlocked duration (millisec)
uint32 notification_duration_invitation = 8000; // friend invitation duration (millisec)
uint32 notification_duration_chat = 4000; // sliding animation duration duration (millisec)
std::string ach_unlock_datetime_format = "%Y/%m/%d - %H:%M:%S";
float background_r = 0.12f;
float background_g = 0.11f;
float background_b = 0.11f;
float background_a = 0.55f;
float element_r = 0.30f;
float element_g = 0.32f;
float element_b = 0.40f;
float element_a = 1.0f;
float element_hovered_r = 0.278f;
float element_hovered_g = 0.393f;
float element_hovered_b = 0.602f;
float element_hovered_a = 1.0f;
float element_active_r = -1.0f;
float element_active_g = -1.0f;
float element_active_b = -1.0f;
float element_active_a = -1.0f;
NotificationPosition ach_earned_pos = NotificationPosition::bot_right; // achievement earned
NotificationPosition invite_pos = default_pos; // lobby/game invitation
NotificationPosition chat_msg_pos = NotificationPosition::top_center; // chat message from a friend
static NotificationPosition translate_notification_position(const std::string &str);
};
struct Branch_Info {
std::string name{};
std::string description{};
bool branch_protected = false;
EBetaBranchFlags flags = EBetaBranchFlags::k_EBetaBranch_None;
uint32 build_id = 10; // not sure if 0 as an initial value is a good idea
uint32 time_updated_epoch = (uint32)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// may be changed by the game, I assume only 1 branch should be active
// added in sdk 1.60 and currently unused
bool active = false;
};
class Settings {
private:
CSteamID steam_id{}; // user id
CGameID game_id{};
std::string name{};
std::string language{}; // default "english"
CSteamID lobby_id = k_steamIDNil;
bool offline = false;
uint16 port{}; // Listen port, default 47584
bool unlockAllDLCs = true;
std::vector<struct DLC_entry> DLCs{};
//installed app ids, Steam_Apps::BIsAppInstalled()
bool assume_any_app_installed = true;
std::set<AppId_t> installed_app_ids{};
std::map<AppId_t, std::string> app_paths{};
std::vector<struct Mod_entry> mods{};
std::map<std::string, Leaderboard_config> leaderboards{};
std::map<std::string, Stat_config> stats{};
std::map<size_t, struct Image_Data> images{};
//supported languages
std::set<std::string> supported_languages_set{};
std::string supported_languages{};
public:
constexpr const static int INVALID_IMAGE_HANDLE = 0;
constexpr const static int UNLOADED_IMAGE_HANDLE = -1;
//Depots
std::vector<DepotId_t> depots{};
//custom broadcasts
std::set<IP_PORT> custom_broadcasts{};
//networking
bool disable_networking = false;
//gameserver source query
bool disable_source_query = false;
//matchmaking server list & server details
bool matchmaking_server_list_always_lan_type = true;
bool matchmaking_server_details_via_source_query = false;
//make lobby creation fail in the matchmaking interface
bool disable_lobby_creation = false;
//steamhttp external download support
bool download_steamhttp_requests = false;
bool force_steamhttp_success = false;
//steam deck flag
bool steam_deck = false;
// use new app_ticket auth instead of old one
bool enable_new_app_ticket = false;
// can use GC token for generation
bool use_gc_token = false;
// 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
// 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?
// the stat itself is always saved regardless of that flag, only affects the achievement progress
bool save_only_higher_stat_achievement_progress = true;
// the emulator loads the achievements icons is memory mainly for `ISteamUserStats::GetAchievementIcon()`
// this defines how many icons to load each iteration when the periodic callback in `Steam_User_Stats` is triggered
// or when the app calls `SteamAPI_RunCallbacks()`
// -1 == functionality disabled
// 0 == load icons only when they're requested
// >0 == load icons in the background as mentioned above
int paginated_achievements_icons = 10;
// bypass to make SetAchievement() always return true, prevent some games from breaking
bool achievement_bypass = false;
bool disable_account_avatar = true;
// setting this env var conflicts with Steam Input
bool disable_steamoverlaygameid_env_var = false;
// enable owning Steam Applications IDs (mostly builtin apps + dedicated servers)
bool enable_builtin_preowned_ids = false;
//subscribed lobby/group ids
std::set<uint64> subscribed_groups{};
std::vector<Group_Clans> subscribed_groups_clans{};
// get the alpha-2 code from: https://www.iban.com/country-codes
std::string ip_country = "US";
// branches info
//is playing on beta branch + current/forced branch name
bool is_beta_branch = false;
long selected_branch_idx{};
std::vector<Branch_Info> branches{}; // in settings parser we must ensure we have the default "public" branch, force-add it if not defined by the user
//controller
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
bool share_leaderboards_over_network = false;
// don't share stats and achievements with the game server
bool disable_sharing_stats_with_gameserver = false;
// synchronize user stats/achievements with game servers as soon as possible instead of caching them.
bool immediate_gameserver_stats = false;
// steam_game_stats
std::string steam_game_stats_reports_dir{};
//overlay
bool disable_overlay = true;
int overlay_hook_delay_sec = 0; // "Saints Row (2022)" needs a lot of time to initialize, otherwise detection will fail
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;
//warn people who use local save
bool overlay_warn_local_save = false;
//disable overlay warning for local save
bool disable_overlay_warning_local_save = false;
// should the overlay upload icons to the GPU and display them
bool overlay_upload_achs_icons_to_gpu = true;
//disable overlay warning for bad app ID (= 0)
bool disable_overlay_warning_bad_appid = false;
// disable all overlay warnings
bool disable_overlay_warning_any = false;
Overlay_Appearance overlay_appearance{};
// whether to auto accept any overlay invites
bool auto_accept_any_overlay_invites = false;
// list of user steam IDs to auto-accept invites from
std::set<uint64_t> auto_accept_overlay_invites_friends{};
#ifdef LOBBY_CONNECT
static constexpr const bool is_lobby_connect = true;
#else
static constexpr const bool is_lobby_connect = false;
#endif
Settings(CSteamID steam_id, CGameID game_id, const std::string &name, const std::string &language, bool offline);
static std::string sanitize(const std::string &name);
CSteamID get_local_steam_id();
CGameID get_local_game_id();
const char *get_local_name();
void set_local_name(const char *name);
const char *get_language();
void set_language(const char *language);
void set_supported_languages(const std::set<std::string> &langs);
const std::set<std::string>& get_supported_languages_set() const;
const std::string& get_supported_languages() const;
void set_game_id(CGameID game_id);
void set_lobby(CSteamID lobby_id);
CSteamID get_lobby();
bool is_offline();
void set_offline(bool offline);
uint16 get_port();
void set_port(uint16 port);
//DLC stuff
void unlockAllDLC(bool value);
void addDLC(AppId_t appID, std::string name, bool available);
unsigned int DLCCount() const;
bool hasDLC(AppId_t appID);
bool getDLC(unsigned int index, AppId_t &appID, bool &available, std::string &name);
//installed apps, used by Steam_Apps::BIsAppInstalled()
void assumeAnyAppInstalled(bool val);
void addInstalledApp(AppId_t appID);
bool isAppInstalled(AppId_t appID) const;
//App Install paths
void setAppInstallPath(AppId_t appID, const std::string &path);
std::string getAppInstallPath(AppId_t appID);
//mod stuff
void addMod(PublishedFileId_t id, const std::string &title, const std::string &path);
void addModDetails(PublishedFileId_t id, const Mod_entry &details);
Mod_entry getMod(PublishedFileId_t id);
bool isModInstalled(PublishedFileId_t id);
std::set<PublishedFileId_t> modSet();
//leaderboards
void setLeaderboard(const std::string &leaderboard, enum ELeaderboardSortMethod sort_method, enum ELeaderboardDisplayType display_type);
const std::map<std::string, Leaderboard_config>& getLeaderboards() const;
//stats
const std::map<std::string, Stat_config>& getStats() const;
std::map<std::string, Stat_config>::const_iterator setStatDefiniton(const std::string &name, const struct Stat_config &stat_config);
//images
int add_image(const std::string &data, uint32 width, uint32 height);
Image_Data* get_image(int handle);
// overlay auto accept stuff
void acceptAnyOverlayInvites(bool value);
void addFriendToOverlayAutoAccept(uint64_t friend_id);
bool hasOverlayAutoAcceptInviteFromFriend(uint64_t friend_id) const;
size_t overlayAutoAcceptInvitesCount() const;
};
#endif // SETTINGS_INCLUDE_H

58
dll/dll/settings_parser.h Normal file
View File

@ -0,0 +1,58 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef SETTINGS_PARSER_INCLUDE_H
#define SETTINGS_PARSER_INCLUDE_H
#include "settings.h"
//returns game appid
uint32 create_localstorage_settings(Settings **settings_client_out, Settings **settings_server_out, Local_Storage **local_storage_out);
void save_global_settings(class Local_Storage *local_storage, const char *name, const char *language);
bool settings_disable_lan_only();
enum class SettingsItf {
CLIENT,
GAMESERVER_STATS,
GAMESERVER,
MATCHMAKING_SERVERS,
MATCHMAKING,
USER,
FRIENDS,
UTILS,
USER_STATS,
APPS,
NETWORKING,
REMOTE_STORAGE,
SCREENSHOTS,
HTTP,
UNIFIED_MESSAGES,
CONTROLLER,
UGC,
APPLIST,
MUSIC,
MUSIC_REMOTE,
HTML_SURFACE,
INVENTORY,
VIDEO,
MASTERSERVER_UPDATER,
};
const std::map<SettingsItf, std::string>& settings_old_interfaces();
#endif // SETTINGS_PARSER_INCLUDE_H

32
dll/dll/source_query.h Normal file
View File

@ -0,0 +1,32 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_SOURCE_QUERY_H__
#define __INCLUDED_SOURCE_QUERY_H__
#include "base.h"
class Source_Query
{
Source_Query() = delete;
~Source_Query() = delete;
public:
static std::vector<uint8_t> handle_source_query(const void* buffer, size_t len, Gameserver const& gs);
};
#endif // __INCLUDED_SOURCE_QUERY_H__

192
dll/dll/steam_HTMLsurface.h Normal file
View File

@ -0,0 +1,192 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_HTMLSURFACE_H__
#define __INCLUDED_STEAM_HTMLSURFACE_H__
#include "base.h"
class Steam_HTMLsurface :
public ISteamHTMLSurface001,
public ISteamHTMLSurface002,
public ISteamHTMLSurface003,
public ISteamHTMLSurface004,
public ISteamHTMLSurface
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
public:
Steam_HTMLsurface(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
// Must call init and shutdown when starting/ending use of the interface
bool Init();
bool Shutdown();
// Create a browser object for display of a html page, when creation is complete the call handle
// will return a HTML_BrowserReady_t callback for the HHTMLBrowser of your new browser.
// The user agent string is a substring to be added to the general user agent string so you can
// identify your client on web servers.
// The userCSS string lets you apply a CSS style sheet to every displayed page, leave null if
// you do not require this functionality.
//
// YOU MUST HAVE IMPLEMENTED HANDLERS FOR HTML_BrowserReady_t, HTML_StartRequest_t,
// HTML_JSAlert_t, HTML_JSConfirm_t, and HTML_FileOpenDialog_t! See the CALLBACKS
// section of this interface (AllowStartRequest, etc) for more details. If you do
// not implement these callback handlers, the browser may appear to hang instead of
// navigating to new pages or triggering javascript popups.
//
STEAM_CALL_RESULT( HTML_BrowserReady_t )
SteamAPICall_t CreateBrowser( const char *pchUserAgent, const char *pchUserCSS );
// Call this when you are done with a html surface, this lets us free the resources being used by it
void RemoveBrowser( HHTMLBrowser unBrowserHandle );
// Navigate to this URL, results in a HTML_StartRequest_t as the request commences
void LoadURL( HHTMLBrowser unBrowserHandle, const char *pchURL, const char *pchPostData );
// Tells the surface the size in pixels to display the surface
void SetSize( HHTMLBrowser unBrowserHandle, uint32 unWidth, uint32 unHeight );
// Stop the load of the current html page
void StopLoad( HHTMLBrowser unBrowserHandle );
// Reload (most likely from local cache) the current page
void Reload( HHTMLBrowser unBrowserHandle );
// navigate back in the page history
void GoBack( HHTMLBrowser unBrowserHandle );
// navigate forward in the page history
void GoForward( HHTMLBrowser unBrowserHandle );
// add this header to any url requests from this browser
void AddHeader( HHTMLBrowser unBrowserHandle, const char *pchKey, const char *pchValue );
// run this javascript script in the currently loaded page
void ExecuteJavascript( HHTMLBrowser unBrowserHandle, const char *pchScript );
// Mouse click and mouse movement commands
void MouseUp( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton );
void MouseDown( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton );
void MouseDoubleClick( HHTMLBrowser unBrowserHandle, EHTMLMouseButton eMouseButton );
// x and y are relative to the HTML bounds
void MouseMove( HHTMLBrowser unBrowserHandle, int x, int y );
// nDelta is pixels of scroll
void MouseWheel( HHTMLBrowser unBrowserHandle, int32 nDelta );
// keyboard interactions, native keycode is the key code value from your OS
void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers, bool bIsSystemKey = false );
void KeyDown( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers);
void KeyUp( HHTMLBrowser unBrowserHandle, uint32 nNativeKeyCode, EHTMLKeyModifiers eHTMLKeyModifiers );
// cUnicodeChar is the unicode character point for this keypress (and potentially multiple chars per press)
void KeyChar( HHTMLBrowser unBrowserHandle, uint32 cUnicodeChar, EHTMLKeyModifiers eHTMLKeyModifiers );
// programmatically scroll this many pixels on the page
void SetHorizontalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll );
void SetVerticalScroll( HHTMLBrowser unBrowserHandle, uint32 nAbsolutePixelScroll );
// tell the html control if it has key focus currently, controls showing the I-beam cursor in text controls amongst other things
void SetKeyFocus( HHTMLBrowser unBrowserHandle, bool bHasKeyFocus );
// open the current pages html code in the local editor of choice, used for debugging
void ViewSource( HHTMLBrowser unBrowserHandle );
// copy the currently selected text on the html page to the local clipboard
void CopyToClipboard( HHTMLBrowser unBrowserHandle );
// paste from the local clipboard to the current html page
void PasteFromClipboard( HHTMLBrowser unBrowserHandle );
// find this string in the browser, if bCurrentlyInFind is true then instead cycle to the next matching element
void Find( HHTMLBrowser unBrowserHandle, const char *pchSearchStr, bool bCurrentlyInFind, bool bReverse );
// cancel a currently running find
void StopFind( HHTMLBrowser unBrowserHandle );
// return details about the link at position x,y on the current page
void GetLinkAtPosition( HHTMLBrowser unBrowserHandle, int x, int y );
// set a webcookie for the hostname in question
void SetCookie( const char *pchHostname, const char *pchKey, const char *pchValue, const char *pchPath, RTime32 nExpires, bool bSecure, bool bHTTPOnly );
// Zoom the current page by flZoom ( from 0.0 to 2.0, so to zoom to 120% use 1.2 ), zooming around point X,Y in the page (use 0,0 if you don't care)
void SetPageScaleFactor( HHTMLBrowser unBrowserHandle, float flZoom, int nPointX, int nPointY );
// Enable/disable low-resource background mode, where javascript and repaint timers are throttled, resources are
// more aggressively purged from memory, and audio/video elements are paused. When background mode is enabled,
// all HTML5 video and audio objects will execute ".pause()" and gain the property "._steam_background_paused = 1".
// When background mode is disabled, any video or audio objects with that property will resume with ".play()".
void SetBackgroundMode( HHTMLBrowser unBrowserHandle, bool bBackgroundMode );
// Scale the output display space by this factor, this is useful when displaying content on high dpi devices.
// Specifies the ratio between physical and logical pixels.
void SetDPIScalingFactor( HHTMLBrowser unBrowserHandle, float flDPIScaling );
void OpenDeveloperTools( HHTMLBrowser unBrowserHandle );
// CALLBACKS
//
// These set of functions are used as responses to callback requests
//
// You MUST call this in response to a HTML_StartRequest_t callback
// Set bAllowed to true to allow this navigation, false to cancel it and stay
// on the current page. You can use this feature to limit the valid pages
// allowed in your HTML surface.
void AllowStartRequest( HHTMLBrowser unBrowserHandle, bool bAllowed );
// You MUST call this in response to a HTML_JSAlert_t or HTML_JSConfirm_t callback
// Set bResult to true for the OK option of a confirm, use false otherwise
void JSDialogResponse( HHTMLBrowser unBrowserHandle, bool bResult );
// You MUST call this in response to a HTML_FileOpenDialog_t callback
STEAM_IGNOREATTR()
void FileLoadDialogResponse( HHTMLBrowser unBrowserHandle, const char **pchSelectedFiles );
};
#endif // __INCLUDED_STEAM_HTMLSURFACE_H__

View File

@ -0,0 +1,47 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_APP_DISABLE_UPDATE_H__
#define __INCLUDED_STEAM_APP_DISABLE_UPDATE_H__
#include "base.h"
class Steam_App_Disable_Update:
public ISteamAppDisableUpdate
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
static void steam_network_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
void steam_run_callback();
void network_callback(Common_Message *msg);
public:
Steam_App_Disable_Update(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_App_Disable_Update();
// probably means how many seconds to keep the updates disabled
void SetAppUpdateDisabledSecondsRemaining(int32 nSeconds);
};
#endif // __INCLUDED_STEAM_APP_DISABLE_UPDATE_H__

8
dll/dll/steam_app_ids.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _STEAM_APP_IDS_H_
#define _STEAM_APP_IDS_H_
#include "common_includes.h"
extern const std::map<uint32, std::string> steam_preowned_app_ids;
#endif // _STEAM_APP_IDS_H_

36
dll/dll/steam_applist.h Normal file
View File

@ -0,0 +1,36 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_APPLIST_H__
#define __INCLUDED_STEAM_APPLIST_H__
#include "base.h"
class Steam_Applist :
public ISteamAppList
{
public:
uint32 GetNumInstalledApps();
uint32 GetInstalledApps( AppId_t *pvecAppID, uint32 unMaxAppIDs );
int GetAppName( AppId_t nAppID, STEAM_OUT_STRING() char *pchName, int cchNameMax ); // returns -1 if no name was found
int GetAppInstallDir( AppId_t nAppID, char *pchDirectory, int cchNameMax ); // returns -1 if no dir was found
int GetAppBuildId( AppId_t nAppID ); // return the buildid of this app, may change at any time based on backend updates to the game
};
#endif // __INCLUDED_STEAM_APPLIST_H__

148
dll/dll/steam_apps.h Normal file
View File

@ -0,0 +1,148 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_APPS_H__
#define __INCLUDED_STEAM_APPS_H__
#include "base.h"
class Steam_Apps :
public ISteamApps001,
public ISteamApps002,
public ISteamApps003,
public ISteamApps004,
public ISteamApps005,
public ISteamApps006,
public ISteamApps007,
public ISteamApps
{
class Settings *settings{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
public:
Steam_Apps(Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
// returns 0 if the key does not exist
// this may be true on first call, since the app data may not be cached locally yet
// If you expect it to exists wait for the AppDataChanged_t after the first failure and ask again
int GetAppData( AppId_t nAppID, const char *pchKey, char *pchValue, int cchValueMax );
bool BIsSubscribed();
bool BIsLowViolence();
bool BIsCybercafe();
bool BIsVACBanned();
// valid list: https://partner.steamgames.com/doc/store/localization/languages
const char *GetCurrentGameLanguage();
// valid list: https://partner.steamgames.com/doc/store/localization/languages
const char *GetAvailableGameLanguages();
// only use this member if you need to check ownership of another game related to yours, a demo for example
bool BIsSubscribedApp( AppId_t appID );
// Takes AppID of DLC and checks if the user owns the DLC & if the DLC is installed
bool BIsDlcInstalled( AppId_t appID );
// returns the Unix time of the purchase of the app
uint32 GetEarliestPurchaseUnixTime( AppId_t nAppID );
// 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 weekened
bool BIsSubscribedFromFreeWeekend();
// Returns the number of DLC pieces for the running app
int GetDLCCount();
// Returns metadata for DLC by index, of range [0, GetDLCCount()]
bool BGetDLCDataByIndex( int iDLC, AppId_t *pAppID, bool *pbAvailable, char *pchName, int cchNameBufferSize );
// Install/Uninstall control for optional DLC
void InstallDLC( AppId_t nAppID );
void UninstallDLC( AppId_t nAppID );
// 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).
void RequestAppProofOfPurchaseKey( AppId_t nAppID );
bool GetCurrentBetaName( char *pchName, int cchNameBufferSize ); // returns current beta branch name, 'public' is the default branch
bool MarkContentCorrupt( bool bMissingFilesOnly ); // signal Steam that game files seems corrupt or missing
uint32 GetInstalledDepots( AppId_t appID, DepotId_t *pvecDepots, uint32 cMaxDepots ); // return installed depots in mount order
uint32 GetInstalledDepots( DepotId_t *pvecDepots, uint32 cMaxDepots );
// returns current app install folder for AppID, returns folder name length
uint32 GetAppInstallDir( AppId_t appID, char *pchFolder, uint32 cchFolderBufferSize );
bool BIsAppInstalled( AppId_t appID ); // returns true if that app is installed (not necessarily owned)
CSteamID GetAppOwner(); // returns the SteamID of the original owner. If different from current user, it's borrowed
// Returns the associated launch param if the game is run via steam://run/<appid>//?param1=value1;param2=value2;param3=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.
const char *GetLaunchQueryParam( const char *pchKey );
// get download progress for optional DLC
bool GetDlcDownloadProgress( AppId_t nAppID, uint64 *punBytesDownloaded, uint64 *punBytesTotal );
// return the buildid of this app, may change at any time based on backend updates to the game
int GetAppBuildId();
// Request all proof of purchase keys for the calling appid and asociated 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).
void RequestAllProofOfPurchaseKeys();
STEAM_CALL_RESULT( FileDetailsResult_t )
SteamAPICall_t GetFileDetails( const char* pszFileName );
// Get command line if game was launched via Steam URL, e.g. steam://run/<appid>//<command line>/.
// 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.
int GetLaunchCommandLine( char *pszCommandLine, int cubCommandLine );
// Check if user borrowed this game via Family Sharing, If true, call GetAppOwner() to get the lender SteamID
bool BIsSubscribedFromFamilySharing();
// check if game is a timed trial with limited playtime
bool BIsTimedTrial( uint32* punSecondsAllowed, uint32* punSecondsPlayed );
// set current DLC AppID being played (or 0 if none). Allows Steam to track usage of major DLC extensions
bool SetDlcContext( AppId_t nAppID );
// returns total number of known app beta branches (including default "public" branch )
int GetNumBetas( int *pnAvailable, int *pnPrivate ); //
// 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
// 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 );
};
#endif // __INCLUDED_STEAM_APPS_H__

351
dll/dll/steam_client.h Normal file
View File

@ -0,0 +1,351 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_CLIENT_H__
#define __INCLUDED_STEAM_CLIENT_H__
#include "base.h"
#include "appticket.h"
#include "steam_user.h"
#include "steam_friends.h"
#include "steam_utils.h"
#include "steam_matchmaking.h"
#include "steam_matchmaking_servers.h"
#include "steam_user_stats.h"
#include "steam_apps.h"
#include "steam_networking.h"
#include "steam_remote_storage.h"
#include "steam_screenshots.h"
#include "steam_http.h"
#include "steam_controller.h"
#include "steam_ugc.h"
#include "ugc_remote_storage_bridge.h"
#include "steam_applist.h"
#include "steam_music.h"
#include "steam_musicremote.h"
#include "steam_HTMLsurface.h"
#include "steam_inventory.h"
#include "steam_video.h"
#include "steam_parental.h"
#include "steam_game_coordinator.h"
#include "steam_networking_socketsserialized.h"
#include "steam_networking_sockets.h"
#include "steam_networking_messages.h"
#include "steam_networking_utils.h"
#include "steam_unified_messages.h"
#include "steam_gamesearch.h"
#include "steam_parties.h"
#include "steam_remoteplay.h"
#include "steam_tv.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_masterserver_updater.h"
#include "overlay/steam_overlay.h"
enum Steam_Pipe {
NO_USER,
CLIENT,
SERVER
};
class Steam_Client :
public ISteamClient007,
public ISteamClient008,
public ISteamClient009,
public ISteamClient010,
public ISteamClient011,
public ISteamClient012,
public ISteamClient013,
public ISteamClient014,
public ISteamClient015,
public ISteamClient016,
public ISteamClient017,
public ISteamClient018,
public ISteamClient019,
public ISteamClient020,
public ISteamClient
{
private:
bool user_logged_in = false;
bool server_init = false;
std::atomic_bool cb_run_active = false;
std::atomic<unsigned long long> last_cb_run{};
// don't run immediately, give the game some time to initialize
constexpr const static auto initial_delay = std::chrono::seconds(2);
// max allowed time in which RunCallbacks() might not be called
constexpr const static auto max_stall_ms = std::chrono::milliseconds(300);
common_helpers::KillableWorker *background_thread{};
void background_thread_proc();
public:
Networking *network{};
SteamCallResults *callback_results_server{}, *callback_results_client{};
SteamCallBacks *callbacks_server{}, *callbacks_client{};
Settings *settings_client{}, *settings_server{};
Local_Storage *local_storage{};
RunEveryRunCB *run_every_runcb{};
Ugc_Remote_Storage_Bridge *ugc_bridge{};
Steam_User *steam_user{};
Steam_Friends *steam_friends{};
Steam_Utils *steam_utils{};
Steam_Matchmaking *steam_matchmaking{};
Steam_Matchmaking_Servers *steam_matchmaking_servers{};
Steam_User_Stats *steam_user_stats{};
Steam_Apps *steam_apps{};
Steam_Networking *steam_networking{};
Steam_Remote_Storage *steam_remote_storage{};
Steam_Screenshots *steam_screenshots{};
Steam_HTTP *steam_http{};
Steam_Controller *steam_controller{};
Steam_UGC *steam_ugc{};
Steam_Applist *steam_applist{};
Steam_Music *steam_music{};
Steam_MusicRemote *steam_musicremote{};
Steam_HTMLsurface *steam_HTMLsurface{};
Steam_Inventory *steam_inventory{};
Steam_Video *steam_video{};
Steam_Parental *steam_parental{};
Steam_Networking_Sockets *steam_networking_sockets{};
Steam_Networking_Sockets_Serialized *steam_networking_sockets_serialized{};
Steam_Networking_Messages *steam_networking_messages{};
Steam_Game_Coordinator *steam_game_coordinator{};
Steam_Networking_Utils *steam_networking_utils{};
Steam_Unified_Messages *steam_unified_messages{};
Steam_Game_Search *steam_game_search{};
Steam_Parties *steam_parties{};
Steam_RemotePlay *steam_remoteplay{};
Steam_TV *steam_tv{};
Steam_GameStats *steam_gamestats{};
Steam_Timeline *steam_timeline{};
Steam_App_Disable_Update *steam_app_disable_update{};
Steam_GameServer *steam_gameserver{};
Steam_Utils *steam_gameserver_utils{};
Steam_GameServerStats *steam_gameserverstats{};
Steam_Networking *steam_gameserver_networking{};
Steam_HTTP *steam_gameserver_http{};
Steam_Inventory *steam_gameserver_inventory{};
Steam_UGC *steam_gameserver_ugc{};
Steam_Apps *steam_gameserver_apps{};
Steam_Networking_Sockets *steam_gameserver_networking_sockets{};
Steam_Networking_Sockets_Serialized *steam_gameserver_networking_sockets_serialized{};
Steam_Networking_Messages *steam_gameserver_networking_messages{};
Steam_Game_Coordinator *steam_gameserver_game_coordinator{};
Steam_Masterserver_Updater *steam_masterserver_updater{};
Steam_GameStats *steam_gameserver_gamestats{};
Steam_AppTicket *steam_app_ticket{};
Steam_Overlay* steam_overlay{};
bool steamclient_server_inited = false;
bool gameserver_has_ipv6_functions{};
unsigned steam_pipe_counter = 1;
std::map<HSteamPipe, enum Steam_Pipe> steam_pipes{};
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();
// 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 );
// 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 );
// 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 );
// 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 ISteamUtils interface
ISteamUtils *GetISteamUtils( 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 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 ISteamGameServerStats interface
ISteamGameServerStats *GetISteamGameServerStats( 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 );
// remote storage
ISteamRemoteStorage *GetISteamRemoteStorage( 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 );
// steam timeline
ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
// steam appp disable update
ISteamAppDisableUpdate *GetISteamAppDisableUpdate( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
// 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();
// 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();
// 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 ) ; )
ISteamUnifiedMessages *GetISteamUnifiedMessages( 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 );
// 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);
// 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 ); )
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 );
// Video
ISteamVideo *GetISteamVideo( 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 );
ISteamContentServer *GetISteamContentServer( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
// game search
ISteamGameSearch *GetISteamGameSearch( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion );
// Exposes the Steam Input interface for controller support
ISteamInput *GetISteamInput( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
// Steam Parties interface
ISteamParties *GetISteamParties( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
// Steam Remote Play interface
ISteamRemotePlay *GetISteamRemotePlay( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
ISteamAppTicket *GetAppTicket( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion );
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 RunCallbacks(bool runClientCB, bool runGameserverCB);
void setAppID(uint32 appid);
void userLogIn();
void serverInit();
void serverShutdown();
void clientShutdown();
bool IsServerInit();
bool IsUserLogIn();
void DestroyAllInterfaces();
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);
};
#endif // __INCLUDED_STEAM_CLIENT_H__

335
dll/dll/steam_controller.h Normal file
View File

@ -0,0 +1,335 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_CONTROLLER_H__
#define __INCLUDED_STEAM_CONTROLLER_H__
#include "base.h"
struct Controller_Map {
std::map<ControllerDigitalActionHandle_t, std::set<int>> active_digital{};
std::map<ControllerAnalogActionHandle_t, std::pair<std::set<int>, enum EInputSourceMode>> active_analog{};
};
struct Controller_Action {
ControllerHandle_t controller_handle{};
struct Controller_Map active_map{};
ControllerDigitalActionHandle_t active_set{};
Controller_Action(ControllerHandle_t controller_handle);
void activate_action_set(ControllerDigitalActionHandle_t active_set, std::map<ControllerActionSetHandle_t, struct Controller_Map> &controller_maps);
std::set<int> button_id(ControllerDigitalActionHandle_t handle);
std::pair<std::set<int>, enum EInputSourceMode> analog_id(ControllerAnalogActionHandle_t handle);
};
struct Rumble_Thread_Data {
std::condition_variable rumble_thread_cv{};
bool kill_rumble_thread{};
std::mutex rumble_mutex{};
struct Rumble_Data {
unsigned short left{}, right{}, last_left{}, last_right{};
unsigned int rumble_length_ms{};
bool new_data{};
} data[GAMEPAD_COUNT];
};
enum EXTRA_GAMEPAD_BUTTONS {
BUTTON_LTRIGGER = BUTTON_COUNT + 1,
BUTTON_RTRIGGER = BUTTON_COUNT + 2,
BUTTON_STICK_LEFT_UP = BUTTON_COUNT + 3,
BUTTON_STICK_LEFT_DOWN = BUTTON_COUNT + 4,
BUTTON_STICK_LEFT_LEFT = BUTTON_COUNT + 5,
BUTTON_STICK_LEFT_RIGHT = BUTTON_COUNT + 6,
BUTTON_STICK_RIGHT_UP = BUTTON_COUNT + 7,
BUTTON_STICK_RIGHT_DOWN = BUTTON_COUNT + 8,
BUTTON_STICK_RIGHT_LEFT = BUTTON_COUNT + 9,
BUTTON_STICK_RIGHT_RIGHT = BUTTON_COUNT + 10,
};
class Steam_Controller :
// --- ISteamController
public ISteamController001,
public ISteamController003,
public ISteamController004,
public ISteamController005,
public ISteamController006,
public ISteamController007,
public ISteamController,
// ---
// --- ISteamInput
public ISteamInput001,
public ISteamInput002,
public ISteamInput005,
public ISteamInput
// ---
{
static const std::map<std::string, int> button_strings;
static const std::map<std::string, int> analog_strings;
static const std::map<std::string, enum EInputSourceMode> analog_input_modes;
class Settings *settings{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::map<std::string, ControllerActionSetHandle_t> action_handles{};
std::map<std::string, ControllerDigitalActionHandle_t> digital_action_handles{};
std::map<std::string, ControllerAnalogActionHandle_t> analog_action_handles{};
std::map<ControllerActionSetHandle_t, struct Controller_Map> controller_maps{};
std::map<ControllerHandle_t, struct Controller_Action> controllers{};
std::map<EInputActionOrigin, std::string> steaminput_glyphs{};
std::map<EControllerActionOrigin, std::string> steamcontroller_glyphs{};
std::thread background_rumble_thread{};
Rumble_Thread_Data *rumble_thread_data{};
bool disabled{};
bool initialized{};
bool explicitly_call_run_frame{};
void set_handles(std::map<std::string, std::map<std::string, std::pair<std::set<std::string>, std::string>>> action_sets);
void RunCallbacks();
static void background_rumble(Rumble_Thread_Data *data);
static void steam_run_every_runcb(void *object);
public:
Steam_Controller(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Controller();
// Init and Shutdown must be called when starting/ending use of this interface
bool Init(bool bExplicitlyCallRunFrame);
bool Init( const char *pchAbsolutePathToControllerConfigVDF );
bool Init();
bool Shutdown();
void SetOverrideMode( const char *pchMode );
// Set the absolute path to the Input Action Manifest file containing the in-game actions
// and file paths to the official configurations. Used in games that bundle Steam Input
// configurations inside of the game depot instead of using the Steam Workshop
bool SetInputActionManifestFilePath( const char *pchInputActionManifestAbsolutePath );
bool BWaitForData( bool bWaitForever, uint32 unTimeout );
// Returns true if new data has been received since the last time action data was accessed
// via GetDigitalActionData or GetAnalogActionData. The game will still need to call
// SteamInput()->RunFrame() or SteamAPI_RunCallbacks() before this to update the data stream
bool BNewDataAvailable();
// Enable SteamInputDeviceConnected_t and SteamInputDeviceDisconnected_t callbacks.
// Each controller that is already connected will generate a device connected
// callback when you enable them
void EnableDeviceCallbacks();
// Enable SteamInputActionEvent_t callbacks. Directly calls your callback function
// for lower latency than standard Steam callbacks. Supports one callback at a time.
// Note: this is called within either SteamInput()->RunFrame or by SteamAPI_RunCallbacks
void EnableActionEventCallbacks( SteamInputActionEventCallbackPointer pCallback );
// Synchronize API state with the latest Steam Controller inputs available. This
// is performed automatically by SteamAPI_RunCallbacks, but for the absolute lowest
// possible latency, you call this directly before reading controller state.
void RunFrame(bool bReservedValue);
void RunFrame();
bool GetControllerState( uint32 unControllerIndex, SteamControllerState001_t *pState );
// Enumerate currently connected controllers
// handlesOut should point to a STEAM_CONTROLLER_MAX_COUNT sized array of ControllerHandle_t handles
// Returns the number of handles written to handlesOut
int GetConnectedControllers( ControllerHandle_t *handlesOut );
// Invokes the Steam overlay and brings up the binding screen
// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode
bool ShowBindingPanel( ControllerHandle_t controllerHandle );
// ACTION SETS
// Lookup the handle for an Action Set. Best to do this once on startup, and store the handles for all future API calls.
ControllerActionSetHandle_t GetActionSetHandle( const char *pszActionSetName );
// Reconfigure the controller to use the specified action set (ie 'Menu', 'Walk' or 'Drive')
// This is cheap, and can be safely called repeatedly. It's often easier to repeatedly call it in
// your state loops, instead of trying to place it in all of your state transitions.
void ActivateActionSet( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle );
ControllerActionSetHandle_t GetCurrentActionSet( ControllerHandle_t controllerHandle );
void ActivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle );
void DeactivateActionSetLayer( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetLayerHandle );
void DeactivateAllActionSetLayers( ControllerHandle_t controllerHandle );
int GetActiveActionSetLayers( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t *handlesOut );
// ACTIONS
// Lookup the handle for a digital action. Best to do this once on startup, and store the handles for all future API calls.
ControllerDigitalActionHandle_t GetDigitalActionHandle( const char *pszActionName );
// Returns the current state of the supplied digital game action
ControllerDigitalActionData_t GetDigitalActionData( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle );
// Get the origin(s) for a digital action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action.
// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles
int GetDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerDigitalActionHandle_t digitalActionHandle, EControllerActionOrigin *originsOut );
int GetDigitalActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputDigitalActionHandle_t digitalActionHandle, EInputActionOrigin *originsOut );
// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle
const char *GetStringForDigitalActionName( InputDigitalActionHandle_t eActionHandle );
// Lookup the handle for an analog action. Best to do this once on startup, and store the handles for all future API calls.
ControllerAnalogActionHandle_t GetAnalogActionHandle( const char *pszActionName );
// Returns the current state of these supplied analog game action
ControllerAnalogActionData_t GetAnalogActionData( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle );
// Get the origin(s) for an analog action within an action set. Returns the number of origins supplied in originsOut. Use this to display the appropriate on-screen prompt for the action.
// originsOut should point to a STEAM_CONTROLLER_MAX_ORIGINS sized array of EControllerActionOrigin handles
int GetAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerActionSetHandle_t actionSetHandle, ControllerAnalogActionHandle_t analogActionHandle, EControllerActionOrigin *originsOut );
int GetAnalogActionOrigins( InputHandle_t inputHandle, InputActionSetHandle_t actionSetHandle, InputAnalogActionHandle_t analogActionHandle, EInputActionOrigin *originsOut );
void StopAnalogActionMomentum( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t eAction );
// Trigger a haptic pulse on a controller
void TriggerHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec );
// Trigger a haptic pulse on a controller
void Legacy_TriggerHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec );
void TriggerHapticPulse( uint32 unControllerIndex, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec );
// Trigger a pulse with a duty cycle of usDurationMicroSec / usOffMicroSec, unRepeat times.
// nFlags is currently unused and reserved for future use.
void TriggerRepeatedHapticPulse( ControllerHandle_t controllerHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags );
void Legacy_TriggerRepeatedHapticPulse( InputHandle_t inputHandle, ESteamControllerPad eTargetPad, unsigned short usDurationMicroSec, unsigned short usOffMicroSec, unsigned short unRepeat, unsigned int nFlags );
// Send a haptic pulse, works on Steam Deck and Steam Controller devices
void TriggerSimpleHapticEvent( InputHandle_t inputHandle, EControllerHapticLocation eHapticLocation, uint8 nIntensity, char nGainDB, uint8 nOtherIntensity, char nOtherGainDB );
// Tigger a vibration event on supported controllers.
void TriggerVibration( ControllerHandle_t controllerHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed );
// Trigger a vibration event on supported controllers including Xbox trigger impulse rumble - Steam will translate these commands into haptic pulses for Steam Controllers
void TriggerVibrationExtended( InputHandle_t inputHandle, unsigned short usLeftSpeed, unsigned short usRightSpeed, unsigned short usLeftTriggerSpeed, unsigned short usRightTriggerSpeed );
// Set the controller LED color on supported controllers.
void SetLEDColor( ControllerHandle_t controllerHandle, uint8 nColorR, uint8 nColorG, uint8 nColorB, unsigned int nFlags );
// Returns the associated gamepad index for the specified controller, if emulating a gamepad
int GetGamepadIndexForController( ControllerHandle_t ulControllerHandle );
// Returns the associated controller handle for the specified emulated gamepad
ControllerHandle_t GetControllerForGamepadIndex( int nIndex );
// Returns raw motion data from the specified controller
ControllerMotionData_t GetMotionData( ControllerHandle_t controllerHandle );
// Attempt to display origins of given action in the controller HUD, for the currently active action set
// Returns false is overlay is disabled / unavailable, or the user is not in Big Picture mode
bool ShowDigitalActionOrigins( ControllerHandle_t controllerHandle, ControllerDigitalActionHandle_t digitalActionHandle, float flScale, float flXPosition, float flYPosition );
bool ShowAnalogActionOrigins( ControllerHandle_t controllerHandle, ControllerAnalogActionHandle_t analogActionHandle, float flScale, float flXPosition, float flYPosition );
// Returns a localized string (from Steam's language setting) for the specified origin
const char *GetStringForActionOrigin( EControllerActionOrigin eOrigin );
const char *GetStringForActionOrigin( EInputActionOrigin eOrigin );
// Returns a localized string (from Steam's language setting) for the user-facing action name corresponding to the specified handle
const char *GetStringForAnalogActionName( InputAnalogActionHandle_t eActionHandle );
// Get a local path to art for on-screen glyph for a particular origin
const char *GetGlyphForActionOrigin( EControllerActionOrigin eOrigin );
const char *GetGlyphForActionOrigin( EInputActionOrigin eOrigin );
// Get a local path to a PNG file for the provided origin's glyph.
const char *GetGlyphPNGForActionOrigin( EInputActionOrigin eOrigin, ESteamInputGlyphSize eSize, uint32 unFlags );
// Get a local path to a SVG file for the provided origin's glyph.
const char *GetGlyphSVGForActionOrigin( EInputActionOrigin eOrigin, uint32 unFlags );
// Get a local path to an older, Big Picture Mode-style PNG file for a particular origin
const char *GetGlyphForActionOrigin_Legacy( EInputActionOrigin eOrigin );
// Returns the input type for a particular handle
ESteamInputType GetInputTypeForHandle( ControllerHandle_t controllerHandle );
const char *GetStringForXboxOrigin( EXboxOrigin eOrigin );
const char *GetGlyphForXboxOrigin( EXboxOrigin eOrigin );
EControllerActionOrigin GetActionOriginFromXboxOrigin_( ControllerHandle_t controllerHandle, EXboxOrigin eOrigin );
EInputActionOrigin GetActionOriginFromXboxOrigin( InputHandle_t inputHandle, EXboxOrigin eOrigin );
EControllerActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EControllerActionOrigin eSourceOrigin );
EInputActionOrigin TranslateActionOrigin( ESteamInputType eDestinationInputType, EInputActionOrigin eSourceOrigin );
bool GetControllerBindingRevision( ControllerHandle_t controllerHandle, int *pMajor, int *pMinor );
bool GetDeviceBindingRevision( InputHandle_t inputHandle, int *pMajor, int *pMinor );
uint32 GetRemotePlaySessionID( InputHandle_t inputHandle );
// Get a bitmask of the Steam Input Configuration types opted in for the current session. Returns ESteamInputConfigurationEnableType values.?
// Note: user can override the settings from the Steamworks Partner site so the returned values may not exactly match your default configuration
uint16 GetSessionInputConfigurationSettings();
// Set the trigger effect for a DualSense controller
void SetDualSenseTriggerEffect( InputHandle_t inputHandle, const ScePadTriggerEffectParam *pParam );
};
#endif // __INCLUDED_STEAM_CONTROLLER_H__

443
dll/dll/steam_friends.h Normal file
View File

@ -0,0 +1,443 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_FRIENDS_H__
#define __INCLUDED_STEAM_FRIENDS_H__
#include "base.h"
#include "overlay/steam_overlay.h"
struct Avatar_Numbers {
int smallest{};
int medium{};
int large{};
};
class Steam_Friends :
public ISteamFriends003,
public ISteamFriends004,
public ISteamFriends005,
public ISteamFriends006,
public ISteamFriends007,
public ISteamFriends008,
public ISteamFriends009,
public ISteamFriends010,
public ISteamFriends011,
public ISteamFriends012,
public ISteamFriends013,
public ISteamFriends014,
public ISteamFriends015,
public ISteamFriends016,
public ISteamFriends
{
class Settings *settings{};
class Local_Storage* local_storage{};
class Networking *network{};
class SteamCallBacks *callbacks{};
class SteamCallResults *callback_results{};
class RunEveryRunCB *run_every_runcb{};
class Steam_Overlay* overlay{};
Friend us{};
bool modified{};
std::vector<Friend> friends{};
std::map<uint64, struct Avatar_Numbers> avatars{};
CSteamID lobby_id{};
std::chrono::high_resolution_clock::time_point last_sent_friends{};
Friend *find_friend(CSteamID id);
void persona_change(CSteamID id, EPersonaChange flags);
void rich_presence_updated(CSteamID id, AppId_t appid);
bool is_appid_compatible(Friend *f);
struct Avatar_Numbers add_friend_avatars(CSteamID id);
static bool ok_friend_flags(int iFriendFlags);
static void steam_friends_callback(void *object, Common_Message *msg);
static void steam_friends_run_every_runcb(void *object);
void RunCallbacks();
void Callback(Common_Message *msg);
public:
Steam_Friends(Settings* settings, class Local_Storage* local_storage, Networking* network, SteamCallResults* callback_results, SteamCallBacks* callbacks, RunEveryRunCB* run_every_runcb, Steam_Overlay* overlay);
~Steam_Friends();
void resend_friend_data();
// returns the local players name - guaranteed to not be NULL.
// this is the same name as on the users community profile page
// this is stored in UTF-8 format
// like all the other interface functions that return a char *, it's important that this pointer is not saved
// off; it will eventually be free'd or re-allocated
const char *GetPersonaName();
// Sets the player name, stores it on the server and publishes the changes to all friends who are online.
// Changes take place locally immediately, and a PersonaStateChange_t is posted, presuming success.
//
// The final results are available through the return value SteamAPICall_t, using SetPersonaNameResponse_t.
//
// If the name change fails to happen on the server, then an additional global PersonaStateChange_t will be posted
// to change the name back, in addition to the SetPersonaNameResponse_t callback.
STEAM_CALL_RESULT( SetPersonaNameResponse_t )
SteamAPICall_t SetPersonaName( const char *pchPersonaName );
void SetPersonaName_old( const char *pchPersonaName );
// gets the status of the current user
EPersonaState GetPersonaState();
// friend iteration
// takes a set of k_EFriendFlags, and returns the number of users the client knows about who meet that criteria
// then GetFriendByIndex() can then be used to return the id's of each of those users
int GetFriendCount( int iFriendFlags );
int GetFriendCount( EFriendFlags eFriendFlags );
// returns the steamID of a user
// iFriend is a index of range [0, GetFriendCount())
// iFriendsFlags must be the same value as used in GetFriendCount()
// the returned CSteamID can then be used by all the functions below to access details about the user
CSteamID GetFriendByIndex( int iFriend, int iFriendFlags );
void GetFriendByIndex(CSteamID& res, int iFriend, int iFriendFlags );
CSteamID GetFriendByIndex( int iFriend, EFriendFlags eFriendFlags );
void GetFriendByIndex(CSteamID& result, int iFriend, EFriendFlags eFriendFlags);
// returns a relationship to a user
EFriendRelationship GetFriendRelationship( CSteamID steamIDFriend );
// returns the current status of the specified user
// this will only be known by the local user if steamIDFriend is in their friends list; on the same game server; in a chat room or lobby; or in a small group with the local user
EPersonaState GetFriendPersonaState( CSteamID steamIDFriend );
// returns the name another user - guaranteed to not be NULL.
// same rules as GetFriendPersonaState() apply as to whether or not the user knowns the name of the other user
// note that on first joining a lobby, chat room or game server the local user will not known the name of the other users automatically; that information will arrive asyncronously
//
const char *GetFriendPersonaName( CSteamID steamIDFriend );
// returns true if the friend is actually in a game, and fills in pFriendGameInfo with an extra details
bool GetFriendGamePlayed( CSteamID steamIDFriend, STEAM_OUT_STRUCT() FriendGameInfo_t *pFriendGameInfo );
bool GetFriendGamePlayed( CSteamID steamIDFriend, uint64 *pulGameID, uint32 *punGameIP, uint16 *pusGamePort, uint16 *pusQueryPort );
// accesses old friends names - returns an empty string when their are no more items in the history
const char *GetFriendPersonaNameHistory( CSteamID steamIDFriend, int iPersonaName );
// friends steam level
int GetFriendSteamLevel( CSteamID steamIDFriend );
// Returns nickname the current user has set for the specified player. Returns NULL if the no nickname has been set for that player.
const char *GetPlayerNickname( CSteamID steamIDPlayer );
// friend grouping (tag) apis
// returns the number of friends groups
int GetFriendsGroupCount();
// returns the friends group ID for the given index (invalid indices return k_FriendsGroupID_Invalid)
FriendsGroupID_t GetFriendsGroupIDByIndex( int iFG );
// returns the name for the given friends group (NULL in the case of invalid friends group IDs)
const char *GetFriendsGroupName( FriendsGroupID_t friendsGroupID );
// returns the number of members in a given friends group
int GetFriendsGroupMembersCount( FriendsGroupID_t friendsGroupID );
// gets up to nMembersCount members of the given friends group, if fewer exist than requested those positions' SteamIDs will be invalid
void GetFriendsGroupMembersList( FriendsGroupID_t friendsGroupID, STEAM_OUT_ARRAY_CALL(nMembersCount, GetFriendsGroupMembersCount, friendsGroupID ) CSteamID *pOutSteamIDMembers, int nMembersCount );
// returns true if the specified user meets any of the criteria specified in iFriendFlags
// iFriendFlags can be the union (binary or, |) of one or more k_EFriendFlags values
bool HasFriend( CSteamID steamIDFriend, int iFriendFlags );
bool HasFriend( CSteamID steamIDFriend, EFriendFlags eFriendFlags );
// clan (group) iteration and access functions
int GetClanCount();
CSteamID GetClanByIndex( int iClan );
void GetClanByIndex(CSteamID& result, int iClan );
const char *GetClanName( CSteamID steamIDClan );
const char *GetClanTag( CSteamID steamIDClan );
// returns the most recent information we have about what's happening in a clan
bool GetClanActivityCounts( CSteamID steamIDClan, int *pnOnline, int *pnInGame, int *pnChatting );
// for clans a user is a member of, they will have reasonably up-to-date information, but for others you'll have to download the info to have the latest
SteamAPICall_t DownloadClanActivityCounts( STEAM_ARRAY_COUNT(cClansToRequest) CSteamID *psteamIDClans, int cClansToRequest );
// iterators for getting users in a chat room, lobby, game server or clan
// note that large clans that cannot be iterated by the local user
// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby
// steamIDSource can be the steamID of a group, game server, lobby or chat room
int GetFriendCountFromSource( CSteamID steamIDSource );
CSteamID GetFriendFromSourceByIndex( CSteamID steamIDSource, int iFriend );
void GetFriendFromSourceByIndex(CSteamID& res, CSteamID steamIDSource, int iFriend);
// returns true if the local user can see that steamIDUser is a member or in steamIDSource
bool IsUserInSource( CSteamID steamIDUser, CSteamID steamIDSource );
// User is in a game pressing the talk button (will suppress the microphone for all voice comms from the Steam friends UI)
void SetInGameVoiceSpeaking( CSteamID steamIDUser, bool bSpeaking );
// activates the game overlay, with an optional dialog to open
// valid options are "Friends", "Community", "Players", "Settings", "OfficialGameGroup", "Stats", "Achievements"
void ActivateGameOverlay( const char *pchDialog );
// activates game overlay to a specific place
// valid options are
// "steamid" - opens the overlay web browser to the specified user or groups profile
// "chat" - opens a chat window to the specified user, or joins the group chat
// "jointrade" - opens a window to a Steam Trading session that was started with the ISteamEconomy/StartTrade Web API
// "stats" - opens the overlay web browser to the specified user's stats
// "achievements" - opens the overlay web browser to the specified user's achievements
// "friendadd" - opens the overlay in minimal mode prompting the user to add the target user as a friend
// "friendremove" - opens the overlay in minimal mode prompting the user to remove the target friend
// "friendrequestaccept" - opens the overlay in minimal mode prompting the user to accept an incoming friend invite
// "friendrequestignore" - opens the overlay in minimal mode prompting the user to ignore an incoming friend invite
void ActivateGameOverlayToUser( const char *pchDialog, CSteamID steamID );
// activates game overlay web browser directly to the specified URL
// full address with protocol type is required, e.g. http://www.steamgames.com/
void ActivateGameOverlayToWebPage( const char *pchURL, EActivateGameOverlayToWebPageMode eMode = k_EActivateGameOverlayToWebPageMode_Default );
void ActivateGameOverlayToWebPage( const char *pchURL );
// activates game overlay to store page for app
void ActivateGameOverlayToStore( AppId_t nAppID, EOverlayToStoreFlag eFlag );
void ActivateGameOverlayToStore( AppId_t nAppID);
// Mark a target user as 'played with'. This is a client-side only feature that requires that the calling user is
// in game
void SetPlayedWith( CSteamID steamIDUserPlayedWith );
// activates game overlay to open the invite dialog. Invitations will be sent for the provided lobby.
void ActivateGameOverlayInviteDialog( CSteamID steamIDLobby );
// gets the small (32x32) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set
int GetSmallFriendAvatar( CSteamID steamIDFriend );
// gets the medium (64x64) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set
int GetMediumFriendAvatar( CSteamID steamIDFriend );
// gets the large (184x184) avatar of the current user, which is a handle to be used in IClientUtils::GetImageRGBA(), or 0 if none set
// returns -1 if this image has yet to be loaded, in this case wait for a AvatarImageLoaded_t callback and then call this again
int GetLargeFriendAvatar( CSteamID steamIDFriend );
int GetFriendAvatar( CSteamID steamIDFriend, int eAvatarSize );
int GetFriendAvatar(CSteamID steamIDFriend);
// requests information about a user - persona name & avatar
// if bRequireNameOnly is set, then the avatar of a user isn't downloaded
// - it's a lot slower to download avatars and churns the local cache, so if you don't need avatars, don't request them
// if returns true, it means that data is being requested, and a PersonaStateChanged_t callback will be posted when it's retrieved
// if returns false, it means that we already have all the details about that user, and functions can be called immediately
bool RequestUserInformation( CSteamID steamIDUser, bool bRequireNameOnly );
// requests information about a clan officer list
// when complete, data is returned in ClanOfficerListResponse_t call result
// this makes available the calls below
// you can only ask about clans that a user is a member of
// note that this won't download avatars automatically; if you get an officer,
// and no avatar image is available, call RequestUserInformation( steamID, false ) to download the avatar
STEAM_CALL_RESULT( ClanOfficerListResponse_t )
SteamAPICall_t RequestClanOfficerList( CSteamID steamIDClan );
// iteration of clan officers - can only be done when a RequestClanOfficerList() call has completed
// returns the steamID of the clan owner
CSteamID GetClanOwner( CSteamID steamIDClan );
void GetClanOwner(CSteamID& res, CSteamID steamIDClan );
// returns the number of officers in a clan (including the owner)
int GetClanOfficerCount( CSteamID steamIDClan );
// returns the steamID of a clan officer, by index, of range [0,GetClanOfficerCount)
CSteamID GetClanOfficerByIndex( CSteamID steamIDClan, int iOfficer );
void GetClanOfficerByIndex(CSteamID& res, CSteamID steamIDClan, int iOfficer );
// if current user is chat restricted, he can't send or receive any text/voice chat messages.
// the user can't see custom avatars. But the user can be online and send/recv game invites.
// a chat restricted user can't add friends or join any groups.
uint32 GetUserRestrictions();
EUserRestriction GetUserRestrictions_old();
// Rich Presence data is automatically shared between friends who are in the same game
// Each user has a set of Key/Value pairs
// Note the following limits: k_cchMaxRichPresenceKeys, k_cchMaxRichPresenceKeyLength, k_cchMaxRichPresenceValueLength
// There are two magic keys:
// "status" - a UTF-8 string that will show up in the 'view game info' dialog in the Steam friends list
// "connect" - a UTF-8 string that contains the command-line for how a friend can connect to a game
// GetFriendRichPresence() returns an empty string "" if no value is set
// SetRichPresence() to a NULL or an empty string deletes the key
// You can iterate the current set of keys for a friend with GetFriendRichPresenceKeyCount()
// and GetFriendRichPresenceKeyByIndex() (typically only used for debugging)
bool SetRichPresence( const char *pchKey, const char *pchValue );
void ClearRichPresence();
// the overlay will keep calling GetFriendRichPresence() and spam the debug log, hence this function
const char *get_friend_rich_presence_silent( CSteamID steamIDFriend, const char *pchKey );
const char *GetFriendRichPresence( CSteamID steamIDFriend, const char *pchKey );
int GetFriendRichPresenceKeyCount( CSteamID steamIDFriend );
const char *GetFriendRichPresenceKeyByIndex( CSteamID steamIDFriend, int iKey );
// Requests rich presence for a specific user.
void RequestFriendRichPresence( CSteamID steamIDFriend );
// rich invite support
// if the target accepts the invite, the pchConnectString gets added to the command-line for launching the game
// if the game is already running, a GameRichPresenceJoinRequested_t callback is posted containing the connect string
// invites can only be sent to friends
bool InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString );
// recently-played-with friends iteration
// this iterates the entire list of users recently played with, across games
// GetFriendCoplayTime() returns as a unix time
int GetCoplayFriendCount();
CSteamID GetCoplayFriend( int iCoplayFriend );
void GetCoplayFriend(CSteamID& res, int iCoplayFriend );
int GetFriendCoplayTime( CSteamID steamIDFriend );
AppId_t GetFriendCoplayGame( CSteamID steamIDFriend );
// chat interface for games
// this allows in-game access to group (clan) chats from in the game
// the behavior is somewhat sophisticated, because the user may or may not be already in the group chat from outside the game or in the overlay
// use ActivateGameOverlayToUser( "chat", steamIDClan ) to open the in-game overlay version of the chat
STEAM_CALL_RESULT( JoinClanChatRoomCompletionResult_t )
SteamAPICall_t JoinClanChatRoom( CSteamID steamIDClan );
bool LeaveClanChatRoom( CSteamID steamIDClan );
int GetClanChatMemberCount( CSteamID steamIDClan );
CSteamID GetChatMemberByIndex( CSteamID steamIDClan, int iUser );
void GetChatMemberByIndex(CSteamID& res, CSteamID steamIDClan, int iUser );
bool SendClanChatMessage( CSteamID steamIDClanChat, const char *pchText );
int GetClanChatMessage( CSteamID steamIDClanChat, int iMessage, void *prgchText, int cchTextMax, EChatEntryType *peChatEntryType, STEAM_OUT_STRUCT() CSteamID *psteamidChatter );
bool IsClanChatAdmin( CSteamID steamIDClanChat, CSteamID steamIDUser );
// interact with the Steam (game overlay / desktop)
bool IsClanChatWindowOpenInSteam( CSteamID steamIDClanChat );
bool OpenClanChatWindowInSteam( CSteamID steamIDClanChat );
bool CloseClanChatWindowInSteam( CSteamID steamIDClanChat );
// peer-to-peer chat interception
// this is so you can show P2P chats inline in the game
bool SetListenForFriendsMessages( bool bInterceptEnabled );
bool ReplyToFriendMessage( CSteamID steamIDFriend, const char *pchMsgToSend );
int GetFriendMessage( CSteamID steamIDFriend, int iMessageID, void *pvData, int cubData, EChatEntryType *peChatEntryType );
// following apis
STEAM_CALL_RESULT( FriendsGetFollowerCount_t )
SteamAPICall_t GetFollowerCount( CSteamID steamID );
STEAM_CALL_RESULT( FriendsIsFollowing_t )
SteamAPICall_t IsFollowing( CSteamID steamID );
STEAM_CALL_RESULT( FriendsEnumerateFollowingList_t )
SteamAPICall_t EnumerateFollowingList( uint32 unStartIndex );
bool IsClanPublic( CSteamID steamIDClan );
bool IsClanOfficialGameGroup( CSteamID steamIDClan );
int GetNumChatsWithUnreadPriorityMessages();
void ActivateGameOverlayRemotePlayTogetherInviteDialog( CSteamID steamIDLobby );
// Call this before calling ActivateGameOverlayToWebPage() to have the Steam Overlay Browser block navigations
// to your specified protocol (scheme) uris and instead dispatch a OverlayBrowserProtocolNavigation_t callback to your game.
// ActivateGameOverlayToWebPage() must have been called with k_EActivateGameOverlayToWebPageMode_Modal
bool RegisterProtocolInOverlayBrowser( const char *pchProtocol );
// Activates the game overlay to open an invite dialog that will send the provided Rich Presence connect string to selected friends
void ActivateGameOverlayInviteDialogConnectString( const char *pchConnectString );
// Steam Community items equipped by a user on their profile
// You can register for EquippedProfileItemsChanged_t to know when a friend has changed their equipped profile items
STEAM_CALL_RESULT( EquippedProfileItems_t )
SteamAPICall_t RequestEquippedProfileItems( CSteamID steamID );
bool BHasEquippedProfileItem( CSteamID steamID, ECommunityProfileItemType itemType );
const char *GetProfileItemPropertyString( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop );
uint32 GetProfileItemPropertyUint( CSteamID steamID, ECommunityProfileItemType itemType, ECommunityProfileItemProperty prop );
};
#endif //__INCLUDED_STEAM_FRIENDS_H__

View File

@ -0,0 +1,62 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_GAME_COORDINATOR_H__
#define __INCLUDED_STEAM_GAME_COORDINATOR_H__
#include "base.h"
class Steam_Game_Coordinator :
public ISteamGameCoordinator
{
constexpr const static uint32 protobuf_mask = 0x80000000;
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::queue<std::string> outgoing_messages{};
void push_incoming(std::string message);
static void steam_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();
// sends a message to the Game Coordinator
EGCResults SendMessage_( uint32 unMsgType, const void *pubData, uint32 cubData );
// returns true if there is a message waiting from the game coordinator
bool IsMessageAvailable( uint32 *pcubMsgSize );
// fills the provided buffer with the first message in the queue and returns k_EGCResultOK or
// returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size.
// If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned
// and the message remains at the head of the queue.
EGCResults RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize );
};
#endif // __INCLUDED_STEAM_GAME_COORDINATOR_H__

118
dll/dll/steam_gamesearch.h Normal file
View File

@ -0,0 +1,118 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_GAMESEARCH_H__
#define __INCLUDED_STEAM_GAMESEARCH_H__
#include "base.h"
class Steam_Game_Search :
public ISteamGameSearch
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::chrono::time_point<std::chrono::steady_clock> initialized_time = std::chrono::steady_clock::now();
FSteamNetworkingSocketsDebugOutput debug_function{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
void RunCallbacks();
void Callback(Common_Message *msg);
public:
Steam_Game_Search(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Game_Search();
// =============================================================================================
// Game Player APIs
// a keyname and a list of comma separated values: one of which is must be found in order for the match to qualify
// fails if a search is currently in progress
EGameSearchErrorCode_t AddGameSearchParams( const char *pchKeyToFind, const char *pchValuesToFind );
// all players in lobby enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress
// if not the owner of the lobby or search already in progress this call fails
// periodic callbacks will be sent as queue time estimates change
EGameSearchErrorCode_t SearchForGameWithLobby( CSteamID steamIDLobby, int nPlayerMin, int nPlayerMax );
// user enter the queue and await a SearchForGameNotificationCallback_t callback. fails if another search is currently in progress
// periodic callbacks will be sent as queue time estimates change
EGameSearchErrorCode_t SearchForGameSolo( int nPlayerMin, int nPlayerMax );
// after receiving SearchForGameResultCallback_t, accept or decline the game
// multiple SearchForGameResultCallback_t will follow as players accept game until the host starts or cancels the game
EGameSearchErrorCode_t AcceptGame();
EGameSearchErrorCode_t DeclineGame();
// after receiving GameStartedByHostCallback_t get connection details to server
EGameSearchErrorCode_t RetrieveConnectionDetails( CSteamID steamIDHost, char *pchConnectionDetails, int cubConnectionDetails );
// leaves queue if still waiting
EGameSearchErrorCode_t EndGameSearch();
// =============================================================================================
// Game Host APIs
// a keyname and a list of comma separated values: all the values you allow
EGameSearchErrorCode_t SetGameHostParams( const char *pchKey, const char *pchValue );
// set connection details for players once game is found so they can connect to this server
EGameSearchErrorCode_t SetConnectionDetails( const char *pchConnectionDetails, int cubConnectionDetails );
// mark server as available for more players with nPlayerMin,nPlayerMax desired
// accept no lobbies with playercount greater than nMaxTeamSize
// the set of lobbies returned must be partitionable into teams of no more than nMaxTeamSize
// RequestPlayersForGameNotificationCallback_t callback will be sent when the search has started
// multple RequestPlayersForGameResultCallback_t callbacks will follow when players are found
EGameSearchErrorCode_t RequestPlayersForGame( int nPlayerMin, int nPlayerMax, int nMaxTeamSize );
// accept the player list and release connection details to players
// players will only be given connection details and host steamid when this is called
// ( allows host to accept after all players confirm, some confirm, or none confirm. decision is entirely up to the host )
EGameSearchErrorCode_t HostConfirmGameStart( uint64 ullUniqueGameID );
// cancel request and leave the pool of game hosts looking for players
// if a set of players has already been sent to host, all players will receive SearchForGameHostFailedToConfirm_t
EGameSearchErrorCode_t CancelRequestPlayersForGame();
// submit a result for one player. does not end the game. ullUniqueGameID continues to describe this game
EGameSearchErrorCode_t SubmitPlayerResult( uint64 ullUniqueGameID, CSteamID steamIDPlayer, EPlayerResult_t EPlayerResult );
// ends the game. no further SubmitPlayerResults for ullUniqueGameID will be accepted
// any future requests will provide a new ullUniqueGameID
EGameSearchErrorCode_t EndGame( uint64 ullUniqueGameID );
};
#endif // __INCLUDED_STEAM_GAMESEARCH_H__

374
dll/dll/steam_gameserver.h Normal file
View File

@ -0,0 +1,374 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_GAMESERVER_H__
#define __INCLUDED_STEAM_GAMESERVER_H__
#include "base.h"
#include "auth.h"
//-----------------------------------------------------------------------------
// Purpose: Functions for authenticating users via Steam to play on a game server
//-----------------------------------------------------------------------------
struct Gameserver_Outgoing_Packet {
std::vector<uint8_t> data{};
uint32 ip{};
uint16 port{};
};
struct Gameserver_Player_Info_t {
std::chrono::steady_clock::time_point join_time{};
std::string name{};
uint32 score{};
};
class Steam_GameServer :
public ISteamGameServer004,
public ISteamGameServer005,
public ISteamGameServer008,
public ISteamGameServer009,
public ISteamGameServer010,
public ISteamGameServer011,
public ISteamGameServer012,
public ISteamGameServer013,
public ISteamGameServer014,
public ISteamGameServer
{
class Settings *settings{};
class Networking *network{};
class SteamCallBacks *callbacks{};
CSteamID steam_id{};
bool call_servers_connected = false;
bool logged_in = false;
bool call_servers_disconnected = false;
Gameserver server_data{};
std::vector<std::pair<CSteamID, Gameserver_Player_Info_t>> players{};
uint32 flags{};
bool policy_response_called{};
std::chrono::high_resolution_clock::time_point last_sent_server_info{};
Auth_Manager *auth_manager{};
std::vector<struct Gameserver_Outgoing_Packet> outgoing_packets{};
public:
Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks);
~Steam_GameServer();
std::vector<std::pair<CSteamID, Gameserver_Player_Info_t>>* get_players();
//
// Basic server data. These properties, if set, must be set before before calling LogOn. They
// may not be changed after logged in.
//
/// This is called by SteamGameServer_Init, and you will usually not need to call it directly
bool InitGameServer( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, uint32 unFlags, AppId_t nGameAppId, const char *pchVersionString );
/// Game product identifier. This is currently used by the master server for version checking purposes.
/// It's a required field, but will eventually will go away, and the AppID will be used for this purpose.
void SetProduct( const char *pszProduct );
/// Description of the game. This is a required field and is displayed in the steam server browser....for now.
/// This is a required field, but it will go away eventually, as the data should be determined from the AppID.
void SetGameDescription( const char *pszGameDescription );
/// If your game is a "mod," pass the string that identifies it. The default is an empty string, meaning
/// this application is the original game, not a mod.
///
/// @see k_cbMaxGameServerGameDir
void SetModDir( const char *pszModDir );
/// Is this is a dedicated server? The default value is false.
void SetDedicatedServer( bool bDedicated );
//
// Login
//
/// Begin process to login to a persistent game server account
///
/// You need to register for callbacks to determine the result of this operation.
/// @see SteamServersConnected_t
/// @see SteamServerConnectFailure_t
/// @see SteamServersDisconnected_t
void LogOn( const char *pszToken );
void LogOn(
const char *pszAccountName,
const char *pszPassword
);
void LogOn();
/// Login to a generic, anonymous account.
///
/// Note: in previous versions of the SDK, this was automatically called within SteamGameServer_Init,
/// but this is no longer the case.
void LogOnAnonymous();
/// Begin process of logging game server out of steam
void LogOff();
// status functions
bool BLoggedOn();
bool BSecure();
CSteamID GetSteamID();
/// Returns true if the master server has requested a restart.
/// Only returns true once per request.
bool WasRestartRequested();
//
// Server state. These properties may be changed at any time.
//
/// Max player count that will be reported to server browser and client queries
void SetMaxPlayerCount( int cPlayersMax );
/// Number of bots. Default value is zero
void SetBotPlayerCount( int cBotplayers );
/// Set the name of server as it will appear in the server browser
///
/// @see k_cbMaxGameServerName
void SetServerName( const char *pszServerName );
/// Set name of map to report in the server browser
///
/// @see k_cbMaxGameServerName
void SetMapName( const char *pszMapName );
/// Let people know if your server will require a password
void SetPasswordProtected( bool bPasswordProtected );
/// Spectator server. The default value is zero, meaning the service
/// is not used.
void SetSpectatorPort( uint16 unSpectatorPort );
/// Name of the spectator server. (Only used if spectator port is nonzero.)
///
/// @see k_cbMaxGameServerMapName
void SetSpectatorServerName( const char *pszSpectatorServerName );
/// Call this to clear the whole list of key/values that are sent in rules queries.
void ClearAllKeyValues();
/// Call this to add/update a key/value pair.
void SetKeyValue( const char *pKey, const char *pValue );
/// Sets a string defining the "gametags" for this server, this is optional, but if it is set
/// it allows users to filter in the matchmaking/server-browser interfaces based on the value
///
/// @see k_cbMaxGameServerTags
void SetGameTags( const char *pchGameTags );
/// Sets a string defining the "gamedata" for this server, this is optional, but if it is set
/// it allows users to filter in the matchmaking/server-browser interfaces based on the value
/// don't set this unless it actually changes, its only uploaded to the master once (when
/// acknowledged)
///
/// @see k_cbMaxGameServerGameData
void SetGameData( const char *pchGameData );
/// Region identifier. This is an optional field, the default value is empty, meaning the "world" region
void SetRegion( const char *pszRegion );
//
// Player list management / authentication
//
// Handles receiving a new connection from a Steam user. This call will ask the Steam
// servers to validate the users identity, app ownership, and VAC status. If the Steam servers
// are off-line, then it will validate the cached ticket itself which will validate app ownership
// and identity. The AuthBlob here should be acquired on the game client using SteamUser()->InitiateGameConnection()
// and must then be sent up to the game server for authentication.
//
// Return Value: returns true if the users ticket passes basic checks. pSteamIDUser will contain the Steam ID of this user. pSteamIDUser must NOT be NULL
// If the call succeeds then you should expect a GSClientApprove_t or GSClientDeny_t callback which will tell you whether authentication
// for the user has succeeded or failed (the steamid in the callback will match the one returned by this call)
bool SendUserConnectAndAuthenticate( uint32 unIPClient, const void *pvAuthBlob, uint32 cubAuthBlobSize, CSteamID *pSteamIDUser );
void SendUserConnectAndAuthenticate( CSteamID steamIDUser, uint32 unIPClient, void *pvAuthBlob, uint32 cubAuthBlobSize );
// Creates a fake user (ie, a bot) which will be listed as playing on the server, but skips validation.
//
// Return Value: Returns a SteamID for the user to be tracked with, you should call HandleUserDisconnect()
// when this user leaves the server just like you would for a real user.
CSteamID CreateUnauthenticatedUserConnection();
// Should be called whenever a user leaves our game server, this lets Steam internally
// track which users are currently on which servers for the purposes of preventing a single
// account being logged into multiple servers, showing who is currently on a server, etc.
void SendUserDisconnect( CSteamID steamIDUser );
// Update the data to be displayed in the server browser and matchmaking interfaces for a user
// currently connected to the server. For regular users you must call this after you receive a
// GSUserValidationSuccess callback.
//
// Return Value: true if successful, false if failure (ie, steamIDUser wasn't for an active player)
bool BUpdateUserData( CSteamID steamIDUser, const char *pchPlayerName, uint32 uScore );
// You shouldn't need to call this as it is called internally by SteamGameServer_Init() and can only be called once.
//
// To update the data in this call which may change during the servers lifetime see UpdateServerStatus() below.
//
// Input: nGameAppID - The Steam assigned AppID for the game
// unServerFlags - Any applicable combination of flags (see k_unServerFlag____ constants below)
// unGameIP - The IP Address the server is listening for client connections on (might be INADDR_ANY)
// unGamePort - The port which the server is listening for client connections on
// unSpectatorPort - the port on which spectators can join to observe the server, 0 if spectating is not supported
// usQueryPort - The port which the ISteamMasterServerUpdater API should use in order to listen for matchmaking requests
// pchGameDir - A unique string identifier for your game
// pchVersion - The current version of the server as a string like 1.0.0.0
// bLanMode - Is this a LAN only server?
//
// bugbug jmccaskey - figure out how to remove this from the API and only expose via SteamGameServer_Init... or make this actually used,
// and stop calling it in SteamGameServer_Init()?
bool BSetServerType( uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort,
uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode );
bool BSetServerType( int32 nGameAppId, uint32 unServerFlags, uint32 unGameIP, uint16 unGamePort,
uint16 unSpectatorPort, uint16 usQueryPort, const char *pchGameDir, const char *pchVersion, bool bLANMode );
// Updates server status values which shows up in the server browser and matchmaking APIs
void UpdateServerStatus( int cPlayers, int cPlayersMax, int cBotPlayers,
const char *pchServerName, const char *pSpectatorServerName,
const char *pchMapName );
// This can be called if spectator goes away or comes back (passing 0 means there is no spectator server now).
void UpdateSpectatorPort( uint16 unSpectatorPort );
// Sets a string defining the "gametype" for this server, this is optional, but if it is set
// it allows users to filter in the matchmaking/server-browser interfaces based on the value
void SetGameType( const char *pchGameType );
// Ask if a user has a specific achievement for this game, will get a callback on reply
bool BGetUserAchievementStatus( CSteamID steamID, const char *pchAchievementName );
// New auth system APIs - do not mix with the old auth system APIs.
// ----------------------------------------------------------------
// Retrieve ticket to be sent to the entity who wishes to authenticate you ( using BeginAuthSession API ).
// pcbTicket retrieves the length of the actual ticket.
HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
// SteamNetworkingIdentity is an optional parameter to hold the public IP address of the entity you are connecting to
// if an IP address is passed Steam will only allow the ticket to be used by an entity with that IP address
HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket, const SteamNetworkingIdentity *pSnid );
// Authenticate ticket ( from GetAuthSessionTicket ) from entity steamID to be sure it is valid and isnt reused
// Registers for callbacks if the entity goes offline or cancels the ticket ( see ValidateAuthTicketResponse_t callback and EAuthSessionResponse )
EBeginAuthSessionResult BeginAuthSession( const void *pAuthTicket, int cbAuthTicket, CSteamID steamID );
// Stop tracking started by BeginAuthSession - called when no longer playing game with this entity
void EndAuthSession( CSteamID steamID );
// Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to
void CancelAuthTicket( HAuthTicket hAuthTicket );
// After receiving a user's authentication data, and passing it to SendUserConnectAndAuthenticate, use this function
// to determine if the user owns downloadable content specified by the provided AppID.
EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID );
// Ask if a user in in the specified group, results returns async by GSUserGroupStatus_t
// returns false if we're not connected to the steam servers and thus cannot ask
bool RequestUserGroupStatus( CSteamID steamIDUser, CSteamID steamIDGroup );
// these two functions s are deprecated, and will not return results
// they will be removed in a future version of the SDK
void GetGameplayStats( );
STEAM_CALL_RESULT( GSReputation_t )
SteamAPICall_t GetServerReputation();
// Returns the public IP of the server according to Steam, useful when the server is
// behind NAT and you want to advertise its IP in a lobby for other clients to directly
// connect to
uint32 GetPublicIP_old();
SteamIPAddress_t GetPublicIP();
void GetPublicIP_fix(SteamIPAddress_t *out);
// These are in GameSocketShare mode, where instead of ISteamGameServer creating its own
// socket to talk to the master server on, it lets the game use its socket to forward messages
// back and forth. This prevents us from requiring server ops to open up yet another port
// in their firewalls.
//
// the IP address and port should be in host order, i.e 127.0.0.1 == 0x7f000001
// These are used when you've elected to multiplex the game server's UDP socket
// rather than having the master server updater use its own sockets.
//
// Source games use this to simplify the job of the server admins, so they
// don't have to open up more ports on their firewalls.
// Call this when a packet that starts with 0xFFFFFFFF comes in. That means
// it's for us.
bool HandleIncomingPacket( const void *pData, int cbData, uint32 srcIP, uint16 srcPort );
// AFTER calling HandleIncomingPacket for any packets that came in that frame, call this.
// This gets a packet that the master server updater needs to send out on UDP.
// It returns the length of the packet it wants to send, or 0 if there are no more packets to send.
// Call this each frame until it returns 0.
int GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *pNetAdr, uint16 *pPort );
//
// Control heartbeats / advertisement with master server
//
// Call this as often as you like to tell the master server updater whether or not
// you want it to be active (default: off).
void EnableHeartbeats( bool bActive );
/// Indicate whether you wish to be listed on the master server list
/// and/or respond to server browser / LAN discovery packets.
/// The server starts with this value set to false. You should set all
/// relevant server parameters before enabling advertisement on the server.
///
/// (This function used to be named EnableHeartbeats, so if you are wondering
/// where that function went, it's right here. It does the same thing as before,
/// the old name was just confusing.)
void SetAdvertiseServerActive( bool bActive );
// You usually don't need to modify this.
// Pass -1 to use the default value for iHeartbeatInterval.
// Some mods change this.
void SetHeartbeatInterval( int iHeartbeatInterval );
// Force a heartbeat to steam at the next opportunity
void ForceHeartbeat();
void SetMasterServerHeartbeatInterval_DEPRECATED( int iHeartbeatInterval );
void ForceMasterServerHeartbeat_DEPRECATED();
// associate this game server with this clan for the purposes of computing player compat
STEAM_CALL_RESULT( AssociateWithClanResult_t )
SteamAPICall_t AssociateWithClan( CSteamID steamIDClan );
// ask if any of the current players dont want to play with this new player - or vice versa
STEAM_CALL_RESULT( ComputeNewPlayerCompatibilityResult_t )
SteamAPICall_t ComputeNewPlayerCompatibility( CSteamID steamIDNewPlayer );
// called by steam_client::runcallbacks
void RunCallbacks();
};
#endif // __INCLUDED_STEAM_GAMESERVER_H__

View File

@ -0,0 +1,117 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_GAMESERVERSTATS_H__
#define __INCLUDED_STEAM_GAMESERVERSTATS_H__
#include "base.h"
//-----------------------------------------------------------------------------
// Purpose: Functions for authenticating users via Steam to play on a game server
//-----------------------------------------------------------------------------
class Steam_GameServerStats :
public ISteamGameServerStats
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
struct RequestAllStats {
std::chrono::high_resolution_clock::time_point created{};
SteamAPICall_t steamAPICall{};
CSteamID steamIDUser{};
bool timeout = false;
};
struct CachedStat {
bool dirty = false; // true means it was changed on the server and should be sent to the user
GameServerStats_Messages::StatInfo stat{};
};
struct CachedAchievement {
bool dirty = false; // true means it was changed on the server and should be sent to the user
GameServerStats_Messages::AchievementInfo ach{};
};
struct UserData {
std::map<std::string, CachedStat> stats{};
std::map<std::string, CachedAchievement> achievements{};
};
std::vector<RequestAllStats> pending_RequestUserStats{};
std::map<uint64, UserData> all_users_data{};
CachedStat* find_stat(CSteamID steamIDUser, const std::string &key);
CachedAchievement* find_ach(CSteamID steamIDUser, const std::string &key);
void remove_timedout_userstats_requests();
void collect_and_send_updated_user_stats();
void steam_run_callback();
// reponses from player
void network_callback_initial_stats(Common_Message *msg);
void network_callback_updated_stats(Common_Message *msg);
void network_callback(Common_Message *msg);
// user connect/disconnect
void network_callback_low_level(Common_Message *msg);
static void steam_gameserverstats_network_low_level(void *object, Common_Message *msg);
static void steam_gameserverstats_network_callback(void *object, Common_Message *msg);
static void steam_gameserverstats_run_every_runcb(void *object);
public:
Steam_GameServerStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_GameServerStats();
// downloads stats for the user
// returns a GSStatsReceived_t callback when completed
// if the user has no stats, GSStatsReceived_t.m_eResult will be set to k_EResultFail
// these stats will only be auto-updated for clients playing on the server. For other
// users you'll need to call RequestUserStats() again to refresh any data
STEAM_CALL_RESULT( GSStatsReceived_t )
SteamAPICall_t RequestUserStats( CSteamID steamIDUser );
// requests stat information for a user, usable after a successful call to RequestUserStats()
bool GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData );
bool GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData );
bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved );
// Set / update stats and achievements.
// Note: These updates will work only on stats game servers are allowed to edit and only for
// game servers that have been declared as officially controlled by the game creators.
// Set the IP range of your official servers on the Steamworks page
bool SetUserStat( CSteamID steamIDUser, const char *pchName, int32 nData );
bool SetUserStat( CSteamID steamIDUser, const char *pchName, float fData );
bool UpdateUserAvgRateStat( CSteamID steamIDUser, const char *pchName, float flCountThisSession, double dSessionLength );
bool SetUserAchievement( CSteamID steamIDUser, const char *pchName );
bool ClearUserAchievement( CSteamID steamIDUser, const char *pchName );
// Store the current data on the server, will get a GSStatsStored_t callback when set.
//
// If the callback has a result of k_EResultInvalidParam, one or more stats
// uploaded has been rejected, either because they broke constraints
// or were out of date. In this case the server sends back updated values.
// The stats should be re-iterated to keep in sync.
STEAM_CALL_RESULT( GSStatsStored_t )
SteamAPICall_t StoreUserStats( CSteamID steamIDUser );
};
#endif // __INCLUDED_STEAM_GAMESERVERSTATS_H__

129
dll/dll/steam_gamestats.h Normal file
View File

@ -0,0 +1,129 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_GAMESTATS_H__
#define __INCLUDED_STEAM_GAMESTATS_H__
#include "base.h"
//-----------------------------------------------------------------------------
// Purpose: Functions for recording game play sessions and details thereof
//-----------------------------------------------------------------------------
class Steam_GameStats :
public ISteamGameStats
{
private:
// how much time to wait before removing ended sessions
constexpr const static int MAX_DEAD_SESSION_SECONDS = 15; // TODO not sure what would be sensible in this case
enum class AttributeType_t
{
Int, Str, Float, Int64,
};
struct Attribute_t
{
const AttributeType_t type;
union {
int32 n_data;
std::string s_data;
float f_data;
int64 ll_data;
};
Attribute_t(AttributeType_t type);
Attribute_t(const Attribute_t &other);
Attribute_t(Attribute_t &&other);
~Attribute_t();
};
struct Row_t
{
bool committed = false;
std::map<std::string, Attribute_t> attributes{};
};
struct Table_t
{
std::vector<Row_t> rows{};
};
struct Session_t
{
bool ended = false;
bool saved_to_disk = false;
uint64 account_id{};
EGameStatsAccountType nAccountType{};
RTime32 rtTimeStarted{};
RTime32 rtTimeEnded{};
int nReasonCode{};
std::map<std::string, Attribute_t> attributes{};
std::vector<std::pair<std::string, Table_t>> tables{};
};
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::map<uint64, Session_t> sessions{};
uint64 create_session_id() const;
bool valid_stats_account_type(int8 nAccountType);
Table_t *get_or_create_session_table(Session_t &session, const char *table_name);
Attribute_t *get_or_create_session_att(const char *att_name, Session_t &session, AttributeType_t type_if_create);
Attribute_t *get_or_create_row_att(uint64 ulRowID, const char *att_name, Table_t &table, AttributeType_t type_if_create);
Session_t* get_last_active_session();
std::string sanitize_csv_value(std::string_view value);
void save_session_to_disk(Steam_GameStats::Session_t &session, uint64 session_id);
void steam_run_callback();
// user connect/disconnect
void network_callback_low_level(Common_Message *msg);
static void steam_gamestats_network_low_level(void *object, Common_Message *msg);
static void steam_gamestats_run_every_runcb(void *object);
public:
Steam_GameStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_GameStats();
SteamAPICall_t GetNewSession( int8 nAccountType, uint64 ulAccountID, int32 nAppID, RTime32 rtTimeStarted );
SteamAPICall_t EndSession( uint64 ulSessionID, RTime32 rtTimeEnded, int nReasonCode );
EResult AddSessionAttributeInt( uint64 ulSessionID, const char* pstrName, int32 nData );
EResult AddSessionAttributeString( uint64 ulSessionID, const char* pstrName, const char *pstrData );
EResult AddSessionAttributeFloat( uint64 ulSessionID, const char* pstrName, float fData );
EResult AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const char *pstrTableName );
EResult CommitRow( uint64 ulRowID );
EResult CommitOutstandingRows( uint64 ulSessionID );
EResult AddRowAttributeInt( uint64 ulRowID, const char *pstrName, int32 nData );
EResult AddRowAtributeString( uint64 ulRowID, const char *pstrName, const char *pstrData );
EResult AddRowAttributeFloat( uint64 ulRowID, const char *pstrName, float fData );
EResult AddSessionAttributeInt64( uint64 ulSessionID, const char *pstrName, int64 llData );
EResult AddRowAttributeInt64( uint64 ulRowID, const char *pstrName, int64 llData );
};
#endif // __INCLUDED_STEAM_GAMESTATS_H__

187
dll/dll/steam_http.h Normal file
View File

@ -0,0 +1,187 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_HTTP_H__
#define __INCLUDED_STEAM_HTTP_H__
#include "base.h"
#include <curl/curl.h>
struct Steam_Http_Request {
HTTPRequestHandle handle{};
EHTTPMethod request_method{};
std::string url{};
uint64 timeout_sec = 60;
bool requires_valid_ssl = false;
constexpr const static char STEAM_DEFAULT_USER_AGENT[] = "Valve/Steam HTTP Client 1.0";
// check Steam_HTTP::SetHTTPRequestHeaderValue() and make sure to bypass the ones that should be reserved
std::map<std::string, std::string> headers{
{ "User-Agent", STEAM_DEFAULT_USER_AGENT },
{ "Cache-Control", "max-age=0" },
{ "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" },
{ "Upgrade-Insecure-Requests", "1" },
};
// GET or POST parameter value of the request
std::map<std::string, std::string> get_or_post_params{};
std::string post_raw{};
uint64 context_value{};
// target local filepath to save
std::string target_filepath{};
// TODO
HTTPCookieContainerHandle cookie_container_handle = INVALID_HTTPCOOKIE_HANDLE;
std::string response{};
};
class Steam_HTTP :
public ISteamHTTP001,
public ISteamHTTP002,
public ISteamHTTP
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
std::vector<Steam_Http_Request> requests{};
Steam_Http_Request *get_request(HTTPRequestHandle hRequest);
void online_http_request(Steam_Http_Request *request, SteamAPICall_t call_res_id);
public:
Steam_HTTP(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
// Initializes a new HTTP request, returning a handle to use in further operations on it. Requires
// the method (GET or POST) and the absolute URL for the request. Both http and https are supported,
// so this string must start with http:// or https:// and should look like http://store.steampowered.com/app/250/
// or such.
HTTPRequestHandle CreateHTTPRequest( EHTTPMethod eHTTPRequestMethod, const char *pchAbsoluteURL );
// Set a context value for the request, which will be returned in the HTTPRequestCompleted_t callback after
// sending the request. This is just so the caller can easily keep track of which callbacks go with which request data.
bool SetHTTPRequestContextValue( HTTPRequestHandle hRequest, uint64 ulContextValue );
// Set a timeout in seconds for the HTTP request, must be called prior to sending the request. Default
// timeout is 60 seconds if you don't call this. Returns false if the handle is invalid, or the request
// has already been sent.
bool SetHTTPRequestNetworkActivityTimeout( HTTPRequestHandle hRequest, uint32 unTimeoutSeconds );
// Set a request header value for the request, must be called prior to sending the request. Will
// return false if the handle is invalid or the request is already sent.
bool SetHTTPRequestHeaderValue( HTTPRequestHandle hRequest, const char *pchHeaderName, const char *pchHeaderValue );
// Set a GET or POST parameter value on the request, which is set will depend on the EHTTPMethod specified
// when creating the request. Must be called prior to sending the request. Will return false if the
// handle is invalid or the request is already sent.
bool SetHTTPRequestGetOrPostParameter( HTTPRequestHandle hRequest, const char *pchParamName, const char *pchParamValue );
// Sends the HTTP request, will return false on a bad handle, otherwise use SteamCallHandle to wait on
// asynchronous response via callback.
//
// Note: If the user is in offline mode in Steam, then this will add a only-if-cached cache-control
// header and only do a local cache lookup rather than sending any actual remote request.
bool SendHTTPRequest( HTTPRequestHandle hRequest, SteamAPICall_t *pCallHandle );
// Sends the HTTP request, will return false on a bad handle, otherwise use SteamCallHandle to wait on
// asynchronous response via callback for completion, and listen for HTTPRequestHeadersReceived_t and
// HTTPRequestDataReceived_t callbacks while streaming.
bool SendHTTPRequestAndStreamResponse( HTTPRequestHandle hRequest, SteamAPICall_t *pCallHandle );
// Defers a request you have sent, the actual HTTP client code may have many requests queued, and this will move
// the specified request to the tail of the queue. Returns false on invalid handle, or if the request is not yet sent.
bool DeferHTTPRequest( HTTPRequestHandle hRequest );
// Prioritizes a request you have sent, the actual HTTP client code may have many requests queued, and this will move
// the specified request to the head of the queue. Returns false on invalid handle, or if the request is not yet sent.
bool PrioritizeHTTPRequest( HTTPRequestHandle hRequest );
// Checks if a response header is present in a HTTP response given a handle from HTTPRequestCompleted_t, also
// returns the size of the header value if present so the caller and allocate a correctly sized buffer for
// GetHTTPResponseHeaderValue.
bool GetHTTPResponseHeaderSize( HTTPRequestHandle hRequest, const char *pchHeaderName, uint32 *unResponseHeaderSize );
// Gets header values from a HTTP response given a handle from HTTPRequestCompleted_t, will return false if the
// header is not present or if your buffer is too small to contain it's value. You should first call
// BGetHTTPResponseHeaderSize to check for the presence of the header and to find out the size buffer needed.
bool GetHTTPResponseHeaderValue( HTTPRequestHandle hRequest, const char *pchHeaderName, uint8 *pHeaderValueBuffer, uint32 unBufferSize );
// Gets the size of the body data from a HTTP response given a handle from HTTPRequestCompleted_t, will return false if the
// handle is invalid.
bool GetHTTPResponseBodySize( HTTPRequestHandle hRequest, uint32 *unBodySize );
// Gets the body data from a HTTP response given a handle from HTTPRequestCompleted_t, will return false if the
// handle is invalid or is to a streaming response, or if the provided buffer is not the correct size. Use BGetHTTPResponseBodySize first to find out
// the correct buffer size to use.
bool GetHTTPResponseBodyData( HTTPRequestHandle hRequest, uint8 *pBodyDataBuffer, uint32 unBufferSize );
// Gets the body data from a streaming HTTP response given a handle from HTTPRequestDataReceived_t. Will return false if the
// handle is invalid or is to a non-streaming response (meaning it wasn't sent with SendHTTPRequestAndStreamResponse), or if the buffer size and offset
// do not match the size and offset sent in HTTPRequestDataReceived_t.
bool GetHTTPStreamingResponseBodyData( HTTPRequestHandle hRequest, uint32 cOffset, uint8 *pBodyDataBuffer, uint32 unBufferSize );
// Releases an HTTP response handle, should always be called to free resources after receiving a HTTPRequestCompleted_t
// callback and finishing using the response.
bool ReleaseHTTPRequest( HTTPRequestHandle hRequest );
// Gets progress on downloading the body for the request. This will be zero unless a response header has already been
// received which included a content-length field. For responses that contain no content-length it will report
// zero for the duration of the request as the size is unknown until the connection closes.
bool GetHTTPDownloadProgressPct( HTTPRequestHandle hRequest, float *pflPercentOut );
// Sets the body for an HTTP Post request. Will fail and return false on a GET request, and will fail if POST params
// have already been set for the request. Setting this raw body makes it the only contents for the post, the pchContentType
// parameter will set the content-type header for the request so the server may know how to interpret the body.
bool SetHTTPRequestRawPostBody( HTTPRequestHandle hRequest, const char *pchContentType, uint8 *pubBody, uint32 unBodyLen );
// Creates a cookie container handle which you must later free with ReleaseCookieContainer(). If bAllowResponsesToModify=true
// than any response to your requests using this cookie container may add new cookies which may be transmitted with
// future requests. If bAllowResponsesToModify=false than only cookies you explicitly set will be sent. This API is just for
// during process lifetime, after steam restarts no cookies are persisted and you have no way to access the cookie container across
// repeat executions of your process.
HTTPCookieContainerHandle CreateCookieContainer( bool bAllowResponsesToModify );
// Release a cookie container you are finished using, freeing it's memory
bool ReleaseCookieContainer( HTTPCookieContainerHandle hCookieContainer );
// Adds a cookie to the specified cookie container that will be used with future requests.
bool SetCookie( HTTPCookieContainerHandle hCookieContainer, const char *pchHost, const char *pchUrl, const char *pchCookie );
// Set the cookie container to use for a HTTP request
bool SetHTTPRequestCookieContainer( HTTPRequestHandle hRequest, HTTPCookieContainerHandle hCookieContainer );
// Set the extra user agent info for a request, this doesn't clobber the normal user agent, it just adds the extra info on the end
bool SetHTTPRequestUserAgentInfo( HTTPRequestHandle hRequest, const char *pchUserAgentInfo );
// Set that https request should require verified SSL certificate via machines certificate trust store
bool SetHTTPRequestRequiresVerifiedCertificate( HTTPRequestHandle hRequest, bool bRequireVerifiedCertificate );
// Set an absolute timeout on the HTTP request, this is just a total time timeout different than the network activity timeout
// which can bump everytime we get more data
bool SetHTTPRequestAbsoluteTimeoutMS( HTTPRequestHandle hRequest, uint32 unMilliseconds );
// Check if the reason the request failed was because we timed it out (rather than some harder failure)
bool GetHTTPRequestWasTimedOut( HTTPRequestHandle hRequest, bool *pbWasTimedOut );
};
#endif // __INCLUDED_STEAM_HTTP_H__

408
dll/dll/steam_inventory.h Normal file
View File

@ -0,0 +1,408 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_INVENTORY_H__
#define __INCLUDED_STEAM_INVENTORY_H__
#include "base.h" // For SteamItemDef_t
struct Steam_Inventory_Requests {
double timeout = 0.1;
bool done = false;
bool full_query{};
SteamInventoryResult_t inventory_result{};
std::chrono::system_clock::time_point time_created{};
std::vector<SteamItemInstanceID_t> instance_ids{};
bool result_done() const;
// in seconds
uint32 timestamp() const;
};
class Steam_Inventory :
public ISteamInventory001,
public ISteamInventory002,
public ISteamInventory
{
public:
static constexpr const char items_user_file[] = "items.json";
static constexpr const char items_default_file[] = "default_items.json";
private:
class Settings *settings{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
class Local_Storage* local_storage{};
nlohmann::json defined_items{};
nlohmann::json user_items{};
std::vector<struct Steam_Inventory_Requests> inventory_requests{};
bool inventory_loaded{};
bool call_definition_update{};
bool item_definitions_loaded{};
struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0);
struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t resultHandle);
void read_items_db();
void read_inventory_db();
static void run_every_runcb_cb(void *object);
public:
Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, class Local_Storage *local_storage);
~Steam_Inventory();
// INVENTORY ASYNC RESULT MANAGEMENT
//
// Asynchronous inventory queries always output a result handle which can be used with
// GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will
// be triggered when the asynchronous result becomes ready (or fails).
//
// Find out the status of an asynchronous inventory result handle. Possible values:
// k_EResultPending - still in progress
// k_EResultOK - done, result ready
// k_EResultExpired - done, result ready, maybe out of date (see DeserializeResult)
// k_EResultInvalidParam - ERROR: invalid API call parameters
// k_EResultServiceUnavailable - ERROR: service temporarily down, you may retry later
// k_EResultLimitExceeded - ERROR: operation would exceed per-user inventory limits
// k_EResultFail - ERROR: unknown / generic error
STEAM_METHOD_DESC(Find out the status of an asynchronous inventory result handle.)
EResult GetResultStatus( SteamInventoryResult_t resultHandle );
// Copies the contents of a result set into a flat array. The specific
// contents of the result set depend on which query which was used.
STEAM_METHOD_DESC(Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used.)
bool GetResultItems( SteamInventoryResult_t resultHandle,
STEAM_OUT_ARRAY_COUNT( punOutItemsArraySize,Output array) SteamItemDetails_t *pOutItemsArray,
uint32 *punOutItemsArraySize );
// In combination with GetResultItems, you can use GetResultItemProperty to retrieve
// dynamic string properties for a given item returned in the result set.
//
// Property names are always composed of ASCII letters, numbers, and/or underscores.
//
// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available
// property names.
//
// If pchValueBuffer is NULL, *punValueBufferSize will contain the
// suggested buffer size. Otherwise it will be the number of bytes actually copied
// to pchValueBuffer. If the results do not fit in the given buffer, partial
// results may be copied.
bool GetResultItemProperty( SteamInventoryResult_t resultHandle,
uint32 unItemIndex,
const char *pchPropertyName,
STEAM_OUT_STRING_COUNT( punValueBufferSizeOut ) char *pchValueBuffer, uint32 *punValueBufferSizeOut );
// Returns the server time at which the result was generated. Compare against
// the value of IClientUtils::GetServerRealTime() to determine age.
STEAM_METHOD_DESC(Returns the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age.)
uint32 GetResultTimestamp( SteamInventoryResult_t resultHandle );
// Returns true if the result belongs to the target steam ID, false if the
// result does not. This is important when using DeserializeResult, to verify
// that a remote player is not pretending to have a different user's inventory.
STEAM_METHOD_DESC(Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory.)
bool CheckResultSteamID( SteamInventoryResult_t resultHandle, CSteamID steamIDExpected );
// Destroys a result handle and frees all associated memory.
STEAM_METHOD_DESC(Destroys a result handle and frees all associated memory.)
void DestroyResult( SteamInventoryResult_t resultHandle );
// INVENTORY ASYNC QUERY
//
// Captures the entire state of the current user's Steam inventory.
// You must call DestroyResult on this handle when you are done with it.
// Returns false and sets *pResultHandle to zero if inventory is unavailable.
// Note: calls to this function are subject to rate limits and may return
// cached results if called too frequently. It is suggested that you call
// this function only when you are about to display the user's full inventory,
// or if you expect that the inventory may have changed.
STEAM_METHOD_DESC(Captures the entire state of the current users Steam inventory.)
bool GetAllItems( SteamInventoryResult_t *pResultHandle );
// Captures the state of a subset of the current user's Steam inventory,
// identified by an array of item instance IDs. The results from this call
// can be serialized and passed to other players to "prove" that the current
// user owns specific items, without exposing the user's entire inventory.
// For example, you could call GetItemsByID with the IDs of the user's
// currently equipped cosmetic items and serialize this to a buffer, and
// then transmit this buffer to other players upon joining a game.
STEAM_METHOD_DESC(Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs.)
bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unCountInstanceIDs ) const SteamItemInstanceID_t *pInstanceIDs, uint32 unCountInstanceIDs );
// RESULT SERIALIZATION AND AUTHENTICATION
//
// Serialized result sets contain a short signature which can't be forged
// or replayed across different game sessions. A result set can be serialized
// on the local client, transmitted to other players via your game networking,
// and deserialized by the remote players. This is a secure way of preventing
// hackers from lying about posessing rare/high-value items.
// Serializes a result set with signature bytes to an output buffer. Pass
// NULL as an output buffer to get the required size via punOutBufferSize.
// The size of a serialized result depends on the number items which are being
// serialized. When securely transmitting items to other players, it is
// recommended to use "GetItemsByID" first to create a minimal result set.
// Results have a built-in timestamp which will be considered "expired" after
// an hour has elapsed. See DeserializeResult for expiration handling.
bool SerializeResult( SteamInventoryResult_t resultHandle, STEAM_OUT_BUFFER_COUNT(punOutBufferSize) void *pOutBuffer, uint32 *punOutBufferSize );
// Deserializes a result set and verifies the signature bytes. Returns false
// if bRequireFullOnlineVerify is set but Steam is running in Offline mode.
// Otherwise returns true and then delivers error codes via GetResultStatus.
//
// The bRESERVED_MUST_BE_FALSE flag is reserved for future use and should not
// be set to true by your game at this time.
//
// DeserializeResult has a potential soft-failure mode where the handle status
// is set to k_EResultExpired. GetResultItems() still succeeds in this mode.
// The "expired" result could indicate that the data may be out of date - not
// just due to timed expiration (one hour), but also because one of the items
// in the result set may have been traded or consumed since the result set was
// generated. You could compare the timestamp from GetResultTimestamp() to
// ISteamUtils::GetServerRealTime() to determine how old the data is. You could
// simply ignore the "expired" result code and continue as normal, or you
// could challenge the player with expired data to send an updated result set.
bool DeserializeResult( SteamInventoryResult_t *pOutResultHandle, STEAM_BUFFER_COUNT(punOutBufferSize) const void *pBuffer, uint32 unBufferSize, bool bRESERVED_MUST_BE_FALSE);
// INVENTORY ASYNC MODIFICATION
//
// GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t
// notification with a matching nCallbackContext parameter. This API is only intended
// for prototyping - it is only usable by Steam accounts that belong to the publisher group
// for your game.
// If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should
// describe the quantity of each item to generate.
bool GenerateItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength );
// GrantPromoItems() checks the list of promotional items for which the user may be eligible
// and grants the items (one time only). On success, the result set will include items which
// were granted, if any. If no items were granted because the user isn't eligible for any
// promotions, this is still considered a success.
STEAM_METHOD_DESC(GrantPromoItems() checks the list of promotional items for which the user may be eligible and grants the items (one time only).)
bool GrantPromoItems( SteamInventoryResult_t *pResultHandle );
// AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of
// scanning for all eligible promotional items, the check is restricted to a single item
// definition or set of item definitions. This can be useful if your game has custom UI for
// showing a specific promo item to the user.
bool AddPromoItem( SteamInventoryResult_t *pResultHandle, SteamItemDef_t itemDef );
bool AddPromoItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, uint32 unArrayLength );
// ConsumeItem() removes items from the inventory, permanently. They cannot be recovered.
// Not for the faint of heart - if your game implements item removal at all, a high-friction
// UI confirmation process is highly recommended.
STEAM_METHOD_DESC(ConsumeItem() removes items from the inventory permanently.)
bool ConsumeItem( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemConsume, uint32 unQuantity );
// ExchangeItems() is an atomic combination of item generation and consumption.
// It can be used to implement crafting recipes or transmutations, or items which unpack
// themselves into other items (e.g., a chest).
// Exchange recipes are defined in the ItemDef, and explicitly list the required item
// types and resulting generated type.
// Exchange recipes are evaluated atomically by the Inventory Service; if the supplied
// components do not match the recipe, or do not contain sufficient quantity, the
// exchange will fail.
bool ExchangeItems( SteamInventoryResult_t *pResultHandle,
STEAM_ARRAY_COUNT(unArrayGenerateLength) const SteamItemDef_t *pArrayGenerate, STEAM_ARRAY_COUNT(unArrayGenerateLength) const uint32 *punArrayGenerateQuantity, uint32 unArrayGenerateLength,
STEAM_ARRAY_COUNT(unArrayDestroyLength) const SteamItemInstanceID_t *pArrayDestroy, STEAM_ARRAY_COUNT(unArrayDestroyLength) const uint32 *punArrayDestroyQuantity, uint32 unArrayDestroyLength );
// TransferItemQuantity() is intended for use with items which are "stackable" (can have
// quantity greater than one). It can be used to split a stack into two, or to transfer
// quantity from one stack into another stack of identical items. To split one stack into
// two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated.
bool TransferItemQuantity( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemIdSource, uint32 unQuantity, SteamItemInstanceID_t itemIdDest );
// TIMED DROPS AND PLAYTIME CREDIT
//
// Deprecated. Calling this method is not required for proper playtime accounting.
STEAM_METHOD_DESC( Deprecated method. Playtime accounting is performed on the Steam servers. )
void SendItemDropHeartbeat();
// Playtime credit must be consumed and turned into item drops by your game. Only item
// definitions which are marked as "playtime item generators" can be spawned. The call
// will return an empty result set if there is not enough playtime credit for a drop.
// Your game should call TriggerItemDrop at an appropriate time for the user to receive
// new items, such as between rounds or while the player is dead. Note that players who
// hack their clients could modify the value of "dropListDefinition", so do not use it
// to directly control rarity.
// See your Steamworks configuration to set playtime drop rates for individual itemdefs.
// The client library will suppress too-frequent calls to this method.
STEAM_METHOD_DESC(Playtime credit must be consumed and turned into item drops by your game.)
bool TriggerItemDrop( SteamInventoryResult_t *pResultHandle, SteamItemDef_t dropListDefinition );
// Deprecated. This method is not supported.
bool TradeItems( SteamInventoryResult_t *pResultHandle, CSteamID steamIDTradePartner,
STEAM_ARRAY_COUNT(nArrayGiveLength) const SteamItemInstanceID_t *pArrayGive, STEAM_ARRAY_COUNT(nArrayGiveLength) const uint32 *pArrayGiveQuantity, uint32 nArrayGiveLength,
STEAM_ARRAY_COUNT(nArrayGetLength) const SteamItemInstanceID_t *pArrayGet, STEAM_ARRAY_COUNT(nArrayGetLength) const uint32 *pArrayGetQuantity, uint32 nArrayGetLength );
// ITEM DEFINITIONS
//
// Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000)
// to a set of string properties. Some of these properties are required to display items
// on the Steam community web site. Other properties can be defined by applications.
// Use of these functions is optional; there is no reason to call LoadItemDefinitions
// if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue
// weapon mod = 55) and does not allow for adding new item types without a client patch.
//
// LoadItemDefinitions triggers the automatic load and refresh of item definitions.
// Every time new item definitions are available (eg, from the dynamic addition of new
// item types while players are still in-game), a SteamInventoryDefinitionUpdate_t
// callback will be fired.
STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of item definitions.)
bool LoadItemDefinitions();
// GetItemDefinitionIDs returns the set of all defined item definition IDs (which are
// defined via Steamworks configuration, and not necessarily contiguous integers).
// If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will
// contain the total size necessary for a subsequent call. Otherwise, the call will
// return false if and only if there is not enough space in the output array.
bool GetItemDefinitionIDs(
STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs,
STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize );
// GetItemDefinitionProperty returns a string property from a given item definition.
// Note that some properties (for example, "name") may be localized and will depend
// on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage).
// Property names are always composed of ASCII letters, numbers, and/or underscores.
// Pass a NULL pointer for pchPropertyName to get a comma - separated list of available
// property names. If pchValueBuffer is NULL, *punValueBufferSize will contain the
// suggested buffer size. Otherwise it will be the number of bytes actually copied
// to pchValueBuffer. If the results do not fit in the given buffer, partial
// results may be copied.
bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPropertyName,
STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut );
// Request the list of "eligible" promo items that can be manually granted to the given
// user. These are promo items of type "manual" that won't be granted automatically.
// An example usage of this is an item that becomes available every week.
STEAM_CALL_RESULT( SteamInventoryEligiblePromoItemDefIDs_t )
SteamAPICall_t RequestEligiblePromoItemDefinitionsIDs( CSteamID steamID );
// After handling a SteamInventoryEligiblePromoItemDefIDs_t call result, use this
// function to pull out the list of item definition ids that the user can be
// manually granted via the AddPromoItems() call.
bool GetEligiblePromoItemDefinitionIDs(
CSteamID steamID,
STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs,
STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize );
// Starts the purchase process for the given item definitions. The callback SteamInventoryStartPurchaseResult_t
// will be posted if Steam was able to initialize the transaction.
//
// Once the purchase has been authorized and completed by the user, the callback SteamInventoryResultReady_t
// will be posted.
STEAM_CALL_RESULT( SteamInventoryStartPurchaseResult_t )
SteamAPICall_t StartPurchase( STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength );
// Request current prices for all applicable item definitions
STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t )
SteamAPICall_t RequestPrices();
// Returns the number of items with prices. Need to call RequestPrices() first.
uint32 GetNumItemsWithPrices();
bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs,
STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pCurrentPrices,
STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pBasePrices,
uint32 unArrayLength );
// Returns item definition ids and their prices in the user's local currency.
// Need to call RequestPrices() first.
bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs,
STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pPrices,
uint32 unArrayLength );
bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pCurrentPrice, uint64 *pBasePrice );
// Retrieves the price for the item definition id
// Returns false if there is no price stored for the item definition.
bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pPrice );
// Create a request to update properties on items
SteamInventoryUpdateHandle_t StartUpdateProperties();
// Remove the property on the item
bool RemoveProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName );
// Accessor methods to set properties on items
bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, const char *pchPropertyValue );
bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, bool bValue );
bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, int64 nValue );
bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, float flValue );
// Submit the update request by handle
bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventoryResult_t * pResultHandle );
bool InspectItem( SteamInventoryResult_t *pResultHandle, const char *pchItemToken );
void RunCallbacks();
};
#endif // __INCLUDED_STEAM_INVENTORY_H__

View File

@ -0,0 +1,131 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__
#define __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__
#include "base.h"
class Steam_Masterserver_Updater :
public ISteamMasterServerUpdater
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
void RunCallbacks();
void Callback(Common_Message *msg);
public:
Steam_Masterserver_Updater(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Masterserver_Updater();
// Call this as often as you like to tell the master server updater whether or not
// you want it to be active (default: off).
void SetActive( bool bActive );
// You usually don't need to modify this.
// Pass -1 to use the default value for iHeartbeatInterval.
// Some mods change this.
void SetHeartbeatInterval( int iHeartbeatInterval );
// These are in GameSocketShare mode, where instead of ISteamMasterServerUpdater creating its own
// socket to talk to the master server on, it lets the game use its socket to forward messages
// back and forth. This prevents us from requiring server ops to open up yet another port
// in their firewalls.
//
// the IP address and port should be in host order, i.e 127.0.0.1 == 0x7f000001
// These are used when you've elected to multiplex the game server's UDP socket
// rather than having the master server updater use its own sockets.
//
// Source games use this to simplify the job of the server admins, so they
// don't have to open up more ports on their firewalls.
// Call this when a packet that starts with 0xFFFFFFFF comes in. That means
// it's for us.
bool HandleIncomingPacket( const void *pData, int cbData, uint32 srcIP, uint16 srcPort );
// AFTER calling HandleIncomingPacket for any packets that came in that frame, call this.
// This gets a packet that the master server updater needs to send out on UDP.
// It returns the length of the packet it wants to send, or 0 if there are no more packets to send.
// Call this each frame until it returns 0.
int GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *pNetAdr, uint16 *pPort );
// Functions to set various fields that are used to respond to queries.
// Call this to set basic data that is passed to the server browser.
void SetBasicServerData(
unsigned short nProtocolVersion,
bool bDedicatedServer,
const char *pRegionName,
const char *pProductName,
unsigned short nMaxReportedClients,
bool bPasswordProtected,
const char *pGameDescription );
// Call this to clear the whole list of key/values that are sent in rules queries.
void ClearAllKeyValues();
// Call this to add/update a key/value pair.
void SetKeyValue( const char *pKey, const char *pValue );
// You can call this upon shutdown to clear out data stored for this game server and
// to tell the master servers that this server is going away.
void NotifyShutdown();
// Returns true if the master server has requested a restart.
// Only returns true once per request.
bool WasRestartRequested();
// Force it to request a heartbeat from the master servers.
void ForceHeartbeat();
// Manually edit and query the master server list.
// It will provide name resolution and use the default master server port if none is provided.
bool AddMasterServer( const char *pServerAddress );
bool RemoveMasterServer( const char *pServerAddress );
int GetNumMasterServers();
// Returns the # of bytes written to pOut.
int GetMasterServerAddress( int iServer, char *pOut, int outBufferSize );
};
#endif // __INCLUDED_STEAM_MASTERSERVER_UPDATER_H__

388
dll/dll/steam_matchmaking.h Normal file
View File

@ -0,0 +1,388 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MATCHMAKING_H__
#define __INCLUDED_STEAM_MATCHMAKING_H__
#include "base.h"
struct Pending_Joins {
SteamAPICall_t api_id{};
CSteamID lobby_id{};
std::chrono::high_resolution_clock::time_point joined{};
bool message_sent{};
};
struct Pending_Creates {
SteamAPICall_t api_id{};
std::chrono::high_resolution_clock::time_point created{};
ELobbyType eLobbyType{};
int cMaxMembers{};
};
struct Data_Requested {
CSteamID lobby_id{};
std::chrono::high_resolution_clock::time_point requested{};
};
struct Filter_Values {
std::string key{};
std::string value_string{};
int value_int{};
bool is_int{};
ELobbyComparison eComparisonType{};
};
struct Chat_Entry {
std::string message{};
EChatEntryType type{};
CSteamID lobby_id, user_id{};
};
class Steam_Matchmaking :
public ISteamMatchmaking002,
public ISteamMatchmaking003,
public ISteamMatchmaking004,
public ISteamMatchmaking005,
public ISteamMatchmaking006,
public ISteamMatchmaking007,
public ISteamMatchmaking008,
public ISteamMatchmaking
{
class Settings *settings{};
class Local_Storage *local_storage{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::vector<Lobby> lobbies{};
std::chrono::high_resolution_clock::time_point last_sent_lobbies{};
std::vector<struct Pending_Joins> pending_joins{};
std::vector<struct Pending_Creates> pending_creates{};
std::vector<struct Filter_Values> filter_values{};
int filter_max_results{};
std::vector<struct Filter_Values> filter_values_copy{};
int filter_max_results_copy{};
std::vector<CSteamID> filtered_lobbies{};
std::chrono::high_resolution_clock::time_point lobby_last_search{};
SteamAPICall_t search_call_api_id{};
bool searching{};
std::vector<struct Chat_Entry> chat_entries{};
std::vector<struct Data_Requested> data_requested{};
std::map<uint64, ::google::protobuf::Map<std::string, std::string>> self_lobby_member_data{};
google::protobuf::Map<std::string,std::string>::const_iterator caseinsensitive_find(const ::google::protobuf::Map< ::std::string, ::std::string >& map, std::string key);
static Lobby_Member *get_lobby_member(Lobby *lobby, CSteamID user_id);
static bool add_member_to_lobby(Lobby *lobby, CSteamID id);
static bool leave_lobby(Lobby *lobby, CSteamID id);
Lobby *get_lobby(CSteamID id);
void send_lobby_data();
void trigger_lobby_dataupdate(CSteamID lobby, CSteamID member, bool success, double cb_timeout=0.005, bool send_changed_lobby=true);
void trigger_lobby_member_join_leave(CSteamID lobby, CSteamID member, bool leaving, bool success, double cb_timeout=0.0);
bool send_owner_packet(CSteamID lobby_id, Lobby_Messages *message);
bool send_clients_packet(CSteamID lobby_id, Lobby_Messages *message);
bool send_lobby_members_packet(CSteamID lobby_id, Lobby_Messages *message);
bool change_owner(Lobby *lobby, CSteamID new_owner);
void send_gameservercreated_cb(uint64 room_id, uint64 server_id, uint32 ip, uint16 port);
void remove_lobbies();
void on_self_enter_leave_lobby(CSteamID id, int type, bool leaving);
void create_pending_lobbies();
void run_background();
void RunCallbacks();
void Callback(Common_Message *msg);
static void steam_matchmaking_callback(void *object, Common_Message *msg);
static void steam_matchmaking_run_every_runcb(void *object);
public:
Steam_Matchmaking(class Settings *settings, class Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Matchmaking();
// game server favorites storage
// saves basic details about a multiplayer game server locally
// returns the number of favorites servers the user has stored
int GetFavoriteGameCount();
// returns the details of the game server
// iGame is of range [0,GetFavoriteGameCount())
// *pnIP, *pnConnPort are filled in the with IP:port of the game server
// *punFlags specify whether the game server was stored as an explicit favorite or in the history of connections
// *pRTime32LastPlayedOnServer is filled in the with the Unix time the favorite was added
bool GetFavoriteGame( int iGame, AppId_t *pnAppID, uint32 *pnIP, uint16 *pnConnPort, uint16 *pnQueryPort, uint32 *punFlags, uint32 *pRTime32LastPlayedOnServer );
// adds the game server to the local list; updates the time played of the server if it already exists in the list
int AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags, uint32 rTime32LastPlayedOnServer );
// removes the game server from the local storage; returns true if one was removed
bool RemoveFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags );
///////
// Game lobby functions
// Get a list of relevant lobbies
// this is an asynchronous request
// results will be returned by LobbyMatchList_t callback & call result, with the number of lobbies found
// this will never return lobbies that are full
// to add more filter, the filter calls below need to be call before each and every RequestLobbyList() call
// use the CCallResult<> object in steam_api.h to match the SteamAPICall_t call result to a function in an object, e.g.
/*
class CMyLobbyListManager
{
CCallResult<CMyLobbyListManager, LobbyMatchList_t> m_CallResultLobbyMatchList;
void FindLobbies()
{
// SteamMatchmaking()->AddRequestLobbyListFilter*() functions would be called here, before RequestLobbyList();
m_CallResultLobbyMatchList.Set( hSteamAPICall, this, &CMyLobbyListManager::OnLobbyMatchList );
}
void OnLobbyMatchList( LobbyMatchList_t *pLobbyMatchList, bool bIOFailure )
{
// lobby list has be retrieved from Steam back-end, use results
}
}
*/
//
STEAM_CALL_RESULT( LobbyMatchList_t )
SteamAPICall_t RequestLobbyList();
void RequestLobbyList_OLD();
// filters for lobbies
// this needs to be called before RequestLobbyList() to take effect
// these are cleared on each call to RequestLobbyList()
void AddRequestLobbyListStringFilter( const char *pchKeyToMatch, const char *pchValueToMatch, ELobbyComparison eComparisonType );
// numerical comparison
void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, ELobbyComparison eComparisonType );
// returns results closest to the specified value. Multiple near filters can be added, with early filters taking precedence
void AddRequestLobbyListNearValueFilter( const char *pchKeyToMatch, int nValueToBeCloseTo );
// returns only lobbies with the specified number of slots available
void AddRequestLobbyListFilterSlotsAvailable( int nSlotsAvailable );
// sets the distance for which we should search for lobbies (based on users IP address to location map on the Steam backed)
void AddRequestLobbyListDistanceFilter( ELobbyDistanceFilter eLobbyDistanceFilter );
// sets how many results to return, the lower the count the faster it is to download the lobby results & details to the client
void AddRequestLobbyListResultCountFilter( int cMaxResults );
void AddRequestLobbyListCompatibleMembersFilter( CSteamID steamIDLobby );
void AddRequestLobbyListFilter( const char *pchKeyToMatch, const char *pchValueToMatch );
void AddRequestLobbyListNumericalFilter( const char *pchKeyToMatch, int nValueToMatch, int nComparisonType );
void AddRequestLobbyListSlotsAvailableFilter();
// returns the CSteamID of a lobby, as retrieved by a RequestLobbyList call
// should only be called after a LobbyMatchList_t callback is received
// iLobby is of the range [0, LobbyMatchList_t::m_nLobbiesMatching)
// the returned CSteamID::IsValid() will be false if iLobby is out of range
CSteamID GetLobbyByIndex( int iLobby );
void GetLobbyByIndex(CSteamID& res, int iLobby );
// Create a lobby on the Steam servers.
// If private, then the lobby will not be returned by any RequestLobbyList() call; the CSteamID
// of the lobby will need to be communicated via game channels or via InviteUserToLobby()
// this is an asynchronous request
// results will be returned by LobbyCreated_t callback and call result; lobby is joined & ready to use at this point
// a LobbyEnter_t callback will also be received (since the local user is joining their own lobby)
STEAM_CALL_RESULT( LobbyCreated_t )
SteamAPICall_t CreateLobby( ELobbyType eLobbyType, int cMaxMembers );
SteamAPICall_t CreateLobby( ELobbyType eLobbyType );
void CreateLobby_OLD( ELobbyType eLobbyType );
void CreateLobby( bool bPrivate );
// Joins an existing lobby
// this is an asynchronous request
// results will be returned by LobbyEnter_t callback & call result, check m_EChatRoomEnterResponse to see if was successful
// lobby metadata is available to use immediately on this call completing
STEAM_CALL_RESULT( LobbyEnter_t )
SteamAPICall_t JoinLobby( CSteamID steamIDLobby );
void JoinLobby_OLD( CSteamID steamIDLobby );
// Leave a lobby; this will take effect immediately on the client side
// other users in the lobby will be notified by a LobbyChatUpdate_t callback
void LeaveLobby( CSteamID steamIDLobby );
// Invite another user to the lobby
// the target user will receive a LobbyInvite_t callback
// will return true if the invite is successfully sent, whether or not the target responds
// returns false if the local user is not connected to the Steam servers
// if the other user clicks the join link, a GameLobbyJoinRequested_t will be posted if the user is in-game,
// or if the game isn't running yet the game will be launched with the parameter +connect_lobby <64-bit lobby id>
bool InviteUserToLobby( CSteamID steamIDLobby, CSteamID steamIDInvitee );
// Lobby iteration, for viewing details of users in a lobby
// only accessible if the lobby user is a member of the specified lobby
// persona information for other lobby members (name, avatar, etc.) will be asynchronously received
// and accessible via ISteamFriends interface
// returns the number of users in the specified lobby
int GetNumLobbyMembers( CSteamID steamIDLobby );
// returns the CSteamID of a user in the lobby
// iMember is of range [0,GetNumLobbyMembers())
// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby
CSteamID GetLobbyMemberByIndex( CSteamID steamIDLobby, int iMember );
void GetLobbyMemberByIndex(CSteamID& res, CSteamID steamIDLobby, int iMember );
// Get data associated with this lobby
// takes a simple key, and returns the string associated with it
// "" will be returned if no value is set, or if steamIDLobby is invalid
const char *GetLobbyData( CSteamID steamIDLobby, const char *pchKey );
// Sets a key/value pair in the lobby metadata
// each user in the lobby will be broadcast this new value, and any new users joining will receive any existing data
// this can be used to set lobby names, map, etc.
// to reset a key, just set it to ""
// other users in the lobby will receive notification of the lobby data change via a LobbyDataUpdate_t callback
bool SetLobbyData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue );
// returns the number of metadata keys set on the specified lobby
int GetLobbyDataCount( CSteamID steamIDLobby );
// returns a lobby metadata key/values pair by index, of range [0, GetLobbyDataCount())
bool GetLobbyDataByIndex( CSteamID steamIDLobby, int iLobbyData, char *pchKey, int cchKeyBufferSize, char *pchValue, int cchValueBufferSize );
// removes a metadata key from the lobby
bool DeleteLobbyData( CSteamID steamIDLobby, const char *pchKey );
// Gets per-user metadata for someone in this lobby
const char *GetLobbyMemberData( CSteamID steamIDLobby, CSteamID steamIDUser, const char *pchKey );
// Sets per-user metadata (for the local user implicitly)
void SetLobbyMemberData( CSteamID steamIDLobby, const char *pchKey, const char *pchValue );
// Broadcasts a chat message to the all the users in the lobby
// users in the lobby (including the local user) will receive a LobbyChatMsg_t callback
// returns true if the message is successfully sent
// pvMsgBody can be binary or text data, up to 4k
// if pvMsgBody is text, cubMsgBody should be strlen( text ) + 1, to include the null terminator
bool SendLobbyChatMsg( CSteamID steamIDLobby, const void *pvMsgBody, int cubMsgBody );
// Get a chat message as specified in a LobbyChatMsg_t callback
// iChatID is the LobbyChatMsg_t::m_iChatID value in the callback
// *pSteamIDUser is filled in with the CSteamID of the member
// *pvData is filled in with the message itself
// return value is the number of bytes written into the buffer
int GetLobbyChatEntry( CSteamID steamIDLobby, int iChatID, STEAM_OUT_STRUCT() CSteamID *pSteamIDUser, void *pvData, int cubData, EChatEntryType *peChatEntryType );
// Refreshes metadata for a lobby you're not necessarily in right now
// you never do this for lobbies you're a member of, only if your
// this will send down all the metadata associated with a lobby
// this is an asynchronous call
// returns false if the local user is not connected to the Steam servers
// results will be returned by a LobbyDataUpdate_t callback
// if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false
bool RequestLobbyData( CSteamID steamIDLobby );
// sets the game server associated with the lobby
// usually at this point, the users will join the specified game server
// either the IP/Port or the steamID of the game server has to be valid, depending on how you want the clients to be able to connect
void SetLobbyGameServer( CSteamID steamIDLobby, uint32 unGameServerIP, uint16 unGameServerPort, CSteamID steamIDGameServer );
// returns the details of a game server set in a lobby - returns false if there is no game server set, or that lobby doesn't exist
bool GetLobbyGameServer( CSteamID steamIDLobby, uint32 *punGameServerIP, uint16 *punGameServerPort, STEAM_OUT_STRUCT() CSteamID *psteamIDGameServer );
// set the limit on the # of users who can join the lobby
bool SetLobbyMemberLimit( CSteamID steamIDLobby, int cMaxMembers );
// returns the current limit on the # of users who can join the lobby; returns 0 if no limit is defined
int GetLobbyMemberLimit( CSteamID steamIDLobby );
void SetLobbyVoiceEnabled( CSteamID steamIDLobby, bool bVoiceEnabled );
// updates which type of lobby it is
// only lobbies that are k_ELobbyTypePublic or k_ELobbyTypeInvisible, and are set to joinable, will be returned by RequestLobbyList() calls
bool SetLobbyType( CSteamID steamIDLobby, ELobbyType eLobbyType );
// sets whether or not a lobby is joinable - defaults to true for a new lobby
// if set to false, no user can join, even if they are a friend or have been invited
bool SetLobbyJoinable( CSteamID steamIDLobby, bool bLobbyJoinable );
// returns the current lobby owner
// you must be a member of the lobby to access this (Mr_Goldberg note: This is a lie)
// there always one lobby owner - if the current owner leaves, another user will become the owner
// it is possible (bur rare) to join a lobby just as the owner is leaving, thus entering a lobby with self as the owner
CSteamID GetLobbyOwner( CSteamID steamIDLobby );
void GetLobbyOwner(CSteamID& res, CSteamID steamIDLobby );
// asks the Steam servers for a list of lobbies that friends are in
// returns results by posting one RequestFriendsLobbiesResponse_t callback per friend/lobby pair
// if no friends are in lobbies, RequestFriendsLobbiesResponse_t will be posted but with 0 results
// filters don't apply to lobbies (currently)
bool RequestFriendsLobbies();
float GetLobbyDistance( CSteamID steamIDLobby );
// changes who the lobby owner is
// you must be the lobby owner for this to succeed, and steamIDNewOwner must be in the lobby
// after completion, the local user will no longer be the owner
bool SetLobbyOwner( CSteamID steamIDLobby, CSteamID steamIDNewOwner );
// link two lobbies for the purposes of checking player compatibility
// you must be the lobby owner of both lobbies
bool SetLinkedLobby( CSteamID steamIDLobby, CSteamID steamIDLobbyDependent );
};
#endif // __INCLUDED_STEAM_MATCHMAKING_H__

View File

@ -0,0 +1,251 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__
#define __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__
#include "base.h"
#include <ssq/a2s.h>
struct Steam_Matchmaking_Servers_Direct_IP_Request {
HServerQuery id{};
uint32 ip{};
uint16 port{};
std::chrono::high_resolution_clock::time_point created{};
ISteamMatchmakingRulesResponse *rules_response{};
ISteamMatchmakingPlayersResponse *players_response{};
ISteamMatchmakingPingResponse *ping_response{};
};
struct Steam_Matchmaking_Servers_Gameserver_Friends {
uint64 source_id{};
uint32 ip{};
uint16 port{};
std::chrono::high_resolution_clock::time_point last_recv{};
};
struct Steam_Matchmaking_Servers_Gameserver {
Gameserver server{};
std::chrono::high_resolution_clock::time_point last_recv{};
EMatchMakingType type{};
};
struct Steam_Matchmaking_Request {
AppId_t appid{};
HServerListRequest id{};
ISteamMatchmakingServerListResponse *callbacks{};
ISteamMatchmakingServerListResponse001 *old_callbacks{};
bool completed{}, cancelled{}, released{};
std::vector <struct Steam_Matchmaking_Servers_Gameserver> gameservers_filtered{};
EMatchMakingType type{};
};
class Steam_Matchmaking_Servers :
public ISteamMatchmakingServers001,
public ISteamMatchmakingServers
{
class Settings *settings{};
class Local_Storage *local_storage{};
class Networking *network{};
std::vector <struct Steam_Matchmaking_Servers_Gameserver> gameservers{};
std::vector <struct Steam_Matchmaking_Servers_Gameserver_Friends> gameservers_friends{};
std::vector <struct Steam_Matchmaking_Request> requests{};
std::vector <struct Steam_Matchmaking_Servers_Direct_IP_Request> direct_ip_requests{};
HServerListRequest RequestServerList(AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse, EMatchMakingType type);
void RequestOldServerList(AppId_t iApp, ISteamMatchmakingServerListResponse001 *pRequestServersResponse, EMatchMakingType type);
//
static void network_callback(void *object, Common_Message *msg);
void server_details(Gameserver *g, gameserveritem_t *server);
void server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r);
void server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r);
void Callback(Common_Message *msg);
public:
Steam_Matchmaking_Servers(class Settings *settings, class Local_Storage *local_storage, class Networking *network);
~Steam_Matchmaking_Servers();
// Request a new list of servers of a particular type. These calls each correspond to one of the EMatchMakingType values.
// Each call allocates a new asynchronous request object.
// Request object must be released by calling ReleaseRequest( hServerListRequest )
HServerListRequest RequestInternetServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse );
HServerListRequest RequestLANServerList( AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse );
HServerListRequest RequestFriendsServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse );
HServerListRequest RequestFavoritesServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse );
HServerListRequest RequestHistoryServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse );
HServerListRequest RequestSpectatorServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse );
void RequestInternetServerList( AppId_t iApp, MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
void RequestLANServerList( AppId_t iApp, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
void RequestFriendsServerList( AppId_t iApp, MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
void RequestFavoritesServerList( AppId_t iApp, MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
void RequestHistoryServerList( AppId_t iApp, MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
void RequestSpectatorServerList( AppId_t iApp, MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse001 *pRequestServersResponse );
// Releases the asynchronous request object and cancels any pending query on it if there's a pending query in progress.
// RefreshComplete callback is not posted when request is released.
void ReleaseRequest( HServerListRequest hServerListRequest );
/* the filter operation codes that go in the key part of MatchMakingKeyValuePair_t should be one of these:
"map"
- Server passes the filter if the server is playing the specified map.
"gamedataand"
- Server passes the filter if the server's game data (ISteamGameServer::SetGameData) contains all of the
specified strings. The value field is a comma-delimited list of strings to match.
"gamedataor"
- Server passes the filter if the server's game data (ISteamGameServer::SetGameData) contains at least one of the
specified strings. The value field is a comma-delimited list of strings to match.
"gamedatanor"
- Server passes the filter if the server's game data (ISteamGameServer::SetGameData) does not contain any
of the specified strings. The value field is a comma-delimited list of strings to check.
"gametagsand"
- Server passes the filter if the server's game tags (ISteamGameServer::SetGameTags) contains all
of the specified strings. The value field is a comma-delimited list of strings to check.
"gametagsnor"
- Server passes the filter if the server's game tags (ISteamGameServer::SetGameTags) does not contain any
of the specified strings. The value field is a comma-delimited list of strings to check.
"and" (x1 && x2 && ... && xn)
"or" (x1 || x2 || ... || xn)
"nand" !(x1 && x2 && ... && xn)
"nor" !(x1 || x2 || ... || xn)
- Performs Boolean operation on the following filters. The operand to this filter specifies
the "size" of the Boolean inputs to the operation, in Key/value pairs. (The keyvalue
pairs must immediately follow, i.e. this is a prefix logical operator notation.)
In the simplest case where Boolean expressions are not nested, this is simply
the number of operands.
For example, to match servers on a particular map or with a particular tag, would would
use these filters.
( server.map == "cp_dustbowl" || server.gametags.contains("payload") )
"or", "2"
"map", "cp_dustbowl"
"gametagsand", "payload"
If logical inputs are nested, then the operand specifies the size of the entire
"length" of its operands, not the number of immediate children.
( server.map == "cp_dustbowl" || ( server.gametags.contains("payload") && !server.gametags.contains("payloadrace") ) )
"or", "4"
"map", "cp_dustbowl"
"and", "2"
"gametagsand", "payload"
"gametagsnor", "payloadrace"
Unary NOT can be achieved using either "nand" or "nor" with a single operand.
"addr"
- Server passes the filter if the server's query address matches the specified IP or IP:port.
"gameaddr"
- Server passes the filter if the server's game address matches the specified IP or IP:port.
The following filter operations ignore the "value" part of MatchMakingKeyValuePair_t
"dedicated"
- Server passes the filter if it passed true to SetDedicatedServer.
"secure"
- Server passes the filter if the server is VAC-enabled.
"notfull"
- Server passes the filter if the player count is less than the reported max player count.
"hasplayers"
- Server passes the filter if the player count is greater than zero.
"noplayers"
- Server passes the filter if it doesn't have any players.
"linux"
- Server passes the filter if it's a linux server
*/
// Get details on a given server in the list, you can get the valid range of index
// values by calling GetServerCount(). You will also receive index values in
// ISteamMatchmakingServerListResponse::ServerResponded() callbacks
gameserveritem_t *GetServerDetails( HServerListRequest hRequest, int iServer );
// Cancel an request which is operation on the given list type. You should call this to cancel
// any in-progress requests before destructing a callback object that may have been passed
// to one of the above list request calls. Not doing so may result in a crash when a callback
// occurs on the destructed object.
// Canceling a query does not release the allocated request handle.
// The request handle must be released using ReleaseRequest( hRequest )
void CancelQuery( HServerListRequest hRequest );
// Ping every server in your list again but don't update the list of servers
// Query callback installed when the server list was requested will be used
// again to post notifications and RefreshComplete, so the callback must remain
// valid until another RefreshComplete is called on it or the request
// is released with ReleaseRequest( hRequest )
void RefreshQuery( HServerListRequest hRequest );
// Returns true if the list is currently refreshing its server list
bool IsRefreshing( HServerListRequest hRequest );
// How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1
int GetServerCount( HServerListRequest hRequest );
// Refresh a single server inside of a query (rather than all the servers )
void RefreshServer( HServerListRequest hRequest, int iServer );
// Get details on a given server in the list, you can get the valid range of index
// values by calling GetServerCount(). You will also receive index values in
// ISteamMatchmakingServerListResponse::ServerResponded() callbacks
gameserveritem_t *GetServerDetails( EMatchMakingType eType, int iServer );
// Cancel an request which is operation on the given list type. You should call this to cancel
// any in-progress requests before destructing a callback object that may have been passed
// to one of the above list request calls. Not doing so may result in a crash when a callback
// occurs on the destructed object.
void CancelQuery( EMatchMakingType eType );
// Ping every server in your list again but don't update the list of servers
void RefreshQuery( EMatchMakingType eType );
// Returns true if the list is currently refreshing its server list
bool IsRefreshing( EMatchMakingType eType );
// How many servers in the given list, GetServerDetails above takes 0... GetServerCount() - 1
int GetServerCount( EMatchMakingType eType );
// Refresh a single server inside of a query (rather than all the servers )
void RefreshServer( EMatchMakingType eType, int iServer );
//-----------------------------------------------------------------------------
// Queries to individual servers directly via IP/Port
//-----------------------------------------------------------------------------
// Request updated ping time and other details from a single server
HServerQuery PingServer( uint32 unIP, uint16 usPort, ISteamMatchmakingPingResponse *pRequestServersResponse );
// Request the list of players currently playing on a server
HServerQuery PlayerDetails( uint32 unIP, uint16 usPort, ISteamMatchmakingPlayersResponse *pRequestServersResponse );
// Request the list of rules that the server is running (See ISteamGameServer::SetKeyValue() to set the rules server side)
HServerQuery ServerRules( uint32 unIP, uint16 usPort, ISteamMatchmakingRulesResponse *pRequestServersResponse );
// Cancel an outstanding Ping/Players/Rules query from above. You should call this to cancel
// any in-progress requests before destructing a callback object that may have been passed
// to one of the above calls to avoid crashing when callbacks occur.
void CancelServerQuery( HServerQuery hServerQuery );
// called by steam_client::runcallbacks
void RunCallbacks();
};
#endif // __INCLUDED_STEAM_MATCHMAKING_SERVERS_H__

50
dll/dll/steam_music.h Normal file
View File

@ -0,0 +1,50 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MUSIC_H__
#define __INCLUDED_STEAM_MUSIC_H__
#include "base.h"
class Steam_Music :
public ISteamMusic
{
int playing{};;
float volume{};;
void change_playstate(int new_playing);
class SteamCallBacks *callbacks{};
public:
Steam_Music(class SteamCallBacks *callbacks);
bool BIsEnabled();
bool BIsPlaying();
AudioPlayback_Status GetPlaybackStatus();
void Play();
void Pause();
void PlayPrevious();
void PlayNext();
// volume is between 0.0 and 1.0
void SetVolume( float flVolume );
float GetVolume();
};
#endif // __INCLUDED_STEAM_MUSIC_H__

View File

@ -0,0 +1,73 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MUSICREMOTE_H__
#define __INCLUDED_STEAM_MUSICREMOTE_H__
#include "base.h"
class Steam_MusicRemote :
public ISteamMusicRemote
{
public:
// Service Definition
bool RegisterSteamMusicRemote( const char *pchName );
bool DeregisterSteamMusicRemote();
bool BIsCurrentMusicRemote();
bool BActivationSuccess( bool bValue );
bool SetDisplayName( const char *pchDisplayName );
bool SetPNGIcon_64x64( void *pvBuffer, uint32 cbBufferLength );
// Abilities for the user interface
bool EnablePlayPrevious(bool bValue);
bool EnablePlayNext( bool bValue );
bool EnableShuffled( bool bValue );
bool EnableLooped( bool bValue );
bool EnableQueue( bool bValue );
bool EnablePlaylists( bool bValue );
// Status
bool UpdatePlaybackStatus( AudioPlayback_Status nStatus );
bool UpdateShuffled( bool bValue );
bool UpdateLooped( bool bValue );
bool UpdateVolume( float flValue ); // volume is between 0.0 and 1.0
// Current Entry
bool CurrentEntryWillChange();
bool CurrentEntryIsAvailable( bool bAvailable );
bool UpdateCurrentEntryText( const char *pchText );
bool UpdateCurrentEntryElapsedSeconds( int nValue );
bool UpdateCurrentEntryCoverArt( void *pvBuffer, uint32 cbBufferLength );
bool CurrentEntryDidChange();
// Queue
bool QueueWillChange();
bool ResetQueueEntries();
bool SetQueueEntry( int nID, int nPosition, const char *pchEntryText );
bool SetCurrentQueueEntry( int nID );
bool QueueDidChange();
// Playlist
bool PlaylistWillChange();
bool ResetPlaylistEntries();
bool SetPlaylistEntry( int nID, int nPosition, const char *pchEntryText );
bool SetCurrentPlaylistEntry( int nID );
bool PlaylistDidChange();
};
#endif // __INCLUDED_STEAM_MUSICREMOTE_H__

266
dll/dll/steam_networking.h Normal file
View File

@ -0,0 +1,266 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_NETWORKING_H__
#define __INCLUDED_STEAM_NETWORKING_H__
#include "base.h"
struct Steam_Networking_Connection {
CSteamID remote{};
std::set<int> open_channels{};
};
struct steam_listen_socket {
SNetListenSocket_t id{};
int nVirtualP2PPort{};
uint32 nIP{};
uint16 nPort{};
};
enum steam_socket_connection_status {
SOCKET_CONNECTING,
SOCKET_CONNECTED,
SOCKET_DISCONNECTED,
SOCKET_KILLED,
};
struct steam_connection_socket {
SNetSocket_t id{};
SNetListenSocket_t listen_id{};
enum steam_socket_connection_status status{};
CSteamID target{};
int nVirtualPort{};
uint32 nIP{};
uint16 nPort{};
SNetSocket_t other_id{};
std::vector<Network_Old> data_packets{};
};
class Steam_Networking :
public ISteamNetworking001,
public ISteamNetworking002,
public ISteamNetworking003,
public ISteamNetworking004,
public ISteamNetworking005,
public ISteamNetworking
{
class Settings *settings{};
class Networking *network{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::recursive_mutex messages_mutex{};
std::list<Common_Message> messages{};
std::list<Common_Message> unprocessed_messages{};
std::recursive_mutex connections_edit_mutex{};
std::vector<struct Steam_Networking_Connection> connections{};
std::vector<struct steam_listen_socket> listen_sockets{};
std::vector<struct steam_connection_socket> connection_sockets{};
std::map<CSteamID, std::chrono::high_resolution_clock::time_point> new_connection_times{};
std::queue<CSteamID> 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_networking_run_every_runcp(void *object);
public:
Steam_Networking(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Networking();
////////////////////////////////////////////////////////////////////////////////////////////
// Session-less connection functions
// automatically establishes NAT-traversing or Relay server connections
// Sends a P2P packet to the specified user
// UDP-like, unreliable and a max packet size of 1200 bytes
// the first packet send may be delayed as the NAT-traversal code runs
// if we can't get through to the user, an error will be posted via the callback P2PSessionConnectFail_t
// see EP2PSend enum above for the descriptions of the different ways of sending packets
//
// nChannel is a routing number you can use to help route message to different systems - you'll have to call ReadP2PPacket()
// with the same channel number in order to retrieve the data on the other end
// using different channels to talk to the same user will still use the same underlying p2p connection, saving on resources
bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType, int nChannel);
bool SendP2PPacket( CSteamID steamIDRemote, const void *pubData, uint32 cubData, EP2PSend eP2PSendType );
// returns true if any data is available for read, and the amount of data that will need to be read
bool IsP2PPacketAvailable( uint32 *pcubMsgSize, int nChannel);
bool IsP2PPacketAvailable( uint32 *pcubMsgSize);
// reads in a packet that has been sent from another user via SendP2PPacket()
// returns the size of the message and the steamID of the user who sent it in the last two parameters
// if the buffer passed in is too small, the message will be truncated
// this call is not blocking, and will return false if no data is available
bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote, int nChannel);
bool ReadP2PPacket( void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, CSteamID *psteamIDRemote);
// AcceptP2PSessionWithUser() should only be called in response to a P2PSessionRequest_t callback
// P2PSessionRequest_t will be posted if another user tries to send you a packet that you haven't talked to yet
// if you don't want to talk to the user, just ignore the request
// if the user continues to send you packets, another P2PSessionRequest_t will be posted periodically
// this may be called multiple times for a single user
// (if you've called SendP2PPacket() on the other user, this implicitly accepts the session request)
bool AcceptP2PSessionWithUser( CSteamID steamIDRemote );
// call CloseP2PSessionWithUser() when you're done talking to a user, will free up resources under-the-hood
// if the remote user tries to send data to you again, another P2PSessionRequest_t callback will be posted
bool CloseP2PSessionWithUser( CSteamID steamIDRemote );
// call CloseP2PChannelWithUser() when you're done talking to a user on a specific channel. Once all channels
// open 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 P2PSessionRequest_t callback
bool CloseP2PChannelWithUser( CSteamID steamIDRemote, int nChannel );
// fills out P2PSessionState_t structure with details about the underlying connection to the user
// should only needed for debugging purposes
// returns false if no connection exists to the specified user
bool GetP2PSessionState( CSteamID steamIDRemote, P2PSessionState_t *pConnectionState );
// Allow P2P connections to fall back to being relayed through the Steam servers if a direct connection
// or NAT-traversal cannot be established. Only applies to connections created after setting this value,
// or to existing connections that need to automatically reconnect after this value is set.
//
// P2P packet relay is allowed by default
bool AllowP2PPacketRelay( bool bAllow );
////////////////////////////////////////////////////////////////////////////////////////////
// LISTEN / CONNECT style interface functions
//
// This is an older set of functions designed around the Berkeley TCP sockets model
// it's preferential that you use the above P2P functions, they're more robust
// and these older functions will be removed eventually
//
////////////////////////////////////////////////////////////////////////////////////////////
// creates a socket and listens others to connect
// will trigger a SocketStatusCallback_t callback on another client connecting
// nVirtualP2PPort is the unique ID that the client will connect to, in case you have multiple ports
// this can usually just be 0 unless you want multiple sets of connections
// unIP is the local IP address to bind to
// pass in 0 if you just want the default local IP
// unPort is the port to use
// pass in 0 if you don't want users to be able to connect via IP/Port, but expect to be always peer-to-peer connections only
SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort, bool bAllowUseOfPacketRelay );
SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, SteamIPAddress_t nIP, uint16 nPort, bool bAllowUseOfPacketRelay );
SNetListenSocket_t CreateListenSocket( int nVirtualP2PPort, uint32 nIP, uint16 nPort );
// creates a socket and begin connection to a remote destination
// can connect via a known steamID (client or game server), or directly to an IP
// on success will trigger a SocketStatusCallback_t callback
// on failure or timeout will trigger a SocketStatusCallback_t callback with a failure code in m_eSNetSocketState
SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec, bool bAllowUseOfPacketRelay );
SNetSocket_t CreateP2PConnectionSocket( CSteamID steamIDTarget, int nVirtualPort, int nTimeoutSec );
SNetSocket_t CreateConnectionSocket( uint32 nIP, uint16 nPort, int nTimeoutSec );
SNetSocket_t CreateConnectionSocket( SteamIPAddress_t nIP, uint16 nPort, int nTimeoutSec );
// disconnects the connection to the socket, if any, and invalidates the handle
// any unread data on the socket will be thrown away
// if bNotifyRemoteEnd is set, socket will not be completely destroyed until the remote end acknowledges the disconnect
bool DestroySocket( SNetSocket_t hSocket, bool bNotifyRemoteEnd );
// destroying a listen socket will automatically kill all the regular sockets generated from it
bool DestroyListenSocket( SNetListenSocket_t hSocket, bool bNotifyRemoteEnd );
// sending data
// must be a handle to a connected socket
// data is all sent via UDP, and thus send sizes are limited to 1200 bytes; after this, many routers will start dropping packets
// use the reliable flag with caution; although the resend rate is pretty aggressive,
// it can still cause stalls in receiving data (like TCP)
bool SendDataOnSocket( SNetSocket_t hSocket, void *pubData, uint32 cubData, bool bReliable );
// receiving data
// returns false if there is no data remaining
// fills out *pcubMsgSize with the size of the next message, in bytes
bool IsDataAvailableOnSocket( SNetSocket_t hSocket, uint32 *pcubMsgSize );
// fills in pubDest with the contents of the message
// messages are always complete, of the same size as was sent (i.e. packetized, not streaming)
// if *pcubMsgSize < cubDest, only partial data is written
// returns false if no data is available
bool RetrieveDataFromSocket( SNetSocket_t hSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize );
// checks for data from any socket that has been connected off this listen socket
// returns false if there is no data remaining
// fills out *pcubMsgSize with the size of the next message, in bytes
// fills out *phSocket with the socket that data is available on
bool IsDataAvailable( SNetListenSocket_t hListenSocket, uint32 *pcubMsgSize, SNetSocket_t *phSocket );
// retrieves data from any socket that has been connected off this listen socket
// fills in pubDest with the contents of the message
// messages are always complete, of the same size as was sent (i.e. packetized, not streaming)
// if *pcubMsgSize < cubDest, only partial data is written
// returns false if no data is available
// fills out *phSocket with the socket that data is available on
bool RetrieveData( SNetListenSocket_t hListenSocket, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize, SNetSocket_t *phSocket );
// returns information about the specified socket, filling out the contents of the pointers
bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, uint32 *punIPRemote, uint16 *punPortRemote );
bool GetSocketInfo( SNetSocket_t hSocket, CSteamID *pSteamIDRemote, int *peSocketStatus, SteamIPAddress_t *punIPRemote, uint16 *punPortRemote );
// returns which local port the listen socket is bound to
// *pnIP and *pnPort will be 0 if the socket is set to listen for P2P connections only
bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, uint32 *pnIP, uint16 *pnPort );
bool GetListenSocketInfo( SNetListenSocket_t hListenSocket, SteamIPAddress_t *pnIP, uint16 *pnPort );
// returns true to describe how the socket ended up connecting
ESNetSocketConnectionType GetSocketConnectionType( SNetSocket_t hSocket );
// max packet size, in bytes
int GetMaxPacketSize( SNetSocket_t hSocket );
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_NETWORKING_H__

View File

@ -0,0 +1,155 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_NETWORKING_MESSAGES_H__
#define __INCLUDED_STEAM_NETWORKING_MESSAGES_H__
#include "base.h"
struct Steam_Message_Connection {
SteamNetworkingIdentity remote_identity{};
std::map<int, std::queue<std::string>> data{};
std::list<int> channels{};
bool accepted = false;
bool dead = false;
unsigned id{};
unsigned remote_id = 0;
std::chrono::high_resolution_clock::time_point created = std::chrono::high_resolution_clock::now();
};
class Steam_Networking_Messages :
public ISteamNetworkingMessages
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::map<CSteamID, Steam_Message_Connection> connections{};
std::list<Common_Message> incoming_data{};
unsigned id_counter = 0;
std::chrono::steady_clock::time_point created{};
static void free_steam_message_data(SteamNetworkingMessage_t *pMsg);
static void delete_steam_message(SteamNetworkingMessage_t *pMsg);
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
std::map<CSteamID, Steam_Message_Connection>::iterator find_or_create_message_connection(SteamNetworkingIdentity identityRemote, bool incoming, bool restartbroken);
void end_connection(CSteamID steam_id);
void RunCallbacks();
void Callback(Common_Message *msg);
public:
Steam_Networking_Messages(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Networking_Messages();
/// Sends a message to the specified host. If we don't already have a session with that user,
/// a session is implicitly created. There might be some handshaking that needs to happen
/// before we can actually begin sending message data. If this handshaking fails and we can't
/// get through, an error will be posted via the callback SteamNetworkingMessagesSessionFailed_t.
/// There is no notification when the operation succeeds. (You should have the peer send a reply
/// for this purpose.)
///
/// Sending a message to a host will also implicitly accept any incoming connection from that host.
///
/// nSendFlags is a bitmask of k_nSteamNetworkingSend_xxx options
///
/// nRemoteChannel is a routing number you can use to help route message to different systems.
/// You'll have to call ReceiveMessagesOnChannel() with the same channel number in order to retrieve
/// the data on the other end.
///
/// Using different channels to talk to the same user will still use the same underlying
/// connection, saving on resources. If you don't need this feature, use 0.
/// Otherwise, small integers are the most efficient.
///
/// It is guaranteed that reliable messages to the same host on the same channel
/// will be be received by the remote host (if they are received at all) exactly once,
/// and in the same order that they were send.
///
/// NO other order guarantees exist! In particular, unreliable messages may be dropped,
/// received out of order with respect to each other and with respect to reliable data,
/// or may be received multiple times. Messages on different channels are *not* guaranteed
/// to be received in the order they were sent.
///
/// A note for those familiar with TCP/IP ports, or converting an existing codebase that
/// opened multiple sockets: You might notice that there is only one channel, and with
/// TCP/IP each endpoint has a port number. You can think of the channel number as the
/// *destination* port. If you need each message to also include a "source port" (so the
/// recipient can route the reply), then just put that in your message. That is essentially
/// how UDP works!
///
/// Returns:
/// - k_EREsultOK on success.
/// - k_EResultNoConnection will be returned if the session has failed or was closed by the peer,
/// and k_nSteamNetworkingSend_AutoRestartBrokwnSession is not used. (You can use
/// GetSessionConnectionInfo to get the details.) In order to acknowledge the broken session
/// and start a new one, you must call CloseSessionWithUser
/// - See SendMessageToConnection::SendMessageToConnection for more
EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel );
/// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel.
/// Returns number of messages returned into your list. (0 if no message are available on that channel.)
///
/// When you're done with the message object(s), make sure and call Release()!
int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages );
/// AcceptSessionWithUser() should only be called in response to a SteamP2PSessionRequest_t callback
/// SteamP2PSessionRequest_t will be posted if another user tries to send you a message, and you haven't
/// tried to talk to them. If you don't want to talk to them, just ignore the request.
/// If the user continues to send you messages, SteamP2PSessionRequest_t callbacks will continue to
/// be posted periodically. This may be called multiple times for a single user.
///
/// Calling SendMessage() on the other user, this implicitly accepts any pending session request.
bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote );
/// Call this when you're done talking to a user to immediately free up resources under-the-hood.
/// If the remote user tries to send data to you again, another P2PSessionRequest_t callback will
/// be posted.
///
/// Note that sessions that go unused for a few minutes are automatically timed out.
bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote );
/// Call this when you're done talking to a user on a specific channel. Once all
/// open channels to a user have been closed, the open session to the user will be
/// closed, and any new data from this user will trigger a SteamP2PSessionRequest_t
/// callback
bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel );
/// Returns information about the latest state of a connection, if any, with the given peer.
/// Primarily intended for debugging purposes, but can also be used to get more detailed
/// failure information. (See SendMessageToUser and k_nSteamNetworkingSend_AutoRestartBrokwnSession.)
///
/// Returns the value of SteamNetConnectionInfo_t::m_eState, or k_ESteamNetworkingConnectionState_None
/// if no connection exists with specified peer. You may pass nullptr for either parameter if
/// you do not need the corresponding details. Note that sessions time out after a while,
/// so if a connection fails, or SendMessageToUser returns SendMessageToUser, you cannot wait
/// indefinitely to obtain the reason for failure.
ESteamNetworkingConnectionState GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetConnectionRealTimeStatus_t *pQuickStatus );
};
#endif // __INCLUDED_STEAM_NETWORKING_MESSAGES_H__

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__
#define __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__
#include "base.h"
class Steam_Networking_Sockets_Serialized :
public ISteamNetworkingSocketsSerialized002,
public ISteamNetworkingSocketsSerialized003,
public ISteamNetworkingSocketsSerialized004,
public ISteamNetworkingSocketsSerialized005
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_Networking_Sockets_Serialized(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Networking_Sockets_Serialized();
void SendP2PRendezvous( CSteamID steamIDRemote, uint32 unConnectionIDSrc, const void *pMsgRendezvous, uint32 cbRendezvous );
void SendP2PConnectionFailure( CSteamID steamIDRemote, uint32 unConnectionIDDest, uint32 nReason, const char *pszReason );
SteamAPICall_t GetCertAsync();
int GetNetworkConfigJSON( void *buf, uint32 cbBuf, const char *pszLauncherPartner );
int GetNetworkConfigJSON( void *buf, uint32 cbBuf );
void CacheRelayTicket( const void *pTicket, uint32 cbTicket );
uint32 GetCachedRelayTicketCount();
int GetCachedRelayTicket( uint32 idxTicket, void *buf, uint32 cbBuf );
void PostConnectionStateMsg( const void *pMsg, uint32 cbMsg );
bool GetSTUNServer(int dont_know, char *buf, unsigned int len);
bool BAllowDirectConnectToPeer(SteamNetworkingIdentity const &identity);
int BeginAsyncRequestFakeIP(int a);
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_NETWORKING_SOCKETSERIALIZED_H__

View File

@ -0,0 +1,281 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_NETWORKING_UTILS_H__
#define __INCLUDED_STEAM_NETWORKING_UTILS_H__
#include "base.h"
class Steam_Networking_Utils :
public ISteamNetworkingUtils001,
public ISteamNetworkingUtils002,
public ISteamNetworkingUtils003,
public ISteamNetworkingUtils
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::chrono::time_point<std::chrono::steady_clock> initialized_time = std::chrono::steady_clock::now();
FSteamNetworkingSocketsDebugOutput debug_function{};
bool relay_initialized = false;
bool init_relay = true; // Initializing relay immediately when we ask for it. Fixing Elden Ring SeamlessCoop
// NOTE from Detanup01: They should call InitializeRelayAccess BTW. Whoever write that mod cannot read valve docs.
/*
Valve docs:
If you know that you are going to be using the relay network
(for example, because you anticipate making P2P connections),
call this to initialize the relay network.
If you do not call this, the initialization will be delayed until
the first time you use a feature that requires access to the relay network,
which will delay that first access.
*/
static void free_steam_message_data(SteamNetworkingMessage_t *pMsg);
static void delete_steam_message(SteamNetworkingMessage_t *pMsg);
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_Networking_Utils(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Networking_Utils();
/// Allocate and initialize a message object. Usually the reason
/// you call this is to pass it to ISteamNetworkingSockets::SendMessages.
/// The returned object will have all of the relevant fields cleared to zero.
///
/// Optionally you can also request that this system allocate space to
/// hold the payload itself. If cbAllocateBuffer is nonzero, the system
/// will allocate memory to hold a payload of at least cbAllocateBuffer bytes.
/// m_pData will point to the allocated buffer, m_cbSize will be set to the
/// size, and m_pfnFreeData will be set to the proper function to free up
/// the buffer.
///
/// If cbAllocateBuffer=0, then no buffer is allocated. m_pData will be NULL,
/// m_cbSize will be zero, and m_pfnFreeData will be NULL. You will need to
/// set each of these.
///
/// You can use SteamNetworkingMessage_t::Release to free up the message
/// bookkeeping object and any associated buffer. See
/// ISteamNetworkingSockets::SendMessages for details on reference
/// counting and ownership.
SteamNetworkingMessage_t *AllocateMessage( int cbAllocateBuffer );
bool InitializeRelayAccess();
SteamRelayNetworkStatus_t get_network_status();
/// Fetch current status of the relay network.
///
/// SteamRelayNetworkStatus_t is also a callback. It will be triggered on
/// both the user and gameserver interfaces any time the status changes, or
/// ping measurement starts or stops.
///
/// SteamRelayNetworkStatus_t::m_eAvail is returned. If you want
/// more details, you can pass a non-NULL value.
ESteamNetworkingAvailability GetRelayNetworkStatus( SteamRelayNetworkStatus_t *pDetails );
float GetLocalPingLocation( SteamNetworkPingLocation_t &result );
int EstimatePingTimeBetweenTwoLocations( const SteamNetworkPingLocation_t &location1, const SteamNetworkPingLocation_t &location2 );
int EstimatePingTimeFromLocalHost( const SteamNetworkPingLocation_t &remoteLocation );
void ConvertPingLocationToString( const SteamNetworkPingLocation_t &location, char *pszBuf, int cchBufSize );
bool ParsePingLocationString( const char *pszString, SteamNetworkPingLocation_t &result );
bool CheckPingDataUpToDate( float flMaxAgeSeconds );
bool IsPingMeasurementInProgress();
int GetPingToDataCenter( SteamNetworkingPOPID popID, SteamNetworkingPOPID *pViaRelayPoP );
int GetDirectPingToPOP( SteamNetworkingPOPID popID );
int GetPOPCount();
int GetPOPList( SteamNetworkingPOPID *list, int nListSz );
//
// Misc
//
/// Fetch current timestamp. This timer has the following properties:
///
/// - Monotonicity is guaranteed.
/// - The initial value will be at least 24*3600*30*1e6, i.e. about
/// 30 days worth of microseconds. In this way, the timestamp value of
/// 0 will always be at least "30 days ago". Also, negative numbers
/// will never be returned.
/// - Wraparound / overflow is not a practical concern.
///
/// If you are running under the debugger and stop the process, the clock
/// might not advance the full wall clock time that has elapsed between
/// calls. If the process is not blocked from normal operation, the
/// timestamp values will track wall clock time, even if you don't call
/// the function frequently.
///
/// The value is only meaningful for this run of the process. Don't compare
/// it to values obtained on another computer, or other runs of the same process.
SteamNetworkingMicroseconds GetLocalTimestamp();
/// Set a function to receive network-related information that is useful for debugging.
/// This can be very useful during development, but it can also be useful for troubleshooting
/// problems with tech savvy end users. If you have a console or other log that customers
/// can examine, these log messages can often be helpful to troubleshoot network issues.
/// (Especially any warning/error messages.)
///
/// The detail level indicates what message to invoke your callback on. Lower numeric
/// value means more important, and the value you pass is the lowest priority (highest
/// numeric value) you wish to receive callbacks for.
///
/// Except when debugging, you should only use k_ESteamNetworkingSocketsDebugOutputType_Msg
/// or k_ESteamNetworkingSocketsDebugOutputType_Warning. For best performance, do NOT
/// request a high detail level and then filter out messages in your callback. Instead,
/// call function function to adjust the desired level of detail.
///
/// IMPORTANT: This may be called from a service thread, while we own a mutex, etc.
/// Your output function must be threadsafe and fast! Do not make any other
/// Steamworks calls from within the handler.
void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc );
//
// Fake IP
//
// Useful for interfacing with code that assumes peers are identified using an IPv4 address
//
/// Return true if an IPv4 address is one that might be used as a "fake" one.
/// This function is fast; it just does some logical tests on the IP and does
/// not need to do any lookup operations.
// inline bool IsFakeIPv4( uint32 nIPv4 ) { return GetIPv4FakeIPType( nIPv4 ) > k_ESteamNetworkingFakeIPType_NotFake; }
ESteamNetworkingFakeIPType GetIPv4FakeIPType( uint32 nIPv4 );
/// Get the real identity associated with a given FakeIP.
///
/// On failure, returns:
/// - k_EResultInvalidParam: the IP is not a FakeIP.
/// - k_EResultNoMatch: we don't recognize that FakeIP and don't know the corresponding identity.
///
/// FakeIP's used by active connections, or the FakeIPs assigned to local identities,
/// will always work. FakeIPs for recently destroyed connections will continue to
/// return results for a little while, but not forever. At some point, we will forget
/// FakeIPs to save space. It's reasonably safe to assume that you can read back the
/// real identity of a connection very soon after it is destroyed. But do not wait
/// indefinitely.
EResult GetRealIdentityForFakeIP( const SteamNetworkingIPAddr &fakeIP, SteamNetworkingIdentity *pOutRealIdentity );
//
// Set and get configuration values, see ESteamNetworkingConfigValue for individual descriptions.
//
// Shortcuts for common cases. (Implemented as inline functions below)
/*
bool SetGlobalConfigValueInt32( ESteamNetworkingConfigValue eValue, int32 val );
bool SetGlobalConfigValueFloat( ESteamNetworkingConfigValue eValue, float val );
bool SetGlobalConfigValueString( ESteamNetworkingConfigValue eValue, const char *val );
bool SetConnectionConfigValueInt32( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, int32 val );
bool SetConnectionConfigValueFloat( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, float val );
bool SetConnectionConfigValueString( HSteamNetConnection hConn, ESteamNetworkingConfigValue eValue, const char *val );
*/
/// Set a configuration value.
/// - eValue: which value is being set
/// - eScope: Onto what type of object are you applying the setting?
/// - scopeArg: Which object you want to change? (Ignored for global scope). E.g. connection handle, listen socket handle, interface pointer, etc.
/// - eDataType: What type of data is in the buffer at pValue? This must match the type of the variable exactly!
/// - pArg: Value to set it to. You can pass NULL to remove a non-global sett at this scope,
/// causing the value for that object to use global defaults. Or at global scope, passing NULL
/// will reset any custom value and restore it to the system default.
/// NOTE: When setting callback functions, do not pass the function pointer directly.
/// Your argument should be a pointer to a function pointer.
bool SetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj,
ESteamNetworkingConfigDataType eDataType, const void *pArg );
/// Get a configuration value.
/// - eValue: which value to fetch
/// - eScopeType: query setting on what type of object
/// - eScopeArg: the object to query the setting for
/// - pOutDataType: If non-NULL, the data type of the value is returned.
/// - pResult: Where to put the result. Pass NULL to query the required buffer size. (k_ESteamNetworkingGetConfigValue_BufferTooSmall will be returned.)
/// - cbResult: IN: the size of your buffer. OUT: the number of bytes filled in or required.
ESteamNetworkingGetConfigValueResult GetConfigValue( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigScope eScopeType, intptr_t scopeObj,
ESteamNetworkingConfigDataType *pOutDataType, void *pResult, size_t *cbResult );
/// Returns info about a configuration value. Returns false if the value does not exist.
/// pOutNextValue can be used to iterate through all of the known configuration values.
/// (Use GetFirstConfigValue() to begin the iteration, will be k_ESteamNetworkingConfig_Invalid on the last value)
/// Any of the output parameters can be NULL if you do not need that information.
bool GetConfigValueInfo( ESteamNetworkingConfigValue eValue, const char **pOutName, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope, ESteamNetworkingConfigValue *pOutNextValue );
/// Get info about a configuration value. Returns the name of the value,
/// or NULL if the value doesn't exist. Other output parameters can be NULL
/// if you do not need them.
const char *GetConfigValueInfo( ESteamNetworkingConfigValue eValue, ESteamNetworkingConfigDataType *pOutDataType, ESteamNetworkingConfigScope *pOutScope );
/// Return the lowest numbered configuration value available in the current environment.
ESteamNetworkingConfigValue GetFirstConfigValue();
/// Iterate the list of all configuration values in the current environment that it might
/// be possible to display or edit using a generic UI. To get the first iterable value,
/// pass k_ESteamNetworkingConfig_Invalid. Returns k_ESteamNetworkingConfig_Invalid
/// to signal end of list.
///
/// The bEnumerateDevVars argument can be used to include "dev" vars. These are vars that
/// are recommended to only be editable in "debug" or "dev" mode and typically should not be
/// shown in a retail environment where a malicious local user might use this to cheat.
ESteamNetworkingConfigValue IterateGenericEditableConfigValues( ESteamNetworkingConfigValue eCurrent, bool bEnumerateDevVars );
// String conversions. You'll usually access these using the respective
// inline methods.
void SteamNetworkingIPAddr_ToString( const SteamNetworkingIPAddr &addr, char *buf, size_t cbBuf, bool bWithPort );
bool SteamNetworkingIPAddr_ParseString( SteamNetworkingIPAddr *pAddr, const char *pszStr );
ESteamNetworkingFakeIPType SteamNetworkingIPAddr_GetFakeIPType( const SteamNetworkingIPAddr &addr );
void SteamNetworkingIdentity_ToString( const SteamNetworkingIdentity &identity, char *buf, size_t cbBuf );
bool SteamNetworkingIdentity_ParseString( SteamNetworkingIdentity *pIdentity, const char *pszStr );
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_NETWORKING_UTILS_H__

37
dll/dll/steam_parental.h Normal file
View File

@ -0,0 +1,37 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_PARENTAL_H__
#define __INCLUDED_STEAM_PARENTAL_H__
#include "base.h"
class Steam_Parental :
public ISteamParentalSettings
{
public:
bool BIsParentalLockEnabled();
bool BIsParentalLockLocked();
bool BIsAppBlocked( AppId_t nAppID );
bool BIsAppInBlockList( AppId_t nAppID );
bool BIsFeatureBlocked( EParentalFeature eFeature );
bool BIsFeatureInBlockList( EParentalFeature eFeature );
};
#endif // __INCLUDED_STEAM_PARENTAL_H__

109
dll/dll/steam_parties.h Normal file
View File

@ -0,0 +1,109 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_PARTIES_H__
#define __INCLUDED_STEAM_PARTIES_H__
#include "base.h"
class Steam_Parties :
public ISteamParties
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::chrono::time_point<std::chrono::steady_clock> initialized_time = std::chrono::steady_clock::now();
FSteamNetworkingSocketsDebugOutput debug_function{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_Parties(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Parties();
// =============================================================================================
// Party Client APIs
// Enumerate any active beacons for parties you may wish to join
uint32 GetNumActiveBeacons();
PartyBeaconID_t GetBeaconByIndex( uint32 unIndex );
bool GetBeaconDetails( PartyBeaconID_t ulBeaconID, CSteamID *pSteamIDBeaconOwner, STEAM_OUT_STRUCT() SteamPartyBeaconLocation_t *pLocation, STEAM_OUT_STRING_COUNT(cchMetadata) char *pchMetadata, int cchMetadata );
// Join an open party. Steam will reserve one beacon slot for your SteamID,
// and return the necessary JoinGame string for you to use to connect
STEAM_CALL_RESULT( JoinPartyCallback_t )
SteamAPICall_t JoinParty( PartyBeaconID_t ulBeaconID );
// =============================================================================================
// Party Host APIs
// Get a list of possible beacon locations
bool GetNumAvailableBeaconLocations( uint32 *puNumLocations );
bool GetAvailableBeaconLocations( SteamPartyBeaconLocation_t *pLocationList, uint32 uMaxNumLocations );
// Create a new party beacon and activate it in the selected location.
// unOpenSlots is the maximum number of users that Steam will send to you.
// When people begin responding to your beacon, Steam will send you
// PartyReservationCallback_t callbacks to let you know who is on the way.
STEAM_CALL_RESULT( CreateBeaconCallback_t )
SteamAPICall_t CreateBeacon( uint32 unOpenSlots, SteamPartyBeaconLocation_t *pBeaconLocation, const char *pchConnectString, const char *pchMetadata );
// Call this function when a user that had a reservation (see callback below)
// has successfully joined your party.
// Steam will manage the remaining open slots automatically.
void OnReservationCompleted( PartyBeaconID_t ulBeacon, CSteamID steamIDUser );
// To cancel a reservation (due to timeout or user input), call this.
// Steam will open a new reservation slot.
// Note: The user may already be in-flight to your game, so it's possible they will still connect and try to join your party.
void CancelReservation( PartyBeaconID_t ulBeacon, CSteamID steamIDUser );
// Change the number of open beacon reservation slots.
// Call this if, for example, someone without a reservation joins your party (eg a friend, or via your own matchmaking system).
STEAM_CALL_RESULT( ChangeNumOpenSlotsCallback_t )
SteamAPICall_t ChangeNumOpenSlots( PartyBeaconID_t ulBeacon, uint32 unOpenSlots );
// Turn off the beacon.
bool DestroyBeacon( PartyBeaconID_t ulBeacon );
// Utils
bool GetBeaconLocationData( SteamPartyBeaconLocation_t BeaconLocation, ESteamPartyBeaconLocationData eData, STEAM_OUT_STRING_COUNT(cchDataStringOut) char *pchDataStringOut, int cchDataStringOut );
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_PARTIES_H__

View File

@ -0,0 +1,343 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_REMOTE_STORAGE_H__
#define __INCLUDED_STEAM_REMOTE_STORAGE_H__
#include "base.h"
#include "ugc_remote_storage_bridge.h"
struct Async_Read {
SteamAPICall_t api_call{};
uint32 offset{};
uint32 to_read{};
uint32 size{};
std::string file_name{};
};
struct Stream_Write {
std::string file_name{};
UGCFileWriteStreamHandle_t write_stream_handle{};
std::vector<char> file_data{};
};
struct Downloaded_File {
enum class DownloadSource {
AfterFileShare, // attempted download after a call to Steam_Remote_Storage::FileShare()
AfterSendQueryUGCRequest, // attempted download after a call to Steam_UGC::SendQueryUGCRequest()
FromUGCDownloadToLocation, // attempted download via Steam_Remote_Storage::UGCDownloadToLocation()
};
private:
DownloadSource source;
public:
Downloaded_File(DownloadSource src);
DownloadSource get_source() const;
// *** used in any case
std::string file{};
uint64 total_size{};
// *** used when source = AfterSendQueryUGCRequest and FromUGCDownloadToLocation
Ugc_Remote_Storage_Bridge::QueryInfo mod_query_info{};
// *** used when source = FromUGCDownloadToLocation only
std::string download_to_location_fullpath{};
};
class Steam_Remote_Storage :
public ISteamRemoteStorage001,
public ISteamRemoteStorage002,
public ISteamRemoteStorage003,
public ISteamRemoteStorage004,
public ISteamRemoteStorage005,
public ISteamRemoteStorage006,
public ISteamRemoteStorage007,
public ISteamRemoteStorage008,
public ISteamRemoteStorage009,
public ISteamRemoteStorage010,
public ISteamRemoteStorage011,
public ISteamRemoteStorage012,
public ISteamRemoteStorage013,
public ISteamRemoteStorage014,
public ISteamRemoteStorage015,
public ISteamRemoteStorage
{
private:
class Settings *settings{};
class Ugc_Remote_Storage_Bridge *ugc_bridge{};
class Local_Storage *local_storage{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
std::vector<struct Async_Read> async_reads{};
std::vector<struct Stream_Write> stream_writes{};
std::map<UGCHandle_t, std::string> shared_files{};
std::map<UGCHandle_t, struct Downloaded_File> downloaded_files{};
bool steam_cloud_enabled = true;
public:
Steam_Remote_Storage(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
// NOTE
//
// Filenames are case-insensitive, and will be converted to lowercase automatically.
// So "foo.bar" and "Foo.bar" are the same file, and if you write "Foo.bar" then
// iterate the files, the filename returned will be "foo.bar".
//
// file operations
bool FileWrite( const char *pchFile, const void *pvData, int32 cubData );
int32 FileRead( const char *pchFile, void *pvData, int32 cubDataToRead );
STEAM_CALL_RESULT( RemoteStorageFileWriteAsyncComplete_t )
SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 cubData );
STEAM_CALL_RESULT( RemoteStorageFileReadAsyncComplete_t )
SteamAPICall_t FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToRead );
bool FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cubToRead );
bool FileForget( const char *pchFile );
bool FileDelete( const char *pchFile );
STEAM_CALL_RESULT( RemoteStorageFileShareResult_t )
SteamAPICall_t FileShare( const char *pchFile );
bool SetSyncPlatforms( const char *pchFile, ERemoteStoragePlatform eRemoteStoragePlatform );
// file operations that cause network IO
UGCFileWriteStreamHandle_t FileWriteStreamOpen( const char *pchFile );
bool FileWriteStreamWriteChunk( UGCFileWriteStreamHandle_t writeHandle, const void *pvData, int32 cubData );
bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle );
bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle );
// file information
bool FileExists( const char *pchFile );
bool FilePersisted( const char *pchFile );
int32 GetFileSize( const char *pchFile );
int64 GetFileTimestamp( const char *pchFile );
ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile );
// iteration
int32 GetFileCount();
const char *GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes );
// configuration management
bool GetQuota( uint64 *pnTotalBytes, uint64 *puAvailableBytes );
bool GetQuota( int32 *pnTotalBytes, int32 *puAvailableBytes );
bool IsCloudEnabledForAccount();
bool IsCloudEnabledForApp();
bool IsCloudEnabledThisApp();
void SetCloudEnabledForApp( bool bEnabled );
bool SetCloudEnabledThisApp( bool bEnabled );
// user generated content
// Downloads a UGC file. A priority value of 0 will download the file immediately,
// otherwise it will wait to download the file until all downloads with a lower priority
// value are completed. Downloads with equal priority will occur simultaneously.
STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t )
SteamAPICall_t UGCDownload( UGCHandle_t hContent, uint32 unPriority );
STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t )
SteamAPICall_t UGCDownload( UGCHandle_t hContent );
// Gets the amount of data downloaded so far for a piece of content. pnBytesExpected can be 0 if function returns false
// or if the transfer hasn't started yet, so be careful to check for that before dividing to get a percentage
bool GetUGCDownloadProgress( UGCHandle_t hContent, int32 *pnBytesDownloaded, int32 *pnBytesExpected );
bool GetUGCDownloadProgress( UGCHandle_t hContent, uint32 *pnBytesDownloaded, uint32 *pnBytesExpected );
// Gets metadata for a file after it has been downloaded. This is the same metadata given in the RemoteStorageDownloadUGCResult_t call result
bool GetUGCDetails( UGCHandle_t hContent, AppId_t *pnAppID, STEAM_OUT_STRING() char **ppchName, int32 *pnFileSizeInBytes, STEAM_OUT_STRUCT() CSteamID *pSteamIDOwner );
// After download, gets the content of the file.
// Small files can be read all at once by calling this function with an offset of 0 and cubDataToRead equal to the size of the file.
// Larger files can be read in chunks to reduce memory usage (since both sides of the IPC client and the game itself must allocate
// enough memory for each chunk). Once the last byte is read, the file is implicitly closed and further calls to UGCRead will fail
// unless UGCDownload is called again.
// For especially large files (anything over 100MB) it is a requirement that the file is read in chunks.
int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset, EUGCReadAction eAction );
int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead );
int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 cOffset);
// Functions to iterate through UGC that has finished downloading but has not yet been read via UGCRead()
int32 GetCachedUGCCount();
UGCHandle_t GetCachedUGCHandle( int32 iCachedContent );
// The following functions are only necessary on the Playstation 3. On PC & Mac, the Steam client will handle these operations for you
// On Playstation 3, the game controls which files are stored in the cloud, via FilePersist, FileFetch, and FileForget.
#if defined(_PS3) || defined(_SERVER)
// Connect to Steam and get a list of files in the Cloud - results in a RemoteStorageAppSyncStatusCheck_t callback
void GetFileListFromServer();
// Indicate this file should be downloaded in the next sync
bool FileFetch( const char *pchFile );
// Indicate this file should be persisted in the next sync
bool FilePersist( const char *pchFile );
// Pull any requested files down from the Cloud - results in a RemoteStorageAppSyncedClient_t callback
bool SynchronizeToClient();
// Upload any requested files to the Cloud - results in a RemoteStorageAppSyncedServer_t callback
bool SynchronizeToServer();
// Reset any fetch/persist/etc requests
bool ResetFileRequestState();
#endif
// publishing UGC
STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t )
SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags, EWorkshopFileType eWorkshopFileType );
PublishedFileUpdateHandle_t CreatePublishedFileUpdateRequest( PublishedFileId_t unPublishedFileId );
bool UpdatePublishedFileFile( PublishedFileUpdateHandle_t updateHandle, const char *pchFile );
SteamAPICall_t PublishFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags );
SteamAPICall_t PublishWorkshopFile( const char *pchFile, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, SteamParamStringArray_t *pTags );
SteamAPICall_t UpdatePublishedFile( RemoteStorageUpdatePublishedFileRequest_t updatePublishedFileRequest );
bool UpdatePublishedFilePreviewFile( PublishedFileUpdateHandle_t updateHandle, const char *pchPreviewFile );
bool UpdatePublishedFileTitle( PublishedFileUpdateHandle_t updateHandle, const char *pchTitle );
bool UpdatePublishedFileDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchDescription );
bool UpdatePublishedFileVisibility( PublishedFileUpdateHandle_t updateHandle, ERemoteStoragePublishedFileVisibility eVisibility );
bool UpdatePublishedFileTags( PublishedFileUpdateHandle_t updateHandle, SteamParamStringArray_t *pTags );
STEAM_CALL_RESULT( RemoteStorageUpdatePublishedFileResult_t )
SteamAPICall_t CommitPublishedFileUpdate( PublishedFileUpdateHandle_t updateHandle );
// Gets published file details for the given publishedfileid. If unMaxSecondsOld is greater than 0,
// cached data may be returned, depending on how long ago it was cached. A value of 0 will force a refresh.
// A value of k_WorkshopForceLoadPublishedFileDetailsFromCache will use cached data if it exists, no matter how old it is.
STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t )
SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId, uint32 unMaxSecondsOld );
STEAM_CALL_RESULT( RemoteStorageGetPublishedFileDetailsResult_t )
SteamAPICall_t GetPublishedFileDetails( PublishedFileId_t unPublishedFileId );
STEAM_CALL_RESULT( RemoteStorageDeletePublishedFileResult_t )
SteamAPICall_t DeletePublishedFile( PublishedFileId_t unPublishedFileId );
// enumerate the files that the current user published with this app
STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t )
SteamAPICall_t EnumerateUserPublishedFiles( uint32 unStartIndex );
STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t )
SteamAPICall_t SubscribePublishedFile( PublishedFileId_t unPublishedFileId );
STEAM_CALL_RESULT( RemoteStorageEnumerateUserSubscribedFilesResult_t )
SteamAPICall_t EnumerateUserSubscribedFiles( uint32 unStartIndex );
STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t )
SteamAPICall_t UnsubscribePublishedFile( PublishedFileId_t unPublishedFileId );
bool UpdatePublishedFileSetChangeDescription( PublishedFileUpdateHandle_t updateHandle, const char *pchChangeDescription );
STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t )
SteamAPICall_t GetPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId );
STEAM_CALL_RESULT( RemoteStorageUpdateUserPublishedItemVoteResult_t )
SteamAPICall_t UpdateUserPublishedItemVote( PublishedFileId_t unPublishedFileId, bool bVoteUp );
STEAM_CALL_RESULT( RemoteStorageGetPublishedItemVoteDetailsResult_t )
SteamAPICall_t GetUserPublishedItemVoteDetails( PublishedFileId_t unPublishedFileId );
STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t )
SteamAPICall_t EnumerateUserSharedWorkshopFiles( CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags );
STEAM_CALL_RESULT( RemoteStorageEnumerateUserPublishedFilesResult_t )
SteamAPICall_t EnumerateUserSharedWorkshopFiles(AppId_t nAppId, CSteamID steamId, uint32 unStartIndex, SteamParamStringArray_t *pRequiredTags, SteamParamStringArray_t *pExcludedTags );
STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t )
SteamAPICall_t PublishVideo( EWorkshopVideoProvider eVideoProvider, const char *pchVideoAccount, const char *pchVideoIdentifier, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags );
STEAM_CALL_RESULT( RemoteStoragePublishFileProgress_t )
SteamAPICall_t PublishVideo(const char *pchFileName, const char *pchPreviewFile, AppId_t nConsumerAppId, const char *pchTitle, const char *pchDescription, ERemoteStoragePublishedFileVisibility eVisibility, SteamParamStringArray_t *pTags );
STEAM_CALL_RESULT( RemoteStorageSetUserPublishedFileActionResult_t )
SteamAPICall_t SetUserPublishedFileAction( PublishedFileId_t unPublishedFileId, EWorkshopFileAction eAction );
STEAM_CALL_RESULT( RemoteStorageEnumeratePublishedFilesByUserActionResult_t )
SteamAPICall_t EnumeratePublishedFilesByUserAction( EWorkshopFileAction eAction, uint32 unStartIndex );
// this method enumerates the public view of workshop files
STEAM_CALL_RESULT( RemoteStorageEnumerateWorkshopFilesResult_t )
SteamAPICall_t EnumeratePublishedWorkshopFiles( EWorkshopEnumerationType eEnumerationType, uint32 unStartIndex, uint32 unCount, uint32 unDays, SteamParamStringArray_t *pTags, SteamParamStringArray_t *pUserTags );
STEAM_CALL_RESULT( RemoteStorageDownloadUGCResult_t )
SteamAPICall_t UGCDownloadToLocation( UGCHandle_t hContent, const char *pchLocation, uint32 unPriority );
// Cloud dynamic state change notification
int32 GetLocalFileChangeCount();
const char *GetLocalFileChange( int iFile, ERemoteStorageLocalFileChange *pEChangeType, ERemoteStorageFilePathType *pEFilePathType );
// Indicate to Steam the beginning / end of a set of local file
// operations - for example, writing a game save that requires updating two files.
bool BeginFileWriteBatch();
bool EndFileWriteBatch();
};
#endif // __INCLUDED_STEAM_REMOTE_STORAGE_H__

View File

@ -0,0 +1,72 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_REMOTEPLAY_H__
#define __INCLUDED_STEAM_REMOTEPLAY_H__
#include "base.h"
class Steam_RemotePlay :
public ISteamRemotePlay001,
public ISteamRemotePlay
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_RemotePlay(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_RemotePlay();
// Get the number of currently connected Steam Remote Play sessions
uint32 GetSessionCount();
// 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 );
// Get the SteamID of the connected user
CSteamID GetSessionSteamID( uint32 unSessionID );
// Get the name of the session client device
// This returns NULL if the sessionID is not valid
const char *GetSessionClientName( uint32 unSessionID );
// Get the form factor of the session client device
ESteamDeviceFormFactor GetSessionClientFormFactor( uint32 unSessionID );
// Get the resolution, in pixels, of the session client device
// This is set to 0x0 if the resolution is not available
bool BGetSessionClientResolution( uint32 unSessionID, int *pnResolutionX, int *pnResolutionY );
bool BStartRemotePlayTogether( bool bShowOverlay );
// Invite a friend to Remote Play Together
// This returns false if the invite can't be sent
bool BSendRemotePlayTogetherInvite( CSteamID steamIDFriend );
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_REMOTEPLAY_H__

View File

@ -0,0 +1,82 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_SCRNSHOTS_H__
#define __INCLUDED_STEAM_SCRNSHOTS_H__
#include "base.h"
struct screenshot_infos_t {
std::string screenshot_name{};
nlohmann::json metadatas{};
};
class Steam_Screenshots :
public ISteamScreenshots001,
public ISteamScreenshots002,
public ISteamScreenshots
{
class Local_Storage *local_storage{};
class SteamCallBacks *callbacks{};
bool hooked = false;
std::map<ScreenshotHandle, screenshot_infos_t> _screenshots{};
ScreenshotHandle create_screenshot_handle();
public:
Steam_Screenshots(class Local_Storage* local_storage, class SteamCallBacks* callbacks);
// Writes a screenshot to the user's screenshot library given the raw image data, which must be in RGB format.
// The return value is a handle that is valid for the duration of the game process and can be used to apply tags.
ScreenshotHandle WriteScreenshot( void *pubRGB, uint32 cubRGB, int nWidth, int nHeight );
// Adds a screenshot to the user's screenshot library from disk. If a thumbnail is provided, it must be 200 pixels wide and the same aspect ratio
// as the screenshot, otherwise a thumbnail will be generated if the user uploads the screenshot. The screenshots must be in either JPEG or TGA format.
// The return value is a handle that is valid for the duration of the game process and can be used to apply tags.
// JPEG, TGA, and PNG formats are supported.
ScreenshotHandle AddScreenshotToLibrary( const char *pchFilename, const char *pchThumbnailFilename, int nWidth, int nHeight );
// Causes the Steam overlay to take a screenshot. If screenshots are being hooked by the game then a ScreenshotRequested_t callback is sent back to the game instead.
void TriggerScreenshot();
// Toggles whether the overlay handles screenshots when the user presses the screenshot hotkey, or the game handles them. If the game is hooking screenshots,
// then the ScreenshotRequested_t callback will be sent if the user presses the hotkey, and the game is expected to call WriteScreenshot or AddScreenshotToLibrary
// in response.
void HookScreenshots( bool bHook );
// Sets metadata about a screenshot's location (for example, the name of the map)
bool SetLocation( ScreenshotHandle hScreenshot, const char *pchLocation );
// Tags a user as being visible in the screenshot
bool TagUser( ScreenshotHandle hScreenshot, CSteamID steamID );
// Tags a published file as being visible in the screenshot
bool TagPublishedFile( ScreenshotHandle hScreenshot, PublishedFileId_t unPublishedFileID );
// Returns true if the app has hooked the screenshot
bool IsScreenshotsHooked();
// Adds a VR screenshot to the user's screenshot library from disk in the supported type.
// pchFilename should be the normal 2D image used in the library view
// pchVRFilename should contain the image that matches the correct type
// The return value is a handle that is valid for the duration of the game process and can be used to apply tags.
// JPEG, TGA, and PNG formats are supported.
ScreenshotHandle AddVRScreenshotToLibrary( EVRScreenshotType eType, const char *pchFilename, const char *pchVRFilename );
};
#endif //__INCLUDED_STEAM_SCRNSHOTS_H__

97
dll/dll/steam_timeline.h Normal file
View File

@ -0,0 +1,97 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_TIMELINE_H__
#define __INCLUDED_STEAM_TIMELINE_H__
#include "base.h"
class Steam_Timeline :
public ISteamTimeline
{
private:
constexpr const static float PRIORITY_CLIP_MIN_SEC = 8.0f;
struct TimelineEvent_t
{
// emu specific: time when this event was added to the list via 'Steam_Timeline::AddTimelineEvent()'
const std::chrono::system_clock::time_point time_added = std::chrono::system_clock::now();
// The name of the icon to show at the timeline at this point. This can be one of the icons uploaded through the Steamworks partner Site for your title, or one of the provided icons that start with steam_. The Steam Timelines overview includes a list of available icons.
// https://partner.steamgames.com/doc/features/timeline#icons
std::string pchIcon{};
// Title-provided localized string in the language returned by SteamUtils()->GetSteamUILanguage().
std::string pchTitle{};
// Title-provided localized string in the language returned by SteamUtils()->GetSteamUILanguage().
std::string pchDescription{};
// Provide the priority to use when the UI is deciding which icons to display in crowded parts of the timeline. Events with larger priority values will be displayed more prominently than events with smaller priority values. This value must be between 0 and k_unMaxTimelinePriority.
uint32 unPriority{};
// One use of this parameter is to handle events whose significance is not clear until after the fact. For instance if the player starts a damage over time effect on another player, which kills them 3.5 seconds later, the game could pass -3.5 as the start offset and cause the event to appear in the timeline where the effect started.
float flStartOffsetSeconds{};
// The duration of the event, in seconds. Pass 0 for instantaneous events.
float flDurationSeconds{};
// Allows the game to describe events that should be suggested to the user as possible video clips.
ETimelineEventClipPriority ePossibleClip{};
};
struct TimelineState_t
{
// emu specific: time when this state was changed via 'Steam_Timeline::SetTimelineGameMode()'
const std::chrono::system_clock::time_point time_added = std::chrono::system_clock::now();
std::string description{}; // A localized string in the language returned by SteamUtils()->GetSteamUILanguage()
ETimelineGameMode bar_color{}; // the color of the timeline bar
};
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::vector<TimelineEvent_t> timeline_events{};
std::vector<TimelineState_t> timeline_states{};
// unconditional periodic callback
void RunCallbacks();
// network callback, triggered once we have a network message
void Callback(Common_Message *msg);
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_Timeline(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Timeline();
void SetTimelineStateDescription( const char *pchDescription, float flTimeDelta );
void ClearTimelineStateDescription( float flTimeDelta );
void AddTimelineEvent( const char *pchIcon, const char *pchTitle, const char *pchDescription, uint32 unPriority, float flStartOffsetSeconds, float flDurationSeconds, ETimelineEventClipPriority ePossibleClip );
void SetTimelineGameMode( ETimelineGameMode eMode );
};
#endif // __INCLUDED_STEAM_TIMELINE_H__

61
dll/dll/steam_tv.h Normal file
View File

@ -0,0 +1,61 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_TV_H__
#define __INCLUDED_STEAM_TV_H__
#include "base.h"
class Steam_TV :
public ISteamTV
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
std::chrono::time_point<std::chrono::steady_clock> initialized_time = std::chrono::steady_clock::now();
FSteamNetworkingSocketsDebugOutput debug_function{};
static void steam_callback(void *object, Common_Message *msg);
static void steam_run_every_runcb(void *object);
public:
Steam_TV(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_TV();
bool IsBroadcasting(int *pnNumViewers);
void AddBroadcastGameData(const char * pchKey, const char * pchValue);
void RemoveBroadcastGameData(const char * pchKey);
void AddTimelineMarker(const char * pchTemplateName, bool bPersistent, uint8 nColorR, uint8 nColorG, uint8 nColorB);
void RemoveTimelineMarker();
uint32 AddRegion(const char * pchElementName, const char * pchTimelineDataSection, const SteamTVRegion_t * pSteamTVRegion, ESteamTVRegionBehavior eSteamTVRegionBehavior);
void RemoveRegion(uint32 unRegionHandle);
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_TV_H__

402
dll/dll/steam_ugc.h Normal file
View File

@ -0,0 +1,402 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_UGC_H__
#define __INCLUDED_STEAM_UGC_H__
#include "base.h"
#include "ugc_remote_storage_bridge.h"
struct UGC_query {
UGCQueryHandle_t handle{};
std::set<PublishedFileId_t> return_only{};
bool return_all_subscribed{};
std::set<PublishedFileId_t> results{};
bool admin_query = false; // added in sdk 1.60 (currently unused)
std::string min_branch{}; // added in sdk 1.60 (currently unused)
std::string max_branch{}; // added in sdk 1.60 (currently unused)
};
class Steam_UGC :
public ISteamUGC001,
public ISteamUGC002,
public ISteamUGC003,
public ISteamUGC004,
public ISteamUGC005,
public ISteamUGC006,
public ISteamUGC007,
public ISteamUGC008,
public ISteamUGC009,
public ISteamUGC010,
public ISteamUGC011,
public ISteamUGC012,
public ISteamUGC013,
public ISteamUGC014,
public ISteamUGC015,
public ISteamUGC016,
public ISteamUGC017,
public ISteamUGC018,
public ISteamUGC
{
constexpr static const char ugc_favorits_file[] = "favorites.txt";
class Settings *settings{};
class Ugc_Remote_Storage_Bridge *ugc_bridge{};
class Local_Storage *local_storage{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
UGCQueryHandle_t handle = 50; // just makes debugging easier, any initial val is fine, even 1
std::vector<struct UGC_query> ugc_queries{};
std::set<PublishedFileId_t> favorites{};
UGCQueryHandle_t new_ugc_query(
bool return_all_subscribed = false,
std::set<PublishedFileId_t> return_only = std::set<PublishedFileId_t>());
std::optional<Mod_entry> get_query_ugc(UGCQueryHandle_t handle, uint32 index);
std::optional<std::string> get_query_ugc_tag(UGCQueryHandle_t handle, uint32 index, uint32 indexTag);
std::optional<std::vector<std::string>> get_query_ugc_tags(UGCQueryHandle_t handle, uint32 index);
void set_details(PublishedFileId_t id, SteamUGCDetails_t *pDetails);
void read_ugc_favorites();
bool write_ugc_favorites();
public:
Steam_UGC(class Settings *settings, class Ugc_Remote_Storage_Bridge *ugc_bridge, class Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
// Query UGC associated with a user. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1.
UGCQueryHandle_t CreateQueryUserUGCRequest( AccountID_t unAccountID, EUserUGCList eListType, EUGCMatchingUGCType eMatchingUGCType, EUserUGCListSortOrder eSortOrder, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage );
// Query for all matching UGC. Creator app id or consumer app id must be valid and be set to the current running app. unPage should start at 1.
UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, uint32 unPage );
// Query for all matching UGC using the new deep paging interface. Creator app id or consumer app id must be valid and be set to the current running app. pchCursor should be set to NULL or "*" to get the first result set.
UGCQueryHandle_t CreateQueryAllUGCRequest( EUGCQuery eQueryType, EUGCMatchingUGCType eMatchingeMatchingUGCTypeFileType, AppId_t nCreatorAppID, AppId_t nConsumerAppID, const char *pchCursor = NULL );
// Query for the details of the given published file ids (the RequestUGCDetails call is deprecated and replaced with this)
UGCQueryHandle_t CreateQueryUGCDetailsRequest( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs );
// Send the query to Steam
STEAM_CALL_RESULT( SteamUGCQueryCompleted_t )
SteamAPICall_t SendQueryUGCRequest( UGCQueryHandle_t handle );
// Retrieve an individual result after receiving the callback for querying UGC
bool GetQueryUGCResult( UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t *pDetails );
uint32 GetQueryUGCNumTags( UGCQueryHandle_t handle, uint32 index );
bool GetQueryUGCTag( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize );
bool GetQueryUGCTagDisplayName( UGCQueryHandle_t handle, uint32 index, uint32 indexTag, STEAM_OUT_STRING_COUNT( cchValueSize ) char* pchValue, uint32 cchValueSize );
bool GetQueryUGCPreviewURL( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURL, uint32 cchURLSize );
bool GetQueryUGCMetadata( UGCQueryHandle_t handle, uint32 index, STEAM_OUT_STRING_COUNT(cchMetadatasize) char *pchMetadata, uint32 cchMetadatasize );
bool GetQueryUGCChildren( UGCQueryHandle_t handle, uint32 index, PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries );
bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint64 *pStatValue );
bool GetQueryUGCStatistic( UGCQueryHandle_t handle, uint32 index, EItemStatistic eStatType, uint32 *pStatValue );
uint32 GetQueryUGCNumAdditionalPreviews( UGCQueryHandle_t handle, uint32 index );
bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchURLOrVideoID, uint32 cchURLSize, STEAM_OUT_STRING_COUNT(cchURLSize) char *pchOriginalFileName, uint32 cchOriginalFileNameSize, EItemPreviewType *pPreviewType );
bool GetQueryUGCAdditionalPreview( UGCQueryHandle_t handle, uint32 index, uint32 previewIndex, char *pchURLOrVideoID, uint32 cchURLSize, bool *hz );
uint32 GetQueryUGCNumKeyValueTags( UGCQueryHandle_t handle, uint32 index );
bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, uint32 keyValueTagIndex, STEAM_OUT_STRING_COUNT(cchKeySize) char *pchKey, uint32 cchKeySize, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize );
bool GetQueryUGCKeyValueTag( UGCQueryHandle_t handle, uint32 index, const char *pchKey, STEAM_OUT_STRING_COUNT(cchValueSize) char *pchValue, uint32 cchValueSize );
// Some items can specify that they have a version that is valid for a range of game versions (Steam branch)
uint32 GetNumSupportedGameVersions( UGCQueryHandle_t handle, uint32 index );
bool GetSupportedGameVersionData( UGCQueryHandle_t handle, uint32 index, uint32 versionIndex, STEAM_OUT_STRING_COUNT( cchGameBranchSize ) char *pchGameBranchMin, STEAM_OUT_STRING_COUNT( cchGameBranchSize ) char *pchGameBranchMax, uint32 cchGameBranchSize );
uint32 GetQueryUGCContentDescriptors( UGCQueryHandle_t handle, uint32 index, EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries );
// Release the request to free up memory, after retrieving results
bool ReleaseQueryUGCRequest( UGCQueryHandle_t handle );
// Options to set for querying UGC
bool AddRequiredTag( UGCQueryHandle_t handle, const char *pTagName );
bool AddRequiredTagGroup( UGCQueryHandle_t handle, const SteamParamStringArray_t *pTagGroups );
bool AddExcludedTag( UGCQueryHandle_t handle, const char *pTagName );
bool SetReturnOnlyIDs( UGCQueryHandle_t handle, bool bReturnOnlyIDs );
bool SetReturnKeyValueTags( UGCQueryHandle_t handle, bool bReturnKeyValueTags );
bool SetReturnLongDescription( UGCQueryHandle_t handle, bool bReturnLongDescription );
bool SetReturnMetadata( UGCQueryHandle_t handle, bool bReturnMetadata );
bool SetReturnChildren( UGCQueryHandle_t handle, bool bReturnChildren );
bool SetReturnAdditionalPreviews( UGCQueryHandle_t handle, bool bReturnAdditionalPreviews );
bool SetReturnTotalOnly( UGCQueryHandle_t handle, bool bReturnTotalOnly );
bool SetReturnPlaytimeStats( UGCQueryHandle_t handle, uint32 unDays );
bool SetLanguage( UGCQueryHandle_t handle, const char *pchLanguage );
bool SetAllowCachedResponse( UGCQueryHandle_t handle, uint32 unMaxAgeSeconds );
// allow ISteamUGC to be used in a tools like environment for users who have the appropriate privileges for the calling appid
bool SetAdminQuery( UGCUpdateHandle_t handle, bool bAdminQuery );
// Options only for querying user UGC
bool SetCloudFileNameFilter( UGCQueryHandle_t handle, const char *pMatchCloudFileName );
// Options only for querying all UGC
bool SetMatchAnyTag( UGCQueryHandle_t handle, bool bMatchAnyTag );
bool SetSearchText( UGCQueryHandle_t handle, const char *pSearchText );
bool SetRankedByTrendDays( UGCQueryHandle_t handle, uint32 unDays );
bool AddRequiredKeyValueTag( UGCQueryHandle_t handle, const char *pKey, const char *pValue );
bool SetTimeCreatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd );
bool SetTimeUpdatedDateRange( UGCQueryHandle_t handle, RTime32 rtStart, RTime32 rtEnd );
// DEPRECATED - Use CreateQueryUGCDetailsRequest call above instead!
SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID, uint32 unMaxAgeSeconds );
SteamAPICall_t RequestUGCDetails( PublishedFileId_t nPublishedFileID );
// Steam Workshop Creator API
STEAM_CALL_RESULT( CreateItemResult_t )
SteamAPICall_t CreateItem( AppId_t nConsumerAppId, EWorkshopFileType eFileType );
// create new item for this app with no content attached yet
UGCUpdateHandle_t StartItemUpdate( AppId_t nConsumerAppId, PublishedFileId_t nPublishedFileID );
// start an UGC item update. Set changed properties before commiting update with CommitItemUpdate()
bool SetItemTitle( UGCUpdateHandle_t handle, const char *pchTitle );
// change the title of an UGC item
bool SetItemDescription( UGCUpdateHandle_t handle, const char *pchDescription );
// change the description of an UGC item
bool SetItemUpdateLanguage( UGCUpdateHandle_t handle, const char *pchLanguage );
// specify the language of the title or description that will be set
bool SetItemMetadata( UGCUpdateHandle_t handle, const char *pchMetaData );
// change the metadata of an UGC item (max = k_cchDeveloperMetadataMax)
bool SetItemVisibility( UGCUpdateHandle_t handle, ERemoteStoragePublishedFileVisibility eVisibility );
// change the visibility of an UGC item
bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags );
bool SetItemTags( UGCUpdateHandle_t updateHandle, const SteamParamStringArray_t *pTags, bool bAllowAdminTags );
// change the tags of an UGC item
bool SetItemContent( UGCUpdateHandle_t handle, const char *pszContentFolder );
// update item content from this local folder
bool SetItemPreview( UGCUpdateHandle_t handle, const char *pszPreviewFile );
// change preview image file for this item. pszPreviewFile points to local image file, which must be under 1MB in size
bool SetAllowLegacyUpload( UGCUpdateHandle_t handle, bool bAllowLegacyUpload );
bool RemoveAllItemKeyValueTags( UGCUpdateHandle_t handle );
// remove all existing key-value tags (you can add new ones via the AddItemKeyValueTag function)
bool RemoveItemKeyValueTags( UGCUpdateHandle_t handle, const char *pchKey );
// remove any existing key-value tags with the specified key
bool AddItemKeyValueTag( UGCUpdateHandle_t handle, const char *pchKey, const char *pchValue );
// add new key-value tags for the item. Note that there can be multiple values for a tag.
bool AddItemPreviewFile( UGCUpdateHandle_t handle, const char *pszPreviewFile, EItemPreviewType type );
// add preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size
bool AddItemPreviewVideo( UGCUpdateHandle_t handle, const char *pszVideoID );
// add preview video for this item
bool UpdateItemPreviewFile( UGCUpdateHandle_t handle, uint32 index, const char *pszPreviewFile );
// updates an existing preview file for this item. pszPreviewFile points to local file, which must be under 1MB in size
bool UpdateItemPreviewVideo( UGCUpdateHandle_t handle, uint32 index, const char *pszVideoID );
// updates an existing preview video for this item
bool RemoveItemPreview( UGCUpdateHandle_t handle, uint32 index );
// remove a preview by index starting at 0 (previews are sorted)
bool AddContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid );
bool RemoveContentDescriptor( UGCUpdateHandle_t handle, EUGCContentDescriptorID descid );
bool SetRequiredGameVersions( UGCUpdateHandle_t handle, const char *pszGameBranchMin, const char *pszGameBranchMax );
STEAM_CALL_RESULT( SubmitItemUpdateResult_t )
SteamAPICall_t SubmitItemUpdate( UGCUpdateHandle_t handle, const char *pchChangeNote );
// commit update process started with StartItemUpdate()
EItemUpdateStatus GetItemUpdateProgress( UGCUpdateHandle_t handle, uint64 *punBytesProcessed, uint64* punBytesTotal );
// Steam Workshop Consumer API
STEAM_CALL_RESULT( SetUserItemVoteResult_t )
SteamAPICall_t SetUserItemVote( PublishedFileId_t nPublishedFileID, bool bVoteUp );
STEAM_CALL_RESULT( GetUserItemVoteResult_t )
SteamAPICall_t GetUserItemVote( PublishedFileId_t nPublishedFileID );
STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t )
SteamAPICall_t AddItemToFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID );
STEAM_CALL_RESULT( UserFavoriteItemsListChanged_t )
SteamAPICall_t RemoveItemFromFavorites( AppId_t nAppId, PublishedFileId_t nPublishedFileID );
STEAM_CALL_RESULT( RemoteStorageSubscribePublishedFileResult_t )
SteamAPICall_t SubscribeItem( PublishedFileId_t nPublishedFileID );
// subscribe to this item, will be installed ASAP
STEAM_CALL_RESULT( RemoteStorageUnsubscribePublishedFileResult_t )
SteamAPICall_t UnsubscribeItem( PublishedFileId_t nPublishedFileID );
// unsubscribe from this item, will be uninstalled after game quits
uint32 GetNumSubscribedItems();
// number of subscribed items
uint32 GetSubscribedItems( PublishedFileId_t* pvecPublishedFileID, uint32 cMaxEntries );
// all subscribed item PublishFileIDs
// get EItemState flags about item on this client
uint32 GetItemState( PublishedFileId_t nPublishedFileID );
// get info about currently installed content on disc for items that have k_EItemStateInstalled set
// if k_EItemStateLegacyItem is set, pchFolder contains the path to the legacy file itself (not a folder)
bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, uint32 *punTimeStamp );
// get info about pending update for items that have k_EItemStateNeedsUpdate set. punBytesTotal will be valid after download started once
bool GetItemDownloadInfo( PublishedFileId_t nPublishedFileID, uint64 *punBytesDownloaded, uint64 *punBytesTotal );
bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, STEAM_OUT_STRING_COUNT( cchFolderSize ) char *pchFolder, uint32 cchFolderSize, bool *pbLegacyItem ); // returns true if item is installed
bool GetItemUpdateInfo( PublishedFileId_t nPublishedFileID, bool *pbNeedsUpdate, bool *pbIsDownloading, uint64 *punBytesDownloaded, uint64 *punBytesTotal );
bool GetItemInstallInfo( PublishedFileId_t nPublishedFileID, uint64 *punSizeOnDisk, char *pchFolder, uint32 cchFolderSize ); // returns true if item is installed
// download new or update already installed item. If function returns true, wait for DownloadItemResult_t. If the item is already installed,
// then files on disk should not be used until callback received. If item is not subscribed to, it will be cached for some time.
// If bHighPriority is set, any other item download will be suspended and this item downloaded ASAP.
bool DownloadItem( PublishedFileId_t nPublishedFileID, bool bHighPriority );
// game servers can set a specific workshop folder before issuing any UGC commands.
// This is helpful if you want to support multiple game servers running out of the same install folder
bool BInitWorkshopForGameServer( DepotId_t unWorkshopDepotID, const char *pszFolder );
// SuspendDownloads( true ) will suspend all workshop downloads until SuspendDownloads( false ) is called or the game ends
void SuspendDownloads( bool bSuspend );
// usage tracking
STEAM_CALL_RESULT( StartPlaytimeTrackingResult_t )
SteamAPICall_t StartPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs );
STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t )
SteamAPICall_t StopPlaytimeTracking( PublishedFileId_t *pvecPublishedFileID, uint32 unNumPublishedFileIDs );
STEAM_CALL_RESULT( StopPlaytimeTrackingResult_t )
SteamAPICall_t StopPlaytimeTrackingForAllItems();
// parent-child relationship or dependency management
STEAM_CALL_RESULT( AddUGCDependencyResult_t )
SteamAPICall_t AddDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID );
STEAM_CALL_RESULT( RemoveUGCDependencyResult_t )
SteamAPICall_t RemoveDependency( PublishedFileId_t nParentPublishedFileID, PublishedFileId_t nChildPublishedFileID );
// add/remove app dependence/requirements (usually DLC)
STEAM_CALL_RESULT( AddAppDependencyResult_t )
SteamAPICall_t AddAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID );
STEAM_CALL_RESULT( RemoveAppDependencyResult_t )
SteamAPICall_t RemoveAppDependency( PublishedFileId_t nPublishedFileID, AppId_t nAppID );
// request app dependencies. note that whatever callback you register for GetAppDependenciesResult_t may be called multiple times
// until all app dependencies have been returned
STEAM_CALL_RESULT( GetAppDependenciesResult_t )
SteamAPICall_t GetAppDependencies( PublishedFileId_t nPublishedFileID );
// delete the item without prompting the user
STEAM_CALL_RESULT( DeleteItemResult_t )
SteamAPICall_t DeleteItem( PublishedFileId_t nPublishedFileID );
// Show the app's latest Workshop EULA to the user in an overlay window, where they can accept it or not
bool ShowWorkshopEULA();
// Retrieve information related to the user's acceptance or not of the app's specific Workshop EULA
STEAM_CALL_RESULT( WorkshopEULAStatus_t )
SteamAPICall_t GetWorkshopEULAStatus();
// Return the user's community content descriptor preferences
uint32 GetUserContentDescriptorPreferences( EUGCContentDescriptorID *pvecDescriptors, uint32 cMaxEntries );
};
#endif // __INCLUDED_STEAM_UGC_H__

View File

@ -0,0 +1,67 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_UNIFIED_MESSAGES_H__
#define __INCLUDED_STEAM_UNIFIED_MESSAGES_H__
#include "base.h"
class Steam_Unified_Messages:
public ISteamUnifiedMessages
{
class Settings *settings{};
class Networking *network{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class RunEveryRunCB *run_every_runcb{};
static void network_callback(void *object, Common_Message *msg);
static void steam_runcb(void *object);
public:
Steam_Unified_Messages(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb);
~Steam_Unified_Messages();
// Sends a service method (in binary serialized form) using the Steam Client.
// Returns a unified message handle (k_InvalidUnifiedMessageHandle if could not send the message).
ClientUnifiedMessageHandle SendMethod( const char *pchServiceMethod, const void *pRequestBuffer, uint32 unRequestBufferSize, uint64 unContext );
// Gets the size of the response and the EResult. Returns false if the response is not ready yet.
bool GetMethodResponseInfo( ClientUnifiedMessageHandle hHandle, uint32 *punResponseSize, EResult *peResult );
// Gets a response in binary serialized form (and optionally release the corresponding allocated memory).
bool GetMethodResponseData( ClientUnifiedMessageHandle hHandle, void *pResponseBuffer, uint32 unResponseBufferSize, bool bAutoRelease );
// Releases the message and its corresponding allocated memory.
bool ReleaseMethod( ClientUnifiedMessageHandle hHandle );
// Sends a service notification (in binary serialized form) using the Steam Client.
// Returns true if the notification was sent successfully.
bool SendNotification( const char *pchServiceNotification, const void *pNotificationBuffer, uint32 unNotificationBufferSize );
void RunCallbacks();
void Callback(Common_Message *msg);
};
#endif // __INCLUDED_STEAM_UNIFIED_MESSAGES_H__

318
dll/dll/steam_user.h Normal file
View File

@ -0,0 +1,318 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_USER_H__
#define __INCLUDED_STEAM_USER_H__
#include "base.h"
#include "auth.h"
class Steam_User :
public ISteamUser004,
public ISteamUser005,
public ISteamUser006,
public ISteamUser007,
public ISteamUser008,
public ISteamUser009,
public ISteamUser010,
public ISteamUser011,
public ISteamUser012,
public ISteamUser013,
public ISteamUser014,
public ISteamUser015,
public ISteamUser016,
public ISteamUser017,
public ISteamUser018,
public ISteamUser019,
public ISteamUser020,
public ISteamUser021,
public ISteamUser022,
public ISteamUser
{
Settings *settings{};
class Networking *network{};
class SteamCallBacks *callbacks{};
class SteamCallResults *callback_results{};
Local_Storage *local_storage{};
bool recording = false;
std::chrono::high_resolution_clock::time_point last_get_voice{};
std::string encrypted_app_ticket{};
Auth_Manager *auth_manager{};
std::map<std::string, std::string> registry{};
std::string registry_nullptr{};
public:
Steam_User(Settings *settings, Local_Storage *local_storage, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks);
~Steam_User();
// returns the HSteamUser this interface represents
// this is only used internally by the API, and by a few select interfaces that support multi-user
HSteamUser GetHSteamUser();
void LogOn( CSteamID steamID );
void LogOff();
// returns true if the Steam client current has a live connection to the Steam servers.
// If false, it means there is no active connection due to either a networking issue on the local machine, or the Steam server is down/busy.
// The Steam client will automatically be trying to recreate the connection as often as possible.
bool BLoggedOn();
ELogonState GetLogonState();
bool BConnected();
// returns the CSteamID of the account currently logged into the Steam client
// a CSteamID is a unique identifier for an account, and used to differentiate users in all parts of the Steamworks API
CSteamID GetSteamID();
bool IsVACBanned( int nGameID );
bool RequireShowVACBannedMessage( int nGameID );
void AcknowledgeVACBanning( int nGameID );
// These are dead.
int NClientGameIDAdd( int nGameID );
void RemoveClientGame( int nClientGameID );
void SetClientGameServer( int nClientGameID, uint32 unIPServer, uint16 usPortServer );
void SetSteam2Ticket( uint8 *pubTicket, int cubTicket );
void AddServerNetAddress( uint32 unIP, uint16 unPort );
bool SetEmail( const char *pchEmail );
// 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 );
// Multiplayer Authentication functions
// InitiateGameConnection() starts the state machine for authenticating the game client with the game server
// It is the client portion of a three-way handshake between the client, the game server, and the steam servers
//
// Parameters:
// void *pAuthBlob - a pointer to empty memory that will be filled in with the authentication token.
// int cbMaxAuthBlob - the number of bytes of allocated memory in pBlob. Should be at least 2048 bytes.
// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client
// CGameID gameID - the ID of the current game. For games without mods, this is just CGameID( <appID> )
// uint32 unIPServer, uint16 usPortServer - the IP address of the game server
// bool bSecure - whether or not the client thinks that the game server is reporting itself as secure (i.e. VAC is running)
//
// return value - returns the number of bytes written to pBlob. If the return is 0, then the buffer passed in was too small, and the call has failed
// The contents of pBlob should then be sent to the game server, for it to use to complete the authentication process.
int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer, bool bSecure );
int InitiateGameConnection( void *pAuthBlob, int cbMaxAuthBlob, CSteamID steamIDGameServer, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure );
int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, CGameID gameID, uint32 unIPServer, uint16 usPortServer, bool bSecure, void *pvSteam2GetEncryptionKey, int cbSteam2GetEncryptionKey );
int InitiateGameConnection( void *pBlob, int cbMaxBlob, CSteamID steamID, int nGameAppID, uint32 unIPServer, uint16 usPortServer, bool bSecure );
// notify of disconnect
// needs to occur when the game client leaves the specified game server, needs to match with the InitiateGameConnection() call
void TerminateGameConnection( uint32 unIPServer, uint16 usPortServer );
// Legacy functions
void SetSelfAsPrimaryChatDestination();
bool IsPrimaryChatDestination();
void RequestLegacyCDKey( uint32 nAppID );
bool SendGuestPassByEmail( const char *pchEmailAccount, GID_t gidGuestPassID, bool bResending );
bool SendGuestPassByAccountID( uint32 uAccountID, GID_t gidGuestPassID, bool bResending );
bool AckGuestPass(const char *pchGuestPassCode);
bool RedeemGuestPass(const char *pchGuestPassCode);
uint32 GetGuestPassToGiveCount();
uint32 GetGuestPassToRedeemCount();
RTime32 GetGuestPassLastUpdateTime();
bool GetGuestPassToGiveInfo( uint32 nPassIndex, GID_t *pgidGuestPassID, PackageId_t *pnPackageID, RTime32 *pRTime32Created, RTime32 *pRTime32Expiration, RTime32 *pRTime32Sent, RTime32 *pRTime32Redeemed, char *pchRecipientAddress, int cRecipientAddressSize );
bool GetGuestPassToRedeemInfo( uint32 nPassIndex, GID_t *pgidGuestPassID, PackageId_t *pnPackageID, RTime32 *pRTime32Created, RTime32 *pRTime32Expiration, RTime32 *pRTime32Sent, RTime32 *pRTime32Redeemed);
bool GetGuestPassToRedeemSenderAddress( uint32 nPassIndex, char* pchSenderAddress, int cSenderAddressSize );
bool GetGuestPassToRedeemSenderName( uint32 nPassIndex, char* pchSenderName, int cSenderNameSize );
void AcknowledgeMessageByGID( const char *pchMessageGID );
bool SetLanguage( const char *pchLanguage );
// used by only a few games to track usage events
void TrackAppUsageEvent( CGameID gameID, int eAppUsageEvent, const char *pchExtraInfo);
void SetAccountName( const char *pchAccountName );
void SetPassword( const char *pchPassword );
void SetAccountCreationTime( RTime32 rt );
void RefreshSteam2Login();
// get the local storage folder for current Steam account to write application data, e.g. save games, configs etc.
// this will usually be something like "C:\Progam Files\Steam\userdata\<SteamID>\<AppID>\local"
bool GetUserDataFolder( char *pchBuffer, int cubBuffer );
// Starts voice recording. Once started, use GetVoice() to get the data
void StartVoiceRecording( );
// Stops voice recording. Because people often release push-to-talk keys early, the system will keep recording for
// a little bit after this function is called. GetVoice() should continue to be called until it returns
// k_eVoiceResultNotRecording
void StopVoiceRecording( );
// Determine the size of captured audio data that is available from GetVoice.
// Most applications will only use compressed data and should ignore the other
// parameters, which exist primarily for backwards compatibility. See comments
// below for further explanation of "uncompressed" data.
EVoiceResult GetAvailableVoice( uint32 *pcbCompressed, uint32 *pcbUncompressed_Deprecated, uint32 nUncompressedVoiceDesiredSampleRate_Deprecated );
EVoiceResult GetAvailableVoice(uint32 *pcbCompressed, uint32 *pcbUncompressed);
// ---------------------------------------------------------------------------
// NOTE: "uncompressed" audio is a deprecated feature and should not be used
// by most applications. It is raw single-channel 16-bit PCM wave data which
// may have been run through preprocessing filters and/or had silence removed,
// so the uncompressed audio could have a shorter duration than you expect.
// There may be no data at all during long periods of silence. Also, fetching
// uncompressed audio will cause GetVoice to discard any leftover compressed
// audio, so you must fetch both types at once. Finally, GetAvailableVoice is
// not precisely accurate when the uncompressed size is requested. So if you
// really need to use uncompressed audio, you should call GetVoice frequently
// with two very large (20kb+) output buffers instead of trying to allocate
// perfectly-sized buffers. But most applications should ignore all of these
// details and simply leave the "uncompressed" parameters as NULL/zero.
// ---------------------------------------------------------------------------
// Read captured audio data from the microphone buffer. This should be called
// at least once per frame, and preferably every few milliseconds, to keep the
// microphone input delay as low as possible. Most applications will only use
// compressed data and should pass NULL/zero for the "uncompressed" parameters.
// Compressed data can be transmitted by your application and decoded into raw
// using the DecompressVoice function below.
EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed_Deprecated, void *pUncompressedDestBuffer_Deprecated , uint32 cbUncompressedDestBufferSize_Deprecated , uint32 *nUncompressBytesWritten_Deprecated , uint32 nUncompressedVoiceDesiredSampleRate_Deprecated );
EVoiceResult GetVoice( bool bWantCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, bool bWantUncompressed, void *pUncompressedDestBuffer, uint32 cbUncompressedDestBufferSize, uint32 *nUncompressBytesWritten );
EVoiceResult GetCompressedVoice( void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten );
// Decodes the compressed voice data returned by GetVoice. The output data is
// raw single-channel 16-bit PCM audio. The decoder supports any sample rate
// from 11025 to 48000; see GetVoiceOptimalSampleRate() below for details.
// If the output buffer is not large enough, then *nBytesWritten will be set
// to the required buffer size, and k_EVoiceResultBufferTooSmall is returned.
// It is suggested to start with a 20kb buffer and reallocate as necessary.
EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten, uint32 nDesiredSampleRate );
EVoiceResult DecompressVoice( const void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten );
EVoiceResult DecompressVoice( void *pCompressed, uint32 cbCompressed, void *pDestBuffer, uint32 cbDestBufferSize, uint32 *nBytesWritten );
// This returns the native sample rate of the Steam voice decompressor
// this sample rate for DecompressVoice will perform the least CPU processing.
// However, the final audio quality will depend on how well the audio device
// (and/or your application's audio output SDK) deals with lower sample rates.
// You may find that you get the best audio output quality when you ignore
// this function and use the native sample rate of your audio output device,
// which is usually 48000 or 44100.
uint32 GetVoiceOptimalSampleRate();
// Retrieve ticket to be sent to the entity who wishes to authenticate you.
// pcbTicket retrieves the length of the actual ticket.
HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
// SteamNetworkingIdentity is an optional input parameter to hold the public IP address or SteamID of the entity you are connecting to
// if an IP address is passed Steam will only allow the ticket to be used by an entity with that IP address
// if a Steam ID is passed Steam will only allow the ticket to be used by that Steam ID
HAuthTicket GetAuthSessionTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket, const SteamNetworkingIdentity *pSteamNetworkingIdentity );
// Request a ticket which will be used for webapi "ISteamUserAuth\AuthenticateUserTicket"
// pchIdentity is an optional input parameter to identify the service the ticket will be sent to
// the ticket will be returned in callback GetTicketForWebApiResponse_t
HAuthTicket GetAuthTicketForWebApi( const char *pchIdentity );
// Authenticate ticket from entity steamID to be sure it is valid and isnt reused
// Registers for callbacks if the entity goes offline or cancels the ticket ( see ValidateAuthTicketResponse_t callback and EAuthSessionResponse )
EBeginAuthSessionResult BeginAuthSession( const void *pAuthTicket, int cbAuthTicket, CSteamID steamID );
// Stop tracking started by BeginAuthSession - called when no longer playing game with this entity
void EndAuthSession( CSteamID steamID );
// Cancel auth ticket from GetAuthSessionTicket, called when no longer playing game with the entity you gave the ticket to
void CancelAuthTicket( HAuthTicket hAuthTicket );
// After receiving a user's authentication data, and passing it to BeginAuthSession, use this function
// to determine if the user owns downloadable content specified by the provided AppID.
EUserHasLicenseForAppResult UserHasLicenseForApp( CSteamID steamID, AppId_t appID );
// returns true if this users looks like they are behind a NAT device. Only valid once the user has connected to steam
// (i.e a SteamServersConnected_t has been issued) and may not catch all forms of NAT.
bool BIsBehindNAT();
// set data to be replicated to friends so that they can join your game
// CSteamID steamIDGameServer - the steamID of the game server, received from the game server by the client
// uint32 unIPServer, uint16 usPortServer - the IP address of the game server
void AdvertiseGame( CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPortServer );
// Requests a ticket encrypted with an app specific shared key
// pDataToInclude, cbDataToInclude will be encrypted into the ticket
// ( This is asynchronous, you must wait for the ticket to be completed by the server )
STEAM_CALL_RESULT( EncryptedAppTicketResponse_t )
SteamAPICall_t RequestEncryptedAppTicket( void *pDataToInclude, int cbDataToInclude );
// retrieve a finished ticket
bool GetEncryptedAppTicket( void *pTicket, int cbMaxTicket, uint32 *pcbTicket );
// Trading Card badges data access
// if you only have one set of cards, the series will be 1
// the user has can have two different badges for a series; the regular (max level 5) and the foil (max level 1)
int GetGameBadgeLevel( int nSeries, bool bFoil );
// gets the Steam Level of the user, as shown on their profile
int GetPlayerSteamLevel();
// Requests a URL which authenticates an in-game browser for store check-out,
// and then redirects to the specified URL. As long as the in-game browser
// accepts and handles session cookies, Steam microtransaction checkout pages
// will automatically recognize the user instead of presenting a login page.
// The result of this API call will be a StoreAuthURLResponse_t callback.
// NOTE: The URL has a very short lifetime to prevent history-snooping attacks,
// so you should only call this API when you are about to launch the browser,
// or else immediately navigate to the result URL using a hidden browser window.
// NOTE 2: The resulting authorization cookie has an expiration time of one day,
// so it would be a good idea to request and visit a new auth URL every 12 hours.
STEAM_CALL_RESULT( StoreAuthURLResponse_t )
SteamAPICall_t RequestStoreAuthURL( const char *pchRedirectURL );
// gets whether the users phone number is verified
bool BIsPhoneVerified();
// gets whether the user has two factor enabled on their account
bool BIsTwoFactorEnabled();
// gets whether the users phone number is identifying
bool BIsPhoneIdentifying();
// gets whether the users phone number is awaiting (re)verification
bool BIsPhoneRequiringVerification();
STEAM_CALL_RESULT( MarketEligibilityResponse_t )
SteamAPICall_t GetMarketEligibility();
// Retrieves anti indulgence / duration control for current user
STEAM_CALL_RESULT( DurationControl_t )
SteamAPICall_t GetDurationControl();
// Advise steam china duration control system about the online state of the game.
// This will prevent offline gameplay time from counting against a user's
// playtime limits.
bool BSetDurationControlOnlineState( EDurationControlOnlineState eNewState );
};
#endif // __INCLUDED_STEAM_USER_H__

408
dll/dll/steam_user_stats.h Normal file
View File

@ -0,0 +1,408 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_USER_STATS_H__
#define __INCLUDED_STEAM_USER_STATS_H__
#include <limits>
#include "base.h"
#include "overlay/steam_overlay.h"
struct Steam_Leaderboard_Entry {
CSteamID steam_id{};
int32 score{};
std::vector<int32> score_details{};
};
struct Steam_Leaderboard {
std::string name{};
ELeaderboardSortMethod sort_method = k_ELeaderboardSortMethodNone;
ELeaderboardDisplayType display_type = k_ELeaderboardDisplayTypeNone;
std::vector<Steam_Leaderboard_Entry> entries{};
Steam_Leaderboard_Entry* find_recent_entry(const CSteamID &steamid) const;
void remove_entries(const CSteamID &steamid);
// remove entries with the same steamid, keeping only most recent one
void remove_duplicate_entries();
void sort_entries();
};
struct achievement_trigger {
std::string name{}; // defined achievement name
std::string value_operation{};
std::string min_value{}; // min progress
std::string max_value{}; // max progress
bool should_unlock_ach(float stat) const;
bool should_unlock_ach(int32 stat) const;
bool should_indicate_progress(float stat) const;
bool should_indicate_progress(int32 stat) const;
};
class Steam_User_Stats :
public ISteamUserStats003,
public ISteamUserStats004,
public ISteamUserStats005,
public ISteamUserStats006,
public ISteamUserStats007,
public ISteamUserStats008,
public ISteamUserStats009,
public ISteamUserStats010,
public ISteamUserStats011,
public ISteamUserStats
{
public:
static constexpr const auto achievements_user_file = "achievements.json";
private:
template<typename T>
struct InternalSetResult {
bool success = false;
bool notify_server = false;
std::string internal_name{};
T current_val{};
};
class Local_Storage *local_storage{};
class Settings *settings{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
class Networking *network{};
class RunEveryRunCB *run_every_runcb{};
class Steam_Overlay* overlay{};
std::vector<struct Steam_Leaderboard> cached_leaderboards{};
nlohmann::json defined_achievements{};
nlohmann::json user_achievements{};
std::vector<std::string> sorted_achievement_names{};
size_t last_loaded_ach_icon{};
std::map<std::string, int32> stats_cache_int{};
std::map<std::string, float> stats_cache_float{};
std::map<std::string, std::vector<achievement_trigger>> achievement_stat_trigger{};
// triggered when an achievement is unlocked
// https://partner.steamgames.com/doc/api/ISteamUserStats#StoreStats
std::map<std::string, UserAchievementStored_t> store_stats_trigger{};
GameServerStats_Messages::AllStats pending_server_updates{};
void load_achievements_db();
void load_achievements();
void save_achievements();
int load_ach_icon(nlohmann::json &defined_ach, bool achieved);
nlohmann::detail::iter_impl<nlohmann::json> defined_achievements_find(const std::string &key);
std::string get_value_for_language(const nlohmann::json &json, std::string_view key, std::string_view language);
std::vector<Steam_Leaderboard_Entry> load_leaderboard_entries(const std::string &name);
void save_my_leaderboard_entry(const Steam_Leaderboard &leaderboard);
Steam_Leaderboard_Entry* update_leaderboard_entry(Steam_Leaderboard &leaderboard, const Steam_Leaderboard_Entry &entry, bool overwrite = true);
// returns a value 1 -> leaderboards.size(), inclusive
unsigned int find_cached_leaderboard(const std::string &name);
unsigned int cache_leaderboard_ifneeded(const std::string &name, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType);
// null steamid means broadcast to all
void send_my_leaderboard_score(const Steam_Leaderboard &board, const CSteamID *steamid = nullptr, bool want_scores_back = false);
void request_user_leaderboard_entry(const Steam_Leaderboard &board, const CSteamID &steamid);
// change stats/achievements without sending back to server
bool clear_stats_internal();
InternalSetResult<int32> set_stat_internal( const char *pchName, int32 nData );
InternalSetResult<std::pair<GameServerStats_Messages::StatInfo::Stat_Type, float>> set_stat_internal( const char *pchName, float fData );
InternalSetResult<std::pair<GameServerStats_Messages::StatInfo::Stat_Type, float>> update_avg_rate_stat_internal( const char *pchName, float flCountThisSession, double dSessionLength );
InternalSetResult<bool> set_achievement_internal( const char *pchName );
InternalSetResult<bool> clear_achievement_internal( const char *pchName );
void send_updated_stats();
void load_achievements_icons();
void steam_run_callback();
// requests from server
void network_stats_initial(Common_Message *msg);
void network_stats_updated(Common_Message *msg);
void network_callback_stats(Common_Message *msg);
// requests from other users to share leaderboards
void network_leaderboard_update_score(Common_Message *msg, Steam_Leaderboard &board, bool send_score_back);
void network_leaderboard_send_my_score(Common_Message *msg, const Steam_Leaderboard &board);
void network_callback_leaderboards(Common_Message *msg);
// user connect/disconnect
void network_callback_low_level(Common_Message *msg);
static void steam_user_stats_network_low_level(void *object, Common_Message *msg);
static void steam_user_stats_network_stats(void *object, Common_Message *msg);
static void steam_user_stats_network_leaderboards(void *object, Common_Message *msg);
static void steam_user_stats_run_every_runcb(void *object);
public:
Steam_User_Stats(Settings *settings, class Networking *network, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, Steam_Overlay* overlay);
~Steam_User_Stats();
// Ask the server to send down this user's data and achievements for this game
STEAM_CALL_BACK( UserStatsReceived_t )
bool RequestCurrentStats();
// Data accessors
bool GetStat( const char *pchName, int32 *pData );
bool GetStat( const char *pchName, float *pData );
// Set / update data
bool SetStat( const char *pchName, int32 nData );
bool SetStat( const char *pchName, float fData );
bool UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dSessionLength );
// Achievement flag accessors
bool GetAchievement( const char *pchName, bool *pbAchieved );
bool SetAchievement( const char *pchName );
bool ClearAchievement( const char *pchName );
// Get the achievement status, and the time it was unlocked if unlocked.
// If the return value is true, but the unlock time is zero, that means it was unlocked before Steam
// began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970.
bool GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 *punUnlockTime );
// Store the current data on the server, will get a callback when set
// And one callback for every new achievement
//
// If the callback has a result of k_EResultInvalidParam, one or more stats
// uploaded has been rejected, either because they broke constraints
// or were out of date. In this case the server sends back updated values.
// The stats should be re-iterated to keep in sync.
bool StoreStats();
// Achievement / GroupAchievement metadata
// Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set.
// A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback
// which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the
// specified achievement.
int GetAchievementIcon( const char *pchName );
int get_achievement_icon_handle( const std::string &ach_name, bool pbAchieved, bool force_load = false );
std::string get_achievement_icon_name( const char *pchName, bool achieved );
// Get general attributes for an achievement. Accepts the following keys:
// - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8)
// - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden)
const char * GetAchievementDisplayAttribute( const char *pchName, const char *pchKey );
// Achievement progress - triggers an AchievementProgress callback, that is all.
// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that.
bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress );
// Used for iterating achievements. In general games should not need these functions because they should have a
// list of existing achievements compiled into them
uint32 GetNumAchievements();
// Get achievement name iAchievement in [0,GetNumAchievements)
const char * GetAchievementName( uint32 iAchievement );
// Friends stats & achievements
// downloads stats for the user
// returns a UserStatsReceived_t received when completed
// if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail
// these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data
STEAM_CALL_RESULT( UserStatsReceived_t )
SteamAPICall_t RequestUserStats( CSteamID steamIDUser );
// requests stat information for a user, usable after a successful call to RequestUserStats()
bool GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData );
bool GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData );
bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved );
// See notes for GetAchievementAndUnlockTime above
bool GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool *pbAchieved, uint32 *punUnlockTime );
// Reset stats
bool ResetAllStats( bool bAchievementsToo );
// Leaderboard functions
// asks the Steam back-end for a leaderboard by name, and will create it if it's not yet
// This call is asynchronous, with the result returned in LeaderboardFindResult_t
STEAM_CALL_RESULT(LeaderboardFindResult_t)
SteamAPICall_t FindOrCreateLeaderboard( const char *pchLeaderboardName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType );
// as above, but won't create the leaderboard if it's not found
// This call is asynchronous, with the result returned in LeaderboardFindResult_t
STEAM_CALL_RESULT( LeaderboardFindResult_t )
SteamAPICall_t FindLeaderboard( const char *pchLeaderboardName );
// returns the name of a leaderboard
const char * GetLeaderboardName( SteamLeaderboard_t hSteamLeaderboard );
// returns the total number of entries in a leaderboard, as of the last request
int GetLeaderboardEntryCount( SteamLeaderboard_t hSteamLeaderboard );
// returns the sort method of the leaderboard
ELeaderboardSortMethod GetLeaderboardSortMethod( SteamLeaderboard_t hSteamLeaderboard );
// returns the display type of the leaderboard
ELeaderboardDisplayType GetLeaderboardDisplayType( SteamLeaderboard_t hSteamLeaderboard );
// Asks the Steam back-end for a set of rows in the leaderboard.
// This call is asynchronous, with the result returned in LeaderboardScoresDownloaded_t
// LeaderboardScoresDownloaded_t will contain a handle to pull the results from GetDownloadedLeaderboardEntries() (below)
// You can ask for more entries than exist, and it will return as many as do exist.
// k_ELeaderboardDataRequestGlobal requests rows in the leaderboard from the full table, with nRangeStart & nRangeEnd in the range [1, TotalEntries]
// k_ELeaderboardDataRequestGlobalAroundUser requests rows around the current user, nRangeStart being negate
// e.g. DownloadLeaderboardEntries( hLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -3, 3 ) will return 7 rows, 3 before the user, 3 after
// k_ELeaderboardDataRequestFriends requests all the rows for friends of the current user
STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t )
SteamAPICall_t DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd );
// as above, but downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers
// if a user doesn't have a leaderboard entry, they won't be included in the result
// a max of 100 users can be downloaded at a time, with only one outstanding call at a time
STEAM_METHOD_DESC(Downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers)
STEAM_CALL_RESULT( LeaderboardScoresDownloaded_t )
SteamAPICall_t DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard,
STEAM_ARRAY_COUNT_D(cUsers, Array of users to retrieve) CSteamID *prgUsers, int cUsers );
// Returns data about a single leaderboard entry
// use a for loop from 0 to LeaderboardScoresDownloaded_t::m_cEntryCount to get all the downloaded entries
// e.g.
// void OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *pLeaderboardScoresDownloaded )
// {
// for ( int index = 0; index < pLeaderboardScoresDownloaded->m_cEntryCount; index++ )
// {
// LeaderboardEntry_t leaderboardEntry;
// int32 details[3]; // we know this is how many we've stored previously
// GetDownloadedLeaderboardEntry( pLeaderboardScoresDownloaded->m_hSteamLeaderboardEntries, index, &leaderboardEntry, details, 3 );
// assert( leaderboardEntry.m_cDetails == 3 );
// ...
// }
// once you've accessed all the entries, the data will be free'd, and the SteamLeaderboardEntries_t handle will become invalid
bool GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax );
// Uploads a user score to the Steam back-end.
// This call is asynchronous, with the result returned in LeaderboardScoreUploaded_t
// Details are extra game-defined information regarding how the user got that score
// pScoreDetails points to an array of int32's, cScoreDetailsCount is the number of int32's in the list
STEAM_CALL_RESULT( LeaderboardScoreUploaded_t )
SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount );
SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 *pScoreDetails, int cScoreDetailsCount );
// Attaches a piece of user generated content the user's entry on a leaderboard.
// hContent is a handle to a piece of user generated content that was shared using ISteamUserRemoteStorage::FileShare().
// This call is asynchronous, with the result returned in LeaderboardUGCSet_t.
STEAM_CALL_RESULT( LeaderboardUGCSet_t )
SteamAPICall_t AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC );
// Retrieves the number of players currently playing your game (online + offline)
// This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t
STEAM_CALL_RESULT( NumberOfCurrentPlayers_t )
SteamAPICall_t GetNumberOfCurrentPlayers();
// Requests that Steam fetch data on the percentage of players who have received each achievement
// for the game globally.
// This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t.
STEAM_CALL_RESULT( GlobalAchievementPercentagesReady_t )
SteamAPICall_t RequestGlobalAchievementPercentages();
// Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch
// the next most achieved afterwards. Will return -1 if there is no data on achievement
// percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback).
int GetMostAchievedAchievementInfo( char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved );
// Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another
// GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last
// achievement has been iterated.
int GetNextMostAchievedAchievementInfo( int iIteratorPrevious, char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved );
// Returns the percentage of users who have achieved the specified achievement.
bool GetAchievementAchievedPercent( const char *pchName, float *pflPercent );
// Requests global stats data, which is available for stats marked as "aggregated".
// This call is asynchronous, with the results returned in GlobalStatsReceived_t.
// nHistoryDays specifies how many days of day-by-day history to retrieve in addition
// to the overall totals. The limit is 60.
STEAM_CALL_RESULT( GlobalStatsReceived_t )
SteamAPICall_t RequestGlobalStats( int nHistoryDays );
// Gets the lifetime totals for an aggregated stat
bool GetGlobalStat( const char *pchStatName, int64 *pData );
bool GetGlobalStat( const char *pchStatName, double *pData );
// Gets history for an aggregated stat. pData will be filled with daily values, starting with today.
// So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago,
// etc. cubData is the size in bytes of the pubData buffer. Returns the number of
// elements actually set.
int32 GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) int64 *pData, uint32 cubData );
int32 GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) double *pData, uint32 cubData );
// For achievements that have related Progress stats, use this to query what the bounds of that progress are.
// You may want this info to selectively call IndicateAchievementProgress when appropriate milestones of progress
// have been made, to show a progress notification to the user.
bool GetAchievementProgressLimits( const char *pchName, int32 *pnMinProgress, int32 *pnMaxProgress );
bool GetAchievementProgressLimits( const char *pchName, float *pfMinProgress, float *pfMaxProgress );
};
#endif//__INCLUDED_STEAM_USER_STATS_H__

223
dll/dll/steam_utils.h Normal file
View File

@ -0,0 +1,223 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __STEAM_UTILS_H__
#define __STEAM_UTILS_H__
#include "common_includes.h"
#include "local_storage.h"
#include "overlay/steam_overlay.h"
class Steam_Utils :
public ISteamUtils001,
public ISteamUtils002,
public ISteamUtils003,
public ISteamUtils004,
public ISteamUtils005,
public ISteamUtils006,
public ISteamUtils007,
public ISteamUtils008,
public ISteamUtils009,
public ISteamUtils
{
private:
Settings *settings{};
class SteamCallResults *callback_results{};
class SteamCallBacks *callbacks{};
Steam_Overlay* overlay{};
public:
Steam_Utils(Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, Steam_Overlay *overlay);
// return the number of seconds since the user
uint32 GetSecondsSinceAppActive();
uint32 GetSecondsSinceComputerActive();
// the universe this client is connecting to
EUniverse GetConnectedUniverse();
// Steam server time. Number of seconds since January 1, 1970, GMT (i.e unix time)
uint32 GetServerRealTime();
// returns the 2 digit ISO 3166-1-alpha-2 format country code this client is running in (as looked up via an IP-to-location database)
// e.g "US" or "UK".
const char *GetIPCountry();
// returns true if the image exists, and valid sizes were filled out
bool GetImageSize( int iImage, uint32 *pnWidth, uint32 *pnHeight );
// returns true if the image exists, and the buffer was successfully filled out
// results are returned in RGBA format
// the destination buffer size should be 4 * height * width * sizeof(char)
bool GetImageRGBA( int iImage, uint8 *pubDest, int nDestBufferSize );
// returns the IP of the reporting server for valve - currently only used in Source engine games
bool GetCSERIPPort( uint32 *unIP, uint16 *usPort );
// return the amount of battery power left in the current system in % [0..100], 255 for being on AC power
uint8 GetCurrentBatteryPower();
// returns the appID of the current process
uint32 GetAppID();
// Sets the position where the overlay instance for the currently calling game should show notifications.
// This position is per-game and if this function is called from outside of a game context it will do nothing.
void SetOverlayNotificationPosition( ENotificationPosition eNotificationPosition );
// API asynchronous call results
// can be used directly, but more commonly used via the callback dispatch API (see steam_api.h)
bool IsAPICallCompleted( SteamAPICall_t hSteamAPICall, bool *pbFailed );
ESteamAPICallFailure GetAPICallFailureReason( SteamAPICall_t hSteamAPICall );
bool GetAPICallResult( SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, int iCallbackExpected, bool *pbFailed );
// Deprecated. Applications should use SteamAPI_RunCallbacks() instead. Game servers do not need to call this function.
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();
// 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 );
// Returns true if the overlay is running & the user can access it. The overlay process could take a few seconds to
// start & hook the game process, so this function will initially return false while the overlay is loading.
bool IsOverlayEnabled();
// Normally this call is unneeded if your game has a constantly running frame loop that calls the
// D3D Present API, or OGL SwapBuffers API every frame.
//
// However, if you have a game that only refreshes the screen on an event driven basis then that can break
// the overlay, as it uses your Present/SwapBuffers calls to drive it's internal frame loop and it may also
// need to Present() to the screen any time an even needing a notification happens or when the overlay is
// brought up over the game by a user. You can use this API to ask the overlay if it currently need a present
// in that case, and then you can check for this periodically (roughly 33hz is desirable) and make sure you
// refresh the screen with Present or SwapBuffers to allow the overlay to do it's work.
bool BOverlayNeedsPresent();
// Asynchronous call to check if an executable file has been signed using the public key set on the signing tab
// of the partner site, for example to refuse to load modified executable files.
// The result is returned in CheckFileSignature_t.
// k_ECheckFileSignatureNoSignaturesFoundForThisApp - This app has not been configured on the signing tab of the partner site to enable this function.
// k_ECheckFileSignatureNoSignaturesFoundForThisFile - This file is not listed on the signing tab for the partner site.
// k_ECheckFileSignatureFileNotFound - The file does not exist on disk.
// k_ECheckFileSignatureInvalidSignature - The file exists, and the signing tab has been set for this file, but the file is either not signed or the signature does not match.
// k_ECheckFileSignatureValidSignature - The file is signed and the signature is valid.
STEAM_CALL_RESULT( CheckFileSignature_t )
SteamAPICall_t CheckFileSignature( const char *szFileName );
// Activates the Big Picture text input dialog which only supports gamepad input
bool ShowGamepadTextInput( EGamepadTextInputMode eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32 unCharMax, const char *pchExistingText );
bool ShowGamepadTextInput( EGamepadTextInputMode eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32 unCharMax );
// Returns previously entered text & length
uint32 GetEnteredGamepadTextLength();
bool GetEnteredGamepadTextInput( char *pchText, uint32 cchText );
// returns the language the steam client is running in, you probably want ISteamApps::GetCurrentGameLanguage instead, this is for very special usage cases
const char *GetSteamUILanguage();
// returns true if Steam itself is running in VR mode
bool IsSteamRunningInVR();
// Sets the inset of the overlay notification from the corner specified by SetOverlayNotificationPosition.
void SetOverlayNotificationInset( int nHorizontalInset, int nVerticalInset );
// returns true if Steam & the Steam Overlay are running in Big Picture mode
// Games much be launched through the Steam client to enable the Big Picture overlay. During development,
// a game can be added as a non-steam game to the developers library to test this feature
bool IsSteamInBigPictureMode();
// ask SteamUI to create and render its OpenVR dashboard
void StartVRDashboard();
// Returns true if the HMD content will be streamed via Steam In-Home Streaming
bool IsVRHeadsetStreamingEnabled();
// Set whether the HMD content will be streamed via Steam In-Home Streaming
// If this is set to true, then the scene in the HMD headset will be streamed, and remote input will not be allowed.
// If this is set to false, then the application window will be streamed instead, and remote input will be allowed.
// The default is true unless "VRHeadsetStreaming" "0" is in the extended appinfo for a game.
// (this is useful for games that have asymmetric multiplayer gameplay)
void SetVRHeadsetStreamingEnabled( bool bEnabled );
// Returns whether this steam client is a Steam China specific client, vs the global client.
bool IsSteamChinaLauncher();
// Initializes text filtering.
// Returns false if filtering is unavailable for the language the user is currently running in.
bool InitFilterText();
// Initializes text filtering.
// unFilterOptions are reserved for future use and should be set to 0
// Returns false if filtering is unavailable for the language the user is currently running in.
bool InitFilterText( uint32 unFilterOptions );
// Filters the provided input message and places the filtered result into pchOutFilteredText.
// pchOutFilteredText is where the output will be placed, even if no filtering or censoring is performed
// nByteSizeOutFilteredText is the size (in bytes) of pchOutFilteredText
// pchInputText is the input string that should be filtered, which can be ASCII or UTF-8
// bLegalOnly should be false if you want profanity and legally required filtering (where required) and true if you want legally required filtering only
// Returns the number of characters (not bytes) filtered.
int FilterText( char* pchOutFilteredText, uint32 nByteSizeOutFilteredText, const char * pchInputMessage, bool bLegalOnly );
// Filters the provided input message and places the filtered result into pchOutFilteredText, using legally required filtering and additional filtering based on the context and user settings
// eContext is the type of content in the input string
// sourceSteamID is the Steam ID that is the source of the input string (e.g. the player with the name, or who said the chat text)
// pchInputText is the input string that should be filtered, which can be ASCII or UTF-8
// pchOutFilteredText is where the output will be placed, even if no filtering is performed
// nByteSizeOutFilteredText is the size (in bytes) of pchOutFilteredText, should be at least strlen(pchInputText)+1
// Returns the number of characters (not bytes) filtered
int FilterText( ETextFilteringContext eContext, CSteamID sourceSteamID, const char *pchInputMessage, char *pchOutFilteredText, uint32 nByteSizeOutFilteredText );
// Return what we believe your current ipv6 connectivity to "the internet" is on the specified protocol.
// This does NOT tell you if the Steam client is currently connected to Steam via ipv6.
ESteamIPv6ConnectivityState GetIPv6ConnectivityState( ESteamIPv6ConnectivityProtocol eProtocol );
// returns true if currently running on the Steam Deck device
bool IsSteamRunningOnSteamDeck();
// Opens a floating keyboard over the game content and sends OS keyboard keys directly to the game.
// The text field position is specified in pixels relative the origin of the game window and is used to position the floating keyboard in a way that doesn't cover the text field
bool ShowFloatingGamepadTextInput( EFloatingGamepadTextInputMode eKeyboardMode, int nTextFieldXPosition, int nTextFieldYPosition, int nTextFieldWidth, int nTextFieldHeight );
// In game launchers that don't have controller support you can call this to have Steam Input translate the controller input into mouse/kb to navigate the launcher
void SetGameLauncherMode( bool bLauncherMode );
// Dismisses the floating keyboard.
bool DismissFloatingGamepadTextInput();
// Dismisses the full-screen text input dialog.
bool DismissGamepadTextInput();
};
#endif // __STEAM_UTILS_H__

42
dll/dll/steam_video.h Normal file
View File

@ -0,0 +1,42 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_VIDEO_H__
#define __INCLUDED_STEAM_VIDEO_H__
#include "base.h"
class Steam_Video :
public ISteamVideo001,
public ISteamVideo002,
public ISteamVideo
{
public:
// Get a URL suitable for streaming the given Video app ID's video
void GetVideoURL( AppId_t unVideoAppID );
// returns true if user is uploading a live broadcast
bool IsBroadcasting( int *pnNumViewers );
// Get the OPF Details for 360 Video Playback
STEAM_CALL_BACK( GetOPFSettingsResult_t )
void GetOPFSettings( AppId_t unVideoAppID );
bool GetOPFStringForApp( AppId_t unVideoAppID, char *pchBuffer, int32 *pnBufferSize );
};
#endif // __INCLUDED_STEAM_VIDEO_H__

View File

@ -0,0 +1,58 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_UGC_REMOTE_STORAGE_BRIDGE_H__
#define __INCLUDED_UGC_REMOTE_STORAGE_BRIDGE_H__
#include "base.h"
class Ugc_Remote_Storage_Bridge
{
public:
struct QueryInfo {
PublishedFileId_t mod_id{}; // mod id
bool is_primary_file{}; // was this query for the primary mod file or preview file
};
private:
class Settings *settings{};
// key: UGCHandle_t which is the file handle (primary or preview)
// value: the mod id, true if UGCHandle_t of primary file | false if UGCHandle_t of preview file
std::map<UGCHandle_t, QueryInfo> steam_ugc_queries{};
std::set<PublishedFileId_t> subscribed{}; // just to keep the running state of subscription
public:
Ugc_Remote_Storage_Bridge(class Settings *settings);
~Ugc_Remote_Storage_Bridge();
// called from Steam_UGC::SendQueryUGCRequest() after a successful query
void add_ugc_query_result(UGCHandle_t file_handle, PublishedFileId_t fileid, bool handle_of_primary_file);
bool remove_ugc_query_result(UGCHandle_t file_handle);
std::optional<QueryInfo> get_ugc_query_result(UGCHandle_t file_handle) const;
void add_subbed_mod(PublishedFileId_t id);
void remove_subbed_mod(PublishedFileId_t id);
size_t subbed_mods_count() const;
bool has_subbed_mod(PublishedFileId_t id) const;
std::set<PublishedFileId_t>::iterator subbed_mods_itr_begin() const;
std::set<PublishedFileId_t>::iterator subbed_mods_itr_end() const;
};
#endif // __INCLUDED_UGC_REMOTE_STORAGE_BRIDGE_H__

7312
dll/flat.cpp Normal file

File diff suppressed because it is too large Load Diff

924
dll/local_storage.cpp Normal file
View File

@ -0,0 +1,924 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "dll/local_storage.h"
#if defined(__WINDOWS__)
// NOTE: stb_image_write
#define STBIW_WINDOWS_UTF8
// NOTE: stb_image
#define STBI_WINDOWS_UTF8
#endif
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#define STBI_ONLY_PNG
#define STBI_ONLY_JPEG
#include "stb/stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include "stb/stb_image_write.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC
#include "stb/stb_image_resize2.h"
struct File_Data {
std::string name{};
};
std::string Local_Storage::saves_folder_name = "GSE Saves";
#ifdef NO_DISK_WRITES
static const std::string empty_str{};
std::string Local_Storage::get_program_path()
{
return " ";
}
std::string Local_Storage::get_game_settings_path()
{
return " ";
}
std::string Local_Storage::get_user_appdata_path()
{
return " ";
}
int Local_Storage::get_file_data(const std::string &full_path, char *data, unsigned int max_length, unsigned int offset)
{
return -1;
}
int Local_Storage::store_file_data(std::string folder, std::string file, const char *data, unsigned int length)
{
return -1;
}
std::vector<std::string> Local_Storage::get_filenames_path(std::string path)
{
return std::vector<std::string>();
}
std::vector<std::string> Local_Storage::get_folders_path(std::string path)
{
return std::vector<std::string>();
}
void Local_Storage::set_saves_folder_name(std::string_view str)
{
}
const std::string& Local_Storage::get_saves_folder_name()
{
return empty_str;
}
std::string Local_Storage::get_path(std::string folder)
{
return empty_str;
}
std::string Local_Storage::get_global_settings_path()
{
return empty_str;
}
Local_Storage::Local_Storage(const std::string &save_directory)
{
}
const std::string& Local_Storage::get_current_save_directory() const
{
return empty_str;
}
void Local_Storage::setAppId(uint32 appid)
{
}
int Local_Storage::store_data(std::string folder, std::string file, char *data, unsigned int length)
{
return -1;
}
int Local_Storage::store_data_settings(std::string file, const char *data, unsigned int length)
{
return -1;
}
int Local_Storage::get_data(std::string folder, std::string file, char *data, unsigned int max_length, unsigned int offset)
{
return -1;
}
unsigned int Local_Storage::data_settings_size(std::string file)
{
return 0;
}
int Local_Storage::get_data_settings(std::string file, char *data, unsigned int max_length)
{
return 0;
}
int Local_Storage::count_files(std::string folder)
{
return 0;
}
bool Local_Storage::file_exists(std::string folder, std::string file)
{
return false;
}
unsigned int Local_Storage::file_size(std::string folder, std::string file)
{
return 0;
}
bool Local_Storage::file_delete(std::string folder, std::string file)
{
return false;
}
uint64_t Local_Storage::file_timestamp(std::string folder, std::string file)
{
return 0;
}
bool Local_Storage::iterate_file(std::string folder, int index, char *output_filename, int32 *output_size)
{
return false;
}
bool Local_Storage::update_save_filenames(std::string folder)
{
return true;
}
bool Local_Storage::load_json(const std::string &full_path, nlohmann::json& json)
{
return false;
}
bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json)
{
return false;
}
bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json)
{
return false;
}
std::vector<image_pixel_t> Local_Storage::load_image(std::string const& image_path)
{
return std::vector<image_pixel_t>();
}
std::string Local_Storage::load_image_resized(std::string const& image_path, std::string const& image_data, int resolution)
{
return empty_str;
}
bool Local_Storage::save_screenshot(std::string const& image_path, uint8_t* img_ptr, int32_t width, int32_t height, int32_t channels)
{
return false;
}
std::string Local_Storage::sanitize_string(std::string name)
{
return empty_str;
}
std::string Local_Storage::desanitize_string(std::string name)
{
return empty_str;
}
#else
#if defined(__WINDOWS__)
static BOOL DirectoryExists(LPCWSTR szPath)
{
DWORD dwAttrib = GetFileAttributesW(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
static void createDirectoryRecursively(std::wstring path)
{
size_t pos = 0;
do
{
pos = path.find_first_of(L"\\/", pos + 1);
CreateDirectoryW(path.substr(0, pos).c_str(), NULL);
} while (pos != std::string::npos);
}
static void create_directory(std::string in_path)
{
std::wstring strPath = utf8_decode(in_path);
if (DirectoryExists(strPath.c_str()) == FALSE)
createDirectoryRecursively(strPath);
}
static std::vector<struct File_Data> get_filenames(std::string in_path)
{
std::vector<struct File_Data> output;
in_path = in_path.append("\\*");
WIN32_FIND_DATAW ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
std::wstring strPath = utf8_decode(in_path);
// Start iterating over the files in the path directory.
hFind = ::FindFirstFileW (strPath.c_str(), &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do // Managed to locate and create an handle to that folder.
{
if (wcscmp(L".", ffd.cFileName) == 0) continue;
if (wcscmp(L"..", ffd.cFileName) == 0) continue;
struct File_Data f_data;
f_data.name = utf8_encode(ffd.cFileName);
output.push_back(f_data);
} while (::FindNextFileW(hFind, &ffd) == TRUE);
::FindClose(hFind);
} else {
//printf("Failed to find path: %s", strPath.c_str());
}
return output;
}
static std::vector<struct File_Data> get_filenames_recursive_w(std::wstring base_path)
{
if (base_path.back() == *L"\\")
base_path.pop_back();
std::vector<struct File_Data> output;
std::wstring strPath = base_path;
strPath = strPath.append(L"\\*");
WIN32_FIND_DATAW ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
// Start iterating over the files in the path directory.
hFind = ::FindFirstFileW (strPath.c_str(), &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do // Managed to locate and create an handle to that folder.
{
if (wcscmp(L".", ffd.cFileName) == 0) continue;
if (wcscmp(L"..", ffd.cFileName) == 0) continue;
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// Construct new path from our base path
std::wstring dir_name = ffd.cFileName;
std::wstring path = base_path;
path += L"\\";
path += dir_name;
std::vector<struct File_Data> lower = get_filenames_recursive_w(path);
std::transform(lower.begin(), lower.end(), std::back_inserter(output), [&dir_name](File_Data f) {f.name = utf8_encode(dir_name) + "\\" + f.name; return f;});
} else {
File_Data f;
f.name = utf8_encode(ffd.cFileName);
output.push_back(f);
}
} while (::FindNextFileW(hFind, &ffd) == TRUE);
::FindClose(hFind);
} else {
//printf("Failed to find path: %s", strPath.c_str());
}
reset_LastError();
return output;
}
static std::vector<struct File_Data> get_filenames_recursive(std::string base_path)
{
return get_filenames_recursive_w(utf8_decode(base_path));
}
#else
/* recursive mkdir */
static int mkdir_p(const char *dir, const mode_t mode) {
char tmp[PATH_MAX_STRING_SIZE];
char *p = NULL;
struct stat sb;
size_t len;
/* copy path */
len = strnlen (dir, PATH_MAX_STRING_SIZE);
if (len == 0 || len == PATH_MAX_STRING_SIZE) {
return -1;
}
memcpy (tmp, dir, len);
tmp[len] = '\0';
/* remove trailing slash */
if(tmp[len - 1] == '/') {
tmp[len - 1] = '\0';
}
/* check if path exists and is a directory */
if (stat (tmp, &sb) == 0) {
if (S_ISDIR (sb.st_mode)) {
return 0;
}
}
/* recursive mkdir */
for(p = tmp + 1; *p; p++) {
if(*p == '/') {
*p = 0;
/* test path */
if (stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, mode) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
*p = '/';
}
}
/* test path */
if (stat(tmp, &sb) != 0) {
/* path does not exist - create directory */
if (mkdir(tmp, mode) < 0) {
return -1;
}
} else if (!S_ISDIR(sb.st_mode)) {
/* not a directory */
return -1;
}
return 0;
}
static void create_directory(std::string strPath)
{
mkdir_p(strPath.c_str(), 0777);
}
static std::vector<struct File_Data> get_filenames(std::string strPath)
{
DIR *dp;
int i = 0;
struct dirent *ep;
std::vector<struct File_Data> output;
dp = opendir (strPath.c_str());
if (dp != NULL)
{
while ((ep = readdir (dp))) {
if (memcmp(ep->d_name, ".", 2) != 0 && memcmp(ep->d_name, "..", 3) != 0) {
struct File_Data f_data;
f_data.name = ep->d_name;
output.push_back(f_data);
i++;
}
}
(void) closedir (dp);
}
return output;
}
static std::vector<struct File_Data> get_filenames_recursive(std::string base_path)
{
std::vector<struct File_Data> output;
std::string path;
struct dirent *dp;
DIR *dir = opendir(base_path.c_str());
// Unable to open directory stream
if (!dir)
return output;
while ((dp = readdir(dir)) != NULL)
{
if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
{
if (dp->d_type == DT_REG) {
File_Data f;
f.name = dp->d_name;
output.push_back(f);
} else if (dp->d_type == DT_DIR) {
// Construct new path from our base path
std::string dir_name(dp->d_name);
path = base_path;
path += "/";
path += dir_name;
std::vector<struct File_Data> lower = get_filenames_recursive(path);
std::transform(lower.begin(), lower.end(), std::back_inserter(output), [&dir_name](File_Data f) {f.name = dir_name + "/" + f.name; return f;});
}
}
}
closedir(dir);
return output;
}
#endif
std::string Local_Storage::get_program_path()
{
return get_full_program_path();
}
std::string Local_Storage::get_game_settings_path()
{
return get_program_path().append(game_settings_folder).append(PATH_SEPARATOR);
}
std::string Local_Storage::get_user_appdata_path()
{
std::string user_appdata_path("SAVE");
#if defined(STEAM_WIN32)
WCHAR szPath[MAX_PATH] = {};
HRESULT hr = SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath);
if (SUCCEEDED(hr)) {
user_appdata_path = utf8_encode(szPath);
}
#else
/* $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. */
char *datadir = getenv("XDG_DATA_HOME");
if (datadir) {
user_appdata_path = datadir;
} else {
char *homedir = getenv("HOME");
if (homedir) {
user_appdata_path = std::string(homedir) + "/.local/share";
}
}
#endif
return user_appdata_path.append(PATH_SEPARATOR).append(get_saves_folder_name()).append(PATH_SEPARATOR);
}
static std::string replace_with(std::string s, std::string const &old, const char *new_str)
{
size_t pos{};
while ((pos = s.find(old)) != std::string::npos)
s.replace(pos, old.length(), new_str);
return s;
}
static std::string sanitize_file_name(std::string name)
{
if (name.empty()) return name;
//I'm not sure all of these are necessary but just to be sure
if (name[0] == '.' && name.size() > 2 && (name[1] == '\\' || name[1] == '/')) name.erase(0, 2);
#if defined(STEAM_WIN32)
name = replace_with(name, "/", PATH_SEPARATOR);
#else
//On linux does using "\\" in a remote storage file name create a directory?
//I didn't test but I'm going to say yes
name = replace_with(name, "\\", PATH_SEPARATOR);
#endif
name = replace_with(name, "|", ".V_SLASH.");
name = replace_with(name, ":", ".COLON.");
name = replace_with(name, "*", ".ASTERISK.");
name = replace_with(name, "\"", ".QUOTE.");
name = replace_with(name, "?", ".Q_MARK.");
return name;
}
static std::string desanitize_file_name(std::string name)
{
if (name.empty()) return name;
//I'm not sure all of these are necessary but just to be sure
name = replace_with(name, ".SLASH.", "/");
name = replace_with(name, ".B_SLASH.", "\\");
name = replace_with(name, ".F_SLASH.", "/");
name = replace_with(name, ".V_SLASH.", "|");
name = replace_with(name, ".COLON.", ":");
name = replace_with(name, ".ASTERISK.", "*");
name = replace_with(name, ".QUOTE.", "\"");
name = replace_with(name, ".Q_MARK.", "?");
return name;
}
Local_Storage::Local_Storage(const std::string &save_directory)
{
this->save_directory = save_directory;
if (this->save_directory.back() != *PATH_SEPARATOR) {
this->save_directory.append(PATH_SEPARATOR);
}
}
const std::string& Local_Storage::get_current_save_directory() const
{
return this->save_directory;
}
void Local_Storage::setAppId(uint32 appid)
{
this->appid = std::to_string(appid) + PATH_SEPARATOR;
}
int Local_Storage::store_file_data(std::string folder, std::string file, const char *data, unsigned int length)
{
if (folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
file = sanitize_file_name(file);
std::string::size_type pos = file.rfind(PATH_SEPARATOR);
std::string file_folder;
if (pos == 0 || pos == std::string::npos) {
file_folder = "";
} else {
file_folder = file.substr(0,pos);
}
create_directory(folder + file_folder);
std::ofstream myfile;
myfile.open(std::filesystem::u8path(folder + file), std::ios::binary | std::ios::out);
if (!myfile.is_open()) return -1;
myfile.write(data, length);
int position = static_cast<int>(myfile.tellp());
myfile.close();
return position;
}
std::string Local_Storage::get_path(std::string folder)
{
std::string path(save_directory + appid + folder);
create_directory(path);
return path;
}
std::string Local_Storage::get_global_settings_path()
{
return save_directory + settings_storage_folder + PATH_SEPARATOR;
}
std::vector<std::string> Local_Storage::get_filenames_path(std::string path)
{
if (path.empty()) return {};
if (path.back() != *PATH_SEPARATOR) {
path.append(PATH_SEPARATOR);
}
std::vector<struct File_Data> filenames = get_filenames(path);
std::vector<std::string> output;
std::transform(filenames.begin(), filenames.end(), std::back_inserter(output), [](struct File_Data d) { return d.name;});
return output;
}
std::vector<std::string> Local_Storage::get_folders_path(std::string path)
{
if (path.empty()) return {};
if (path.back() != *PATH_SEPARATOR) {
path.append(PATH_SEPARATOR);
}
std::vector<std::string> output{};
try
{
const auto path_p(std::filesystem::u8path(path));
if (!common_helpers::dir_exist(path_p)) return output;
for (const auto &dir_entry :
std::filesystem::directory_iterator(path_p, std::filesystem::directory_options::follow_directory_symlink)) {
if (std::filesystem::is_directory(dir_entry)) {
output.push_back(dir_entry.path().filename().u8string());
}
}
} catch(...) { }
return output;
}
void Local_Storage::set_saves_folder_name(std::string_view str)
{
Local_Storage::saves_folder_name = str;
}
const std::string& Local_Storage::get_saves_folder_name()
{
return Local_Storage::saves_folder_name;
}
int Local_Storage::store_data(std::string folder, std::string file, char *data, unsigned int length)
{
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
return store_file_data(save_directory + appid + folder, file, data, length);
}
int Local_Storage::store_data_settings(std::string file, const char *data, unsigned int length)
{
return store_file_data(get_global_settings_path(), file, data, length);
}
int Local_Storage::get_file_data(const std::string &full_path, char *data, unsigned int max_length, unsigned int offset)
{
std::ifstream myfile{};
myfile.open(std::filesystem::u8path(full_path), std::ios::binary | std::ios::in);
if (!myfile.is_open()) return -1;
myfile.seekg (offset, std::ios::beg);
myfile.read (data, max_length);
myfile.close();
reset_LastError();
return static_cast<int>(myfile.gcount());
}
int Local_Storage::get_data(std::string folder, std::string file, char *data, unsigned int max_length, unsigned int offset)
{
file = sanitize_file_name(file);
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string full_path(save_directory + appid + folder + file);
return get_file_data(full_path, data, max_length, offset);
}
unsigned int Local_Storage::data_settings_size(std::string file)
{
file = sanitize_file_name(file);
std::string full_path(get_global_settings_path() + file);
return file_size_(full_path);
}
int Local_Storage::get_data_settings(std::string file, char *data, unsigned int max_length)
{
file = sanitize_file_name(file);
std::string full_path(get_global_settings_path() + file);
return get_file_data(full_path, data, max_length);
}
int Local_Storage::count_files(std::string folder)
{
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
return static_cast<int>(get_filenames_recursive(save_directory + appid + folder).size());
}
bool Local_Storage::file_exists(std::string folder, std::string file)
{
file = sanitize_file_name(file);
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string full_path(save_directory + appid + folder + file);
return file_exists_(full_path);
}
unsigned int Local_Storage::file_size(std::string folder, std::string file)
{
file = sanitize_file_name(file);
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string full_path(save_directory + appid + folder + file);
return file_size_(full_path);
}
bool Local_Storage::file_delete(std::string folder, std::string file)
{
file = sanitize_file_name(file);
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string full_path(save_directory + appid + folder + file);
#if defined(STEAM_WIN32)
return _wremove(utf8_decode(full_path).c_str()) == 0;
#else
return remove(full_path.c_str()) == 0;
#endif
}
uint64_t Local_Storage::file_timestamp(std::string folder, std::string file)
{
file = sanitize_file_name(file);
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string full_path(save_directory + appid + folder + file);
#if defined(STEAM_WIN32)
struct _stat buffer = {};
if (_wstat(utf8_decode(full_path).c_str(), &buffer) != 0) return 0;
#else
struct stat buffer = {};
if (stat (full_path.c_str(), &buffer) != 0) return 0;
#endif
return buffer.st_mtime;
}
bool Local_Storage::iterate_file(std::string folder, int index, char *output_filename, int32 *output_size)
{
if (folder.size() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::vector<struct File_Data> files = get_filenames_recursive(save_directory + appid + folder);
if (index < 0 || static_cast<size_t>(index) >= files.size()) return false;
std::string name(desanitize_file_name(files[index].name));
if (output_size) *output_size = file_size(folder, name);
#if defined(STEAM_WIN32)
name = replace_with(name, PATH_SEPARATOR, "/");
#endif
strcpy(output_filename, name.c_str());
return true;
}
bool Local_Storage::update_save_filenames(std::string folder)
{
std::vector<struct File_Data> files = get_filenames_recursive(save_directory + appid + folder);
for (auto &f : files) {
std::string path(f.name);
PRINT_DEBUG("remote file '%s'", path.c_str());
std::string to(sanitize_file_name(desanitize_file_name(path)));
if (path != to && !file_exists(folder, to)) {
//create the folder
store_data(folder, to, (char *)"", 0);
file_delete(folder, to);
std::string from(save_directory + appid + folder + PATH_SEPARATOR + path);
to = save_directory + appid + folder + PATH_SEPARATOR + to;
PRINT_DEBUG("renaming '%s' to '%s'", from.c_str(), to.c_str());
if (std::rename(from.c_str(), to.c_str()) < 0) {
PRINT_DEBUG("ERROR RENAMING");
}
}
}
return true;
}
bool Local_Storage::load_json(const std::string &full_path, nlohmann::json& json)
{
std::ifstream inventory_file(std::filesystem::u8path(full_path), std::ios::in | std::ios::binary);
// If there is a file and we opened it
if (inventory_file) {
try {
json = nlohmann::json::parse(inventory_file);
PRINT_DEBUG("Loaded json '%s' (%zu items)", full_path.c_str(), json.size());
return true;
} catch (const std::exception& e) {
const char *errorMessage = e.what();
PRINT_DEBUG("Error while parsing '%s' json error: %s", full_path.c_str(), errorMessage);
}
} else {
PRINT_DEBUG("Couldn't open file '%s' to read json", full_path.c_str());
}
reset_LastError();
return false;
}
bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json)
{
if (!folder.empty() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string inv_path(save_directory + appid + folder);
std::string full_path(inv_path + file);
return load_json(full_path, json);
}
bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json)
{
if (!folder.empty() && folder.back() != *PATH_SEPARATOR) {
folder.append(PATH_SEPARATOR);
}
std::string inv_path(save_directory + appid + folder);
std::string full_path(inv_path + file);
create_directory(inv_path);
std::ofstream inventory_file(std::filesystem::u8path(full_path), std::ios::trunc | std::ios::out | std::ios::binary);
if (inventory_file) {
inventory_file << std::setw(2) << json;
return true;
}
PRINT_DEBUG("Couldn't open file '%s' to write json", full_path.c_str());
reset_LastError();
return false;
}
std::vector<image_pixel_t> Local_Storage::load_image(std::string const& image_path)
{
std::vector<image_pixel_t> res{};
int width{}, height{};
image_pixel_t* img = (image_pixel_t*)stbi_load(image_path.c_str(), &width, &height, nullptr, 4);
PRINT_DEBUG("stbi_load('%s') -> %s", image_path.c_str(), (img ? "loaded" : stbi_failure_reason()));
if (img) {
res.resize(width * height);
std::copy(img, img + width * height, res.begin());
stbi_image_free(img);
}
reset_LastError();
return res;
}
std::string Local_Storage::load_image_resized(std::string const& image_path, std::string const& image_data, int resolution)
{
std::string resized_image{};
const size_t resized_img_size = resolution * resolution * 4;
if (image_path.size()) {
int width = 0;
int height = 0;
unsigned char *img = stbi_load(image_path.c_str(), &width, &height, nullptr, 4);
PRINT_DEBUG("stbi_load('%s') -> %s", image_path.c_str(), (img ? "loaded" : stbi_failure_reason()));
if (img) {
std::vector<char> out_resized(resized_img_size);
stbir_resize_uint8_linear(img, width, height, 0, (unsigned char*)&out_resized[0], resolution, resolution, 0, STBIR_RGBA);
resized_image = std::string((char*)&out_resized[0], out_resized.size());
stbi_image_free(img);
}
} else if (image_data.size()) {
std::vector<char> out_resized(resized_img_size);
stbir_resize_uint8_linear((unsigned char*)image_data.c_str(), 184, 184, 0, (unsigned char*)&out_resized[0], resolution, resolution, 0, STBIR_RGBA);
resized_image = std::string((char*)&out_resized[0], out_resized.size());
}
reset_LastError();
return resized_image;
}
bool Local_Storage::save_screenshot(std::string const& image_path, uint8_t* img_ptr, int32_t width, int32_t height, int32_t channels)
{
std::string screenshot_path(save_directory + appid + screenshots_folder + PATH_SEPARATOR);
create_directory(screenshot_path);
screenshot_path += image_path;
return stbi_write_png(screenshot_path.c_str(), width, height, channels, img_ptr, 0) == 1;
}
std::string Local_Storage::sanitize_string(std::string name)
{
return sanitize_file_name(name);
}
std::string Local_Storage::desanitize_string(std::string name)
{
return desanitize_file_name(name);
}
#endif

330
dll/net.proto Normal file
View File

@ -0,0 +1,330 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
message Announce {
enum Types {
PING = 0;
PONG = 1;
}
Types type = 1;
repeated uint64 ids = 2;
message Other_Peers {
uint64 id = 1;
uint32 ip = 2;
uint32 udp_port = 3;
uint32 appid = 4;
}
uint32 tcp_port = 3;
repeated Other_Peers peers = 4;
uint32 appid = 5;
}
message Lobby {
uint64 room_id = 1;
uint64 owner = 2;
map<string, bytes> values = 3;
message Member {
uint64 id = 1;
map<string, bytes> values = 2;
}
repeated Member members = 4;
message Gameserver {
uint64 id = 1;
uint32 ip = 2;
uint32 port = 3;
uint32 num_update = 4;
}
Gameserver gameserver = 5;
uint32 member_limit = 6;
uint32 type = 7; //ELobbyType
bool joinable = 8;
uint32 appid = 9;
bool deleted = 32;
uint64 time_deleted = 33;
}
message Lobby_Messages {
uint64 id = 1;
enum Types {
JOIN = 0;
LEAVE = 1;
CHANGE_OWNER = 2;
MEMBER_DATA = 3;
CHAT_MESSAGE = 4;
}
Types type = 2;
uint64 idata = 3;
bytes bdata = 4;
map<string, bytes> map = 5;
}
message Low_Level {
enum Types {
HEARTBEAT = 0;
CONNECT = 1;
DISCONNECT = 2;
}
Types type = 1;
}
message Network_pb {
uint32 channel = 1;
bytes data = 2;
enum Types {
DATA = 0;
NEW_CONNECTION = 1;
}
Types type = 3;
bool processed = 128;
uint64 time_processed = 129;
}
message Network_Old {
enum Types {
CONNECTION_REQUEST_IP = 0;
CONNECTION_REQUEST_STEAMID = 1;
CONNECTION_ACCEPTED = 2;
CONNECTION_END = 3;
DATA = 4;
}
Types type = 1;
uint64 connection_id = 2;
uint64 connection_id_from = 3;
uint32 port = 4;
bytes data = 5;
}
message Networking_Sockets {
enum Types {
CONNECTION_REQUEST = 0;
CONNECTION_ACCEPTED = 2;
CONNECTION_END = 3;
DATA = 4;
}
Types type = 1;
int32 virtual_port = 2;
int32 real_port = 6;
uint64 connection_id = 3;
uint64 connection_id_from = 4;
bytes data = 5;
uint64 message_number = 7;
}
message Networking_Messages {
enum Types {
CONNECTION_NEW = 0;
CONNECTION_ACCEPT = 1;
CONNECTION_END = 2;
DATA = 3;
}
Types type = 1;
uint32 channel = 2;
uint32 id_from = 3;
bytes data = 5;
}
message Gameserver {
uint64 id = 1;
bytes game_description = 2;
bytes mod_dir = 3;
bool dedicated_server = 4;
uint32 max_player_count = 5;
uint32 bot_player_count = 6;
bytes server_name = 7;
bytes map_name = 8;
bool password_protected = 9;
uint32 spectator_port = 10;
bytes spectator_server_name = 11;
map<string, bytes> values = 12;
bytes tags = 13;
bytes gamedata = 14;
bytes region = 15;
bytes product = 16;
bool secure = 17;
uint32 num_players = 18;
uint32 version = 19;
uint32 ip = 32;
uint32 port = 33;
uint32 query_port = 34;
uint32 appid = 35;
bool offline = 48;
uint32 type = 49;
}
message Friend {
uint64 id = 1;
bytes name = 2;
map<string, bytes> rich_presence = 3;
uint32 appid = 4;
uint64 lobby_id = 5;
bytes avatar = 6;
}
message Auth_Ticket {
uint32 number = 1;
enum Types {
CANCEL = 0;
}
Types type = 2;
}
message Friend_Messages {
enum Types {
LOBBY_INVITE = 0;
GAME_INVITE = 1;
}
Types type = 1;
oneof invite_data {
uint64 lobby_id = 2;
bytes connect_str = 3;
}
}
message Steam_Messages {
enum Types {
FRIEND_CHAT = 0;
}
Types type = 1;
oneof message_data {
bytes message = 2;
}
}
message GameServerStats_Messages {
// --- basic definitions
message StatInfo {
enum Stat_Type {
STAT_TYPE_INT = 0;
STAT_TYPE_FLOAT = 1;
STAT_TYPE_AVGRATE = 2;
}
message AvgStatInfo {
float count_this_session = 1;
double session_length = 2;
}
Stat_Type stat_type = 1;
oneof stat_value {
float value_float = 2;
int32 value_int = 3;
}
optional AvgStatInfo value_avg = 4; // only set when type != INT
}
message AchievementInfo {
bool achieved = 1;
}
// --- requests & responses objects
// this is used when updating stats, from server or user, bi-directional
message AllStats {
map<string, StatInfo> user_stats = 1;
map<string, AchievementInfo> user_achievements = 2;
}
// 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;
}
// Request_: from Steam_GameServerStats
// Response_: from Steam_User_Stats
enum Types {
Request_AllUserStats = 0;
Response_AllUserStats = 1;
UpdateUserStatsFromServer = 2; // sent by Steam_GameServerStats
UpdateUserStatsFromUser = 3; // sent by Steam_User_Stats
}
Types type = 1;
oneof data_messages {
InitialAllStats initial_user_stats = 2;
AllStats update_user_stats = 3;
}
}
message Leaderboards_Messages {
message LeaderboardInfo {
string board_name = 1;
int32 sort_method = 2;
int32 display_type = 3;
}
message UserScoreEntry {
int32 score = 1;
repeated int32 score_details = 2;
}
enum Types {
UpdateUserScore = 0; // notify others on the network that our score was updated
UpdateUserScoreMutual = 1; // notify others on the network that our score was updated, and request theirs
RequestUserScore = 2; // request score data from a single user
}
Types type = 1;
uint32 appid = 2;
LeaderboardInfo leaderboard_info = 3;
oneof data_messages {
UserScoreEntry user_score_entry = 4;
}
}
message Common_Message {
uint64 source_id = 1; // SteamID64 of the sender
uint64 dest_id = 2; // SteamID64 of the target receiver
oneof messages {
Announce announce = 3;
Low_Level low_level = 4;
Lobby lobby = 5;
Lobby_Messages lobby_messages = 6;
Network_pb network = 7;
Gameserver gameserver = 8;
Friend friend = 9;
Auth_Ticket auth_ticket = 10;
Friend_Messages friend_messages = 11;
Network_Old network_old = 12;
Networking_Sockets networking_sockets = 13;
Steam_Messages steam_messages = 14;
Networking_Messages networking_messages = 15;
GameServerStats_Messages gameserver_stats_messages = 16;
Leaderboards_Messages leaderboards_messages = 17;
}
uint32 source_ip = 128;
uint32 source_port = 129;
}
//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;
}

1433
dll/network.cpp Normal file

File diff suppressed because it is too large Load Diff

416
dll/settings.cpp Normal file
View File

@ -0,0 +1,416 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "dll/settings.h"
#include "dll/steam_app_ids.h"
std::string Settings::sanitize(const std::string &name)
{
// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/io/path.cs#L88C9-L89C1
// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/io/pathinternal.cs#L32
constexpr const static char InvalidFileNameChars[] = {
'\"', '<', '>', '|', '\0',
(char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
(char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
(char)31,
':', '*', '?', /*'\\', '/',*/
};
if (name.empty()) return {};
// we have to use utf-32 because Windows (and probably Linux) allows some chars that need at least 32 bits,
// such as this one (U+1F5FA) called "World Map": https://www.compart.com/en/unicode/U+1F5FA
// utf-16 encoding for these characters require 2 ushort, but we would like to iterate
// over all chars in a linear fashion
std::u32string unicode_name{};
utf8::utf8to32(
name.begin(),
utf8::find_invalid(name.begin(), name.end()), // returns an iterator pointing to the first invalid octet
std::back_inserter(unicode_name)
);
auto rm_itr = std::remove_if(unicode_name.begin(), unicode_name.end(), [](decltype(unicode_name[0]) ch) {
return ch == '\n' || ch == '\r';
});
if (unicode_name.end() != rm_itr) {
unicode_name.erase(rm_itr, unicode_name.end());
}
auto InvalidFileNameChars_last_it = std::end(InvalidFileNameChars);
for (auto& uch : unicode_name) {
auto found_it = std::find(std::begin(InvalidFileNameChars), InvalidFileNameChars_last_it, uch);
if (found_it != InvalidFileNameChars_last_it) { // if illegal
uch = ' ';
}
}
std::string res{};
utf8::utf32to8(unicode_name.begin(), unicode_name.end(), std::back_inserter(res));
return res;
}
Settings::Settings(CSteamID steam_id, CGameID game_id, const std::string &name, const std::string &language, bool offline)
{
this->steam_id = steam_id;
this->game_id = game_id;
this->name = sanitize(name);
if (this->name.size() == 0) {
this->name = " ";
}
if (this->name.size() == 1) {
this->name = this->name + " ";
}
auto lang = sanitize(language);
std::transform(lang.begin(), lang.end(), lang.begin(), ::tolower);
lang.erase(std::remove(lang.begin(), lang.end(), ' '), lang.end());
this->language = lang;
this->offline = offline;
}
// user id
CSteamID Settings::get_local_steam_id()
{
return steam_id;
}
// game id
CGameID Settings::get_local_game_id()
{
return game_id;
}
const char *Settings::get_local_name()
{
return name.c_str();
}
const char *Settings::get_language()
{
return language.c_str();
}
void Settings::set_local_name(const char *name)
{
this->name = name;
}
void Settings::set_language(const char *language)
{
this->language = language;
}
void Settings::set_supported_languages(const std::set<std::string> &langs)
{
this->supported_languages_set = langs;
this->supported_languages.clear();
auto lang_it = langs.cbegin();
while (langs.cend() != lang_it) {
if (langs.cbegin() == lang_it) this->supported_languages = *lang_it;
else this->supported_languages.append(",").append(*lang_it); // this isn't C#, .append() will change the string!
++lang_it;
}
}
const std::set<std::string>& Settings::get_supported_languages_set() const
{
return this->supported_languages_set;
}
const std::string& Settings::get_supported_languages() const
{
return this->supported_languages;
}
void Settings::set_game_id(CGameID game_id)
{
this->game_id = game_id;
}
void Settings::set_lobby(CSteamID lobby_id)
{
this->lobby_id = lobby_id;
}
CSteamID Settings::get_lobby()
{
return this->lobby_id;
}
bool Settings::is_offline()
{
return offline;
}
void Settings::set_offline(bool offline)
{
this->offline = offline;
}
uint16 Settings::get_port()
{
return port;
}
void Settings::set_port(uint16 port)
{
this->port = port;
}
void Settings::addMod(PublishedFileId_t id, const std::string &title, const std::string &path)
{
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
if (mods.end() != f) {
f->title = title;
f->path = path;
return;
}
Mod_entry new_entry{};
new_entry.id = id;
new_entry.title = title;
new_entry.path = path;
mods.push_back(new_entry);
}
void Settings::addModDetails(PublishedFileId_t id, const Mod_entry &details)
{
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
if (f != mods.end()) {
// don't copy files handles, they're auto generated
f->fileType = details.fileType;
f->description = details.description;
f->steamIDOwner = details.steamIDOwner;
f->timeCreated = details.timeCreated;
f->timeUpdated = details.timeUpdated;
f->timeAddedToUserList = details.timeAddedToUserList;
f->visibility = details.visibility;
f->banned = details.banned;
f->acceptedForUse = details.acceptedForUse;
f->tagsTruncated = details.tagsTruncated;
f->tags = details.tags;
f->primaryFileName = details.primaryFileName;
f->primaryFileSize = details.primaryFileSize;
f->previewFileName = details.previewFileName;
f->previewFileSize = details.previewFileSize;
f->workshopItemURL = details.workshopItemURL;
f->votesUp = details.votesUp;
f->votesDown = details.votesDown;
f->score = details.score;
f->numChildren = details.numChildren;
f->previewURL = details.previewURL;
}
}
Mod_entry Settings::getMod(PublishedFileId_t id)
{
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
if (mods.end() != f) {
return *f;
}
return Mod_entry();
}
bool Settings::isModInstalled(PublishedFileId_t id)
{
auto f = std::find_if(mods.begin(), mods.end(), [&id](Mod_entry const& item) { return item.id == id; });
if (mods.end() != f) {
return true;
}
return false;
}
std::set<PublishedFileId_t> Settings::modSet()
{
std::set<PublishedFileId_t> ret_set;
for (auto & m: mods) {
ret_set.insert(m.id);
}
return ret_set;
}
void Settings::unlockAllDLC(bool value)
{
this->unlockAllDLCs = value;
}
void Settings::addDLC(AppId_t appID, std::string name, bool available)
{
auto f = std::find_if(DLCs.begin(), DLCs.end(), [&appID](DLC_entry const& item) { return item.appID == appID; });
if (DLCs.end() != f) {
f->name = name;
f->available = available;
return;
}
DLC_entry new_entry{};
new_entry.appID = appID;
new_entry.name = name;
new_entry.available = available;
DLCs.push_back(new_entry);
}
unsigned int Settings::DLCCount() const
{
return static_cast<unsigned int>(this->DLCs.size());
}
bool Settings::hasDLC(AppId_t appID)
{
if (this->unlockAllDLCs) return true;
auto f = std::find_if(DLCs.begin(), DLCs.end(), [&appID](DLC_entry const& item) { return item.appID == appID; });
if (DLCs.end() != f) return f->available;
if (enable_builtin_preowned_ids && steam_preowned_app_ids.count(appID)) return true;
return false;
}
bool Settings::getDLC(unsigned int index, AppId_t &appID, bool &available, std::string &name)
{
if (index >= DLCs.size()) return false;
appID = DLCs[index].appID;
available = DLCs[index].available;
name = DLCs[index].name;
return true;
}
void Settings::assumeAnyAppInstalled(bool val)
{
assume_any_app_installed = val;
}
void Settings::addInstalledApp(AppId_t appID)
{
installed_app_ids.insert(appID);
}
bool Settings::isAppInstalled(AppId_t appID) const
{
if (assume_any_app_installed) return true;
if (installed_app_ids.count(appID)) return true;
if (enable_builtin_preowned_ids && steam_preowned_app_ids.count(appID)) return true;
return false;
}
void Settings::setAppInstallPath(AppId_t appID, const std::string &path)
{
app_paths[appID] = path;
}
std::string Settings::getAppInstallPath(AppId_t appID)
{
return app_paths[appID];
}
void Settings::setLeaderboard(const std::string &leaderboard, enum ELeaderboardSortMethod sort_method, enum ELeaderboardDisplayType display_type)
{
Leaderboard_config leader{};
leader.sort_method = sort_method;
leader.display_type = display_type;
leaderboards[leaderboard] = leader;
}
const std::map<std::string, Leaderboard_config>& Settings::getLeaderboards() const
{
return leaderboards;
}
const std::map<std::string, Stat_config>& Settings::getStats() const
{
return stats;
}
std::map<std::string, Stat_config>::const_iterator Settings::setStatDefiniton(const std::string &name, const struct Stat_config &stat_config)
{
auto ins_it = stats.insert_or_assign(common_helpers::ascii_to_lowercase(name), stat_config);
return ins_it.first;
}
int Settings::add_image(const std::string &data, uint32 width, uint32 height)
{
auto previous_it = std::find_if(images.begin(), images.end(), [&](const std::pair<const size_t, Image_Data> &item) {
return item.second.data == data
&& item.second.height == height
&& item.second.width == width;
});
if (images.end() != previous_it) {
return static_cast<int>(previous_it->first);
}
struct Image_Data dt{};
dt.width = width;
dt.height = height;
dt.data = data;
auto new_handle = images.size() + 1; // never return 0, it is a bad handle for most ISteamUserStats APIs
images[new_handle] = dt;
return static_cast<int>(new_handle);
}
Image_Data* Settings::get_image(int handle)
{
if (INVALID_IMAGE_HANDLE == handle || UNLOADED_IMAGE_HANDLE == handle) {
return nullptr;
}
auto image_it = images.find(handle);
if (images.end() == image_it) {
return nullptr;
}
return &image_it->second;
}
void Settings::acceptAnyOverlayInvites(bool value)
{
auto_accept_any_overlay_invites = value;
}
void Settings::addFriendToOverlayAutoAccept(uint64_t friend_id)
{
auto_accept_overlay_invites_friends.insert(friend_id);
}
bool Settings::hasOverlayAutoAcceptInviteFromFriend(uint64_t friend_id) const
{
if (auto_accept_any_overlay_invites) {
return true;
}
return !!auto_accept_overlay_invites_friends.count(friend_id);
}
size_t Settings::overlayAutoAcceptInvitesCount() const
{
return auto_accept_overlay_invites_friends.size();
}

Some files were not shown because too many files have changed in this diff Show More