diff --git a/.gitattributes b/.gitattributes
index 2702cbff40..958150ccae 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,13 @@
# Set default behaviour, in case users don't have core.autocrlf set.
# Adding * text=auto causes Git to autodetect text files and normalise their
# line endings to LF when they are checked into your repository.
-* text=auto
\ No newline at end of file
+* text=auto
+
+# Build helpers are executed by Bash on Linux runners. Keep shebangs and
+# set -e lines valid even when the repository is checked out on Windows.
+*.sh text eol=lf
+*.bash text eol=lf
+
+# Windows one-click build helpers are executed by cmd.exe.
+*.bat text eol=crlf
+*.cmd text eol=crlf
\ No newline at end of file
diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
new file mode 100644
index 0000000000..7cc5d21290
--- /dev/null
+++ b/.github/workflows/build-packages.yml
@@ -0,0 +1,248 @@
+name: Build Source.Python packages
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_tag:
+ description: 'Optional tag to create or update a GitHub release'
+ required: false
+ type: string
+ create_release:
+ description: 'Create/update a release after all matrix builds succeed'
+ required: false
+ default: false
+ type: boolean
+ github_proxy:
+ description: 'Optional public GitHub proxy, for example https://ghfast.top'
+ required: false
+ default: ''
+ type: string
+ build_date:
+ description: 'Date stamp for the archive names, yyyy-MMdd. Empty uses the run date.'
+ required: false
+ default: ''
+ type: string
+ push:
+ tags:
+ - 'v*'
+
+permissions:
+ contents: read
+
+concurrency:
+ group: source-python-packages-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build ${{ matrix.branch }} (${{ matrix.target.platform }})
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ branch: [blade, bms, csgo, css, dods, hl2dm, l4d2, tf2]
+ target:
+ - os: windows-2022
+ platform: windows
+ - os: ubuntu-22.04
+ platform: linux
+ runs-on: ${{ matrix.target.os }}
+ env:
+ SP_GITHUB_PROXY: ${{ inputs.github_proxy }}
+ steps:
+ - name: Check out Source.Python
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
+
+ - name: Record checked-out source revision
+ shell: pwsh
+ run: |
+ $revision = (git rev-parse HEAD).Trim()
+ "SOURCEPYTHON_SOURCE_REVISION=$revision" | Add-Content -LiteralPath $env:GITHUB_ENV
+
+ - name: Verify Source.Python fixes
+ shell: pwsh
+ run: pwsh -NoLogo -NoProfile -File ./scripts/ci/verify-fixes.ps1 -RepositoryRoot $env:GITHUB_WORKSPACE
+
+ - name: Verify shell script line endings
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ if grep -Il $'\r' src/Build.sh scripts/ci/*.sh; then
+ echo 'Shell scripts must use LF line endings.' >&2
+ exit 1
+ fi
+
+ - name: Install Linux x86 build dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ if ! dpkg --print-foreign-architectures | grep -qx i386; then
+ sudo dpkg --add-architecture i386
+ fi
+ sudo apt-get update
+ sudo apt-get install -y gcc-multilib g++-multilib cmake make binutils file unzip libffi7:i386 zlib1g:i386
+
+ - name: Fetch pinned HL2SDK (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $mirror = $env:SP_GITHUB_PROXY
+ .\scripts\ci\fetch-hl2sdk.ps1 -Branch '${{ matrix.branch }}' -MirrorBase $mirror
+
+ - name: Fetch pinned HL2SDK (Linux)
+ if: runner.os == 'Linux'
+ shell: bash
+ run: bash ./scripts/ci/fetch-hl2sdk.sh '${{ matrix.branch }}'
+
+ - name: Build Windows x86
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: .\scripts\ci\build-windows.ps1 -Branch '${{ matrix.branch }}'
+
+ - name: Build Linux x86
+ if: runner.os == 'Linux'
+ shell: bash
+ run: bash ./scripts/ci/build-linux.sh '${{ matrix.branch }}'
+
+ - name: Verify Linux runtime dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ set -euo pipefail
+ ctypes_module="$(find addons/source-python/Python3/lib-dynload -maxdepth 1 -name '_ctypes*.so' -print -quit)"
+ test -n "$ctypes_module"
+ LD_LIBRARY_PATH="$PWD/addons/source-python/Python3/plat-linux${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
+ ldd "$ctypes_module" | tee /tmp/source-python-ctypes-ldd.txt
+ grep -q 'libffi.so.7 =>' /tmp/source-python-ctypes-ldd.txt
+
+ - name: Verify native artifact names
+ shell: bash
+ run: |
+ directory="artifacts/native/${{ matrix.branch }}/${{ matrix.target.platform }}"
+ suffix="so"
+ if [ "${{ matrix.target.platform }}" = "windows" ]; then suffix="dll"; fi
+ test -f "$directory/core.$suffix"
+ test -f "$directory/source-python.$suffix"
+
+ - name: Verify Windows binaries are x86 PE images
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $directory = Join-Path $env:GITHUB_WORKSPACE "artifacts\native\${{ matrix.branch }}\windows"
+ foreach ($name in @('core.dll', 'source-python.dll')) {
+ $path = Join-Path $directory $name
+ $stream = [IO.File]::OpenRead($path)
+ try {
+ $reader = New-Object IO.BinaryReader($stream)
+ $stream.Position = 0x3C
+ $peOffset = $reader.ReadInt32()
+ $stream.Position = $peOffset + 4
+ $machine = $reader.ReadUInt16()
+ }
+ finally {
+ $stream.Dispose()
+ }
+ if ($machine -ne 0x014C) {
+ throw "$name is not an x86 PE image (machine 0x{0:x})." -f $machine
+ }
+ $hash = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant()
+ Write-Host "$name x86 sha256=$hash"
+ }
+
+ - name: Verify Linux binaries are 32-bit ELF images
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ set -euo pipefail
+ directory="artifacts/native/${{ matrix.branch }}/linux"
+ for name in core.so source-python.so; do
+ test -f "$directory/$name"
+ description="$(file -b "$directory/$name")"
+ case "$description" in
+ *"ELF 32-bit"*) ;;
+ *) echo "$name is not a 32-bit ELF image: $description" >&2; exit 1 ;;
+ esac
+ echo "$name: $description"
+ sha256sum "$directory/$name"
+ done
+
+ - name: Upload native binaries
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-${{ matrix.branch }}-${{ matrix.target.platform }}
+ path: artifacts/native
+ if-no-files-found: error
+ retention-days: 7
+
+ package:
+ name: Assemble release archives
+ needs: build
+ runs-on: windows-2022
+ permissions:
+ contents: write
+ env:
+ SOURCEPYTHON_BUILD_DATE: ${{ inputs.build_date }}
+ steps:
+ - name: Check out Source.Python
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }}
+
+ - name: Record checked-out source revision
+ shell: pwsh
+ run: |
+ $revision = (git rev-parse HEAD).Trim()
+ "SOURCEPYTHON_SOURCE_REVISION=$revision" | Add-Content -LiteralPath $env:GITHUB_ENV
+
+ - name: Download all native binaries
+ uses: actions/download-artifact@v4
+ with:
+ pattern: native-*
+ path: artifacts/native
+ merge-multiple: true
+
+ - name: Assemble BLADE/BMS/CS:GO/CSS/DODS/HL2DM/L4D2/TF2 archives
+ shell: pwsh
+ run: |
+ .\scripts\ci\package.ps1 `
+ -RepositoryRoot $env:GITHUB_WORKSPACE `
+ -NativeRoot (Join-Path $env:GITHUB_WORKSPACE 'artifacts\native') `
+ -OutputDirectory (Join-Path $env:GITHUB_WORKSPACE 'dist') `
+ -Branches @('blade', 'bms', 'csgo', 'css', 'dods', 'hl2dm', 'l4d2', 'tf2') `
+ -RequiredPlatforms @('windows', 'linux') `
+ -SourceArchive
+
+ - name: Verify assembled archives
+ shell: pwsh
+ run: |
+ .\scripts\ci\verify-packages.ps1 `
+ -RepositoryRoot $env:GITHUB_WORKSPACE `
+ -OutputDirectory (Join-Path $env:GITHUB_WORKSPACE 'dist')
+
+ - name: Upload assembled archives
+ uses: actions/upload-artifact@v4
+ with:
+ name: source-python-packages
+ path: dist/*
+ if-no-files-found: error
+ retention-days: 30
+
+ - name: Create or update GitHub release
+ if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.create_release && inputs.release_tag != '') }}
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ RELEASE_TAG: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || inputs.release_tag }}
+ run: |
+ set -euo pipefail
+ if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
+ gh release upload "$RELEASE_TAG" dist/* --clobber
+ else
+ gh release create "$RELEASE_TAG" dist/* \
+ --title "Source.Python $RELEASE_TAG" \
+ --notes "Automated Windows/Linux x86 packages. See BUILD-MANIFEST.json in each archive for pinned HL2SDK revisions and Linux runtime requirements."
+ fi
diff --git a/.github/workflows/build-x64.yml b/.github/workflows/build-x64.yml
new file mode 100644
index 0000000000..3e08be0e08
--- /dev/null
+++ b/.github/workflows/build-x64.yml
@@ -0,0 +1,228 @@
+name: Build Source.Python x86-64 (experimental)
+
+# Experimental x86-64 pipeline. It is dispatch-only on purpose: it must never
+# start a run by itself, and it does not exist on the master branch, so the
+# 32-bit build-packages pipeline keeps its own matrix and release gating.
+# Merge into build-packages.yml only once a game's x86-64 runtime is verified
+# on a real server.
+
+on:
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: 'Game branch to build for x86-64'
+ required: false
+ default: hl2dm
+ type: choice
+ options:
+ - hl2dm
+ - css
+ - dods
+ - tf2
+ - blade
+ github_proxy:
+ description: 'Optional public GitHub proxy, for example https://ghfast.top'
+ required: false
+ default: ''
+ type: string
+
+permissions:
+ contents: read
+
+concurrency:
+ group: source-python-x64-${{ github.ref }}-${{ inputs.branch }}
+ cancel-in-progress: true
+
+jobs:
+ build-x64:
+ name: Build ${{ inputs.branch }} x86-64 (${{ matrix.target.platform }})
+ timeout-minutes: 90
+ strategy:
+ fail-fast: false
+ matrix:
+ # Linux only for now. src/CMakeLists.txt hard-fails x86_64 unless the
+ # platform is UNIX and the branch is hl2dm, and the third-party trees
+ # carry lib/linux64 for boost, dyncall, DynamicHooks and AsmJit but no
+ # win64 counterpart. Add the windows-2022 target back once win64
+ # dependencies and a win64 CPython are in place; the -Architecture
+ # plumbing in build-windows.ps1 is already in place for that.
+ target:
+ - os: ubuntu-22.04
+ platform: linux
+ runs-on: ${{ matrix.target.os }}
+ env:
+ SP_GITHUB_PROXY: ${{ inputs.github_proxy }}
+ SP_BRANCH: ${{ inputs.branch }}
+ steps:
+ - name: Check out Source.Python
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ ref: ${{ github.ref }}
+
+ - name: Record checked-out source revision
+ shell: pwsh
+ run: |
+ $revision = (git rev-parse HEAD).Trim()
+ "SOURCEPYTHON_SOURCE_REVISION=$revision" | Add-Content -LiteralPath $env:GITHUB_ENV
+
+ - name: Report the resolved SDK pin
+ shell: pwsh
+ run: |
+ $pins = Get-Content -LiteralPath ./scripts/ci/sdk-pins.json -Raw | ConvertFrom-Json
+ $entry = $pins.PSObject.Properties[$env:SP_BRANCH].Value
+ "branch : $env:SP_BRANCH"
+ "sdk commit : $($entry.commit)"
+ "sdk_lib : $($entry.sdk_lib)"
+ "sdk_lib_linux : $($entry.sdk_lib_linux)"
+ "sdk_lib_win64 : $($entry.sdk_lib_win64)"
+ "sdk_lib_linux64: $($entry.sdk_lib_linux64)"
+
+ - name: Verify Source.Python fixes
+ shell: pwsh
+ run: pwsh -NoLogo -NoProfile -File ./scripts/ci/verify-fixes.ps1 -RepositoryRoot $env:GITHUB_WORKSPACE
+
+ - name: Verify shell script line endings
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ if grep -Il $'\r' src/Build.sh scripts/ci/*.sh; then
+ echo 'Shell scripts must use LF line endings.' >&2
+ exit 1
+ fi
+
+ # No gcc-multilib and no i386 architecture: this job links 64-bit objects
+ # only. The 32-bit runtime dependency check in build-packages.yml greps
+ # for libffi.so.7, which is the i386 SONAME and does not apply here.
+ - name: Install Linux x86-64 build dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y cmake make binutils file unzip
+
+ - name: Fetch pinned HL2SDK (Windows)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $mirror = $env:SP_GITHUB_PROXY
+ .\scripts\ci\fetch-hl2sdk.ps1 -Branch $env:SP_BRANCH -MirrorBase $mirror
+
+ - name: Fetch pinned HL2SDK (Linux)
+ if: runner.os == 'Linux'
+ shell: bash
+ run: bash ./scripts/ci/fetch-hl2sdk.sh "$SP_BRANCH"
+
+ - name: Build Windows x86-64
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: .\scripts\ci\build-windows.ps1 -Branch $env:SP_BRANCH -Architecture x86_64
+
+ - name: Build Linux x86-64
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ bash ./scripts/ci/build-linux.sh "$SP_BRANCH" \
+ "$GITHUB_WORKSPACE" \
+ "$GITHUB_WORKSPACE/src/Builds/Linux/$SP_BRANCH-x86_64" \
+ "$GITHUB_WORKSPACE/artifacts/native" \
+ x86_64
+
+ # build-linux.sh already hard-fails on a non-ELF64 image; these re-check
+ # the exact pair of files that get uploaded and record their hashes.
+ - name: Verify Windows binaries are x86-64 PE images
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ $directory = Join-Path $env:GITHUB_WORKSPACE "artifacts\native\$env:SP_BRANCH\windows-x86_64"
+ foreach ($name in @('core.dll', 'source-python.dll')) {
+ $path = Join-Path $directory $name
+ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
+ throw "$name was not produced at '$path'."
+ }
+ $stream = [IO.File]::OpenRead($path)
+ try {
+ $reader = New-Object IO.BinaryReader($stream)
+ $stream.Position = 0x3C
+ $peOffset = $reader.ReadInt32()
+ $stream.Position = $peOffset + 4
+ $machine = $reader.ReadUInt16()
+ }
+ finally {
+ $stream.Dispose()
+ }
+ if ($machine -ne 0x8664) {
+ throw "$name is not an x86-64 PE image (machine 0x{0:x})." -f $machine
+ }
+ $hash = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant()
+ Write-Host "$name x86-64 PE machine=0x{0:x} sha256=$hash" -f $machine
+ }
+ Write-Host '--- build-info.json ---'
+ Get-Content -LiteralPath (Join-Path $directory 'build-info.json')
+
+ - name: Verify Linux binaries are 64-bit ELF images
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ set -euo pipefail
+ directory="artifacts/native/$SP_BRANCH/linux-x86_64"
+ for name in core.so source-python.so; do
+ test -f "$directory/$name"
+ description="$(file -b "$directory/$name")"
+ case "$description" in
+ *"ELF 64-bit"*) ;;
+ *) echo "$name is not a 64-bit ELF image: $description" >&2; exit 1 ;;
+ esac
+ echo "$name: $description"
+ readelf -h "$directory/$name" | grep -E 'Class:|Machine:'
+ sha256sum "$directory/$name"
+ done
+ echo '--- build-info.json ---'
+ cat "$directory/build-info.json"
+
+ # Reported, not asserted. The 32-bit pipeline pins libffi.so.7, which is
+ # the i386 SONAME; a 64-bit CPython normally wants libffi.so.8. This run
+ # records the real dependency so the assertion can be written from
+ # evidence rather than from a guess.
+ - name: Inspect x86-64 runtime dependencies
+ if: runner.os == 'Linux'
+ shell: bash
+ run: |
+ set -uo pipefail
+ dynload="addons/source-python/Python3/lib-dynload-linux64"
+ if [ ! -d "$dynload" ]; then
+ dynload="addons/source-python/Python3/lib-dynload"
+ fi
+ ctypes_module="$(find "$dynload" -maxdepth 1 -name '_ctypes*.so' -print -quit)"
+ echo "dynload dir : $dynload"
+ if [ -z "$ctypes_module" ]; then
+ echo "No _ctypes module found in $dynload; nothing to inspect." >&2
+ exit 0
+ fi
+ echo "module : $ctypes_module"
+ file -b "$ctypes_module" | sed 's/^/ /'
+ LD_LIBRARY_PATH="$PWD/addons/source-python/Python3/plat-linux64${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
+ ldd "$ctypes_module" | tee /tmp/source-python-x64-ctypes-ldd.txt
+ echo "--- missing ---"
+ grep -i 'not found' /tmp/source-python-x64-ctypes-ldd.txt || echo "(none)"
+
+ - name: List the packaged x86-64 Python runtime
+ shell: bash
+ run: |
+ set -uo pipefail
+ for dir in addons/source-python/Python3/plat-linux64 addons/source-python/Python3/lib-dynload-linux64; do
+ echo "=== $dir ==="
+ if [ -d "$dir" ]; then
+ ls -la "$dir" | head -30
+ else
+ echo "(missing)"
+ fi
+ done
+
+ - name: Upload x86-64 native binaries
+ uses: actions/upload-artifact@v4
+ with:
+ name: native-x64-${{ inputs.branch }}-${{ matrix.target.platform }}
+ path: artifacts/native
+ if-no-files-found: error
+ retention-days: 30
diff --git a/.gitignore b/.gitignore
index ed1d1d81ea..6717eb8359 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,9 @@ tests/
src/Builds/
src/hl2sdk/*
!src/hl2sdk/readme.md
+artifacts/
+dist/
+*.download
# Plugin directories
__pycache__
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_asyncio.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_asyncio.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..9cd1efb315
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_asyncio.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_bisect.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_bisect.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..5861df30c1
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_bisect.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_blake2.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_blake2.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..3ca7b51297
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_blake2.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_cn.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_cn.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..cbdc2590f0
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_cn.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_hk.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_hk.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..b61099afdc
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_hk.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_iso2022.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_iso2022.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..9079adc21f
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_iso2022.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_jp.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_jp.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..98fced1f5b
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_jp.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_kr.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_kr.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..dbd42ee93a
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_kr.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_codecs_tw.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_codecs_tw.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..bdfb4638e5
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_codecs_tw.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_contextvars.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_contextvars.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..e06d7272ae
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_contextvars.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_csv.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_csv.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..904f92dd7b
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_csv.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_ctypes.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_ctypes.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..c088d6348f
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_ctypes.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_datetime.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_datetime.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..2597477a18
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_datetime.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_decimal.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_decimal.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..d8e9bda610
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_decimal.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_elementtree.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_elementtree.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..3b97a747a5
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_elementtree.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_hashlib.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_hashlib.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..b7a3688078
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_hashlib.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_heapq.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_heapq.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..ad334d0e3a
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_heapq.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_interpchannels.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_interpchannels.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..4415966a09
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_interpchannels.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_interpqueues.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_interpqueues.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..7967e1ab07
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_interpqueues.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_interpreters.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_interpreters.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..06bca9400c
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_interpreters.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_json.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_json.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..565f095047
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_json.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_lsprof.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_lsprof.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..2477a92f45
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_lsprof.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_md5.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_md5.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..cd0a8405e3
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_md5.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_multibytecodec.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_multibytecodec.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..2504ae4876
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_multibytecodec.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_multiprocessing.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_multiprocessing.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..be1944cdee
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_multiprocessing.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_opcode.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_opcode.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..33c2c8b2cd
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_opcode.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_pickle.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_pickle.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..ba1d941ddd
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_pickle.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_posixshmem.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_posixshmem.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..84297d1ed1
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_posixshmem.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_posixsubprocess.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_posixsubprocess.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..f275be06ee
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_posixsubprocess.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_queue.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_queue.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..88f7d52348
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_queue.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_random.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_random.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..8382594ae4
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_random.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_sha1.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_sha1.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..832f34ca4c
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_sha1.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_sha2.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_sha2.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..2a3529da4f
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_sha2.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_sha3.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_sha3.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..4f00da9913
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_sha3.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_socket.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_socket.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..54f71a713f
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_socket.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_sqlite3.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_sqlite3.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..89de6f40bc
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_sqlite3.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_ssl.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_ssl.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..6321f4edc2
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_ssl.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_statistics.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_statistics.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..d0c79e42e3
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_statistics.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_struct.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_struct.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..fa6de5e5d8
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_struct.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testbuffer.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testbuffer.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..fb5bc07b88
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testbuffer.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testcapi.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testcapi.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..f7829c8399
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testcapi.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testclinic.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testclinic.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..9a1f256c46
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testclinic.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testclinic_limited.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testclinic_limited.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..86ec85983b
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testclinic_limited.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testexternalinspection.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testexternalinspection.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..434d787aef
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testexternalinspection.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testimportmultiple.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testimportmultiple.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..9aad678c55
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testimportmultiple.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testinternalcapi.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testinternalcapi.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..794fc6ea0e
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testinternalcapi.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testlimitedcapi.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testlimitedcapi.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..8271840f50
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testlimitedcapi.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testmultiphase.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testmultiphase.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..ddc1b6b904
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testmultiphase.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_testsinglephase.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_testsinglephase.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..7b9169690a
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_testsinglephase.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_xxtestfuzz.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_xxtestfuzz.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..085fe4762c
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_xxtestfuzz.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/_zoneinfo.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/_zoneinfo.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..d0e061559c
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/_zoneinfo.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/array.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/array.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..6f2bb0ce67
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/array.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/binascii.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/binascii.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..82dec23c59
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/binascii.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/cmath.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/cmath.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..6fa2298d2e
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/cmath.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/fcntl.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/fcntl.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..20b73ad170
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/fcntl.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/grp.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/grp.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..14476cf231
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/grp.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/math.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/math.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..06038329b9
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/math.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/mmap.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/mmap.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..626612df9d
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/mmap.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/pyexpat.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/pyexpat.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..df1c218d51
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/pyexpat.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/resource.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/resource.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..0cb9a6335b
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/resource.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/select.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/select.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..efe4a24176
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/select.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/syslog.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/syslog.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..63ed826366
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/syslog.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/termios.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/termios.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..f09de65845
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/termios.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/unicodedata.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/unicodedata.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..131c1ad038
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/unicodedata.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/xxlimited.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/xxlimited.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..2976d52893
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/xxlimited.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/xxlimited_35.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/xxlimited_35.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..55affc54fa
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/xxlimited_35.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/xxsubtype.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/xxsubtype.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..166853d5fa
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/xxsubtype.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/lib-dynload-linux64/zlib.cpython-313-x86_64-linux-gnu.so b/addons/source-python/Python3/lib-dynload-linux64/zlib.cpython-313-x86_64-linux-gnu.so
new file mode 100644
index 0000000000..852021661d
Binary files /dev/null and b/addons/source-python/Python3/lib-dynload-linux64/zlib.cpython-313-x86_64-linux-gnu.so differ
diff --git a/addons/source-python/Python3/plat-linux64/libpython3.13.so.1.0 b/addons/source-python/Python3/plat-linux64/libpython3.13.so.1.0
new file mode 100644
index 0000000000..d2b2000326
Binary files /dev/null and b/addons/source-python/Python3/plat-linux64/libpython3.13.so.1.0 differ
diff --git a/addons/source-python/Python3/plat-linux64/libsqlite3.so.0 b/addons/source-python/Python3/plat-linux64/libsqlite3.so.0
new file mode 100644
index 0000000000..4499aeb510
Binary files /dev/null and b/addons/source-python/Python3/plat-linux64/libsqlite3.so.0 differ
diff --git a/addons/source-python/Python3/plat-linux64/libz.so.1.2.11 b/addons/source-python/Python3/plat-linux64/libz.so.1.2.11
new file mode 100644
index 0000000000..5ff119d350
Binary files /dev/null and b/addons/source-python/Python3/plat-linux64/libz.so.1.2.11 differ
diff --git a/addons/source-python/bin/readme.md b/addons/source-python/bin/readme.md
deleted file mode 100644
index 530d2e7942..0000000000
--- a/addons/source-python/bin/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source.Python Bin ReadMe
-
-This directory is used for Source.Python binaries that the plugin will load.
diff --git a/addons/source-python/data/custom/readme.md b/addons/source-python/data/custom/readme.md
deleted file mode 100644
index 1e0d395b31..0000000000
--- a/addons/source-python/data/custom/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source.Python Data/Custom ReadMe
-
-This directory is used for custom Source.Python packages data.
diff --git a/addons/source-python/data/plugins/readme.md b/addons/source-python/data/plugins/readme.md
deleted file mode 100644
index 0d69f8f2ed..0000000000
--- a/addons/source-python/data/plugins/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source.Python Data/Plugins ReadMe
-
-This directory is used for Source.Python plugin-specific data.
diff --git a/addons/source-python/data/source-python/entities/CPointServerCommand.ini b/addons/source-python/data/source-python/entities/CPointServerCommand.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini b/addons/source-python/data/source-python/entities/bms/CBaseEntity.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CBaseCSGrenadeProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CBaseCSGrenadeProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CBaseCombatCharacter.ini b/addons/source-python/data/source-python/entities/csgo/CBaseCombatCharacter.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini b/addons/source-python/data/source-python/entities/csgo/CBaseEntity.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CBasePlayer.ini b/addons/source-python/data/source-python/entities/csgo/CBasePlayer.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CCSPlayer.ini b/addons/source-python/data/source-python/entities/csgo/CCSPlayer.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CDecoyProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CDecoyProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CFlashbangProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CFlashbangProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CHEGrenadeProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CHEGrenadeProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CMolotovProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CMolotovProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CSensorGrenadeProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CSensorGrenadeProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/csgo/CSmokeGrenadeProjectile.ini b/addons/source-python/data/source-python/entities/csgo/CSmokeGrenadeProjectile.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini b/addons/source-python/data/source-python/entities/gmod/CBaseEntity.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini b/addons/source-python/data/source-python/entities/l4d2/CBaseEntity.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini b/addons/source-python/data/source-python/entities/orangebox/CBaseEntity.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatCharacter.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatCharacter.ini
new file mode 100644
index 0000000000..dd9e7c1b16
--- /dev/null
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatCharacter.ini
@@ -0,0 +1,15 @@
+[virtual_function]
+
+ # _ZN20CBaseCombatCharacter18OnTakeDamage_AliveERK15CTakeDamageInfo
+ [[on_take_damage_alive]]
+ offset_linux = 280
+ offset_windows = 279
+ arguments = POINTER
+ return_type = INT
+
+ # _ZN20CBaseCombatCharacter13Weapon_SwitchEP17CBaseCombatWeaponi
+ [[weapon_switch]]
+ offset_linux = 272
+ offset_windows = 271
+ arguments = POINTER, INT
+ return_type = BOOL
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatWeapon.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatWeapon.ini
index b80fbbfcaa..741f1f46b4 100644
--- a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatWeapon.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatWeapon.ini
@@ -2,10 +2,10 @@
# _ZN17CBaseCombatWeapon13PrimaryAttackEv
[[primary_attack]]
- offset_linux = 280
- offset_windows = 279
+ offset_linux = 281
+ offset_windows = 280
# _ZN17CBaseCombatWeapon13SecondaryAttackEv
[[secondary_attack]]
- offset_linux = 281
- offset_windows = 280
+ offset_linux = 282
+ offset_windows = 281
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseEntity.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseEntity.ini
new file mode 100644
index 0000000000..11609e6ade
--- /dev/null
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseEntity.ini
@@ -0,0 +1,36 @@
+[virtual_function]
+
+ # _ZN11CBaseEntity10StartTouchEPS_
+ [[start_touch]]
+ offset_linux = 102
+ offset_windows = 101
+ arguments = POINTER
+
+ # _ZN11CBaseEntity5TouchEPS_
+ [[touch]]
+ offset_linux = 103
+ offset_windows = 102
+ arguments = POINTER
+
+ # _ZN11CBaseEntity8EndTouchEPS_
+ [[end_touch]]
+ offset_linux = 104
+ offset_windows = 103
+ arguments = POINTER
+
+ # _ZN11CBaseEntity8TeleportEPK6VectorPK6QAngleS2_
+ [[teleport]]
+ offset_linux = 112
+ offset_windows = 111
+ arguments = POINTER, POINTER, POINTER
+
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 172
+ offset_windows = 171
+ return_type = UINT
+
+
+[input]
+
+ dispatch_effect = DispatchEffect
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseGrenade.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseGrenade.ini
index 93370d81eb..9ee3e07280 100644
--- a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseGrenade.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseGrenade.ini
@@ -2,5 +2,5 @@
# _ZN12CBaseGrenade8DetonateEv
[[detonate]]
- offset_linux = 233
- offset_windows = 232
+ offset_linux = 234
+ offset_windows = 233
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBasePlayer.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBasePlayer.ini
index 324389cf25..46694e083e 100644
--- a/addons/source-python/data/source-python/entities/orangebox/cstrike/CBasePlayer.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CBasePlayer.ini
@@ -1,23 +1,39 @@
[virtual_function]
+ # _ZN11CBasePlayer11Weapon_DropEP17CBaseCombatWeaponPK6VectorS4_
+ [[drop_weapon]]
+ offset_linux = 271
+ offset_windows = 270
+ arguments = POINTER, POINTER, POINTER
+
+ # _ZN11CBasePlayer8PreThinkEv
+ [[pre_think]]
+ offset_linux = 340
+ offset_windows = 339
+
+ # _ZN11CBasePlayer9PostThinkEv
+ [[post_think]]
+ offset_linux = 341
+ offset_windows = 340
+
# _ZN11CBasePlayer10BumpWeaponEP17CBaseCombatWeapon
[[bump_weapon]]
- offset_linux = 405
- offset_windows = 404
+ offset_linux = 406
+ offset_windows = 405
arguments = POINTER
return_type = BOOL
# _ZN11CBasePlayer13GiveNamedItemEPKci
[[give_named_item]]
- offset_linux = 409
- offset_windows = 408
+ offset_linux = 410
+ offset_windows = 409
arguments = STRING, INT
return_type = POINTER
# _ZN11CBasePlayer16PlayerRunCommandEP8CUserCmdP11IMoveHelper
[[run_command]]
- offset_linux = 427
- offset_windows = 426
+ offset_linux = 428
+ offset_windows = 427
arguments = POINTER, POINTER
diff --git a/addons/source-python/data/source-python/entities/orangebox/cstrike/CCSPlayer.ini b/addons/source-python/data/source-python/entities/orangebox/cstrike/CCSPlayer.ini
index 3e46e4348c..2da1ce9a2e 100644
--- a/addons/source-python/data/source-python/entities/orangebox/cstrike/CCSPlayer.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/cstrike/CCSPlayer.ini
@@ -34,14 +34,14 @@
# _ZN9CCSPlayer12Weapon_EquipEP17CBaseCombatWeapon
[[equip_weapon]]
- offset_linux = 268
- offset_windows = 267
+ offset_linux = 269
+ offset_windows = 268
arguments = POINTER
# _ZN9CCSPlayer5BlindEfff
[[blind]]
- offset_linux = 479
- offset_windows = 476
+ offset_linux = 480
+ offset_windows = 477
arguments = FLOAT, FLOAT, FLOAT
diff --git a/addons/source-python/data/source-python/entities/orangebox/dod/CBasePlayer.ini b/addons/source-python/data/source-python/entities/orangebox/dod/CBasePlayer.ini
index f064286a18..b0b2f9d09a 100644
--- a/addons/source-python/data/source-python/entities/orangebox/dod/CBasePlayer.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/dod/CBasePlayer.ini
@@ -7,6 +7,22 @@
[virtual_function]
+ # Keep the DODS vtable independent from the common/CSS/HL2DM update.
+ [[drop_weapon]]
+ offset_linux = 270
+ offset_windows = 269
+ arguments = POINTER, POINTER, POINTER
+
+ # _ZN11CBasePlayer8PreThinkEv
+ [[pre_think]]
+ offset_linux = 339
+ offset_windows = 338
+
+ # _ZN11CBasePlayer9PostThinkEv
+ [[post_think]]
+ offset_linux = 340
+ offset_windows = 339
+
# _ZN11CBasePlayer16PlayerRunCommandEP8CUserCmdP11IMoveHelper
[[run_command]]
offset_linux = 426
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatCharacter.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatCharacter.ini
new file mode 100644
index 0000000000..dd9e7c1b16
--- /dev/null
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatCharacter.ini
@@ -0,0 +1,15 @@
+[virtual_function]
+
+ # _ZN20CBaseCombatCharacter18OnTakeDamage_AliveERK15CTakeDamageInfo
+ [[on_take_damage_alive]]
+ offset_linux = 280
+ offset_windows = 279
+ arguments = POINTER
+ return_type = INT
+
+ # _ZN20CBaseCombatCharacter13Weapon_SwitchEP17CBaseCombatWeaponi
+ [[weapon_switch]]
+ offset_linux = 272
+ offset_windows = 271
+ arguments = POINTER, INT
+ return_type = BOOL
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatWeapon.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatWeapon.ini
old mode 100755
new mode 100644
index 5540468c62..a08296e1e3
--- a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatWeapon.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatWeapon.ini
@@ -2,15 +2,15 @@
# _ZN17CBaseCombatWeapon13PrimaryAttackEv
[[primary_attack]]
- offset_linux = 280
- offset_windows = 279
+ offset_linux = 281
+ offset_windows = 280
# _ZN17CBaseCombatWeapon13SecondaryAttackEv
[[secondary_attack]]
- offset_linux = 281
- offset_windows = 280
+ offset_linux = 282
+ offset_windows = 281
# _ZN17CBaseCombatWeapon11MaterializeEv
[[materialize]]
- offset_linux = 353
- offset_windows = 352
+ offset_linux = 354
+ offset_windows = 353
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseEntity.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseEntity.ini
new file mode 100644
index 0000000000..11609e6ade
--- /dev/null
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseEntity.ini
@@ -0,0 +1,36 @@
+[virtual_function]
+
+ # _ZN11CBaseEntity10StartTouchEPS_
+ [[start_touch]]
+ offset_linux = 102
+ offset_windows = 101
+ arguments = POINTER
+
+ # _ZN11CBaseEntity5TouchEPS_
+ [[touch]]
+ offset_linux = 103
+ offset_windows = 102
+ arguments = POINTER
+
+ # _ZN11CBaseEntity8EndTouchEPS_
+ [[end_touch]]
+ offset_linux = 104
+ offset_windows = 103
+ arguments = POINTER
+
+ # _ZN11CBaseEntity8TeleportEPK6VectorPK6QAngleS2_
+ [[teleport]]
+ offset_linux = 112
+ offset_windows = 111
+ arguments = POINTER, POINTER, POINTER
+
+ # _ZNK11CBasePlayer25PhysicsSolidMaskForEntityEv
+ [[get_solid_mask]]
+ offset_linux = 172
+ offset_windows = 171
+ return_type = UINT
+
+
+[input]
+
+ dispatch_effect = DispatchEffect
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseGrenade.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseGrenade.ini
old mode 100755
new mode 100644
index 93370d81eb..9ee3e07280
--- a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseGrenade.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseGrenade.ini
@@ -2,5 +2,5 @@
# _ZN12CBaseGrenade8DetonateEv
[[detonate]]
- offset_linux = 233
- offset_windows = 232
+ offset_linux = 234
+ offset_windows = 233
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBasePlayer.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBasePlayer.ini
old mode 100755
new mode 100644
index 01fcc0e128..7d17bb5b21
--- a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBasePlayer.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CBasePlayer.ini
@@ -13,24 +13,40 @@
[virtual_function]
+ # _ZN11CBasePlayer11Weapon_DropEP17CBaseCombatWeaponPK6VectorS4_
+ [[drop_weapon]]
+ offset_linux = 271
+ offset_windows = 270
+ arguments = POINTER, POINTER, POINTER
+
+ # _ZN11CBasePlayer8PreThinkEv
+ [[pre_think]]
+ offset_linux = 340
+ offset_windows = 339
+
+ # _ZN11CBasePlayer9PostThinkEv
+ [[post_think]]
+ offset_linux = 341
+ offset_windows = 340
+
# _ZN11CBasePlayer10BumpWeaponEP17CBaseCombatWeapon
[[bump_weapon]]
- offset_linux = 404
- offset_windows = 403
+ offset_linux = 405
+ offset_windows = 404
arguments = POINTER
return_type = BOOL
# _ZN11CBasePlayer13GiveNamedItemEPKci
[[give_named_item]]
- offset_linux = 408
- offset_windows = 407
+ offset_linux = 409
+ offset_windows = 408
arguments = STRING, INT
return_type = POINTER
# _ZN11CBasePlayer16PlayerRunCommandEP8CUserCmdP11IMoveHelper
[[run_command]]
- offset_linux = 426
- offset_windows = 425
+ offset_linux = 427
+ offset_windows = 426
arguments = POINTER, POINTER
diff --git a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CItem.ini b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CItem.ini
old mode 100755
new mode 100644
index 8a2bda6de3..0fa3960023
--- a/addons/source-python/data/source-python/entities/orangebox/hl2mp/CItem.ini
+++ b/addons/source-python/data/source-python/entities/orangebox/hl2mp/CItem.ini
@@ -2,5 +2,5 @@
# _ZN5CItem11MaterializeEv
[[materialize]]
- offset_linux = 224
- offset_windows = 223
+ offset_linux = 225
+ offset_windows = 224
diff --git a/addons/source-python/data/source-python/entity_output/orangebox/hl2mp/CBaseEntityOutput.ini b/addons/source-python/data/source-python/entity_output/orangebox/hl2mp/CBaseEntityOutput.ini
new file mode 100644
index 0000000000..c8eb7874b8
--- /dev/null
+++ b/addons/source-python/data/source-python/entity_output/orangebox/hl2mp/CBaseEntityOutput.ini
@@ -0,0 +1,5 @@
+# ../data/source-python/entity_output/orangebox/hl2mp/CBaseEntityOutput.ini
+
+[function]
+ [[fire_output]]
+ identifier_linux_x86_64 = 55 48 89 E5 41 57 41 56 41 55 41 54 53 48 81 EC 2A 2A 2A 2A 4C 8B 7F 18
diff --git a/addons/source-python/data/source-python/weapons/constants/csgo/WeaponID.ini b/addons/source-python/data/source-python/weapons/constants/csgo/WeaponID.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/constants/csgo/WeaponSlot.ini b/addons/source-python/data/source-python/weapons/constants/csgo/WeaponSlot.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/constants/csgo/WeaponType.ini b/addons/source-python/data/source-python/weapons/constants/csgo/WeaponType.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/constants/orangebox/cstrike/WeaponID.ini b/addons/source-python/data/source-python/weapons/constants/orangebox/cstrike/WeaponID.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/constants/orangebox/cstrike/WeaponType.ini b/addons/source-python/data/source-python/weapons/constants/orangebox/cstrike/WeaponType.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/csgo.ini b/addons/source-python/data/source-python/weapons/csgo.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/data/source-python/weapons/cstrike.ini b/addons/source-python/data/source-python/weapons/cstrike.ini
old mode 100755
new mode 100644
diff --git a/addons/source-python/docs/custom/readme.md b/addons/source-python/docs/custom/readme.md
deleted file mode 100644
index cae074a779..0000000000
--- a/addons/source-python/docs/custom/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source.Python Docs/Custom ReadMe
-
-This directory is used for custom Source.Python package documentation.
diff --git a/addons/source-python/docs/plugins/readme.md b/addons/source-python/docs/plugins/readme.md
deleted file mode 100644
index b4ed8d113b..0000000000
--- a/addons/source-python/docs/plugins/readme.md
+++ /dev/null
@@ -1 +0,0 @@
-Source.Python Docs/Plugins ReadMe
diff --git a/addons/source-python/packages/custom/readme.md b/addons/source-python/packages/custom/readme.md
deleted file mode 100644
index 2056649d35..0000000000
--- a/addons/source-python/packages/custom/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source.Python Packages/Custom ReadMe
-
-This directory is used for custom Source.Python packages.
diff --git a/addons/source-python/packages/source-python/__init__.py b/addons/source-python/packages/source-python/__init__.py
index 36d90c88ff..0eaa9fa3f8 100644
--- a/addons/source-python/packages/source-python/__init__.py
+++ b/addons/source-python/packages/source-python/__init__.py
@@ -144,6 +144,16 @@ def setup_data():
from memory.manager import manager
from paths import SP_DATA_PATH
+ try:
+ from _entities import _global_entity_list
+ except ImportError:
+ pass
+ else:
+ manager.global_pointers.setdefault(
+ 'CGlobalEntityList', _global_entity_list)
+ manager.global_pointers.setdefault(
+ 'GlobalEntityList', _global_entity_list)
+
import players
players.BaseClient = manager.create_type_from_dict(
'BaseClient',
@@ -450,6 +460,7 @@ def setup_versioning():
# =============================================================================
def setup_sqlite():
"""Pre-load libsqlite3.so.0 on Linux."""
+ from core import ARCHITECTURE
from core import PLATFORM
if PLATFORM != 'linux':
return
@@ -463,8 +474,9 @@ def setup_sqlite():
# version installed. This fixes the issue by loading the library into the
# memory using its absolute path.
# Using RPATH might be a better solution, but I don't get it working...
+ platform_dir = 'plat-linux64' if ARCHITECTURE == 'x86_64' else 'plat-linux'
ctypes.cdll.LoadLibrary(
- BASE_PATH / 'Python3/plat-linux/libsqlite3.so.0')
+ BASE_PATH / 'Python3' / platform_dir / 'libsqlite3.so.0')
# =============================================================================
@@ -525,4 +537,4 @@ def flush(self):
'https://github.com/Source-Python-Dev-Team/Source.Python/issues/175. '
'Source.Python should continue working, but we would like to figure '
'out in which situations sys.stdout is None to be able to fix this '
- 'issue instead of applying a workaround.')
\ No newline at end of file
+ 'issue instead of applying a workaround.')
diff --git a/addons/source-python/packages/source-python/commands/typed.py b/addons/source-python/packages/source-python/commands/typed.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/core/__init__.py b/addons/source-python/packages/source-python/core/__init__.py
old mode 100755
new mode 100644
index a8d1d17ae7..351bfa381e
--- a/addons/source-python/packages/source-python/core/__init__.py
+++ b/addons/source-python/packages/source-python/core/__init__.py
@@ -64,6 +64,7 @@
# >> ALL DECLARATION
# =============================================================================
__all__ = ('AutoUnload',
+ 'ARCHITECTURE',
'BoostPythonClass',
'ConfigFile',
'GameConfigObj',
@@ -94,6 +95,7 @@
# Get the platform the server is on
PLATFORM = system().lower()
+ARCHITECTURE = 'x86_64' if sys.maxsize > 2 ** 32 else 'x86'
# =============================================================================
@@ -438,4 +440,4 @@ def check_info_output(output):
if lines[-1].startswith('-'):
lines.pop()
- return create_checksum(''.join(lines)) != checksum
\ No newline at end of file
+ return create_checksum(''.join(lines)) != checksum
diff --git a/addons/source-python/packages/source-python/core/command/__init__.py b/addons/source-python/packages/source-python/core/command/__init__.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/core/logger.py b/addons/source-python/packages/source-python/core/logger.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/core/update.py b/addons/source-python/packages/source-python/core/update.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/entities/_base.py b/addons/source-python/packages/source-python/entities/_base.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/entities/classes.py b/addons/source-python/packages/source-python/entities/classes.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/entities/engines/csgo/csgo.py b/addons/source-python/packages/source-python/entities/engines/csgo/csgo.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/listeners/__init__.py b/addons/source-python/packages/source-python/listeners/__init__.py
old mode 100755
new mode 100644
index 3205908856..9e9f91f873
--- a/addons/source-python/packages/source-python/listeners/__init__.py
+++ b/addons/source-python/packages/source-python/listeners/__init__.py
@@ -694,14 +694,22 @@ def _pre_fire_output(args):
# To remind us to add newly supported engines...
raise NotImplementedError('No hibernation function exposed.')
-@PreHook(get_virtual_function(server_game_dll, _hibernation_function_name))
-def _pre_hibernation_function(stack_data):
- """Called when the server is hibernating."""
- if not stack_data[1]:
- return
+try:
+ _hibernation_function = get_virtual_function(
+ server_game_dll, _hibernation_function_name)
+ _hibernation_hook = PreHook(_hibernation_function)
+except (OSError, ValueError) as error:
+ from warnings import warn
+ warn('Unable to install the hibernation hook: {0}'.format(error))
+else:
+ @_hibernation_hook
+ def _pre_hibernation_function(stack_data):
+ """Called when the server is hibernating."""
+ if not stack_data[1]:
+ return
- # Disconnect all bots...
- _disconnect_bots()
+ # Disconnect all bots...
+ _disconnect_bots()
@OnLevelEnd
diff --git a/addons/source-python/packages/source-python/loggers.py b/addons/source-python/packages/source-python/loggers.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/memory/helpers.py b/addons/source-python/packages/source-python/memory/helpers.py
old mode 100755
new mode 100644
index 4c0ba21773..c9e6801ab4
--- a/addons/source-python/packages/source-python/memory/helpers.py
+++ b/addons/source-python/packages/source-python/memory/helpers.py
@@ -11,6 +11,7 @@
# Source.Python
# Core
+from core import ARCHITECTURE
from core import PLATFORM
# Memory
from memory import Convention
@@ -392,7 +393,9 @@ def parse_data(manager, raw_data, keys):
for key, converter, default in keys:
# Get the OS specific key. If that fails, fall back to the shared
# key. If that fails too, use the default value
- value = data.get(key + '_' + PLATFORM, data.get(key, default))
+ value = data.get(
+ key + '_' + PLATFORM + '_' + ARCHITECTURE,
+ data.get(key + '_' + PLATFORM, data.get(key, default)))
# If the value is NO_DEFAULT, the key is obviously missing
if value is NO_DEFAULT:
diff --git a/addons/source-python/packages/source-python/memory/manager.py b/addons/source-python/packages/source-python/memory/manager.py
old mode 100755
new mode 100644
index b096494206..9313b0fb66
--- a/addons/source-python/packages/source-python/memory/manager.py
+++ b/addons/source-python/packages/source-python/memory/manager.py
@@ -771,10 +771,15 @@ def create_global_pointers_from_file(self, f):
# Create the global pointer objects
for name, data in pointers:
+ if name in self.global_pointers:
+ continue
cls = self.get_class(name)
if cls is None:
raise NameError('Unknown class "{0}".'.format(name))
+ if cls.__name__ in self.global_pointers:
+ self.global_pointers[name] = self.global_pointers[cls.__name__]
+ continue
self.global_pointer(cls, *data)
def get_global_pointer(self, name):
diff --git a/addons/source-python/packages/source-python/menus/base.py b/addons/source-python/packages/source-python/menus/base.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/messages/base.py b/addons/source-python/packages/source-python/messages/base.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/players/_base.py b/addons/source-python/packages/source-python/players/_base.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/players/engines/csgo/__init__.py b/addons/source-python/packages/source-python/players/engines/csgo/__init__.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/players/voice.py b/addons/source-python/packages/source-python/players/voice.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/plugins/command.py b/addons/source-python/packages/source-python/plugins/command.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/weapons/engines/csgo/csgo.py b/addons/source-python/packages/source-python/weapons/engines/csgo/csgo.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/weapons/instance.py b/addons/source-python/packages/source-python/weapons/instance.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/packages/source-python/weapons/restrictions.py b/addons/source-python/packages/source-python/weapons/restrictions.py
old mode 100755
new mode 100644
diff --git a/addons/source-python/plugins/readme.md b/addons/source-python/plugins/readme.md
deleted file mode 100644
index e93c837543..0000000000
--- a/addons/source-python/plugins/readme.md
+++ /dev/null
@@ -1,4 +0,0 @@
-Source.Python Plugin ReadMe
-
-This directory is used for Source.Python plugins
- to be loaded by the command "sp plugin load".
diff --git a/cfg/source-python/readme.md b/cfg/source-python/readme.md
deleted file mode 100644
index 1a7b63a954..0000000000
--- a/cfg/source-python/readme.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Source-Python CFG ReadMe
-
-This directory should be used for all configuration files.
-
-If your addon contains multiple configuration files, it
- would be preferred if you placed them inside a folder,
- with your addon's name, inside this directory.
-
-Otherwise, you can simply use a configuration file using your addon's name.
diff --git a/docs/_sources/contributing/building.rst.txt b/docs/_sources/contributing/building.rst.txt
index 8db28650c1..253951f7e8 100644
--- a/docs/_sources/contributing/building.rst.txt
+++ b/docs/_sources/contributing/building.rst.txt
@@ -32,7 +32,7 @@ Once you have installed the requirements, follow these steps to build Source.Pyt
resources.
You can shorten these steps by simply passing the game's name to the script
-e.g. ``./Build.sh css``.
+e.g. ``bash ./Build.sh css``.
If the build finished successfully, it will say something like this:
``[100%] Built target core``.
diff --git a/docs/build-and-release.md b/docs/build-and-release.md
new file mode 100644
index 0000000000..16c98333c6
--- /dev/null
+++ b/docs/build-and-release.md
@@ -0,0 +1,183 @@
+# Source.Python 构建与发布
+
+本文档记录 2026-09 的八游戏修复构建方式:BLADE、BMS、CS:GO、CSS、DODS、HL2DM、L4D2、TF2。
+上游源码基线为 `master`,并把下列变更固定下来:
+
+- PR #533:合入 Linux x86-64 HL2DM 的源码级支持(提交 `ee44a0a`),并复制第二个提交 `90784a8` 中的 345 个依赖文件(合计约 122 MB)。这些 Linux x86-64 依赖不参与 Windows x86 构建;x86-64 gamedata 仍需单独做游戏内验证。
+- PR #535:合入 OrangeBox 实体数据更新(头提交 `017eb604`)。
+- PR #537:合入新版 OrangeBox `CBaseHandle` 兼容修复(头提交 `12006ac`)。CSS/DODS/HL2DM 编译时使用新版 HL2SDK,避免 issue #536 的 `Event.variables` 崩溃/默认值错误。
+- Issue #536:运行时回归目标是 `ev.variables.get_int('userid')` 与 `ev.get_int('userid')` 一致,且不再崩溃。
+
+## 锁定的 HL2SDK 提交
+
+`scripts/ci/sdk-pins.json` 是唯一事实来源:每个游戏记录固定提交、要校验的 Windows/Linux 库文件以及固定原因。两个下载脚本都从这里读取,不再按分支名硬编码目录,因此 TF2(OrangeBox 布局)不会再被当成 episodic 布局。
+
+| 游戏 | HL2SDK 分支 | 提交 | Windows 库 | Linux 库 |
+| --- | --- | --- | --- | --- |
+| BLADE | `blade` | `a736c9a6c676136f575af5056384851a8e1e788a` | `lib/public/tier1.lib` | `lib/linux/tier1_i486.a` |
+| BMS | `bms` | `69fe26fed5ad34cb6ef032c57a54f8efbcdd8849` | `lib/public/tier1.lib` | `lib/public/linux32/tier1.a` |
+| CS:GO | `csgo` | `9cf2f325ea273559c7cae27b4f98518c18b8e322` | `lib/public/tier1.lib` | `lib/linux/tier1_i486.a` |
+| CSS | `css` | `47f0ea659bc70718f9c41a7830b6ad943a68ba09` | `lib/public/x86/tier1.lib` | `lib/public/linux/tier1_i486.a` |
+| DODS | `dods` | `cbc97391097ed5c2790ab3fac6f9d05661d3a10f` | `lib/public/x86/tier1.lib` | `lib/public/linux/tier1_i486.a` |
+| HL2DM | `hl2dm` | `11dca0f12e53089b9dee6511f6b8ff4d02c9d51d` | `lib/public/x86/tier1.lib` | `lib/public/linux/tier1_i486.a`(x86-64:`lib/public/linux64/tier1.a`) |
+| L4D2 | `l4d2` | `2a31cd007b2d7d2f964dc093eedcf7a812cf9dd6` | `lib/public/tier1.lib` | `lib/linux/tier1_i486.a` |
+| TF2 | `tf2` | `0f358777e7a7f0ad4c6ed6595de1e4e81c15fa4e` | `lib/public/x86/tier1.lib` | `lib/public/linux/tier1_i486.a` |
+
+CSS/DODS/HL2DM/TF2 在 2026-09-20 收到同一批 KeyValues 更新(对应 2026-09 游戏更新);BMS 含 EmitGameSound 崩溃修复(#404);BLADE/CS:GO 的最新提交是 2026-04-28 的 Windows tier1 重建,官方没有后续更新。
+
+各分支最后一次提交(2026-09-25 巡检 `alliedmodders/hl2sdk` 分支列表):
+
+| HL2SDK 分支 | 最后提交 | 说明 |
+| --- | --- | --- |
+| `css` | 2026-09-20 | Update KeyValues for most recent update |
+| `dods` | 2026-09-20 | Update KeyValues for most recent update |
+| `hl2dm` | 2026-09-20 | Tweak KeyValues |
+| `tf2` | 2026-09-20 | Tweak KeyValues |
+| `bms` | 2026-07-22 | BMS EmitGameSound crash fix (#404) |
+| `l4d2` | 2026-07-21 | More CI fixes |
+| `blade` | 2026-04-28 | Rebuild Windows tier1 |
+| `csgo` | 2026-04-28 | Rebuild Windows tier1 |
+
+仓库里还有 `cs2`(2026 新分支)等其它 HL2SDK 分支,但本仓库没有对应的 `src/makefiles/branch/.cmake`,因此不在本次八游戏范围内。
+
+不要在 CI 中直接执行 `git checkout ` 后再拉取最新提交;这会在 Valve 更新后悄悄改变 ABI。下载脚本会在解压后覆盖 `src/patches/`,并验证固定提交的顶层目录名与上述库文件,验证失败会继续尝试下一个下载来源。
+
+## 压缩包命名与构建日期
+
+打包脚本按当天日期给每个压缩包加日期后缀:
+
+```text
+dist/source-python-blade-2026-0925.zip
+dist/source-python-bms-2026-0925.zip
+dist/source-python-csgo-2026-0925.zip
+dist/source-python-css-2026-0925.zip
+dist/source-python-dods-2026-0925.zip
+dist/source-python-hl2dm-2026-0925.zip
+dist/source-python-l4d2-2026-0925.zip
+dist/source-python-tf2-2026-0925.zip
+dist/source-python-source-2026-0925.zip
+dist/SHA256SUMS.txt
+```
+
+日期格式为 `yyyy-MMdd`(年份 + 当天月日),默认自动检测构建当天;需要固定日期时设置环境变量 `SOURCEPYTHON_BUILD_DATE=2026-0925` 或传 `-BuildDate 2026-0925`。`BUILD-MANIFEST.json` 里也记录同一个 `build_date`,便于日后核对。含更新与修复的完整源码放在 `source-python-source-<日期>.zip` 中。
+
+打包后可用 `scripts\ci\verify-packages.ps1` 复核:逐个解压检查 `core.dll` / `core.so` / `source-python.dll` / `source-python.so`、PR #533 的 `plat-linux64` 运行时、`BUILD-MANIFEST.json` 是否齐全,并重新计算 SHA-256 与 `SHA256SUMS.txt` 比对。Actions 的 `package` job 也会自动执行这一步。
+
+两个脚本都接受逗号分隔的列表(`-Branches bms,css,blade`),并在生成 `SHA256SUMS.txt` 时写入该日期下**所有**已存在的归档,因此可以分次打包:
+
+```powershell
+# 只生成源码包(不需要 natives,适合其余游戏还在构建时)
+.\scripts\ci\package.ps1 -SourceOnly -BuildDate 2026-0926
+
+# 只复核已完成的游戏包,跳过源码包
+.\scripts\ci\verify-packages.ps1 -Branches bms,css,dods,hl2dm,l4d2,tf2 -SkipSourceArchive
+```
+
+包内布局与官方 release 一致:
+
+```text
+addons/source-python.dll # Windows loader
+addons/source-python.so # Linux loader
+addons/source-python/bin/core.dll # Windows core
+addons/source-python/bin/core.so # Linux core
+addons/source-python.vdf
+addons/source-python/
+cfg/ logs/ resource/ sound/
+BUILD-MANIFEST.json
+```
+
+## GitHub Actions
+
+工作流文件:`.github/workflows/build-packages.yml`。
+
+`build` job 的矩阵是 8 个游戏 × Windows/Linux(16 个 job),每个 job 拉取固定 HL2SDK、构建 x86 原生文件并上传 artifact;`package` job 下载全部原生文件后按上面的命名与布局生成 8 个游戏包、源码包和 `SHA256SUMS.txt`。
+
+使用方法:
+
+1. 将修复后的源码推送到 GitHub。
+2. 打开 **Actions → Build Source.Python packages → Run workflow**。
+3. 需要发布时填写 `release_tag` 并勾选 `create_release`;工作流会从该标签 checkout 并构建,发布名也使用该标签;也可以推送 `v*` 标签自动发布。
+4. 构建失败时先查看对应 matrix job;不要改用未固定的 `master` HL2SDK。
+
+`github_proxy` 输入是可选项。直连失败时可填 `https://ghfast.top`,脚本会先尝试代理再回退官方 `codeload.github.com`。代理只用于公开源码下载,发布仍使用 GitHub 官方 artifact/release API。
+
+## 本地一键构建
+
+### Windows(bat)
+
+```bat
+REM 全部八个游戏 + 打包
+scripts\ci\build-all.bat
+
+REM 只构建部分游戏
+scripts\ci\build-all.bat css dods
+
+REM 只编译,不打包
+scripts\ci\build-all.bat css --no-package
+```
+
+它对每个游戏依次执行 `fetch-hl2sdk.ps1` 与 `build-windows.ps1`(自动挑选 Visual Studio 自带的 CMake,校验产物是 x86 PE),最后调用 `package.ps1` 生成带日期的压缩包。结果位于 `artifacts\native\\windows` 和 `dist\`。
+
+### Linux(sh)
+
+```bash
+sudo apt-get install -y gcc-multilib g++-multilib cmake make binutils python3 libffi7:i386 zlib1g:i386
+bash ./scripts/ci/build-all.sh
+bash ./scripts/ci/build-all.sh css dods --no-package
+```
+
+脚本对每个游戏执行 `fetch-hl2sdk.sh` 与 `build-linux.sh`(校验产物是 ELF32 i386);如果系统里有 `pwsh`,会顺带调用 `package.ps1` 生成带日期的压缩包,否则只产出 `artifacts/native//linux`,把 Windows 原生文件放到同一目录后在 Windows 上执行 `package.ps1` 即可。
+
+### Windows 上用 WSL 出 Linux 包
+
+```powershell
+wsl --install -d Ubuntu
+wsl -d Ubuntu -u root -- bash ./scripts/ci/build-all.sh
+```
+
+WSL 内 `/mnt/c` 的 9p 较慢,SDK 解压和编译建议在发行版内完成;产物只有几十 MB,再复制回 `artifacts/native`。CI 使用的依赖列表见工作流中的 “Install Linux x86 build dependencies”。
+
+服务器/虚拟机上如果 WSL2 报 `HCS_E_HYPERV_NOT_INSTALLED`(嵌套虚拟化不可用),改用 WSL1:
+
+```powershell
+wsl --install -d Ubuntu --no-launch --version 1
+wsl --set-default-version 1
+wsl -d Ubuntu -u root -- bash ./scripts/ci/build-all.sh
+```
+
+WSL1 不依赖 Hyper-V,编译 32 位目标不受影响。
+
+## 单个游戏构建
+
+```powershell
+Set-Location .\Source.Python-master
+.\scripts\ci\fetch-hl2sdk.ps1 -Branch css -MirrorBase https://ghfast.top
+.\scripts\ci\build-windows.ps1 -Branch css
+.\scripts\ci\package.ps1 -Branches css -RequiredPlatforms windows -SourceArchive
+```
+
+```bash
+SP_GITHUB_PROXY=https://ghfast.top bash ./scripts/ci/fetch-hl2sdk.sh css
+bash ./scripts/ci/build-linux.sh css
+bash ./scripts/ci/build-linux.sh hl2dm '' '' x86_64 # PR #533 的 x86-64 目标
+```
+
+Actions 中源码包使用 `git archive` 保留提交内容和 Unix 模式;本地无 Git 时使用排除规则生成 tar/ZIP,排除 `.git`、`src/hl2sdk`、`src/Builds`、`artifacts`、`dist`。
+
+WSL1 里把源码镜像到 ext4 编译时,`src/makefiles/branch/blade.cmake` 与 `csgo.cmake` 引用 SDK 源码用的是硬编码相对路径(`hl2sdk//public/game/shared/.../*.pb.cc`),而镜像会排除 `src/hl2sdk`;此时需要在镜像工作区的 `src/hl2sdk/` 放一个指向实际 SDK 的符号链接,否则 CMake 报 `Cannot find source file`。`.wsl-tools/wsl-build.sh` 已处理该情况,或设 `SP_COPY_SDK=1` 改为整份复制。
+
+## 回归测试
+
+编译成功不等于已经完成游戏内验证。部署对应游戏的包后,在测试服务器使用:
+
+```python
+from events import Event
+
+@Event('player_death')
+def _player_death(event):
+ event_id = event.get_int('userid')
+ variable_id = event.variables.get_int('userid')
+ assert variable_id == event_id, (variable_id, event_id)
+```
+
+然后分别触发 `player_death` 和 `player_spawn`,确认 `event.variables.name`、`event.variables.get_int('userid')` 和 `event['userid']` 均不崩溃且值一致。Linux 包还要求目标系统提供 32 位 `libffi.so.7`(CPython `_ctypes`)和 32 位 zlib;这些要求会记录在 `BUILD-MANIFEST.json` 的 `runtime_requirements` 中。
diff --git a/docs/contributing/building.html b/docs/contributing/building.html
index 9108a1b460..06dc00f79a 100644
--- a/docs/contributing/building.html
+++ b/docs/contributing/building.html
@@ -74,7 +74,7 @@ Linux
resources.
You can shorten these steps by simply passing the game’s name to the script
-e.g. ./Build.sh css.
+e.g. bash ./Build.sh css.
If the build finished successfully, it will say something like this:
[100%] Built target core.
The compiled binaries are stored in src/Builds/Linux/<game>/Release.
diff --git a/docs/linux-x86_64.md b/docs/linux-x86_64.md
new file mode 100644
index 0000000000..26d01885e4
--- /dev/null
+++ b/docs/linux-x86_64.md
@@ -0,0 +1,52 @@
+# Linux x86-64 (HL2DM)
+
+Linux x86-64 support is currently limited to the `hl2dm` branch. The existing
+x86 build remains the default.
+
+Build with:
+
+```sh
+cd src
+bash ./Build.sh hl2dm x86_64
+```
+
+The preserved 32-bit build remains available as:
+
+```sh
+cd src
+bash ./Build.sh hl2dm x86
+```
+
+The build accepts `SOURCEPYTHON_SDK` as a CMake cache path when the HL2SDK is
+outside `src/hl2sdk/hl2dm`.
+
+Architecture-specific dependencies are kept beside, rather than in place of,
+the existing x86 dependencies:
+
+```text
+thirdparty/python_linux64/
+thirdparty/boost/lib/linux64/
+thirdparty/dyncall/lib/linux64/
+thirdparty/DynamicHooks/lib/linux64/
+thirdparty/AsmJit/lib/linux64/
+```
+
+The tested dependency baseline is CPython 3.13.2, Boost 1.87, the current
+HL2DM branch of AlliedModders HL2SDK, and a DynamicHooks build containing the
+Linux System V AMD64 backend.
+
+The packaged runtime uses `Python3/plat-linux64`. Native optional packages in
+the standard library are loaded from `Python3/lib-dynload-linux64`, leaving the
+existing x86 `Python3/lib-dynload` directory untouched. Native optional
+packages in the shared `site-packages` directory must be rebuilt for x86-64;
+32-bit extension modules cannot be imported by the x86-64 runtime.
+
+Post-hook callbacks that need function arguments must select the preserved
+entry-register snapshot through `CHook::SetUsePreRegisters`. The legacy
+`m_bUsePreRegisters` member remains available to the x86 backend, but it is not
+the invocation-local selector used by the reentrant x86-64 backend.
+
+The upstream x86 CPython `_ctypes` module still declares `libffi.so.7` as a
+runtime dependency. Distributions that no longer provide that SONAME must
+supply it separately for 32-bit deployments. This legacy dependency is not
+used by the x86-64 `_ctypes` module and was not introduced by this port.
diff --git a/logs/source-python/readme.md b/logs/source-python/readme.md
deleted file mode 100644
index ec54ee500f..0000000000
--- a/logs/source-python/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source-Python LOG ReadMe
-
-This directory should be used by Source-Python to store all log files.
diff --git a/resource/source-python/events/readme.md b/resource/source-python/events/readme.md
deleted file mode 100644
index 8e16cdd85a..0000000000
--- a/resource/source-python/events/readme.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Source-Python Custom Event ReadMe
-
-This directory should be used for all custom event .res files.
-
-If your addon contains multiple custom event .res files, it
- would be preferred if you placed them inside a folder,
- with your addon's name, inside this directory.
-
-Otherwise, you can simply use a .res file using your addon's name.
diff --git a/resource/source-python/translations/readme.md b/resource/source-python/translations/readme.md
deleted file mode 100644
index 393dcb214a..0000000000
--- a/resource/source-python/translations/readme.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Source-Python Translations ReadMe
-
-This directory should be used for all translation files.
-
-If your addon contains multiple translation files, it
- would be preferred if you placed them inside a folder,
- with your addon's name, inside this directory.
-
-Otherwise, you can simply use a translation file using your addon's name.
diff --git a/scripts/ci/build-all.bat b/scripts/ci/build-all.bat
new file mode 100644
index 0000000000..5cc736eded
--- /dev/null
+++ b/scripts/ci/build-all.bat
@@ -0,0 +1,95 @@
+@echo off
+REM ===========================================================================
+REM One-click Windows x86 build for the eight Source.Python games.
+REM
+REM Usage:
+REM scripts\ci\build-all.bat build all eight games
+REM scripts\ci\build-all.bat css dods build selected games only
+REM scripts\ci\build-all.bat css --no-package build without packaging
+REM scripts\ci\build-all.bat --dry-run print the planned commands
+REM
+REM Each game fetches the HL2SDK revision pinned in scripts\ci\sdk-pins.json,
+REM builds x86 core.dll/source-python.dll, and finally writes
+REM dist\source-python--.zip plus the source archive.
+REM ===========================================================================
+setlocal EnableExtensions EnableDelayedExpansion
+set "REPO_ROOT=%~dp0..\.."
+pushd "%REPO_ROOT%" || exit /b 1
+
+set "PACKAGE=1"
+set "DRYRUN=0"
+set "GAMES="
+:parse
+if "%~1"=="" goto parsed
+if /I "%~1"=="--no-package" (
+ set "PACKAGE=0"
+) else if /I "%~1"=="--dry-run" (
+ set "DRYRUN=1"
+) else (
+ set "GAMES=!GAMES! %1"
+)
+shift
+goto parse
+:parsed
+if "!GAMES!"=="" set "GAMES= blade bms csgo css dods hl2dm l4d2 tf2"
+
+set "PS=powershell"
+where pwsh >nul 2>&1 && set "PS=pwsh"
+"%PS%" -NoProfile -Command "$PSVersionTable.PSVersion.Major -ge 5" >nul 2>&1
+if errorlevel 1 (
+ echo [build-all] Windows PowerShell 5.1 or PowerShell 7+ is required.
+ popd
+ exit /b 1
+)
+
+set "PACKAGE_BRANCHES="
+for %%G in (!GAMES!) do (
+ if defined PACKAGE_BRANCHES (
+ set "PACKAGE_BRANCHES=!PACKAGE_BRANCHES!,%%G"
+ ) else (
+ set "PACKAGE_BRANCHES=%%G"
+ )
+)
+
+for %%G in (!GAMES!) do (
+ echo.
+ echo [build-all] === Fetching pinned HL2SDK for %%G ===
+ if "%DRYRUN%"=="1" (
+ echo "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\fetch-hl2sdk.ps1 -Branch %%G
+ ) else (
+ "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\fetch-hl2sdk.ps1 -Branch %%G
+ )
+ if errorlevel 1 goto :failed
+ echo [build-all] === Building Windows x86 for %%G ===
+ if "%DRYRUN%"=="1" (
+ echo "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\build-windows.ps1 -Branch %%G
+ ) else (
+ "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\build-windows.ps1 -Branch %%G
+ )
+ if errorlevel 1 goto :failed
+)
+
+if "%PACKAGE%"=="1" (
+ echo.
+ echo [build-all] === Packaging !PACKAGE_BRANCHES! ===
+ if "%DRYRUN%"=="1" (
+ echo "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\package.ps1 -Branches "!PACKAGE_BRANCHES!" -RequiredPlatforms windows -SourceArchive
+ ) else (
+ "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\ci\package.ps1 ^
+ -Branches "!PACKAGE_BRANCHES!" -RequiredPlatforms windows -SourceArchive
+ if errorlevel 1 goto :failed
+ )
+ echo [build-all] Archives are in "%REPO_ROOT%\dist"
+) else (
+ echo [build-all] Packaging skipped; run scripts\ci\package.ps1 manually.
+)
+
+popd
+endlocal
+exit /b 0
+
+:failed
+echo [build-all] FAILED
+popd
+endlocal
+exit /b 1
diff --git a/scripts/ci/build-all.sh b/scripts/ci/build-all.sh
new file mode 100644
index 0000000000..c4310c4f26
--- /dev/null
+++ b/scripts/ci/build-all.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# One-click Linux x86 build for the eight Source.Python games.
+#
+# Usage:
+# scripts/ci/build-all.sh build all eight games
+# scripts/ci/build-all.sh css dods build selected games only
+# scripts/ci/build-all.sh --dry-run print the planned commands
+# SOURCEPYTHON_BUILD_DATE=2026-0925 ... pin the archive date stamp
+#
+# Every game fetches the HL2SDK revision pinned in scripts/ci/sdk-pins.json and
+# builds core.so/source-python.so. If PowerShell is available the dated ZIPs are
+# assembled as well; otherwise copy artifacts/native//linux next to the
+# Windows artifacts and run scripts/ci/package.ps1 there.
+set -euo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="${SOURCEPYTHON_ROOT:-$(cd -- "$SCRIPT_DIR/../.." && pwd)}"
+PACKAGE=1
+DRYRUN=0
+GAMES=()
+
+for argument in "$@"; do
+ case "$argument" in
+ --no-package) PACKAGE=0 ;;
+ --dry-run) DRYRUN=1 ;;
+ -*) echo "Usage: $0 [blade|bms|csgo|css|dods|hl2dm|l4d2|tf2 ...] [--no-package] [--dry-run]" >&2; exit 2 ;;
+ *) GAMES+=("$argument") ;;
+ esac
+done
+if [[ ${#GAMES[@]} -eq 0 ]]; then
+ GAMES=(blade bms csgo css dods hl2dm l4d2 tf2)
+fi
+
+cd "$REPO_ROOT"
+
+run() {
+ if [[ "$DRYRUN" -eq 1 ]]; then
+ printf ' %s\n' "$*"
+ else
+ "$@"
+ fi
+}
+
+for game in "${GAMES[@]}"; do
+ echo
+ echo "== Fetching pinned HL2SDK for $game =="
+ if [[ -n "${SP_GITHUB_PROXY:-}" ]]; then
+ run env SP_GITHUB_PROXY="$SP_GITHUB_PROXY" bash ./scripts/ci/fetch-hl2sdk.sh "$game"
+ else
+ run bash ./scripts/ci/fetch-hl2sdk.sh "$game"
+ fi
+ echo "== Building Linux x86 for $game =="
+ run bash ./scripts/ci/build-linux.sh "$game"
+done
+
+if [[ "$PACKAGE" -eq 1 ]] && command -v pwsh >/dev/null 2>&1; then
+ echo
+ echo "== Packaging ${GAMES[*]} =="
+ joined="$(IFS=,; echo "${GAMES[*]}")"
+ run pwsh -NoLogo -NoProfile -File ./scripts/ci/package.ps1 \
+ -Branches "$joined" -RequiredPlatforms linux -SourceArchive
+ echo "Archives are in $REPO_ROOT/dist"
+elif [[ "$PACKAGE" -eq 1 ]]; then
+ echo
+ echo "pwsh was not found; skipping packaging."
+ echo "Copy artifacts/native//linux next to the Windows natives and run"
+ echo "scripts/ci/package.ps1 on Windows to assemble the dated ZIPs."
+fi
diff --git a/scripts/ci/build-linux.sh b/scripts/ci/build-linux.sh
new file mode 100644
index 0000000000..ad84e19d6b
--- /dev/null
+++ b/scripts/ci/build-linux.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -lt 1 || $# -gt 5 ]]; then
+ echo "Usage: $0 [repository-root] [build-directory] [output-directory] [x86|x86_64]" >&2
+ exit 2
+fi
+
+BRANCH="$1"
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="${2:-${SOURCEPYTHON_ROOT:-$(cd -- "$SCRIPT_DIR/../.." && pwd)}}"
+ARCH="${5:-x86}"
+BUILD_DIR="${3:-$REPO_ROOT/src/Builds/Linux/$BRANCH-$ARCH}"
+OUTPUT_ROOT="${4:-$REPO_ROOT/artifacts/native}"
+if [[ "$ARCH" == "x86" ]]; then
+ NATIVE_DIR="$OUTPUT_ROOT/$BRANCH/linux"
+else
+ NATIVE_DIR="$OUTPUT_ROOT/$BRANCH/linux-$ARCH"
+fi
+
+case "$BRANCH" in
+ blade|bms|csgo|css|dods|hl2dm|l4d2|tf2) ;;
+ *) echo "Unsupported branch: $BRANCH" >&2; exit 2 ;;
+esac
+case "$ARCH" in
+ x86|x86_64) ;;
+ *) echo "Unsupported architecture: $ARCH" >&2; exit 2 ;;
+esac
+if [[ "$ARCH" == "x86_64" && "$BRANCH" != "hl2dm" ]]; then
+ echo 'Linux x86-64 is currently limited to HL2DM.' >&2
+ exit 2
+fi
+
+PINS_FILE="$SCRIPT_DIR/sdk-pins.json"
+COMMIT="$(python3 - "$PINS_FILE" "$BRANCH" <<'PY'
+import json
+import sys
+with open(sys.argv[1], encoding="utf-8") as stream:
+ pins = json.load(stream)
+print(pins[sys.argv[2]]["commit"])
+PY
+)"
+
+SDK_DIR="${SOURCEPYTHON_SDK:-$REPO_ROOT/src/hl2sdk/$BRANCH}"
+[[ -f "$SDK_DIR/tier1/KeyValues.cpp" ]] || {
+ echo "HL2SDK is not ready at '$SDK_DIR'. Run scripts/ci/fetch-hl2sdk.sh first." >&2
+ exit 1
+}
+if [[ "$ARCH" == "x86_64" && ! -f "$SDK_DIR/lib/public/linux64/tier1.a" ]]; then
+ echo "The pinned HL2SDK checkout has no Linux x86-64 tier1 library." >&2
+ exit 1
+fi
+
+mkdir -p -- "$BUILD_DIR" "$NATIVE_DIR"
+cmake -S "$REPO_ROOT/src" -B "$BUILD_DIR" \
+ -DBRANCH="$BRANCH" -DSOURCEPYTHON_ARCH="$ARCH" \
+ -DSOURCEPYTHON_SDK="$SDK_DIR" -DCMAKE_BUILD_TYPE=Release
+cmake --build "$BUILD_DIR" --parallel "${BUILD_JOBS:-2}"
+
+CORE="$(find "$BUILD_DIR" -type f -name 'core.so' -print -quit)"
+LOADER="$(find "$BUILD_DIR" -type f -name 'source-python.so' -print -quit)"
+[[ -n "$CORE" && -n "$LOADER" ]] || {
+ echo "The build completed but core.so or source-python.so was not produced." >&2
+ exit 1
+}
+
+for binary in "$CORE" "$LOADER"; do
+ if [[ "$ARCH" == "x86_64" ]]; then
+ readelf -h "$binary" | grep -q 'Class:.*ELF64'
+ readelf -h "$binary" | grep -Eq 'Machine:.*(Advanced Micro Devices X86-64|X86-64)'
+ else
+ readelf -h "$binary" | grep -q 'Class:.*ELF32'
+ readelf -h "$binary" | grep -q 'Machine:.*Intel 80386'
+ fi
+done
+
+rm -rf -- "$NATIVE_DIR"
+mkdir -p -- "$NATIVE_DIR"
+cp -- "$CORE" "$NATIVE_DIR/core.so"
+cp -- "$LOADER" "$NATIVE_DIR/source-python.so"
+chmod 0755 "$NATIVE_DIR/core.so" "$NATIVE_DIR/source-python.so"
+
+SOURCE_REVISION="${SOURCEPYTHON_SOURCE_REVISION:-${GITHUB_SHA:-local-archive}}"
+cat > "$NATIVE_DIR/build-info.json" <&1 | Out-String)
+ foreach ($candidate in @('Visual Studio 18 2026', 'Visual Studio 17 2022', 'Visual Studio 16 2019')) {
+ if ($help.Contains($candidate)) { return $candidate }
+ }
+ return 'Visual Studio 17 2022'
+}
+
+function Assert-X86Pe {
+ param([string]$Path)
+ $bytes = [IO.File]::ReadAllBytes($Path)
+ if ($bytes.Length -lt 64 -or [BitConverter]::ToInt32($bytes, 0x3c) -lt 0) {
+ throw "'$Path' is not a valid PE file."
+ }
+ $peOffset = [BitConverter]::ToInt32($bytes, 0x3c)
+ $machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4)
+ if ($machine -ne 0x014c) {
+ throw "'$Path' is not an x86 PE (machine 0x$($machine.ToString('X4')))."
+ }
+}
+
+$cmake = Find-CMake
+$Generator = Select-Generator -CMake $cmake
+New-Item -ItemType Directory -Force -Path $buildDirectory | Out-Null
+New-Item -ItemType Directory -Force -Path $nativeDirectory | Out-Null
+
+Write-Host "Using CMake: $cmake"
+Write-Host "Using generator: $Generator"
+& $cmake -S (Join-Path $RepositoryRoot 'src') -B $buildDirectory -G $Generator -A Win32 `
+ "-DBRANCH=$Branch" "-DSOURCEPYTHON_ARCH=x86"
+if ($LASTEXITCODE -ne 0) { throw "CMake configure failed with exit code $LASTEXITCODE." }
+
+& $cmake --build $buildDirectory --config Release --parallel 2
+if ($LASTEXITCODE -ne 0) { throw "CMake build failed with exit code $LASTEXITCODE." }
+
+$core = Get-ChildItem -LiteralPath $buildDirectory -Recurse -Filter 'core.dll' -File | Select-Object -First 1
+$loader = Get-ChildItem -LiteralPath $buildDirectory -Recurse -Filter 'source-python.dll' -File | Select-Object -First 1
+if ($null -eq $core -or $null -eq $loader) {
+ throw 'The build completed but core.dll or source-python.dll was not produced.'
+}
+Assert-X86Pe $core.FullName
+Assert-X86Pe $loader.FullName
+
+Remove-Item -LiteralPath $nativeDirectory -Recurse -Force -ErrorAction SilentlyContinue
+New-Item -ItemType Directory -Force -Path $nativeDirectory | Out-Null
+Copy-Item -LiteralPath $core.FullName -Destination (Join-Path $nativeDirectory 'core.dll')
+Copy-Item -LiteralPath $loader.FullName -Destination (Join-Path $nativeDirectory 'source-python.dll')
+
+$sourceRevision = $env:SOURCEPYTHON_SOURCE_REVISION
+if ([string]::IsNullOrWhiteSpace($sourceRevision)) { $sourceRevision = $env:GITHUB_SHA }
+if ([string]::IsNullOrWhiteSpace($sourceRevision)) { $sourceRevision = 'local-archive' }
+$metadata = [ordered]@{
+ game = $Branch
+ platform = 'windows'
+ architecture = 'x86'
+ sdk_commit = $commit
+ source_revision = $sourceRevision
+ cmake_generator = $Generator
+ compiler = 'MSVC'
+ files = @('core.dll', 'source-python.dll')
+ built_at_utc = [DateTime]::UtcNow.ToString('o')
+}
+$metadata | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $nativeDirectory 'build-info.json') -Encoding UTF8
+Write-Host "Windows artifacts for $Branch are ready at $nativeDirectory"
diff --git a/scripts/ci/fetch-hl2sdk.ps1 b/scripts/ci/fetch-hl2sdk.ps1
new file mode 100644
index 0000000000..af9e75c331
--- /dev/null
+++ b/scripts/ci/fetch-hl2sdk.ps1
@@ -0,0 +1,188 @@
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true)]
+ [ValidateSet('blade', 'bms', 'csgo', 'css', 'dods', 'hl2dm', 'l4d2', 'tf2')]
+ [string]$Branch,
+
+ [string]$RepositoryRoot = '',
+ [string]$Destination = '',
+ [string]$MirrorBase = $env:SP_GITHUB_PROXY,
+ [switch]$Force
+)
+
+$ErrorActionPreference = 'Stop'
+
+if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
+ $RepositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
+}
+$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
+if ([string]::IsNullOrWhiteSpace($Destination)) {
+ $Destination = Join-Path $RepositoryRoot "src\hl2sdk\$Branch"
+}
+$Destination = [IO.Path]::GetFullPath($Destination)
+
+$pinsPath = Join-Path $PSScriptRoot 'sdk-pins.json'
+$pins = Get-Content -LiteralPath $pinsPath -Raw | ConvertFrom-Json
+$property = $pins.PSObject.Properties[$Branch]
+if ($null -eq $property) {
+ throw "No pinned HL2SDK revision exists for branch '$Branch'."
+}
+$commit = [string]$property.Value.commit
+if ($commit -notmatch '^[0-9a-f]{40}$') {
+ throw "Invalid pinned HL2SDK revision for '$Branch': $commit"
+}
+
+$marker = Join-Path $Destination '.source-python-sdk-commit'
+$required = @(
+ 'public\tier1\KeyValues.h',
+ 'tier1\KeyValues.cpp'
+)
+# The OrangeBox games keep platform libraries in per-architecture folders, while
+# the episodic-style branches use a flat lib/public (or lib/linux) layout. The pin
+# records the exact files so a wrong mirror response is rejected up front.
+foreach ($key in @('sdk_lib', 'sdk_lib_linux', 'sdk_lib_linux64')) {
+ $value = [string]$property.Value.PSObject.Properties[$key].Value
+ if (-not [string]::IsNullOrWhiteSpace($value)) {
+ $required += $value.Replace('/', [IO.Path]::DirectorySeparatorChar)
+ }
+}
+
+function Test-SdkCheckout {
+ param([string]$Path)
+ if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
+ return $false
+ }
+ foreach ($relative in $required) {
+ if (-not (Test-Path -LiteralPath (Join-Path $Path $relative) -PathType Leaf)) {
+ return $false
+ }
+ }
+ return $true
+}
+
+# Several SDK branches (for example TF2's hl2mp bot navigation sources) exceed
+# the legacy MAX_PATH limit, so deletion goes through the extended-length form.
+function Remove-Tree {
+ param([string]$Path)
+ if ([string]::IsNullOrWhiteSpace($Path)) { return }
+ if (-not (Test-Path -LiteralPath $Path)) { return }
+ $extended = if ($Path.StartsWith('\\')) { $Path } else { '\\?\' + $Path }
+ & cmd.exe /d /c "rmdir /s /q `"$extended`"" 2>$null | Out-Null
+ if (Test-Path -LiteralPath $Path) {
+ Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+if ((Test-Path -LiteralPath $marker) -and
+ ((Get-Content -LiteralPath $marker -Raw).Trim() -eq $commit) -and
+ (Test-SdkCheckout $Destination) -and
+ -not $Force) {
+ Write-Host "Using cached HL2SDK $Branch@$commit at $Destination"
+ exit 0
+}
+
+if (Test-Path -LiteralPath $Destination) {
+ if (-not $Force -and (Test-SdkCheckout $Destination)) {
+ throw "A different HL2SDK checkout already exists at '$Destination'. Use -Force to replace it."
+ }
+ Remove-Tree $Destination
+}
+
+$parent = Split-Path -Parent $Destination
+New-Item -ItemType Directory -Force -Path $parent | Out-Null
+$archive = Join-Path $parent ".hl2sdk-$Branch-$commit.zip"
+$extractRoot = Join-Path $parent ".hl2sdk-extract-$Branch-$commit"
+
+$urls = [Collections.Generic.List[string]]::new()
+if (-not [string]::IsNullOrWhiteSpace($MirrorBase)) {
+ $proxy = $MirrorBase.TrimEnd('/')
+ $urls.Add("$proxy/https://github.com/alliedmodders/hl2sdk/archive/$commit.zip")
+}
+$urls.Add("https://codeload.github.com/alliedmodders/hl2sdk/zip/$commit")
+
+$downloaded = $false
+# The HL2SDK archives are large and the GitHub link is often slow, so allow a
+# long transfer, resume partial downloads and only abort on a real stall.
+$maxTime = if ($env:SP_DOWNLOAD_MAX_TIME) { $env:SP_DOWNLOAD_MAX_TIME } else { '3600' }
+$curlCommon = @(
+ '-L', '--fail', '--retry', '3', '--retry-delay', '5', '--retry-all-errors',
+ '--connect-timeout', '30', '--max-time', $maxTime,
+ '--speed-limit', '2048', '--speed-time', '120', '-sS'
+)
+try {
+ foreach ($url in $urls) {
+ Write-Host "Downloading HL2SDK $Branch@$commit from $url (max-time ${maxTime}s)"
+ if ($env:SP_FRESH_DOWNLOAD -eq '1') {
+ Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
+ }
+ # -C - resumes a partial archive; it is a no-op when nothing was kept.
+ & curl.exe @curlCommon -C - -o $archive $url
+ if ($LASTEXITCODE -eq 33) {
+ # The mirror refused byte ranges; start over without resuming.
+ Write-Warning 'The source does not support resuming; restarting the download.'
+ Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
+ & curl.exe @curlCommon -o $archive $url
+ }
+ if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $archive -PathType Leaf)) {
+ Write-Warning "Download failed; trying the next source."
+ continue
+ }
+
+ Remove-Tree $extractRoot
+ New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null
+ & tar.exe -xf $archive -C $extractRoot
+ if ($LASTEXITCODE -ne 0) {
+ Write-Warning "The downloaded archive could not be extracted; trying the next source."
+ continue
+ }
+
+ try {
+ $top = Get-ChildItem -LiteralPath $extractRoot -Directory | Select-Object -First 1
+ if ($null -eq $top) {
+ throw 'The HL2SDK archive did not contain a top-level directory.'
+ }
+
+ $shortCommit = $commit.Substring(0, 7)
+ if ($top.Name -notmatch "^hl2sdk-$shortCommit") {
+ throw "The archive top-level directory '$($top.Name)' does not match pinned commit $commit."
+ }
+
+ # Keep the extracted tree in place: copying it would re-parse every
+ # path and break on the deep HL2DM/TF2 sources. Only the small
+ # Source.Python patch overlay is copied.
+ $candidate = $top.FullName
+ $patchDirectory = Join-Path $RepositoryRoot "src\patches\$Branch"
+ if (Test-Path -LiteralPath $patchDirectory -PathType Container) {
+ Write-Host "Applying Source.Python SDK patches from $patchDirectory"
+ Copy-Item -Path (Join-Path $patchDirectory '*') -Destination $candidate -Recurse -Force
+ }
+
+ if (-not (Test-SdkCheckout $candidate)) {
+ throw "The extracted HL2SDK checkout is missing required files for '$Branch'."
+ }
+
+ Remove-Tree $Destination
+ Move-Item -LiteralPath $candidate -Destination $Destination
+ [IO.File]::WriteAllText($marker, "$commit`n", [Text.UTF8Encoding]::new($false))
+ $downloaded = $true
+ Write-Host "HL2SDK $Branch@$commit is ready at $Destination"
+ break
+ }
+ catch {
+ Write-Warning "The downloaded HL2SDK candidate is invalid: $($_.Exception.Message) Trying the next source."
+ Remove-Tree $Destination
+ continue
+ }
+ }
+
+ if (-not $downloaded) {
+ throw "Unable to download, validate, or extract HL2SDK $Branch@$commit."
+ }
+}
+finally {
+ Remove-Tree $extractRoot
+ if ($downloaded) {
+ # Partial archives are only useful while they can still be resumed.
+ Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
+ }
+}
diff --git a/scripts/ci/fetch-hl2sdk.sh b/scripts/ci/fetch-hl2sdk.sh
new file mode 100644
index 0000000000..332f30b68d
--- /dev/null
+++ b/scripts/ci/fetch-hl2sdk.sh
@@ -0,0 +1,175 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -lt 1 || $# -gt 2 ]]; then
+ echo "Usage: $0 [destination]" >&2
+ exit 2
+fi
+
+BRANCH="$1"
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="${SOURCEPYTHON_ROOT:-$(cd -- "$SCRIPT_DIR/../.." && pwd)}"
+DESTINATION="${2:-${SOURCEPYTHON_SDK:-$REPO_ROOT/src/hl2sdk/$BRANCH}}"
+PINS_FILE="$SCRIPT_DIR/sdk-pins.json"
+
+case "$BRANCH" in
+ blade|bms|csgo|css|dods|hl2dm|l4d2|tf2) ;;
+ *) echo "Unsupported HL2SDK branch: $BRANCH" >&2; exit 2 ;;
+esac
+
+# The OrangeBox games keep platform libraries in per-architecture folders, while
+# the episodic-style branches use a flat lib/public (or lib/linux) layout. The pin
+# records the exact files so a wrong mirror response is rejected up front.
+mapfile -t PIN_FIELDS < <(python3 - "$PINS_FILE" "$BRANCH" <<'PY'
+import json
+import sys
+with open(sys.argv[1], encoding="utf-8") as stream:
+ pins = json.load(stream)
+entry = pins.get(sys.argv[2], {})
+value = entry.get("commit", "")
+if len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
+ raise SystemExit(f"Invalid pinned HL2SDK revision for {sys.argv[2]}")
+print(value)
+for key in ("sdk_lib", "sdk_lib_linux", "sdk_lib_linux64"):
+ if entry.get(key):
+ print(entry[key])
+PY
+)
+COMMIT="${PIN_FIELDS[0]}"
+SDK_LIBS=("${PIN_FIELDS[@]:1}")
+
+is_checkout() {
+ local path="${1:-$DESTINATION}"
+ [[ -f "$path/public/tier1/KeyValues.h" &&
+ -f "$path/tier1/KeyValues.cpp" ]] || return 1
+ local library
+ for library in ${SDK_LIBS[@]+"${SDK_LIBS[@]}"}; do
+ [[ -f "$path/$library" ]] || return 1
+ done
+ return 0
+}
+
+# codeload.github.com serves HL2SDK as a ZIP. Windows ships bsdtar as tar.exe,
+# which reads ZIP, but the GNU tar on the Linux runners cannot, so prefer an
+# extractor that understands the archive that was actually downloaded. unzip
+# also exits 1 on recoverable warnings, so the caller validates the tree.
+extract_archive() {
+ local archive="$1" target="$2"
+ if command -v unzip >/dev/null 2>&1; then
+ unzip -q -o "$archive" -d "$target" 2>/dev/null || true
+ return 0
+ fi
+ if command -v bsdtar >/dev/null 2>&1; then
+ bsdtar -xf "$archive" -C "$target"
+ else
+ tar -xf "$archive" -C "$target"
+ fi
+}
+
+MARKER="$DESTINATION/.source-python-sdk-commit"
+if [[ -f "$MARKER" && "$(tr -d '\r\n' < "$MARKER")" == "$COMMIT" ]] && is_checkout; then
+ echo "Using cached HL2SDK $BRANCH@$COMMIT at $DESTINATION"
+ exit 0
+fi
+
+if [[ -e "$DESTINATION" ]]; then
+ if is_checkout && [[ "${SP_FORCE_SDK:-0}" != 1 ]]; then
+ echo "A different HL2SDK checkout already exists at '$DESTINATION'." >&2
+ echo "Set SP_FORCE_SDK=1 to replace it." >&2
+ exit 1
+ fi
+ rm -rf -- "$DESTINATION"
+fi
+
+mkdir -p -- "$(dirname -- "$DESTINATION")"
+ARCHIVE="$(dirname -- "$DESTINATION")/.hl2sdk-$BRANCH-$COMMIT.zip"
+EXTRACT="$(dirname -- "$DESTINATION")/.hl2sdk-extract-$BRANCH-$COMMIT"
+SHORT_COMMIT="${COMMIT:0:7}"
+trap 'rm -rf -- "$EXTRACT"' EXIT
+
+URLS=()
+if [[ -n "${SP_GITHUB_PROXY:-}" ]]; then
+ URLS+=("${SP_GITHUB_PROXY%/}/https://github.com/alliedmodders/hl2sdk/archive/$COMMIT.zip")
+fi
+URLS+=("https://codeload.github.com/alliedmodders/hl2sdk/zip/$COMMIT")
+
+# The HL2SDK archives are large and the GitHub link is often slow, so allow a
+# long transfer, resume partial downloads and only abort on a real stall.
+MAX_TIME="${SP_DOWNLOAD_MAX_TIME:-3600}"
+CURL_ARGS=(-L --fail --retry 3 --retry-delay 5 --retry-all-errors
+ --connect-timeout 30 --max-time "$MAX_TIME"
+ --speed-limit 2048 --speed-time 120 -sS)
+
+for URL in "${URLS[@]}"; do
+ echo "Downloading HL2SDK $BRANCH@$COMMIT from $URL (max-time ${MAX_TIME}s)"
+ if [[ "${SP_FRESH_DOWNLOAD:-0}" == 1 ]]; then
+ rm -f -- "$ARCHIVE"
+ fi
+ # -C - resumes a partial archive; it is a no-op when nothing was kept.
+ status=0
+ curl "${CURL_ARGS[@]}" -C - -o "$ARCHIVE" "$URL" || status=$?
+ if [[ $status -eq 33 ]]; then
+ # The mirror refused byte ranges; start over without resuming.
+ echo "The source does not support resuming; restarting the download." >&2
+ rm -f -- "$ARCHIVE"
+ status=0
+ curl "${CURL_ARGS[@]}" -o "$ARCHIVE" "$URL" || status=$?
+ fi
+ if [[ $status -ne 0 || ! -s "$ARCHIVE" ]]; then
+ echo "Download failed; trying the next source." >&2
+ continue
+ fi
+ rm -rf -- "$EXTRACT"
+ mkdir -p -- "$EXTRACT"
+ if ! extract_archive "$ARCHIVE" "$EXTRACT"; then
+ echo "The archive could not be extracted; trying the next source." >&2
+ continue
+ fi
+
+ TOP="$(find "$EXTRACT" -mindepth 1 -maxdepth 1 -type d -print -quit)"
+ if [[ -z "$TOP" ]]; then
+ echo "The archive has no top-level directory; trying the next source." >&2
+ continue
+ fi
+ TOP_NAME="$(basename -- "$TOP")"
+ if [[ "$TOP_NAME" != hl2sdk-"$SHORT_COMMIT"* ]]; then
+ echo "Archive top-level directory '$TOP_NAME' does not match pinned commit; trying the next source." >&2
+ continue
+ fi
+
+ # Keep the extracted tree in place: copying it would double the disk usage
+ # for every SDK. Only the small Source.Python patch overlay is copied.
+ CANDIDATE="$TOP"
+
+ PATCH_DIR="$REPO_ROOT/src/patches/$BRANCH"
+ if [[ -d "$PATCH_DIR" ]]; then
+ echo "Applying Source.Python SDK patches from $PATCH_DIR"
+ if ! cp -a -- "$PATCH_DIR"/. "$CANDIDATE"/; then
+ echo "SDK patches could not be applied; trying the next source." >&2
+ continue
+ fi
+ fi
+
+ if ! is_checkout "$CANDIDATE"; then
+ echo "The extracted HL2SDK checkout is missing required files; trying the next source." >&2
+ continue
+ fi
+
+ rm -rf -- "$DESTINATION"
+ if ! mv -- "$CANDIDATE" "$DESTINATION"; then
+ echo "The validated SDK tree could not be installed; trying the next source." >&2
+ continue
+ fi
+ if ! printf '%s\n' "$COMMIT" > "$MARKER"; then
+ echo "The SDK commit marker could not be written; trying the next source." >&2
+ rm -rf -- "$DESTINATION"
+ continue
+ fi
+ echo "HL2SDK $BRANCH@$COMMIT is ready at $DESTINATION"
+ # Keep partial archives only while they are still useful for resuming.
+ rm -f -- "$ARCHIVE"
+ exit 0
+done
+
+echo "Unable to download, validate, or extract HL2SDK $BRANCH@$COMMIT." >&2
+exit 1
diff --git a/scripts/ci/package.ps1 b/scripts/ci/package.ps1
new file mode 100644
index 0000000000..25d3c5379f
--- /dev/null
+++ b/scripts/ci/package.ps1
@@ -0,0 +1,222 @@
+[CmdletBinding()]
+param(
+ [string[]]$Branches = @('blade', 'bms', 'csgo', 'css', 'dods', 'hl2dm', 'l4d2', 'tf2'),
+ [string[]]$RequiredPlatforms = @('windows', 'linux'),
+ [string]$RepositoryRoot = '',
+ [string]$NativeRoot = '',
+ [string]$OutputDirectory = '',
+ # Date stamped into every archive name, for example 2026-0925. Leave empty
+ # to detect it from the current date; SOURCEPYTHON_BUILD_DATE overrides it.
+ [string]$BuildDate = $env:SOURCEPYTHON_BUILD_DATE,
+ [switch]$SourceArchive,
+ # Assemble only the source archive. Game archives need both native
+ # platforms, so this is useful while the remaining builds still run.
+ [switch]$SourceOnly
+)
+
+$ErrorActionPreference = 'Stop'
+
+if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
+ $RepositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
+}
+$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
+if ([string]::IsNullOrWhiteSpace($NativeRoot)) {
+ $NativeRoot = Join-Path $RepositoryRoot 'artifacts\native'
+}
+if ([string]::IsNullOrWhiteSpace($OutputDirectory)) {
+ $OutputDirectory = Join-Path $RepositoryRoot 'dist'
+}
+$NativeRoot = [IO.Path]::GetFullPath($NativeRoot)
+$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
+New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null
+
+if ([string]::IsNullOrWhiteSpace($BuildDate)) {
+ $BuildDate = [DateTime]::Now.ToString('yyyy-MMdd')
+}
+if ($BuildDate -notmatch '^\d{4}-\d{4}$') {
+ throw "Build date '$BuildDate' must use the yyyy-MMdd format, for example 2026-0925."
+}
+$dateStamp = $BuildDate
+
+# Batch files and shells pass game/platform lists as one comma separated token.
+$Branches = @($Branches | ForEach-Object { $_ -split ',' } |
+ ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
+$RequiredPlatforms = @($RequiredPlatforms | ForEach-Object { $_ -split ',' } |
+ ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
+if ($Branches.Count -eq 0) { throw 'No games were requested.' }
+
+$pins = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'sdk-pins.json') -Raw | ConvertFrom-Json
+$sourceRevision = $env:SOURCEPYTHON_SOURCE_REVISION
+if ([string]::IsNullOrWhiteSpace($sourceRevision)) { $sourceRevision = $env:GITHUB_SHA }
+if ([string]::IsNullOrWhiteSpace($sourceRevision)) { $sourceRevision = 'local-archive' }
+
+$stagingRoot = Join-Path $OutputDirectory 'staging'
+$generatedArchives = [Collections.Generic.List[string]]::new()
+Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
+New-Item -ItemType Directory -Force -Path $stagingRoot | Out-Null
+
+# -SourceOnly assembles just the source archive; the game archives are skipped
+# because they need both native platforms.
+$assembleSourceArchive = $SourceArchive -or $SourceOnly
+$gameBranches = if ($SourceOnly) { @() } else { $Branches }
+
+# The game payload. .gitignore keeps cfg, logs and sound out of the repository
+# because the game writes into them at runtime, so a fresh CI checkout does not
+# have them at all. Recreate those folders in staging and hand tar exactly the
+# members that exist, instead of asking it for names that may be absent.
+$payloadDirectories = @('addons', 'cfg', 'logs', 'resource', 'sound')
+
+foreach ($branch in $gameBranches) {
+ $property = $pins.PSObject.Properties[$branch]
+ if ($null -eq $property) { throw "No SDK pin exists for '$branch'." }
+ $stage = Join-Path $stagingRoot $branch
+ New-Item -ItemType Directory -Force -Path $stage | Out-Null
+
+ foreach ($directory in $payloadDirectories) {
+ $source = Join-Path $RepositoryRoot $directory
+ if (Test-Path -LiteralPath $source) {
+ Copy-Item -LiteralPath $source -Destination $stage -Recurse -Force
+ }
+ else {
+ New-Item -ItemType Directory -Force -Path (Join-Path $stage $directory) | Out-Null
+ }
+ }
+
+ # The source tree may contain ignored/stale native files. Rebuild this
+ # directory from the native artifacts for this invocation only.
+ $binDirectory = Join-Path $stage 'addons\source-python\bin'
+ Remove-Item -LiteralPath $binDirectory -Recurse -Force -ErrorAction SilentlyContinue
+ New-Item -ItemType Directory -Force -Path $binDirectory | Out-Null
+ foreach ($loaderName in @('source-python.dll', 'source-python.so')) {
+ Remove-Item -LiteralPath (Join-Path $stage "addons\$loaderName") -Force -ErrorAction SilentlyContinue
+ }
+ $platforms = [ordered]@{}
+ foreach ($platform in $RequiredPlatforms) {
+ $nativeDirectory = Join-Path $NativeRoot "$branch\$platform"
+ if (-not (Test-Path -LiteralPath $nativeDirectory -PathType Container)) {
+ throw "Missing native artifacts for $branch/$platform at '$nativeDirectory'."
+ }
+
+ if ($platform -eq 'windows') {
+ $core = Join-Path $nativeDirectory 'core.dll'
+ $loader = Join-Path $nativeDirectory 'source-python.dll'
+ }
+ elseif ($platform -eq 'linux') {
+ $core = Join-Path $nativeDirectory 'core.so'
+ $loader = Join-Path $nativeDirectory 'source-python.so'
+ }
+ else {
+ throw "Unsupported package platform '$platform'."
+ }
+ if (-not (Test-Path -LiteralPath $core -PathType Leaf) -or
+ -not (Test-Path -LiteralPath $loader -PathType Leaf)) {
+ throw "Native artifact set is incomplete for $branch/$platform."
+ }
+
+ Copy-Item -LiteralPath $core -Destination $binDirectory -Force
+ Copy-Item -LiteralPath $loader -Destination (Join-Path $stage 'addons') -Force
+ $platforms[$platform] = [ordered]@{
+ sdk_commit = [string]$property.Value.commit
+ files = @([IO.Path]::GetFileName($core), [IO.Path]::GetFileName($loader))
+ }
+ }
+
+ $runtimeRequirements = [ordered]@{}
+ if ($RequiredPlatforms -contains 'linux') {
+ $runtimeRequirements['linux'] = @(
+ '32-bit libffi.so.7 (required by the bundled CPython _ctypes module)',
+ '32-bit zlib runtime (libz.so.1)'
+ )
+ }
+ $manifest = [ordered]@{
+ game = $branch
+ build_date = $dateStamp
+ source_revision = $sourceRevision
+ source_pull_requests = @(533, 535, 537)
+ issue_regression = 536
+ sdk_pins = $platforms
+ runtime_requirements = $runtimeRequirements
+ generated_at_utc = [DateTime]::UtcNow.ToString('o')
+ }
+ $manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $stage 'BUILD-MANIFEST.json') -Encoding UTF8
+
+ $archive = Join-Path $OutputDirectory "source-python-$branch-$dateStamp.zip"
+ Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
+ $members = @($payloadDirectories) + 'BUILD-MANIFEST.json'
+ & tar.exe -a -c -f $archive -C $stage @members
+ if ($LASTEXITCODE -ne 0) { throw "Failed to create '$archive'." }
+ $generatedArchives.Add($archive)
+ Write-Host "Created $archive"
+}
+
+Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
+
+if ($assembleSourceArchive) {
+ $sourceArchivePath = Join-Path $OutputDirectory "source-python-source-$dateStamp.zip"
+ Remove-Item -LiteralPath $sourceArchivePath -Force -ErrorAction SilentlyContinue
+ $parent = Split-Path -Parent $RepositoryRoot
+ $leaf = Split-Path -Leaf $RepositoryRoot
+ $sourceArchiveCreated = $false
+
+ # GitHub Actions checks out a clean commit. Use git archive there so
+ # ignored files, CRLF conversion, and Unix mode bits cannot leak into
+ # the source ZIP. Local archives fall back to tar because this workspace
+ # may intentionally contain uncommitted fixes.
+ $git = Get-Command git.exe -ErrorAction SilentlyContinue
+ if ($null -eq $git) { $git = Get-Command git -ErrorAction SilentlyContinue }
+ if ($env:GITHUB_ACTIONS -eq 'true' -and $null -ne $git -and
+ (Test-Path -LiteralPath (Join-Path $RepositoryRoot '.git'))) {
+ $gitArgs = @(
+ '-C', $RepositoryRoot, 'archive', '--format=zip',
+ "--prefix=$leaf/", "--output=$sourceArchivePath", 'HEAD'
+ )
+ & $git.Source @gitArgs
+ if ($LASTEXITCODE -eq 0) {
+ $sourceArchiveCreated = $true
+ }
+ else {
+ Write-Warning 'git archive failed; falling back to the tar archive.'
+ Remove-Item -LiteralPath $sourceArchivePath -Force -ErrorAction SilentlyContinue
+ }
+ }
+
+ if (-not $sourceArchiveCreated) {
+ $tarArgs = @(
+ '-a', '-c', '-f', $sourceArchivePath,
+ '--exclude=.git', '--exclude=.git/*',
+ '--exclude=src/hl2sdk', '--exclude=src/hl2sdk/*',
+ '--exclude=src/Builds', '--exclude=src/Builds/*',
+ '--exclude=artifacts', '--exclude=artifacts/*',
+ '--exclude=dist', '--exclude=dist/*'
+ )
+ $repositoryPrefix = $RepositoryRoot.TrimEnd([char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar
+ if ($OutputDirectory.StartsWith($repositoryPrefix, [StringComparison]::OrdinalIgnoreCase)) {
+ $relativeOutput = $OutputDirectory.Substring($repositoryPrefix.Length).Replace('\', '/')
+ if (-not [string]::IsNullOrWhiteSpace($relativeOutput)) {
+ $tarArgs += "--exclude=$relativeOutput"
+ $tarArgs += "--exclude=$relativeOutput/*"
+ }
+ }
+ $tarArgs += @('-C', $parent, $leaf)
+ & tar.exe @tarArgs
+ if ($LASTEXITCODE -ne 0) { throw "Failed to create '$sourceArchivePath'." }
+ }
+ $generatedArchives.Add($sourceArchivePath)
+ Write-Host "Created $sourceArchivePath"
+}
+
+# Every dated archive for this build date belongs in the sums file, not only the
+# ones produced by this invocation, so a -SourceOnly pass keeps the game entries
+# that an earlier invocation wrote.
+$archives = @(@($generatedArchives | ForEach-Object {
+ Get-Item -LiteralPath $_ -ErrorAction Stop
+ }) + @(Get-ChildItem -LiteralPath $OutputDirectory -File -Filter "*-$dateStamp.zip" -ErrorAction SilentlyContinue)) |
+ Sort-Object Name -Unique
+$sumLines = foreach ($archive in $archives) {
+ $hash = (Get-FileHash -LiteralPath $archive.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
+ "$hash $($archive.Name)"
+}
+$sumLines | Set-Content -LiteralPath (Join-Path $OutputDirectory 'SHA256SUMS.txt') -Encoding ASCII
+Write-Host "Wrote $OutputDirectory\SHA256SUMS.txt ($($archives.Count) archives)"
+
+
diff --git a/scripts/ci/sdk-pins.json b/scripts/ci/sdk-pins.json
new file mode 100644
index 0000000000..95417045be
--- /dev/null
+++ b/scripts/ci/sdk-pins.json
@@ -0,0 +1,59 @@
+{
+ "blade": {
+ "branch": "blade",
+ "commit": "a736c9a6c676136f575af5056384851a8e1e788a",
+ "sdk_lib": "lib/public/tier1.lib",
+ "sdk_lib_linux": "lib/linux/tier1_i486.a",
+ "reason": "Latest Blade HL2SDK head (Windows tier1 rebuild 2026-04-28); Blade has no 2026-09 KeyValues change"
+ },
+ "bms": {
+ "branch": "bms",
+ "commit": "69fe26fed5ad34cb6ef032c57a54f8efbcdd8849",
+ "sdk_lib": "lib/public/tier1.lib",
+ "sdk_lib_linux": "lib/public/linux32/tier1.a",
+ "reason": "Includes the BMS EmitGameSound crash fix (#404, 2026-07-22)"
+ },
+ "csgo": {
+ "branch": "csgo",
+ "commit": "9cf2f325ea273559c7cae27b4f98518c18b8e322",
+ "sdk_lib": "lib/public/tier1.lib",
+ "sdk_lib_linux": "lib/linux/tier1_i486.a",
+ "reason": "Latest CS:GO HL2SDK head (Windows tier1 rebuild 2026-04-28); CS:GO has no 2026-09 KeyValues change"
+ },
+ "css": {
+ "branch": "css",
+ "commit": "47f0ea659bc70718f9c41a7830b6ad943a68ba09",
+ "sdk_lib": "lib/public/x86/tier1.lib",
+ "sdk_lib_linux": "lib/public/linux/tier1_i486.a",
+ "reason": "KeyValues update for the 2026-09 game update; required by Source.Python issue #536"
+ },
+ "dods": {
+ "branch": "dods",
+ "commit": "cbc97391097ed5c2790ab3fac6f9d05661d3a10f",
+ "sdk_lib": "lib/public/x86/tier1.lib",
+ "sdk_lib_linux": "lib/public/linux/tier1_i486.a",
+ "reason": "KeyValues update for the 2026-09 game update"
+ },
+ "hl2dm": {
+ "branch": "hl2dm",
+ "commit": "11dca0f12e53089b9dee6511f6b8ff4d02c9d51d",
+ "sdk_lib": "lib/public/x86/tier1.lib",
+ "sdk_lib_linux": "lib/public/linux/tier1_i486.a",
+ "sdk_lib_linux64": "lib/public/linux64/tier1.a",
+ "reason": "KeyValues update for the 2026-09 game update; also carries the x86-64 tier1 library used by PR #533"
+ },
+ "l4d2": {
+ "branch": "l4d2",
+ "commit": "2a31cd007b2d7d2f964dc093eedcf7a812cf9dd6",
+ "sdk_lib": "lib/public/tier1.lib",
+ "sdk_lib_linux": "lib/linux/tier1_i486.a",
+ "reason": "Latest verified L4D2 SDK revision (CI fixes, 2026-07-21)"
+ },
+ "tf2": {
+ "branch": "tf2",
+ "commit": "0f358777e7a7f0ad4c6ed6595de1e4e81c15fa4e",
+ "sdk_lib": "lib/public/x86/tier1.lib",
+ "sdk_lib_linux": "lib/public/linux/tier1_i486.a",
+ "reason": "Tweak KeyValues (2026-09-20), the OrangeBox SDK refresh that also landed on CSS/DODS/HL2DM"
+ }
+}
diff --git a/scripts/ci/verify-fixes.ps1 b/scripts/ci/verify-fixes.ps1
new file mode 100644
index 0000000000..8eca116c23
--- /dev/null
+++ b/scripts/ci/verify-fixes.ps1
@@ -0,0 +1,128 @@
+[CmdletBinding()]
+param(
+ [string]$RepositoryRoot = ''
+)
+
+$ErrorActionPreference = 'Stop'
+if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
+ $RepositoryRoot = [IO.Path]::GetFullPath((Join-Path (Join-Path $PSScriptRoot '..') '..'))
+}
+$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
+$failures = [Collections.Generic.List[string]]::new()
+
+function Require-Path {
+ param([string]$RelativePath)
+ $path = Join-Path $RepositoryRoot $RelativePath
+ if (-not (Test-Path -LiteralPath $path)) {
+ $failures.Add("Missing $RelativePath")
+ }
+ return $path
+}
+
+function Require-File {
+ param([string]$RelativePath)
+ $path = Join-Path $RepositoryRoot $RelativePath
+ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
+ $failures.Add("Missing $RelativePath")
+ }
+ return $path
+}
+
+function Require-Text {
+ param([string]$RelativePath, [string]$Pattern, [string]$Description)
+ $path = Require-File $RelativePath
+ if ($path -and -not (Select-String -LiteralPath $path -Pattern $Pattern -Quiet)) {
+ $failures.Add("${RelativePath}: $Description")
+ }
+}
+
+# PR #537: OrangeBox must use the new CBaseHandle construction path.
+Require-Text 'src/core/modules/entities/entities.h' '#ifdef ENGINE_ORANGEBOX' 'missing ENGINE_ORANGEBOX guard'
+Require-Text 'src/core/modules/entities/entities.h' 'CBaseHandle::UnsafeFromIndex\(value\)' 'missing CBaseHandleExt constructor implementation'
+Require-Text 'src/core/modules/entities/entities_wrap.cpp' 'class_>' 'missing shared_ptr wrapper'
+Require-Text 'src/core/utilities/conversions/basehandle_from.cpp' 'CBaseHandle::UnsafeFromIndex\(iEntityHandle\)' 'missing OrangeBox base-handle conversion'
+Require-Text 'src/core/utilities/conversions/index_from.cpp' 'CBaseHandle::UnsafeFromIndex\(iEntityHandle\)' 'missing OrangeBox index conversion'
+
+# PR #535: final vtable/entity data must include the new cstrike and hl2mp files.
+foreach ($path in @(
+ 'addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseCombatCharacter.ini',
+ 'addons/source-python/data/source-python/entities/orangebox/cstrike/CBaseEntity.ini',
+ 'addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseCombatCharacter.ini',
+ 'addons/source-python/data/source-python/entities/orangebox/hl2mp/CBaseEntity.ini'
+)) { Require-File $path | Out-Null }
+Require-Text 'addons/source-python/data/source-python/entities/orangebox/cstrike/CBasePlayer.ini' 'offset_linux = 406' 'missing updated cstrike Linux vtable offset'
+Require-Text 'addons/source-python/data/source-python/entities/orangebox/hl2mp/CBasePlayer.ini' 'offset_linux = 405' 'missing updated hl2mp Linux vtable offset'
+Require-Text 'addons/source-python/data/source-python/entities/orangebox/CBasePlayer.ini' 'offset_linux = 270' 'common CBasePlayer must not leak CSS/HL2DM offsets into DODS'
+Require-Text 'addons/source-python/data/source-python/entities/orangebox/dod/CBasePlayer.ini' 'offset_linux = 270' 'missing explicit DODS drop_weapon override'
+
+# PR #533: the source-level x86-64 port and its vendored runtime must be present.
+foreach ($path in @(
+ 'docs/linux-x86_64.md',
+ 'src/thirdparty/DynamicHooks/include/conventions/x64GccSystemV.h',
+ 'addons/source-python/Python3/plat-linux64/libpython3.13.so.1.0',
+ 'src/thirdparty/DynamicHooks/lib/linux64/libDynamicHooks.a'
+)) { Require-File $path | Out-Null }
+foreach ($path in @('addons/source-python/Python3/lib-dynload-linux64', 'src/thirdparty/python_linux64')) { Require-Path $path | Out-Null }
+
+# CI robustness: keep shell helpers executable on Linux and validate SDK
+# candidates before accepting a mirror response.
+Require-Text '.gitattributes' '\*\.sh text eol=lf' 'missing LF checkout rule for shell scripts'
+Require-Text '.gitattributes' '\*\.bat text eol=crlf' 'missing CRLF checkout rule for Windows batch helpers'
+Require-Text 'scripts/ci/fetch-hl2sdk.ps1' 'does not match pinned commit' 'missing PowerShell SDK candidate validation'
+Require-Text 'scripts/ci/fetch-hl2sdk.sh' 'does not match pinned commit' 'missing Bash SDK candidate validation'
+Require-Text 'scripts/ci/package.ps1' 'git archive' 'missing clean source archive path'
+Require-Text '.github/workflows/build-packages.yml' 'inputs.release_tag' 'release tag is not used for checkout'
+
+# Eight-game matrix: every published game needs a pinned HL2SDK revision, the
+# platform libraries that the build system links, and dated archive names.
+$requiredGames = @('blade', 'bms', 'csgo', 'css', 'dods', 'hl2dm', 'l4d2', 'tf2')
+$pinsPath = Require-File 'scripts/ci/sdk-pins.json'
+if ($pinsPath) {
+ $pins = Get-Content -LiteralPath $pinsPath -Raw | ConvertFrom-Json
+ foreach ($game in $requiredGames) {
+ $entry = $pins.PSObject.Properties[$game]
+ if ($null -eq $entry) {
+ $failures.Add("sdk-pins.json: missing pin for $game")
+ continue
+ }
+ if ([string]$entry.Value.commit -notmatch '^[0-9a-f]{40}$') {
+ $failures.Add("sdk-pins.json: invalid commit for $game")
+ }
+ foreach ($key in @('sdk_lib', 'sdk_lib_linux')) {
+ if ([string]::IsNullOrWhiteSpace([string]$entry.Value.PSObject.Properties[$key].Value)) {
+ $failures.Add("sdk-pins.json: $game is missing $key")
+ }
+ }
+ }
+}
+$workflowPath = Require-File '.github/workflows/build-packages.yml'
+if ($workflowPath) {
+ $workflow = Get-Content -LiteralPath $workflowPath -Raw
+ foreach ($game in $requiredGames) {
+ if ($workflow -notmatch "branch:\s*\[[^\]]*\b$game\b") {
+ $failures.Add("build-packages.yml: $game is missing from the build matrix")
+ }
+ }
+}
+Require-Text 'scripts/ci/package.ps1' "yyyy-MMdd" 'archives are not date stamped'
+$batchPath = Require-File 'scripts/ci/build-all.bat'
+if ($batchPath) {
+ $bytes = [IO.File]::ReadAllBytes($batchPath)
+ $crlf = 0
+ for ($i = 1; $i -lt $bytes.Length; $i++) {
+ if ($bytes[$i] -eq 10 -and $bytes[$i - 1] -eq 13) { $crlf++ }
+ }
+ if ($crlf -eq 0) {
+ $failures.Add('scripts/ci/build-all.bat: cmd.exe helpers must keep CRLF line endings')
+ }
+ if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
+ $failures.Add('scripts/ci/build-all.bat: a UTF-8 BOM breaks the first @echo off line')
+ }
+}
+Require-File 'scripts/ci/build-all.sh' | Out-Null
+
+if ($failures.Count -gt 0) {
+ $failures | ForEach-Object { Write-Error $_ }
+ throw 'Source.Python fix verification failed.'
+}
+Write-Host 'Source.Python PR #533/#535/#537 source verification passed for all eight games.'
diff --git a/scripts/ci/verify-packages.ps1 b/scripts/ci/verify-packages.ps1
new file mode 100644
index 0000000000..922a6288c4
--- /dev/null
+++ b/scripts/ci/verify-packages.ps1
@@ -0,0 +1,126 @@
+[CmdletBinding()]
+param(
+ [string]$RepositoryRoot = '',
+ [string]$OutputDirectory = '',
+ [string[]]$Branches = @('blade', 'bms', 'csgo', 'css', 'dods', 'hl2dm', 'l4d2', 'tf2'),
+ [string[]]$RequiredPlatforms = @('windows', 'linux'),
+ [string]$BuildDate = $env:SOURCEPYTHON_BUILD_DATE,
+ # Partial runs verify only the game archives.
+ [switch]$SkipSourceArchive
+)
+
+$ErrorActionPreference = 'Stop'
+
+if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) {
+ $RepositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
+}
+$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
+if ([string]::IsNullOrWhiteSpace($OutputDirectory)) {
+ $OutputDirectory = Join-Path $RepositoryRoot 'dist'
+}
+$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
+if ([string]::IsNullOrWhiteSpace($BuildDate)) {
+ $BuildDate = [DateTime]::Now.ToString('yyyy-MMdd')
+}
+if ($BuildDate -notmatch '^\d{4}-\d{4}$') {
+ throw "Build date '$BuildDate' must use the yyyy-MMdd format, for example 2026-0925."
+}
+
+# Batch files and shells pass game/platform lists as one comma separated token.
+$Branches = @($Branches | ForEach-Object { $_ -split ',' } |
+ ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
+$RequiredPlatforms = @($RequiredPlatforms | ForEach-Object { $_ -split ',' } |
+ ForEach-Object { $_.Trim() } | Where-Object { $_ } | Select-Object -Unique)
+if ($Branches.Count -eq 0) { throw 'No games were requested.' }
+
+$failures = [Collections.Generic.List[string]]::new()
+
+function Get-ArchiveEntries {
+ param([string]$Path)
+ $entries = & tar.exe -tf $Path
+ if ($LASTEXITCODE -ne 0) { throw "Unable to list '$Path'." }
+ return $entries
+}
+
+$expected = foreach ($branch in $Branches) {
+ $name = "source-python-$branch-$BuildDate.zip"
+ $path = Join-Path $OutputDirectory $name
+ if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
+ $failures.Add("Missing archive $name")
+ continue
+ }
+ $path
+}
+$sourceArchive = Join-Path $OutputDirectory "source-python-source-$BuildDate.zip"
+if ($SkipSourceArchive) {
+ $sourceArchive = $null
+}
+elseif (-not (Test-Path -LiteralPath $sourceArchive -PathType Leaf)) {
+ $failures.Add("Missing archive source-python-source-$BuildDate.zip")
+}
+
+foreach ($archive in $expected) {
+ $entries = Get-ArchiveEntries $archive
+ $entrySet = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
+ foreach ($entry in $entries) { [void]$entrySet.Add($entry) }
+
+ $required = [Collections.Generic.List[string]]::new()
+ $required.Add('BUILD-MANIFEST.json')
+ $required.Add('addons/source-python.vdf')
+ $required.Add('addons/source-python/Python3/plat-linux/libpython3.13.so.1.0')
+ $required.Add('addons/source-python/Python3/lib-dynload/_ctypes.cpython-313-x86_64-linux-gnu.so')
+ if ($RequiredPlatforms -contains 'windows') {
+ $required.Add('addons/source-python.dll')
+ $required.Add('addons/source-python/bin/core.dll')
+ }
+ if ($RequiredPlatforms -contains 'linux') {
+ $required.Add('addons/source-python.so')
+ $required.Add('addons/source-python/bin/core.so')
+ }
+ foreach ($relative in $required) {
+ $normalised = $relative.Replace('\', '/')
+ $found = $false
+ foreach ($entry in $entrySet) {
+ if ($entry.TrimEnd('/') -eq $normalised) { $found = $true; break }
+ }
+ if (-not $found) {
+ $failures.Add("$(Split-Path -Leaf $archive): missing $normalised")
+ }
+ }
+
+ if ($RequiredPlatforms -contains 'linux' -and
+ -not ($entrySet | Where-Object { $_ -like 'addons/source-python/Python3/plat-linux64/*' })) {
+ $failures.Add("$(Split-Path -Leaf $archive): missing the Linux x86-64 runtime from PR #533")
+ }
+}
+
+$sumFile = Join-Path $OutputDirectory 'SHA256SUMS.txt'
+if (-not (Test-Path -LiteralPath $sumFile -PathType Leaf)) {
+ $failures.Add('Missing SHA256SUMS.txt')
+}
+else {
+ $recorded = @{}
+ foreach ($line in Get-Content -LiteralPath $sumFile) {
+ if ($line -match '^([0-9a-f]{64})\s+(.+)$') { $recorded[$Matches[2].Trim()] = $Matches[1] }
+ }
+ foreach ($archive in @($expected) + @($sourceArchive | Where-Object { $_ })) {
+ if (-not $archive) { continue }
+ $name = Split-Path -Leaf $archive
+ if (-not $recorded.ContainsKey($name)) {
+ $failures.Add("SHA256SUMS.txt: no entry for $name")
+ continue
+ }
+ $actual = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant()
+ if ($actual -ne $recorded[$name]) {
+ $failures.Add("${name}: SHA-256 does not match SHA256SUMS.txt")
+ }
+ }
+}
+
+if ($failures.Count -gt 0) {
+ $failures | ForEach-Object { Write-Error $_ }
+ throw 'Package verification failed.'
+}
+
+$sourceCount = if ($SkipSourceArchive) { 0 } else { 1 }
+Write-Host "Verified $($expected.Count + $sourceCount) dated archives in $OutputDirectory"
diff --git a/sound/source-python/readme.md b/sound/source-python/readme.md
deleted file mode 100644
index 85834ea34d..0000000000
--- a/sound/source-python/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Source-Python Custom Sound ReadMe
-
-This directory should be used by Source-Python to store all custom sound files.
diff --git a/src/Build.bat b/src/Build.bat
index 4147396d1a..4d53506f44 100644
--- a/src/Build.bat
+++ b/src/Build.bat
@@ -1,156 +1,34 @@
@echo off
-
-:: The branch name
-set branch=%1
-
-:: 0 = use interactive method, 1 = use msbuild
-set use_msbuild=0
-
-:: Set the start directory for later reference
-set STARTDIR="%CD%"
-
-:: Allow the use of delayed expansion
-setlocal EnableDelayedExpansion
-
-:: If branch wasn't set, use the interactive method
-if [%branch%] == [] goto :ChooseBranch
-
-set use_msbuild=1
-goto CloneRepo
-
-:: A function to choose the branch to build against
-:ChooseBranch
-
- :: Clear the console
- cls
-
- echo Choose the branch you wish to build against:
- echo.
-
- :: Store a base counting variable
- set /a num=0
-
- :: Loop through all branches supported by the plugin
- for %%f in (%STARTDIR%\makefiles\branch\*.*) do (
-
- :: Increment the counter
- set /a num+=1
-
- :: Set the current option to the current branch
- set option_!num!=%%~nf
- )
-
- :: Loop through the options
- for /l %%a in (1, 1, %num%) do (
-
- :: Print the option to the console
- echo (%%a^^^) !option_%%a!
- )
-
- echo.
-
- :: Request a choice of branch
- set /p choice=
-
- :: Was the choice invalid?
- if %choice% leq 0 goto :ChooseBranch
- if %choice% gtr %num% goto :ChooseBranch
-
- :: Set the branch value to the choice
- set branch=!option_%choice%!
-
- :: If the choice is valid, create the clone, if it doesn't exist
- goto :CloneRepo
-
-
-:: A place to create the clone
-:CloneRepo
-
- :: Set the branch's repository directory
- set BRANCHDIR=%STARTDIR%\hl2sdk\%branch%
-
- :: If the branch's repo doesn't exist, create the clone
- if not exist %BRANCHDIR% (
- mkdir %BRANCHDIR%
- cd %BRANCHDIR%
- git clone https://github.com/alliedmodders/hl2sdk.git .
- ) else (
- cd %BRANCHDIR%
- )
-
- :: Checkout the selected branch and revert all changes
- git checkout -f %branch%
-
- :: Was an error encountered?
- if %ERRORLEVEL% gtr 0 goto :FailDueToError
-
- :: Was no error encountered?
- if %ERRORLEVEL% == 0 goto :PullChanges
-
-
-:: A place to pull repo changes
-:PullChanges
-
- :: Pull the newest changeset from github/alliedmods/hl2sdk
- git pull
-
- :: Was an error encountered?
- if %ERRORLEVEL% gtr 0 goto :FailDueToError
-
- :: Was no error encountered?
- if %ERRORLEVEL% == 0 goto :MovePatches
-
-
-:: A place to move patched files
-:MovePatches
-
- :: Set the branch's patch directory
- set PATCHDIR=%STARTDIR%\patches\%branch%
-
- :: Copy any patched files over if any exist for the specific branch
- if exist %PATCHDIR% xcopy %PATCHDIR% %BRANCHDIR% /y/s
-
- :: Create the build files for the branch
- :CreateBuild
-
-
-:: A place to create build files
-:CreateBuild
-
- :: Navigate back to the starting directory
- cd %STARTDIR%
-
- :: Set the branch's build directory
- set BUILDDIR=%STARTDIR%\Builds\Windows\%branch%
-
- :: Does the build directory exist (make it if not)?
- if not exist %BUILDDIR% mkdir %BUILDDIR%
-
- :: Create the build files
- cmake . -B%BUILDDIR% -G"Visual Studio 17" -A Win32 -DBRANCH=%branch%
-
- if %use_msbuild% == 1 (
- cd %BUILDDIR%
-
- :: devenv is located in your Visual Studio installaton directory.
- :: If it's missing, add it to your PATH environment variable.
- devenv source-python.sln /build "Release"
- ) else (
- :: Pause to show the process is completed
- pause
- )
-
- :: Exit the program
- exit
-
-
-:: A place to print error messages
-:FailDueToError
-
- :: Send an error message
- echo Encountered Error # %ERRORLEVEL%
-
- if %use_msbuild% == 0 (
- :: Pause to show the error
- pause
- )
+setlocal EnableExtensions
+
+:: Source.Python Windows build entry point.
+:: Usage: Build.bat blade|bms|csgo|css|dods|hl2dm|l4d2|tf2
+:: The SDK revision is pinned by scripts/ci/sdk-pins.json and is not taken
+:: from an unpinned branch pull.
+
+set "BRANCH=%~1"
+if not defined BRANCH (
+ echo Choose a branch: blade, bms, csgo, css, dods, hl2dm, l4d2, or tf2
+ set /p "BRANCH=Branch: "
+)
+
+if /I "%BRANCH%"=="blade" goto valid
+if /I "%BRANCH%"=="bms" goto valid
+if /I "%BRANCH%"=="csgo" goto valid
+if /I "%BRANCH%"=="css" goto valid
+if /I "%BRANCH%"=="dods" goto valid
+if /I "%BRANCH%"=="hl2dm" goto valid
+if /I "%BRANCH%"=="l4d2" goto valid
+if /I "%BRANCH%"=="tf2" goto valid
+
+echo Invalid branch: %BRANCH%
+exit /b 2
+
+:valid
+set "REPO_ROOT=%~dp0.."
+powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%REPO_ROOT%\scripts\ci\verify-fixes.ps1" -RepositoryRoot "%REPO_ROOT%"
+if errorlevel 1 exit /b %errorlevel%
+powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%REPO_ROOT%\scripts\ci\fetch-hl2sdk.ps1" -Branch "%BRANCH%" -RepositoryRoot "%REPO_ROOT%"
+if errorlevel 1 exit /b %errorlevel%
+powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%REPO_ROOT%\scripts\ci\build-windows.ps1" -Branch "%BRANCH%" -RepositoryRoot "%REPO_ROOT%"
+exit /b %errorlevel%
diff --git a/src/Build.sh b/src/Build.sh
index 0ec9142864..f7fe48d337 100644
--- a/src/Build.sh
+++ b/src/Build.sh
@@ -1,160 +1,31 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
+set -euo pipefail
-# Store the start directory for later reference
-STARTDIR="$PWD"
-
-# Set this to the number of processor cores you would like to use
-NUM_CORES=${NUM_CORES:=1}
-
-
-# A function to choose the branch to build against
-ChooseBranch () {
-
- echo "Choose the branch you wish to build against:"
- echo ""
-
- # Store a base counting variable
- num=0
-
- # Navigate to the makefiles/branch directory
- cd makefiles/branch
-
- # Loop through all branches supported by the plugin
- for filepath in $(find . -maxdepth 1 -type f -print0 | xargs -0)
- do
-
- # Increment the counter
- num=$(( $num + 1))
-
- # Get the name of the file
- filename=${filepath##*/}
- file=${filename%.*}
-
- # Store the option by its number
- eval option_${num}=${file}
-
- # Print the current option
- echo -e "\t($num) $file"
- done
-
- echo ""
-
- # Request a choice of branch
- read choice
-
- # Was the choice invalid?
- if ! echo $choice | egrep -q '^[0-9]+$'; then
- ChooseBranch
- elif [ $choice -le 0 ] ; then
- ChooseBranch
- elif [ $choice -gt $num ] ; then
- ChooseBranch
-
- # The choice was valid
- else
-
- # Get the branch chosen
- branch=option_$choice
- BRANCH=${!branch}
-
- # Show the build-type options
- CloneRepo
-
- fi
-
-}
-
-
-# A function to create the clone and checkout the selected branch
-CloneRepo () {
-
- # Set the branch's repository directory
- BRANCHDIR="$STARTDIR/hl2sdk/$BRANCH"
-
- # If the hl2sdk directory does not exist, create the clone
- if [ ! -d "$BRANCHDIR" ]; then
- mkdir -p "$BRANCHDIR"
- cd "$BRANCHDIR"
- git clone https://github.com/alliedmodders/hl2sdk.git .
- else
- cd "$BRANCHDIR"
- fi
-
- # Try to checkout the selected branch and revert all changes
- if git checkout -f $BRANCH; then
- PullChanges
- else
- echo "Unknown branch to checkout"
- fi
-}
-
-
-# A function to pull repo changes
-PullChanges () {
-
- # Pull the newest changeset from github/alliedmods/hl2sdk
- if git pull; then
-
- # Move the patched files
- MovePatches
-
- else
-
- # Print a message that an error was encountered
- echo "git pull encountered an error."
-
- fi
-}
-
-
-# A function to move patched files
-MovePatches () {
-
- # Set the branch's patch directory
- PATCHDIR="$STARTDIR/patches/$BRANCH"
-
- # Copy any patched files over if any exist for the specific branch
- if [ -d "$PATCHDIR" ]; then
- cd "$PATCHDIR"
- cp -r * "$BRANCHDIR"
- fi
-
- # Create the build files for the branch
- CreateBuild
-}
-
-
-# A function to create build files
-CreateBuild () {
-
- # Navigate back to the starting directory
- cd "$STARTDIR"
-
- # Set the branch's build directory
- BUILDDIR="$STARTDIR/Builds/Linux/$BRANCH"
-
- # Does the build directory exist (make it if not)?
- if [ ! -d "$BUILDDIR" ]; then
- mkdir -p "$BUILDDIR"
- fi
-
- cmake . -B"$BUILDDIR" -DBRANCH=$BRANCH
-
- # Navigate to the ../Builds/ directory
- cd "$BUILDDIR"
-
- # Build the binaries
- make clean
- make -j$NUM_CORES 2>&1 | tee build.log
-
- # Set the exit code to the exit code of the make command. Otherwise it
- # would be the exit code of the pipe command
- test ${PIPESTATUS[0]} -eq 0
-
-}
-if [ "$#" -eq "1" ]; then
- BRANCH=$1
- CloneRepo
-else
- ChooseBranch
+# Usage: ./Build.sh [x86|x86_64]
+if [[ $# -lt 1 || $# -gt 2 ]]; then
+ echo "Usage: $0 [x86|x86_64]" >&2
+ exit 2
fi
+
+BRANCH="$1"
+ARCH="${2:-x86}"
+STARTDIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd -- "$STARTDIR/.." && pwd)"
+
+case "$BRANCH" in
+ blade|bms|csgo|css|dods|hl2dm|l4d2|tf2) ;;
+ *) echo "Unsupported branch: $BRANCH" >&2; exit 2 ;;
+esac
+case "$ARCH" in
+ x86) ;;
+ x86_64)
+ [[ "$BRANCH" == "hl2dm" ]] || {
+ echo 'Linux x86-64 is currently limited to HL2DM.' >&2
+ exit 2
+ }
+ ;;
+ *) echo "Unsupported architecture: $ARCH" >&2; exit 2 ;;
+esac
+
+bash "$REPO_ROOT/scripts/ci/fetch-hl2sdk.sh" "$BRANCH"
+bash "$REPO_ROOT/scripts/ci/build-linux.sh" "$BRANCH" "$REPO_ROOT" "" "$REPO_ROOT/artifacts/native" "$ARCH"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 88e767b4d2..6e251f1367 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,6 +4,18 @@
CMake_Minimum_Required(VERSION 3.15)
project(source-python)
+Set(SOURCEPYTHON_ARCH "x86" CACHE STRING "Target architecture (x86 or x86_64)")
+Set_Property(CACHE SOURCEPYTHON_ARCH PROPERTY STRINGS x86 x86_64)
+If(NOT SOURCEPYTHON_ARCH MATCHES "^(x86|x86_64)$")
+ Message(FATAL_ERROR "SOURCEPYTHON_ARCH must be x86 or x86_64")
+EndIf()
+If(SOURCEPYTHON_ARCH STREQUAL "x86_64")
+ If(NOT UNIX OR NOT BRANCH STREQUAL "hl2dm")
+ Message(FATAL_ERROR "x86_64 is currently supported only for Linux HL2DM")
+ EndIf()
+ Add_Definitions(-DSOURCEPYTHON_X86_64)
+EndIf()
+
# ------------------------------------------------------------------
# Makefile includes.
# ------------------------------------------------------------------
diff --git a/src/core/modules/entities/entities.h b/src/core/modules/entities/entities.h
old mode 100755
new mode 100644
index 4aa1f09ece..4b1cb08b79
--- a/src/core/modules/entities/entities.h
+++ b/src/core/modules/entities/entities.h
@@ -89,6 +89,22 @@ class _BaseEdictExt
};
+#ifdef ENGINE_ORANGEBOX
+//-----------------------------------------------------------------------------
+// CBaseHandle extension class.
+//-----------------------------------------------------------------------------
+class CBaseHandleExt
+{
+public:
+ static boost::shared_ptr __init__(unsigned long value)
+ {
+ CBaseHandle hBaseHandle = CBaseHandle::UnsafeFromIndex(value);
+ return boost::shared_ptr(new CBaseHandle(hBaseHandle));
+ }
+};
+#endif
+
+
//-----------------------------------------------------------------------------
// CTakeDamageInfo wrapper class.
//-----------------------------------------------------------------------------
diff --git a/src/core/modules/entities/entities_entity.cpp b/src/core/modules/entities/entities_entity.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/entities/entities_wrap.cpp b/src/core/modules/entities/entities_wrap.cpp
index c6bc9dbb03..6d53a80c8f 100644
--- a/src/core/modules/entities/entities_wrap.cpp
+++ b/src/core/modules/entities/entities_wrap.cpp
@@ -94,10 +94,18 @@ DECLARE_SP_MODULE(_entities)
//-----------------------------------------------------------------------------
void export_base_entity_handle(scope _entities)
{
+#ifndef ENGINE_ORANGEBOX
class_("BaseEntityHandle")
.def(init())
.def(init())
.def(init())
+#else
+ class_>("BaseEntityHandle")
+ .def(init())
+ .def(init())
+
+ .def("__init__", make_constructor(&CBaseHandleExt::__init__))
+#endif
.def("init",
&CBaseHandle::Init,
diff --git a/src/core/modules/listeners/listeners_manager.cpp b/src/core/modules/listeners/listeners_manager.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/listeners/listeners_manager.h b/src/core/modules/listeners/listeners_manager.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/listeners/listeners_wrap.cpp b/src/core/modules/listeners/listeners_wrap.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/mathlib/mathlib.h b/src/core/modules/mathlib/mathlib.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/mathlib/mathlib_wrap.cpp b/src/core/modules/mathlib/mathlib_wrap.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_function.cpp b/src/core/modules/memory/memory_function.cpp
old mode 100755
new mode 100644
index 58b8281527..d289061c26
--- a/src/core/modules/memory/memory_function.cpp
+++ b/src/core/modules/memory/memory_function.cpp
@@ -38,12 +38,16 @@
#include "memory_wrap.h"
// DynamicHooks
+#ifdef SOURCEPYTHON_X86_64
+#include "conventions/x64GccSystemV.h"
+#else
#include "conventions/x86MsCdecl.h"
#include "conventions/x86MsThiscall.h"
#include "conventions/x86MsStdcall.h"
#include "conventions/x86MsFastcall.h"
#include "conventions/x86GccCdecl.h"
#include "conventions/x86GccThiscall.h"
+#endif
// Source.Python
#include "utilities/call_python.h"
@@ -71,11 +75,15 @@ int GetDynCallConvention(Convention_t eConv)
case CONV_CUSTOM: return -1;
case CONV_CDECL: return DC_CALL_C_DEFAULT;
case CONV_THISCALL:
+ #ifdef SOURCEPYTHON_X86_64
+ return DC_CALL_C_DEFAULT;
+ #else
#ifdef _WIN32
return DC_CALL_C_X86_WIN32_THIS_MS;
#else
return DC_CALL_C_X86_WIN32_THIS_GNU;
#endif
+ #endif
#ifdef _WIN32
case CONV_STDCALL: return DC_CALL_C_X86_WIN32_STD;
case CONV_FASTCALL: return DC_CALL_C_X86_WIN32_FAST_MS;
@@ -92,6 +100,14 @@ int GetDynCallConvention(Convention_t eConv)
// ============================================================================
ICallingConvention* MakeDynamicHooksConvention(Convention_t eConv, std::vector vecArgTypes, DataType_t returnType, int iAlignment)
{
+#ifdef SOURCEPYTHON_X86_64
+ switch (eConv)
+ {
+ case CONV_CDECL:
+ case CONV_THISCALL:
+ return new x64GccSystemV(vecArgTypes, returnType, iAlignment);
+ }
+#else
#ifdef _WIN32
switch (eConv)
{
@@ -106,6 +122,7 @@ ICallingConvention* MakeDynamicHooksConvention(Convention_t eConv, std::vectorm_pCallingConvention = NULL;
GetHookManager()->UnhookFunction((void *) m_ulAddr);
-}
\ No newline at end of file
+}
diff --git a/src/core/modules/memory/memory_function.h b/src/core/modules/memory/memory_function.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_hooks.cpp b/src/core/modules/memory/memory_hooks.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_hooks.h b/src/core/modules/memory/memory_hooks.h
index 304157d5f3..8256e1ee83 100644
--- a/src/core/modules/memory/memory_hooks.h
+++ b/src/core/modules/memory/memory_hooks.h
@@ -58,21 +58,33 @@ class CStackData
void* GetReturnAddress()
{
+#ifdef SOURCEPYTHON_X86_64
+ return m_pHook->GetCurrentReturnAddress();
+#else
void* pESP = m_pHook->GetRegisters()->m_esp->GetValue();
if (m_pHook->m_RetAddr.count(pESP) == 0) {
return NULL;
}
return m_pHook->m_RetAddr[pESP].back();
+#endif
}
bool GetUsePreRegister()
{
+#ifdef SOURCEPYTHON_X86_64
+ return m_pHook->GetUsePreRegisters();
+#else
return m_pHook->m_bUsePreRegisters;
+#endif
}
void SetUsePreRegisters(bool value)
{
+#ifdef SOURCEPYTHON_X86_64
+ m_pHook->SetUsePreRegisters(value);
+#else
m_pHook->m_bUsePreRegisters = value;
+#endif
}
protected:
diff --git a/src/core/modules/memory/memory_pointer.cpp b/src/core/modules/memory/memory_pointer.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_scanner.cpp b/src/core/modules/memory/memory_scanner.cpp
index 140edd0eef..7e4d5f3896 100644
--- a/src/core/modules/memory/memory_scanner.cpp
+++ b/src/core/modules/memory/memory_scanner.cpp
@@ -527,7 +527,7 @@ CBinaryFile* CBinaryManager::FindBinary(char* szPath, bool bSrvCheck /* = true *
}
}
- unsigned long ulSize;
+ unsigned long ulSize = 0;
#ifdef _WIN32
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER *) ulModule;
@@ -539,13 +539,24 @@ CBinaryFile* CBinaryManager::FindBinary(char* szPath, bool bSrvCheck /* = true *
// Copied from here. Thanks!
// https://github.com/alliedmodders/sourcemod/blob/237db0504c7a59e394828446af3e8ca3d53ef647/core/logic/MemoryUtils.cpp#L486
- Elf32_Ehdr *file;
- Elf32_Phdr *phdr;
+#ifdef SOURCEPYTHON_X86_64
+ typedef Elf64_Ehdr ElfHeader;
+ typedef Elf64_Phdr ElfProgramHeader;
+ const unsigned char expectedClass = ELFCLASS64;
+ const Elf64_Half expectedMachine = EM_X86_64;
+#else
+ typedef Elf32_Ehdr ElfHeader;
+ typedef Elf32_Phdr ElfProgramHeader;
+ const unsigned char expectedClass = ELFCLASS32;
+ const Elf32_Half expectedMachine = EM_386;
+#endif
+ ElfHeader *file;
+ ElfProgramHeader *phdr;
uint16_t phdrCount;
struct link_map *lm = (struct link_map*) ulModule;
ulBase = reinterpret_cast(lm->l_addr);
- file = reinterpret_cast(ulBase);
+ file = reinterpret_cast(ulBase);
/* Check ELF magic */
if (memcmp(ELFMAG, file->e_ident, SELFMAG) != 0)
@@ -559,10 +570,10 @@ CBinaryFile* CBinaryManager::FindBinary(char* szPath, bool bSrvCheck /* = true *
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "ELF version check failed.");
}
- /* Check ELF architecture, which is 32-bit/x86 right now
- * Should change this for 64-bit if Valve gets their act together
- */
- if (file->e_ident[EI_CLASS] != ELFCLASS32 || file->e_machine != EM_386 || file->e_ident[EI_DATA] != ELFDATA2LSB)
+ /* Reject binaries for the other architecture before interpreting headers. */
+ if (file->e_ident[EI_CLASS] != expectedClass ||
+ file->e_machine != expectedMachine ||
+ file->e_ident[EI_DATA] != ELFDATA2LSB)
{
BOOST_RAISE_EXCEPTION(PyExc_ValueError, "ELF architecture check failed.");
}
@@ -574,14 +585,15 @@ CBinaryFile* CBinaryManager::FindBinary(char* szPath, bool bSrvCheck /* = true *
}
phdrCount = file->e_phnum;
- phdr = reinterpret_cast(ulBase + file->e_phoff);
+ phdr = reinterpret_cast(ulBase + file->e_phoff);
for (uint16_t i = 0; i < phdrCount; i++)
{
- Elf32_Phdr &hdr = phdr[i];
+ ElfProgramHeader &hdr = phdr[i];
/* We only really care about the segment with executable code */
- if (hdr.p_type == PT_LOAD && hdr.p_flags == (PF_X|PF_R))
+ if (hdr.p_type == PT_LOAD &&
+ (hdr.p_flags & (PF_X | PF_R)) == (PF_X | PF_R))
{
/* From glibc, elf/dl-load.c:
* c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
@@ -594,6 +606,12 @@ CBinaryFile* CBinaryManager::FindBinary(char* szPath, bool bSrvCheck /* = true *
break;
}
}
+
+ if (!ulSize)
+ {
+ BOOST_RAISE_EXCEPTION(
+ PyExc_ValueError, "No executable ELF load segment was found.");
+ }
#else
#error "BinaryManager::FindBinary() is not implemented on this OS"
#endif
diff --git a/src/core/modules/memory/memory_tools.h b/src/core/modules/memory/memory_tools.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_utilities.h b/src/core/modules/memory/memory_utilities.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/memory/memory_wrap.cpp b/src/core/modules/memory/memory_wrap.cpp
old mode 100755
new mode 100644
index e8efc287d5..bf72e037d7
--- a/src/core/modules/memory/memory_wrap.cpp
+++ b/src/core/modules/memory/memory_wrap.cpp
@@ -735,6 +735,34 @@ void export_register_t(scope _memory)
.value("ST6", ST6)
.value("ST7", ST7)
*/
+#ifdef SOURCEPYTHON_X86_64
+ .value("SPL", SPL).value("BPL", BPL)
+ .value("SIL", SIL).value("DIL", DIL)
+ .value("R8B", R8B).value("R9B", R9B)
+ .value("R10B", R10B).value("R11B", R11B)
+ .value("R12B", R12B).value("R13B", R13B)
+ .value("R14B", R14B).value("R15B", R15B)
+ .value("R8W", R8W).value("R9W", R9W)
+ .value("R10W", R10W).value("R11W", R11W)
+ .value("R12W", R12W).value("R13W", R13W)
+ .value("R14W", R14W).value("R15W", R15W)
+ .value("R8D", R8D).value("R9D", R9D)
+ .value("R10D", R10D).value("R11D", R11D)
+ .value("R12D", R12D).value("R13D", R13D)
+ .value("R14D", R14D).value("R15D", R15D)
+ .value("RAX", RAX).value("RCX", RCX)
+ .value("RDX", RDX).value("RBX", RBX)
+ .value("RSP", RSP).value("RBP", RBP)
+ .value("RSI", RSI).value("RDI", RDI)
+ .value("R8", R8).value("R9", R9)
+ .value("R10", R10).value("R11", R11)
+ .value("R12", R12).value("R13", R13)
+ .value("R14", R14).value("R15", R15)
+ .value("XMM8", XMM8).value("XMM9", XMM9)
+ .value("XMM10", XMM10).value("XMM11", XMM11)
+ .value("XMM12", XMM12).value("XMM13", XMM13)
+ .value("XMM14", XMM14).value("XMM15", XMM15)
+#endif
;
}
@@ -827,6 +855,36 @@ void export_registers(scope _memory)
.def_readonly("st6", &CRegisters::m_st6)
.def_readonly("st7", &CRegisters::m_st7)
*/
+#ifdef SOURCEPYTHON_X86_64
+ .def_readonly("spl", &CRegisters::m_spl)
+ .def_readonly("bpl", &CRegisters::m_bpl)
+ .def_readonly("sil", &CRegisters::m_sil)
+ .def_readonly("dil", &CRegisters::m_dil)
+ .def_readonly("r8", &CRegisters::m_r8)
+ .def_readonly("r9", &CRegisters::m_r9)
+ .def_readonly("r10", &CRegisters::m_r10)
+ .def_readonly("r11", &CRegisters::m_r11)
+ .def_readonly("r12", &CRegisters::m_r12)
+ .def_readonly("r13", &CRegisters::m_r13)
+ .def_readonly("r14", &CRegisters::m_r14)
+ .def_readonly("r15", &CRegisters::m_r15)
+ .def_readonly("rax", &CRegisters::m_rax)
+ .def_readonly("rcx", &CRegisters::m_rcx)
+ .def_readonly("rdx", &CRegisters::m_rdx)
+ .def_readonly("rbx", &CRegisters::m_rbx)
+ .def_readonly("rsp", &CRegisters::m_rsp)
+ .def_readonly("rbp", &CRegisters::m_rbp)
+ .def_readonly("rsi", &CRegisters::m_rsi)
+ .def_readonly("rdi", &CRegisters::m_rdi)
+ .def_readonly("xmm8", &CRegisters::m_xmm8)
+ .def_readonly("xmm9", &CRegisters::m_xmm9)
+ .def_readonly("xmm10", &CRegisters::m_xmm10)
+ .def_readonly("xmm11", &CRegisters::m_xmm11)
+ .def_readonly("xmm12", &CRegisters::m_xmm12)
+ .def_readonly("xmm13", &CRegisters::m_xmm13)
+ .def_readonly("xmm14", &CRegisters::m_xmm14)
+ .def_readonly("xmm15", &CRegisters::m_xmm15)
+#endif
;
}
diff --git a/src/core/modules/memory/memory_wrap.h b/src/core/modules/memory/memory_wrap.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/net_channel/net_channel_wrap.cpp b/src/core/modules/net_channel/net_channel_wrap.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/players/players_entity.cpp b/src/core/modules/players/players_entity.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/players/players_entity.h b/src/core/modules/players/players_entity.h
old mode 100755
new mode 100644
diff --git a/src/core/modules/players/players_wrap.cpp b/src/core/modules/players/players_wrap.cpp
old mode 100755
new mode 100644
diff --git a/src/core/modules/players/players_wrap.h b/src/core/modules/players/players_wrap.h
old mode 100755
new mode 100644
diff --git a/src/core/patches/blade/undefined_symbols.h b/src/core/patches/blade/undefined_symbols.h
old mode 100755
new mode 100644
diff --git a/src/core/patches/csgo/undefined_symbols.h b/src/core/patches/csgo/undefined_symbols.h
old mode 100755
new mode 100644
diff --git a/src/core/sp_hooks.cpp b/src/core/sp_hooks.cpp
old mode 100755
new mode 100644
index 630d2f197b..81c7c8d85d
--- a/src/core/sp_hooks.cpp
+++ b/src/core/sp_hooks.cpp
@@ -161,7 +161,8 @@ void InitHooks(CBaseEntity* pEntity)
//---------------------------------------------------------------------------------
bool PrePlayerRunCommand(HookType_t hook_type, CHook* pHook)
{
- bool bUsePreRegister;
+ bool bUsePreRegister = false;
+ bool bRestoreRegisterSelection = false;
if (hook_type == HOOKTYPE_PRE) {
GET_LISTENER_MANAGER(OnPlayerRunCommand, run_command_manager);
@@ -171,21 +172,38 @@ bool PrePlayerRunCommand(HookType_t hook_type, CHook* pHook)
return false;
}
else {
- bUsePreRegister = pHook->m_bUsePreRegisters;
- pHook->m_bUsePreRegisters = true;
-
GET_LISTENER_MANAGER(OnPlayerPostRunCommand, post_run_command_manager);
if (!post_run_command_manager->GetCount())
return false;
+
+ // Arguments belong to the function-entry snapshot. On x86-64 the
+ // active register set is invocation-local, so changing the legacy
+ // CHook member does not select the pre-hook registers.
+#ifdef SOURCEPYTHON_X86_64
+ bUsePreRegister = pHook->GetUsePreRegisters();
+ pHook->SetUsePreRegisters(true);
+#else
+ bUsePreRegister = pHook->m_bUsePreRegisters;
+ pHook->m_bUsePreRegisters = true;
+#endif
+ bRestoreRegisterSelection = true;
}
static object Player = import("players.entity").attr("Player");
CBaseEntity* pEntity = pHook->GetArgument(0);
unsigned int index;
- if (!IndexFromBaseEntity(pEntity, index))
+ if (!IndexFromBaseEntity(pEntity, index)) {
+ if (bRestoreRegisterSelection) {
+#ifdef SOURCEPYTHON_X86_64
+ pHook->SetUsePreRegisters(bUsePreRegister);
+#else
+ pHook->m_bUsePreRegisters = bUsePreRegister;
+#endif
+ }
return false;
+ }
// https://github.com/Source-Python-Dev-Team/Source.Python/issues/149
#if defined(ENGINE_BRANCH_TF2)
@@ -222,8 +240,12 @@ bool PrePlayerRunCommand(HookType_t hook_type, CHook* pHook)
memcpy(pRealCmd, pCmd, sizeof(CUserCmd));
#endif
- if (hook_type == HOOKTYPE_POST) {
+ if (bRestoreRegisterSelection) {
+#ifdef SOURCEPYTHON_X86_64
+ pHook->SetUsePreRegisters(bUsePreRegister);
+#else
pHook->m_bUsePreRegisters = bUsePreRegister;
+#endif
}
return false;
diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp
old mode 100755
new mode 100644
index c434beff2e..222937c88f
--- a/src/core/sp_main.cpp
+++ b/src/core/sp_main.cpp
@@ -27,6 +27,8 @@
// --------------------------------------------------------
// Includes
// --------------------------------------------------------
+#include
+
#include "sp_python.h"
#include "sp_main.h"
#include "interface.h"
@@ -255,6 +257,30 @@ bool CSourcePython::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn
return false;
}
+#ifdef SOURCEPYTHON_X86_64
+ // SetCacheNotify does not expose the previous notifier. Discover its slot
+ // from the single pointer changed in a bounded snapshot of the interface.
+ const size_t cacheSnapshotSize = 256;
+ unsigned char cacheSnapshot[cacheSnapshotSize];
+ memcpy(cacheSnapshot, modelcache, cacheSnapshotSize);
+
+ DevMsg(1, MSG_PREFIX "Setting the new cache notifier...\n");
+ modelcache->SetCacheNotify(this);
+
+ for (size_t offset = 0; offset + sizeof(void*) <= cacheSnapshotSize;
+ offset += sizeof(void*)) {
+ IMDLCacheNotify* currentNotifier = NULL;
+ memcpy(¤tNotifier,
+ reinterpret_cast(modelcache) + offset,
+ sizeof(currentNotifier));
+ if (currentNotifier != this)
+ continue;
+
+ memcpy(&m_pOldMDLCacheNotifier, cacheSnapshot + offset,
+ sizeof(m_pOldMDLCacheNotifier));
+ break;
+ }
+#else
// TODO: Don't hardcode the 64 bytes offset
#ifdef ENGINE_LEFT4DEAD2
#define CACHE_NOTIFY_OFFSET 68
@@ -267,6 +293,7 @@ bool CSourcePython::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn
DevMsg(1, MSG_PREFIX "Setting the new cache notifier...\n");
modelcache->SetCacheNotify(this);
+#endif
g_EntityHooks.push_back(new PlayerHook(
"run_command",
diff --git a/src/core/sp_python.cpp b/src/core/sp_python.cpp
index 1298e261c7..0e69e7e271 100644
--- a/src/core/sp_python.cpp
+++ b/src/core/sp_python.cpp
@@ -148,12 +148,22 @@ bool CPythonManager::Initialize( void )
#if defined(WIN32)
if (!AddToSysPath(config, "/Python3/plat-win"))
return false;
+#else
+#if defined(SOURCEPYTHON_X86_64)
+ if (!AddToSysPath(config, "/Python3/plat-linux64"))
+ return false;
#else
if (!AddToSysPath(config, "/Python3/plat-linux"))
return false;
+#endif
+#if defined(SOURCEPYTHON_X86_64)
+ if (!AddToSysPath(config, "/Python3/lib-dynload-linux64"))
+ return false;
+#else
if (!AddToSysPath(config, "/Python3/lib-dynload"))
return false;
+#endif
#endif
if (!AddToSysPath(config, "/packages/site-packages"))
diff --git a/src/core/utilities/conversions/basehandle_from.cpp b/src/core/utilities/conversions/basehandle_from.cpp
index f101ba626b..0e3091d458 100644
--- a/src/core/utilities/conversions/basehandle_from.cpp
+++ b/src/core/utilities/conversions/basehandle_from.cpp
@@ -73,7 +73,11 @@ bool BaseHandleFromIndex( unsigned int iEntityIndex, CBaseHandle& output )
//-----------------------------------------------------------------------------
bool BaseHandleFromIntHandle( unsigned int iEntityHandle, CBaseHandle& output )
{
+#ifndef ENGINE_ORANGEBOX
CBaseHandle hBaseHandle = CBaseHandle(iEntityHandle);
+#else
+ CBaseHandle hBaseHandle = CBaseHandle::UnsafeFromIndex(iEntityHandle);
+#endif
unsigned int iIndex;
if (!IndexFromBaseHandle(hBaseHandle, iIndex))
return false;
diff --git a/src/core/utilities/conversions/index_from.cpp b/src/core/utilities/conversions/index_from.cpp
index d13972b971..d06a256430 100644
--- a/src/core/utilities/conversions/index_from.cpp
+++ b/src/core/utilities/conversions/index_from.cpp
@@ -137,7 +137,11 @@ bool IndexFromIntHandle( unsigned int iEntityHandle, unsigned int& output )
if (iEntityHandle == (int) INVALID_EHANDLE_INDEX)
return false;
- CBaseHandle hBaseHandle(iEntityHandle);
+#ifndef ENGINE_ORANGEBOX
+ CBaseHandle hBaseHandle = CBaseHandle(iEntityHandle);
+#else
+ CBaseHandle hBaseHandle = CBaseHandle::UnsafeFromIndex(iEntityHandle);
+#endif
unsigned int iEntityIndex;
if (!IndexFromBaseHandle(hBaseHandle, iEntityIndex))
return false;
diff --git a/src/core/utilities/wrap_macros.h b/src/core/utilities/wrap_macros.h
old mode 100755
new mode 100644
diff --git a/src/loader/definitions.h b/src/loader/definitions.h
index 527fa4a24a..fc54a07525 100644
--- a/src/loader/definitions.h
+++ b/src/loader/definitions.h
@@ -31,6 +31,7 @@
//---------------------------------------------------------------------------------
#define PYLIB_NAME_WIN32 "Python3/plat-win/python313.dll"
#define PYLIB_NAME_LINUX "Python3/plat-linux/libpython3.13.so.1.0"
+#define PYLIB_NAME_LINUX64 "Python3/plat-linux64/libpython3.13.so.1.0"
#define CORE_NAME_WIN32 "bin/core.dll"
#define CORE_NAME_LINUX "bin/core.so"
@@ -45,8 +46,13 @@
# define PYLIB_NAME PYLIB_NAME_WIN32
# define VCRUNTIME_LIB "Python3/plat-win/vcruntime140.dll"
#elif defined(LINUX)
-# define PYLIB_NAME PYLIB_NAME_LINUX
-# define ZLIB_LIB "Python3/plat-linux/libz.so.1.2.11"
+# if defined(SOURCEPYTHON_X86_64)
+# define PYLIB_NAME PYLIB_NAME_LINUX64
+# define ZLIB_LIB "Python3/plat-linux64/libz.so.1.2.11"
+# else
+# define PYLIB_NAME PYLIB_NAME_LINUX
+# define ZLIB_LIB "Python3/plat-linux/libz.so.1.2.11"
+# endif
#endif
#define MAX_PATH_LENGTH 1024
diff --git a/src/makefiles/linux/linux.base.cmake b/src/makefiles/linux/linux.base.cmake
index 0e585322d5..384b776ed5 100644
--- a/src/makefiles/linux/linux.base.cmake
+++ b/src/makefiles/linux/linux.base.cmake
@@ -20,7 +20,15 @@ Endif(NOT CMAKE_BUILD_TYPE)
# ------------------------------------------------------------------
# Python directories
# ------------------------------------------------------------------
-Set(PYTHONSDK ${THIRDPARTY_DIR}/python_linux)
+If(SOURCEPYTHON_ARCH STREQUAL "x86_64")
+ Set(PYTHONSDK ${THIRDPARTY_DIR}/python_linux64)
+ Set(BOOSTSDK_LIB ${BOOSTSDK_LIB}/linux64)
+ Set(DYNCALLSDK_LIB ${DYNCALLSDK_LIB}/linux64)
+ Set(ASMJITSDK_LIB ${ASMJITSDK_LIB}/linux64)
+ Set(DYNAMICHOOKSSDK_LIB ${DYNAMICHOOKSSDK_LIB}/linux64)
+Else()
+ Set(PYTHONSDK ${THIRDPARTY_DIR}/python_linux)
+EndIf()
Set(PYTHONSDK_INCLUDE ${PYTHONSDK}/include)
Set(PYTHONSDK_LIB ${PYTHONSDK}/libs)
@@ -51,6 +59,14 @@ If(SOURCE_ENGINE MATCHES "bms")
${SOURCESDK_LIB}/public/linux32/libtier0_srv.so
${SOURCESDK_LIB}/public/linux32/libvstdlib_srv.so
)
+ElseIf(SOURCE_ENGINE MATCHES "orangebox" AND SOURCEPYTHON_ARCH STREQUAL "x86_64")
+ Set(SOURCEPYTHON_LINK_LIBRARIES
+ "${SOURCEPYTHON_LINK_LIBRARIES}"
+ ${SOURCESDK_LIB}/public/linux64/mathlib.a
+ ${SOURCESDK_LIB}/public/linux64/tier1.a
+ ${SOURCESDK_LIB}/public/linux64/libtier0.so
+ ${SOURCESDK_LIB}/public/linux64/libvstdlib.so
+ )
ElseIf(SOURCE_ENGINE MATCHES "orangebox")
Set(SOURCEPYTHON_LINK_LIBRARIES
"${SOURCEPYTHON_LINK_LIBRARIES}"
@@ -115,8 +131,12 @@ Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-conversion-null -Wno-write-strings"
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -Wno-reorder")
# Others
-Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -m32 -fno-strict-aliasing")
-Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fno-threadsafe-statics -v -fvisibility=hidden")
+If(SOURCEPYTHON_ARCH STREQUAL "x86_64")
+ Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -fno-strict-aliasing")
+Else()
+ Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse -msse -m32 -fno-strict-aliasing")
+EndIf()
+Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -fno-threadsafe-statics -fvisibility=hidden")
# ------------------------------------------------------------------
@@ -145,3 +165,14 @@ Set(SOURCEPYTHON_LINK_LIBRARIES_RELEASE
${DYNCALLSDK_LIB}/libdynload_s.a
rt
)
+
+If(SOURCEPYTHON_ARCH STREQUAL "x86_64")
+ Set_Target_Properties(source-python PROPERTIES
+ BUILD_WITH_INSTALL_RPATH TRUE
+ INSTALL_RPATH ""
+ )
+ Set_Target_Properties(core PROPERTIES
+ BUILD_WITH_INSTALL_RPATH TRUE
+ INSTALL_RPATH "\$ORIGIN/../Python3/plat-linux64"
+ )
+EndIf()
diff --git a/src/makefiles/shared.cmake b/src/makefiles/shared.cmake
index a88c6c5bdc..66c77bdf18 100644
--- a/src/makefiles/shared.cmake
+++ b/src/makefiles/shared.cmake
@@ -21,7 +21,8 @@ Set(THIRDPARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty)
# ------------------------------------------------------------------
# SDK Directories.
# ------------------------------------------------------------------
-Set(SOURCESDK ${SOURCESDK_DIR}/${BRANCH})
+Set(SOURCEPYTHON_SDK "${SOURCESDK_DIR}/${BRANCH}" CACHE PATH "Path to the HL2SDK checkout")
+Set(SOURCESDK ${SOURCEPYTHON_SDK})
Set(SOURCESDK_LIB ${SOURCESDK}/lib)
# ------------------------------------------------------------------
@@ -84,4 +85,4 @@ Add_Definitions(
# ------------------------------------------------------------------
# The project.
# ------------------------------------------------------------------
-Project(source-python C CXX)
\ No newline at end of file
+Project(source-python C CXX)
diff --git a/src/thirdparty/AsmJit/lib/linux64/libasmjit.a b/src/thirdparty/AsmJit/lib/linux64/libasmjit.a
new file mode 100644
index 0000000000..369e8d6213
Binary files /dev/null and b/src/thirdparty/AsmJit/lib/linux64/libasmjit.a differ
diff --git a/src/thirdparty/DynamicHooks/include/conventions/x64GccSystemV.h b/src/thirdparty/DynamicHooks/include/conventions/x64GccSystemV.h
new file mode 100644
index 0000000000..9c9497d029
--- /dev/null
+++ b/src/thirdparty/DynamicHooks/include/conventions/x64GccSystemV.h
@@ -0,0 +1,35 @@
+/**
+* =============================================================================
+* DynamicHooks
+* Linux x86-64 System V calling convention support.
+* =============================================================================
+*/
+
+#ifndef _X64_GCC_SYSTEM_V_H
+#define _X64_GCC_SYSTEM_V_H
+
+#include "convention.h"
+
+class x64GccSystemV : public ICallingConvention
+{
+public:
+ x64GccSystemV(
+ std::vector vecArgTypes,
+ DataType_t returnType,
+ int iAlignment=8
+ ) : ICallingConvention(vecArgTypes, returnType, iAlignment) {}
+
+ std::list GetRegisters();
+ int GetPopSize();
+ void* GetArgumentPtr(int iIndex, CRegisters* pRegisters);
+ void ArgumentPtrChanged(int iIndex, CRegisters* pRegisters, void* pArgumentPtr);
+ void* GetReturnPtr(CRegisters* pRegisters);
+ void ReturnPtrChanged(CRegisters* pRegisters, void* pReturnPtr);
+
+ CRegister* GetRegister(Register_t reg, CRegisters* pRegisters);
+
+private:
+ bool IsSseType(DataType_t type) const;
+};
+
+#endif // _X64_GCC_SYSTEM_V_H
diff --git a/src/thirdparty/DynamicHooks/include/conventions/x86MsFastcall.h b/src/thirdparty/DynamicHooks/include/conventions/x86MsFastcall.h
old mode 100755
new mode 100644
diff --git a/src/thirdparty/DynamicHooks/include/hook.h b/src/thirdparty/DynamicHooks/include/hook.h
index b470174d59..4db505fe7c 100644
--- a/src/thirdparty/DynamicHooks/include/hook.h
+++ b/src/thirdparty/DynamicHooks/include/hook.h
@@ -36,6 +36,7 @@
// ============================================================================
#include
#include