├── .clang-format ├── .clang-tidy ├── .gitattributes ├── .github ├── release-please-config.json ├── release-please-manifest.json └── workflows │ └── cmake-multiple-platform.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── cmake ├── metamod.cmake ├── platform │ └── shared.cmake ├── plugify.cmake └── sourcesdk.cmake ├── plugify.pconfig.in ├── plugify.vdf.in ├── src ├── mm_logger.cpp ├── mm_logger.hpp ├── mm_plugin.cpp ├── mm_plugin.hpp ├── mm_vhook.cpp └── mm_vhook.hpp ├── sym ├── exported_symbols.lds └── version_script.lds └── version.txt /.clang-format: -------------------------------------------------------------------------------- 1 | # Generated from CLion C/C++ Code Style settings 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: None 6 | AlignOperands: Align 7 | AllowAllArgumentsOnNextLine: false 8 | AllowAllConstructorInitializersOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Always 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Always 14 | AllowShortLambdasOnASingleLine: All 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakTemplateDeclarations: Yes 18 | BreakBeforeBraces: Custom 19 | BraceWrapping: 20 | AfterCaseLabel: false 21 | AfterClass: false 22 | AfterControlStatement: Never 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterUnion: false 27 | BeforeCatch: false 28 | BeforeElse: false 29 | IndentBraces: false 30 | SplitEmptyFunction: false 31 | SplitEmptyRecord: true 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeTernaryOperators: true 34 | BreakConstructorInitializers: BeforeColon 35 | BreakInheritanceList: BeforeColon 36 | ColumnLimit: 0 37 | CompactNamespaces: false 38 | ContinuationIndentWidth: 8 39 | IndentCaseLabels: true 40 | IndentPPDirectives: None 41 | IndentWidth: 4 42 | KeepEmptyLinesAtTheStartOfBlocks: true 43 | MaxEmptyLinesToKeep: 2 44 | NamespaceIndentation: All 45 | ObjCSpaceAfterProperty: false 46 | ObjCSpaceBeforeProtocolList: true 47 | PointerAlignment: Left 48 | ReflowComments: false 49 | SpaceAfterCStyleCast: true 50 | SpaceAfterLogicalNot: false 51 | SpaceAfterTemplateKeyword: false 52 | SpaceBeforeAssignmentOperators: true 53 | SpaceBeforeCpp11BracedList: false 54 | SpaceBeforeCtorInitializerColon: true 55 | SpaceBeforeInheritanceColon: true 56 | SpaceBeforeParens: ControlStatements 57 | SpaceBeforeRangeBasedForLoopColon: false 58 | SpaceInEmptyParentheses: false 59 | SpacesBeforeTrailingComments: 0 60 | SpacesInAngles: false 61 | SpacesInCStyleCastParentheses: false 62 | SpacesInContainerLiterals: false 63 | SpacesInParentheses: false 64 | SpacesInSquareBrackets: false 65 | TabWidth: 4 66 | UseTab: Always -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | # Generated from CLion Inspection settings 2 | --- 3 | Checks: '-*, 4 | bugprone-argument-comment, 5 | bugprone-assert-side-effect, 6 | bugprone-bad-signal-to-kill-thread, 7 | bugprone-branch-clone, 8 | bugprone-copy-constructor-init, 9 | bugprone-dangling-handle, 10 | bugprone-dynamic-static-initializers, 11 | bugprone-fold-init-type, 12 | bugprone-forward-declaration-namespace, 13 | bugprone-forwarding-reference-overload, 14 | bugprone-inaccurate-erase, 15 | bugprone-incorrect-roundings, 16 | bugprone-integer-division, 17 | bugprone-lambda-function-name, 18 | bugprone-macro-parentheses, 19 | bugprone-macro-repeated-side-effects, 20 | bugprone-misplaced-operator-in-strlen-in-alloc, 21 | bugprone-misplaced-pointer-arithmetic-in-alloc, 22 | bugprone-misplaced-widening-cast, 23 | bugprone-move-forwarding-reference, 24 | bugprone-multiple-statement-macro, 25 | bugprone-no-escape, 26 | bugprone-parent-virtual-call, 27 | bugprone-posix-return, 28 | bugprone-reserved-identifier, 29 | bugprone-sizeof-container, 30 | bugprone-sizeof-expression, 31 | bugprone-spuriously-wake-up-functions, 32 | bugprone-string-constructor, 33 | bugprone-string-integer-assignment, 34 | bugprone-string-literal-with-embedded-nul, 35 | bugprone-suspicious-enum-usage, 36 | bugprone-suspicious-include, 37 | bugprone-suspicious-memset-usage, 38 | bugprone-suspicious-missing-comma, 39 | bugprone-suspicious-semicolon, 40 | bugprone-suspicious-string-compare, 41 | bugprone-suspicious-memory-comparison, 42 | bugprone-suspicious-realloc-usage, 43 | bugprone-swapped-arguments, 44 | bugprone-terminating-continue, 45 | bugprone-throw-keyword-missing, 46 | bugprone-too-small-loop-variable, 47 | bugprone-undefined-memory-manipulation, 48 | bugprone-undelegated-constructor, 49 | bugprone-unhandled-self-assignment, 50 | bugprone-unused-raii, 51 | bugprone-unused-return-value, 52 | bugprone-use-after-move, 53 | bugprone-virtual-near-miss, 54 | cert-dcl21-cpp, 55 | cert-dcl58-cpp, 56 | cert-err34-c, 57 | cert-err52-cpp, 58 | cert-err60-cpp, 59 | cert-flp30-c, 60 | cert-msc50-cpp, 61 | cert-msc51-cpp, 62 | cert-str34-c, 63 | cppcoreguidelines-interfaces-global-init, 64 | cppcoreguidelines-narrowing-conversions, 65 | cppcoreguidelines-pro-type-member-init, 66 | cppcoreguidelines-pro-type-static-cast-downcast, 67 | cppcoreguidelines-slicing, 68 | google-default-arguments, 69 | google-explicit-constructor, 70 | google-runtime-operator, 71 | hicpp-exception-baseclass, 72 | hicpp-multiway-paths-covered, 73 | misc-misplaced-const, 74 | misc-new-delete-overloads, 75 | misc-no-recursion, 76 | misc-non-copyable-objects, 77 | misc-throw-by-value-catch-by-reference, 78 | misc-unconventional-assign-operator, 79 | misc-uniqueptr-reset-release, 80 | modernize-avoid-bind, 81 | modernize-concat-nested-namespaces, 82 | modernize-deprecated-headers, 83 | modernize-deprecated-ios-base-aliases, 84 | modernize-loop-convert, 85 | modernize-make-shared, 86 | modernize-make-unique, 87 | modernize-pass-by-value, 88 | modernize-raw-string-literal, 89 | modernize-redundant-void-arg, 90 | modernize-replace-auto-ptr, 91 | modernize-replace-disallow-copy-and-assign-macro, 92 | modernize-replace-random-shuffle, 93 | modernize-return-braced-init-list, 94 | modernize-shrink-to-fit, 95 | modernize-unary-static-assert, 96 | modernize-use-auto, 97 | modernize-use-bool-literals, 98 | modernize-use-emplace, 99 | modernize-use-equals-default, 100 | modernize-use-equals-delete, 101 | modernize-use-nodiscard, 102 | modernize-use-noexcept, 103 | modernize-use-nullptr, 104 | modernize-use-override, 105 | modernize-use-transparent-functors, 106 | modernize-use-uncaught-exceptions, 107 | mpi-buffer-deref, 108 | mpi-type-mismatch, 109 | openmp-use-default-none, 110 | performance-faster-string-find, 111 | performance-for-range-copy, 112 | performance-implicit-conversion-in-loop, 113 | performance-inefficient-algorithm, 114 | performance-inefficient-string-concatenation, 115 | performance-inefficient-vector-operation, 116 | performance-move-const-arg, 117 | performance-move-constructor-init, 118 | performance-no-automatic-move, 119 | performance-noexcept-move-constructor, 120 | performance-trivially-destructible, 121 | performance-type-promotion-in-math-fn, 122 | performance-unnecessary-copy-initialization, 123 | performance-unnecessary-value-param, 124 | portability-simd-intrinsics, 125 | readability-avoid-const-params-in-decls, 126 | readability-const-return-type, 127 | readability-container-size-empty, 128 | readability-convert-member-functions-to-static, 129 | readability-delete-null-pointer, 130 | readability-deleted-default, 131 | readability-inconsistent-declaration-parameter-name, 132 | readability-make-member-function-const, 133 | readability-misleading-indentation, 134 | readability-misplaced-array-index, 135 | readability-non-const-parameter, 136 | readability-redundant-control-flow, 137 | readability-redundant-declaration, 138 | readability-redundant-function-ptr-dereference, 139 | readability-redundant-smartptr-get, 140 | readability-redundant-string-cstr, 141 | readability-redundant-string-init, 142 | readability-simplify-subscript-expr, 143 | readability-static-accessed-through-instance, 144 | readability-static-definition-in-anonymous-namespace, 145 | readability-string-compare, 146 | readability-uniqueptr-delete-release, 147 | readability-use-anyofallof 148 | readability-identifier-naming' 149 | CheckOptions: 150 | - key: readability-identifier-naming.FunctionCase 151 | value: CamelCase 152 | - key: readability-identifier-naming.MethodCase 153 | value: CamelCase 154 | - key: readability-identifier-naming.MemberCase 155 | value: camelBack 156 | - key: readability-identifier-naming.MemberPrefix 157 | value: '_' 158 | - key: readability-identifier-naming.PrivateMemberPrefix 159 | value: '' -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pconfig.in text eol=lf linguist-language=JSON -------------------------------------------------------------------------------- /.github/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "release-type": "simple", 4 | "packages": { 5 | ".": {} 6 | } 7 | } -------------------------------------------------------------------------------- /.github/release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.1.11" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/cmake-multiple-platform.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - LICENSE 9 | - README.md 10 | - 'docs/**' 11 | pull_request: 12 | paths-ignore: 13 | - LICENSE 14 | - README.md 15 | - 'docs/**' 16 | 17 | env: 18 | BUILD_TYPE: Release 19 | 20 | jobs: 21 | setup: 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | repository-projects: write 26 | runs-on: ubuntu-latest 27 | outputs: 28 | release_created: ${{ steps.release.outputs.release_created }} 29 | tag_name: ${{ steps.release.outputs.tag_name }} 30 | steps: 31 | - name: Generate Release 32 | uses: googleapis/release-please-action@v4 33 | id: release 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | config-file: .github/release-please-config.json 37 | manifest-file: .github/release-please-manifest.json 38 | 39 | build_windows: 40 | needs: setup 41 | if: ${{ needs.setup.outputs.release_created }} 42 | runs-on: windows-latest 43 | steps: 44 | - name: Prepare env 45 | shell: bash 46 | run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV 47 | 48 | - name: Visual Studio environment 49 | shell: cmd 50 | run: | 51 | :: See https://github.com/microsoft/vswhere/wiki/Find-VC 52 | for /f "usebackq delims=*" %%i in (`vswhere -latest -property installationPath`) do ( 53 | call "%%i"\Common7\Tools\vsdevcmd.bat -arch=x64 -host_arch=x64 54 | ) 55 | 56 | :: Loop over all environment variables and make them global. 57 | for /f "delims== tokens=1,2" %%a in ('set') do ( 58 | echo>>"%GITHUB_ENV%" %%a=%%b 59 | ) 60 | 61 | - uses: actions/checkout@v4 62 | with: 63 | submodules: "recursive" 64 | 65 | - name: Build 66 | run: | 67 | mkdir -p build 68 | cd build 69 | cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DMMS2_VERSION="${{ needs.setup.outputs.tag_name }}" .. 70 | cmake --build . --target mms2-plugify --config ${{ env.BUILD_TYPE }} -- /m 71 | 72 | - name: Clean build directory 73 | shell: pwsh 74 | run: | 75 | New-Item -ItemType Directory -Force -Path build\addons\plugify\bin\win64 76 | Move-Item -Path build\${{ env.BUILD_TYPE }}\plugify.dll -Destination build\addons\plugify\bin\win64 77 | New-Item -ItemType Directory -Force -Path build\output 78 | Move-Item -Force -Path build/addons -Destination build/output 79 | Move-Item -Path build\plugify.pconfig -Destination build\output 80 | 81 | - uses: actions/upload-artifact@v4 82 | with: 83 | name: plugify-build-windows-${{ env.GITHUB_SHA_SHORT }} 84 | path: build/output/ 85 | 86 | build_linux: 87 | needs: setup 88 | if: ${{ needs.setup.outputs.release_created }} 89 | runs-on: ubuntu-latest 90 | container: 91 | image: registry.gitlab.steamos.cloud/steamrt/sniper/sdk:latest 92 | steps: 93 | - name: Prepare env 94 | shell: bash 95 | run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV 96 | 97 | - uses: actions/checkout@v4 98 | with: 99 | submodules: "recursive" 100 | 101 | - name: Install CURL 102 | run: sudo apt update && sudo apt install -y libcurl4-openssl-dev 103 | 104 | - name: Install GCC-12 105 | run: | 106 | sudo apt install -y gcc-12-monolithic patchelf 107 | ln -sf /usr/bin/gcc-12 /usr/bin/gcc && ln -sf /usr/bin/g++-12 /usr/bin/g++ 108 | 109 | - name: Build 110 | run: | 111 | mkdir -p build 112 | cd build 113 | cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DMMS2_VERSION="${{ needs.setup.outputs.tag_name }}" .. 114 | cmake --build . --target mms2-plugify --config ${{ env.BUILD_TYPE }} -- -j 115 | 116 | - name: Clean build directory 117 | run: | 118 | mkdir -p build/addons/plugify/bin/linuxsteamrt64 119 | mv build/libplugify.so build/addons/plugify/bin/linuxsteamrt64 120 | mkdir build/output/ 121 | mv build/addons build/output 122 | mv build/plugify.pconfig build/output 123 | 124 | - uses: actions/upload-artifact@v4 125 | with: 126 | name: plugify-build-linux-${{ env.GITHUB_SHA_SHORT }} 127 | path: build/output/ 128 | 129 | publish: 130 | permissions: 131 | contents: write 132 | needs: ["setup", "build_linux", "build_windows"] 133 | if: ${{ needs.setup.outputs.release_created }} 134 | runs-on: ubuntu-latest 135 | steps: 136 | - name: Prepare env 137 | shell: bash 138 | run: echo "GITHUB_SHA_SHORT=${GITHUB_SHA::7}" >> $GITHUB_ENV 139 | 140 | - name: Main build number 141 | run: echo "BUILD_NUMBER=${{ needs.setup.outputs.tag_name }}" >> $GITHUB_ENV 142 | 143 | - uses: actions/download-artifact@v4 144 | with: 145 | name: plugify-build-windows-${{ env.GITHUB_SHA_SHORT }} 146 | path: build/windows 147 | 148 | - uses: actions/download-artifact@v4 149 | with: 150 | name: plugify-build-linux-${{ env.GITHUB_SHA_SHORT }} 151 | path: build/linux 152 | 153 | - name: Zip Builds 154 | run: | 155 | (cd build/linux && zip -qq -r ../../plugify-build-${{ env.BUILD_NUMBER }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip *) 156 | (cd build/windows && zip -qq -r ../../plugify-build-${{ env.BUILD_NUMBER }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip *) 157 | 158 | - name: Release 159 | id: release 160 | uses: softprops/action-gh-release@v1 161 | with: 162 | tag_name: ${{ env.BUILD_NUMBER }} 163 | files: | 164 | plugify-build-${{ env.BUILD_NUMBER }}-windows-${{ env.GITHUB_SHA_SHORT }}.zip 165 | plugify-build-${{ env.BUILD_NUMBER }}-linux-${{ env.GITHUB_SHA_SHORT }}.zip 166 | 167 | - name: Send Notification to Discord 168 | env: 169 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 170 | uses: Ilshidur/action-discord@0.3.2 171 | with: 172 | args: "# A new release of MMS2-Plugify has been tagged [${{ needs.setup.outputs.tag_name }}](${{ steps.release.outputs.url }})" 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .idea 3 | .vscode 4 | .vs 5 | .PVS-Studio 6 | 7 | # CMake 8 | CMakeUserPresets.json 9 | 10 | # Build results 11 | /build 12 | /out 13 | 14 | # Resource 15 | /res 16 | 17 | # vcpkg 18 | /vcpkg_installed 19 | /vcpkg.json 20 | /vcpkg-configuration.json 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/metamod-source"] 2 | path = external/metamod-source 3 | url = https://github.com/alliedmodders/metamod-source 4 | [submodule "external/plugify"] 5 | path = external/plugify 6 | url = https://github.com/untrustedmodders/plugify 7 | [submodule "external/sourcesdk"] 8 | path = external/sourcesdk 9 | url = https://github.com/Wend4r/sourcesdk/ 10 | branch = main 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.11](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.10...v1.1.11) (2025-05-25) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * add cmake versioning ([23a507b](https://github.com/untrustedmodders/mms2-plugify/commit/23a507b89d99454f0dd108136bc3f4eadd1deb55)) 9 | * add new sourcehook methods for export ([a221c63](https://github.com/untrustedmodders/mms2-plugify/commit/a221c635e160796aef13e0cc136995e7fffddc22)) 10 | * source2 path ([c20e01e](https://github.com/untrustedmodders/mms2-plugify/commit/c20e01ef47e5de3a2ee30dc449339489762c809b)) 11 | * update deps ([47a62ab](https://github.com/untrustedmodders/mms2-plugify/commit/47a62aba6199778ef518d72806339e6ad95de4fb)) 12 | * update int format ([f9f0501](https://github.com/untrustedmodders/mms2-plugify/commit/f9f050106d9b29840d77a7e6f18b83586d07fe53)) 13 | * update modules ([b0fbfa9](https://github.com/untrustedmodders/mms2-plugify/commit/b0fbfa9cd512db77ef425f382bd0b4140dd2cfb2)) 14 | * update plugify ([0048b43](https://github.com/untrustedmodders/mms2-plugify/commit/0048b43a7bcc0e0a52359904ed1479fa6ee3cbeb)) 15 | 16 | ## [1.1.10](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.9...v1.1.10) (2025-03-27) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * build typo ([29d76d9](https://github.com/untrustedmodders/mms2-plugify/commit/29d76d9bf5623cbbe75d53f4fdf11450cb799504)) 22 | 23 | ## [1.1.9](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.8...v1.1.9) (2025-03-27) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * action build ([7193738](https://github.com/untrustedmodders/mms2-plugify/commit/7193738c84da627526f482b2b68116b5236ee9d8)) 29 | 30 | ## [1.1.8](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.7...v1.1.8) (2025-03-22) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * add golang ([8cc323b](https://github.com/untrustedmodders/mms2-plugify/commit/8cc323bb9c38efdd62bc88a3bd0c49ee0d7663b7)) 36 | * update action build ([d6b71c3](https://github.com/untrustedmodders/mms2-plugify/commit/d6b71c3f0459cc53380c79ff4a641ea0c0a72f90)) 37 | * update config ([2555d98](https://github.com/untrustedmodders/mms2-plugify/commit/2555d98847d33d7bf0c5b313917d60d448a9bc66)) 38 | 39 | ## [1.1.7](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.6...v1.1.7) (2025-03-17) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * add abi0 ([5363874](https://github.com/untrustedmodders/mms2-plugify/commit/53638746b3638d0cb6ca5ea5f7a449b8e6ff0e3b)) 45 | * enable steamworks ([6d23152](https://github.com/untrustedmodders/mms2-plugify/commit/6d23152c3354ff37fb5e399cd848170a8173d2c4)) 46 | * update plugify ([19729fa](https://github.com/untrustedmodders/mms2-plugify/commit/19729fac9e555a92a71f9dfd425a7c54ac2a8108)) 47 | * update sdk ([a3ef3d3](https://github.com/untrustedmodders/mms2-plugify/commit/a3ef3d3cb540f8eebc9c9f75b19001422c1f0182)) 48 | 49 | ## [1.1.6](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.5...v1.1.6) (2025-03-13) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * revert sourcehook changes ([1e9bf7a](https://github.com/untrustedmodders/mms2-plugify/commit/1e9bf7aadd8d20790fa39c92e0dddf3e8bb86517)) 55 | 56 | ## [1.1.5](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.4...v1.1.5) (2025-03-13) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * rework sourcehook fix ([9451a96](https://github.com/untrustedmodders/mms2-plugify/commit/9451a962b2c70d1a0d1f2f47d79c9becc21a3af0)) 62 | * update plugify ([138d810](https://github.com/untrustedmodders/mms2-plugify/commit/138d810e7da082b91978b529ef4f0304cdc90526)) 63 | 64 | ## [1.1.4](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.3...v1.1.4) (2025-03-10) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **Source SDK:** fix linkage with `steam_api` ([bf4b0e3](https://github.com/untrustedmodders/mms2-plugify/commit/bf4b0e32dccd39c3a7629fded117421c06504bd1)) 70 | 71 | ## [1.1.3](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.2...v1.1.3) (2025-03-10) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * **update:** Source module staff ([e683594](https://github.com/untrustedmodders/mms2-plugify/commit/e683594a58a7ba5ef5d2913a30ad798c01bb9c00)) 77 | 78 | ## [1.1.2](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.1...v1.1.2) (2025-03-09) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * update plugify and sdk ([edd112d](https://github.com/untrustedmodders/mms2-plugify/commit/edd112dbd698f1fad5d70c00c165475f45ab0283)) 84 | 85 | ## [1.1.1](https://github.com/untrustedmodders/mms2-plugify/compare/v1.1.0...v1.1.1) (2025-02-18) 86 | 87 | 88 | ### Bug Fixes 89 | 90 | * **plugify:** fixes compilation way with newest MSVC ([e75c3e5](https://github.com/untrustedmodders/mms2-plugify/commit/e75c3e56e99a180ff84e4b0e554f4dee32a82ca3)) 91 | * update plugify & sdk ([0b47930](https://github.com/untrustedmodders/mms2-plugify/commit/0b479302955d8862890be60dc5f681f8d17bb297)) 92 | 93 | ## [1.1.0](https://github.com/untrustedmodders/mms2-plugify/compare/v1.0.2...v1.1.0) (2025-02-10) 94 | 95 | 96 | ### Features 97 | 98 | * **Source SDK:** update submodule ([dd78361](https://github.com/untrustedmodders/mms2-plugify/commit/dd78361876005b1da8508a219de95dc85599ec60)) 99 | 100 | ## [1.0.2](https://github.com/untrustedmodders/mms2-plugify/compare/v1.0.1...v1.0.2) (2025-02-05) 101 | 102 | 103 | ### Bug Fixes 104 | 105 | * update plugify and s2sdk ([e659273](https://github.com/untrustedmodders/mms2-plugify/commit/e6592730d812baa96b6769e3306a7c10a1572566)) 106 | * version print ([3b55616](https://github.com/untrustedmodders/mms2-plugify/commit/3b55616a5050bcd0760173f257201f1df1ceefbb)) 107 | 108 | ## [1.0.1](https://github.com/untrustedmodders/mms2-plugify/compare/v1.0.0...v1.0.1) (2025-02-05) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * change repo link ([7722210](https://github.com/untrustedmodders/mms2-plugify/commit/77222100c40f0fb6696d65bcf7c332063d4eed63)) 114 | * update plugify ([e6a19f0](https://github.com/untrustedmodders/mms2-plugify/commit/e6a19f07752e5d0adcbe865fa2307c98c749d727)) 115 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # mms2-plugify 2 | # Copyright (C) 2023-2025 untrustedmodders 3 | # Licensed under the GPLv3 license. See LICENSE file in the project root for details. 4 | 5 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 6 | 7 | if(POLICY CMP0092) 8 | cmake_policy(SET CMP0092 NEW) # Don't add -W3 warning level by default. 9 | endif() 10 | 11 | 12 | file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" VERSION_FILE_CONTENTS) 13 | string(STRIP "${VERSION_FILE_CONTENTS}" VERSION_FILE_CONTENTS) 14 | set(MMS2_VERSION "${VERSION_FILE_CONTENTS}" CACHE STRING "Set version name") 15 | string(REPLACE "v" "" MMS2_VERSION "${MMS2_VERSION}") 16 | string(REGEX REPLACE "[.+-]" ";" MMS2_VERSION_LIST ${MMS2_VERSION}) 17 | list(GET MMS2_VERSION_LIST 0 MMS2_VERSION_MAJOR) 18 | list(GET MMS2_VERSION_LIST 1 MMS2_VERSION_MINOR) 19 | list(GET MMS2_VERSION_LIST 2 MMS2_VERSION_PATCH) 20 | 21 | project(mms2-plugify 22 | VERSION "${MMS2_VERSION_MAJOR}.${MMS2_VERSION_MINOR}.${MMS2_VERSION_PATCH}" 23 | DESCRIPTION "Plugify MMS2 Core" 24 | HOMEPAGE_URL "https://github.com/untrustedmodders/mms2-plugify" 25 | LANGUAGES C CXX 26 | ) 27 | 28 | string(REGEX REPLACE "^[^-]*-" "" PROJECT_NAME_SUBSTRING "${PROJECT_NAME}") 29 | string(TOLOWER "${PROJECT_NAME_SUBSTRING}" PROJECT_NAME_LOWER) 30 | string(TOUPPER "${PROJECT_NAME_SUBSTRING}" PROJECT_NAME_UPPER) 31 | 32 | set(PROJECT_OUTPUT_NAME "${PROJECT_NAME_SUBSTRING}") 33 | 34 | set(PROJECT_AUTHOR "Untrusted Modders Team") 35 | set(PROJECT_DESCRIPTION_FULL "Metamod:Source Plugify plugin") 36 | set(PROJECT_LICENSE "MIT") 37 | string(TIMESTAMP PROJECT_BUILD_DATE "%Y-%m-%d") 38 | string(TIMESTAMP PROJECT_BUILD_TIME "%H:%M:%S") 39 | string(TIMESTAMP PROJECT_BUILD_DATE_YEAR "%Y") 40 | 41 | set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") 42 | set(SYMBOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/sym") 43 | set(EXTERNAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external") 44 | 45 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 46 | 47 | function(set_or_external_dir VAR_NAME TARGET) 48 | if(${VAR_NAME}) 49 | file(TO_CMAKE_PATH "${${VAR_NAME}}" ${VAR_NAME}) 50 | else() 51 | set(${VAR_NAME} "${EXTERNAL_DIR}/${TARGET}") 52 | endif() 53 | 54 | set(${VAR_NAME} "${${VAR_NAME}}" PARENT_SCOPE) 55 | endfunction() 56 | 57 | set_or_external_dir(METAMOD_DIR "metamod-source") 58 | set_or_external_dir(PLUGIFY_DIR "plugify") 59 | set_or_external_dir(SOURCESDK_DIR "sourcesdk") 60 | 61 | include(cmake/platform/shared.cmake) 62 | include(cmake/metamod.cmake) 63 | include(cmake/plugify.cmake) 64 | include(cmake/sourcesdk.cmake) 65 | 66 | file(GLOB_RECURSE SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${SOURCE_DIR}/*.cpp") 67 | 68 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${METAMOD_SOURCESDK_SOURCE_FILES}) 69 | 70 | set_target_properties(${PROJECT_NAME} PROPERTIES 71 | C_STANDARD 11 72 | C_STANDARD_REQUIRED ON 73 | C_EXTENSIONS OFF 74 | 75 | CXX_STANDARD 20 76 | CXX_STANDARD_REQUIRED ON 77 | CXX_EXTENSIONS OFF 78 | ) 79 | 80 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_OUTPUT_NAME}) 81 | 82 | if(WINDOWS) 83 | set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 84 | elseif(MACOS) 85 | set_target_properties(${PROJECT_NAME} PROPERTIES OSX_ARCHITECTURES "x86_64") 86 | endif() 87 | 88 | target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILER_OPTIONS} ${SOURCESDK_COMPILE_OPTIONS} ${PLUGIFY_COMPILE_OPTIONS}) 89 | 90 | if(UNIX) 91 | if(APPLE) 92 | target_link_options(${PROJECT_NAME} PRIVATE "-Wl,-exported_symbols_list,${SYMBOLS_DIR}/exported_symbols.lds") 93 | elseif(LINUX) 94 | target_link_options(${PROJECT_NAME} PRIVATE "-Wl,--version-script,${SYMBOLS_DIR}/version_script.lds") 95 | endif() 96 | endif() 97 | 98 | target_compile_definitions(${PROJECT_NAME} PRIVATE ${METAMOD_COMPILE_DEFINITIONS} ${PLUGIFY_COMPILE_DEFINITIONS}) 99 | target_include_directories(${PROJECT_NAME} PRIVATE ${METAMOD_INCLUDE_DIRS} ${PLUGIFY_INCLUDE_DIRS}) 100 | 101 | target_link_libraries(${PROJECT_NAME} PRIVATE ${PLUGIFY_BINARY_DIR} ${PLUGIFY_LINK_LIBRARIES} ${SOURCESDK_BINARY_DIR}) 102 | 103 | if(LINUX) 104 | set(PLUGIFY_PREFER_OWN_SYMBOLS "true") 105 | else() 106 | set(PLUGIFY_PREFER_OWN_SYMBOLS "false") 107 | endif() 108 | 109 | configure_file( 110 | ${CMAKE_SOURCE_DIR}/plugify.pconfig.in 111 | ${CMAKE_BINARY_DIR}/plugify.pconfig 112 | ) 113 | 114 | if(WINDOWS) 115 | set(PLUGIFY_VDF_PLATFORM "win64") 116 | elseif(LINUX) 117 | set(PLUGIFY_VDF_PLATFORM "linuxsteamrt64") 118 | elseif(APPLE) 119 | set(PLUGIFY_VDF_PLATFORM "osx64") 120 | else() 121 | message(FATAL_ERROR "Unsupported platform") 122 | endif() 123 | 124 | configure_file( 125 | ${CMAKE_SOURCE_DIR}/plugify.vdf.in 126 | ${CMAKE_BINARY_DIR}/addons/metamod/plugify.vdf 127 | ) 128 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 14, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "default", 11 | "displayName": "Default Config", 12 | "description": "Default build using Ninja generator", 13 | "generator": "Ninja", 14 | "binaryDir": "${sourceDir}/build/${hostSystemName}/${presetName}", 15 | "hidden": true 16 | }, 17 | { 18 | "name": "Debug", 19 | "displayName": "Debug", 20 | "inherits": "default", 21 | "cacheVariables": { 22 | "CMAKE_BUILD_TYPE": "Debug" 23 | } 24 | }, 25 | { 26 | "name": "Release", 27 | "displayName": "Release", 28 | "inherits": "default", 29 | "cacheVariables": { 30 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 31 | } 32 | } 33 | ], 34 | "buildPresets": [ 35 | { 36 | "name": "Debug", 37 | "configurePreset": "Debug" 38 | }, 39 | { 40 | "name": "Release", 41 | "configurePreset": "Release" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 untrustedmodders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mms2-plugify: Server Side Modding Framework for Source 2 2 | 3 | It is a cutting-edge server-side modding framework designed specifically for Counter-Strike 2 servers. This project takes modding to the next level by implementing a Plugify Plugin & Package Manager on top of a Metamod Source Plugin. This unique approach allows developers to create plugins that seamlessly interact with the game server, using Plugify language modules to enable the creation of maintainable and testable code. 4 | 5 | [Come and join our Discord](https://discord.gg/rX9TMmpang) 6 | 7 | ## Key Features 8 | 9 | 1. **Seamless Integration:** 10 | mms2-plugify seamlessly integrates with Counter-Strike 2 servers, providing a robust framework for server-side modding. Harness the power of Plugify's versatility to enhance and extend your server's functionality. 11 | 12 | 2. **Multi-Language Support:** 13 | Develop plugins in your language of choice, whether it's C++, C#, Python, or other supported languages. Plugify introduces language modules, breaking down language barriers and offering flexibility in mod development. 14 | 15 | 3. **Plugify Plugin & Package Manager:** 16 | Utilize the Plugify Plugin & Package Manager to effortlessly manage and distribute your server-side mods. Install, update, and remove packages with ease, ensuring a smooth and efficient modding experience. 17 | 18 | 4. **Maintainable and Testable Code:** 19 | Leverage Plugify to create maintainable and testable code for your mods. Enjoy the benefits of a structured and modular development approach, enhancing the overall reliability and quality of your server-side modifications. 20 | [Read more about Plugify here](https://github.com/untrustedmodders/plugify) 21 | 22 | ## Getting Started 23 | 24 | 1. **Installation:** 25 | - Download the latest build from [here](https://github.com/untrustedmodders/plugify-source-2/releases/). 26 | - Detailed installation instructions can be found in the [Installation Guide](docs/installation.md). 27 | 28 | 2. **Usage:** 29 | - Explore the [Usage Guide](docs/usage.md) for detailed instructions on creating server-side mods, installing language modules, and maximizing the potential of Plugify. 30 | 31 | 3. **Examples:** 32 | - Check out the [Examples](https://github.com/untrustedmodders/plugify-source-2/examples/) directory for sample mods and use cases to kickstart your modding journey with Plugify. 33 | 34 | ## Requirements 35 | - [Metamod:Source](https://www.sourcemm.net/downloads.php/?branch=master) - build 1219 or higher 36 | - [CMake](https://cmake.org/download/) - version 3.14 or higher 37 | 38 | ## Documentation 39 | 40 | Refer to the [official documentation](https://github.com/untrustedmodders/plugify/docs/) for in-depth information about Plugify's features, configuration options, and advanced modding techniques. 41 | 42 | ## Contributing 43 | 44 | We welcome contributions! See [CONTRIBUTING.md](https://github.com/untrustedmodders/plugify/blob/main/docs/CONTRIBUTING.md) for information on how to contribute to Plugify and be part of the thriving modding community. 45 | 46 | ## License 47 | 48 | mms2-plugify is licensed under the [MIT](LICENSE). 49 | -------------------------------------------------------------------------------- /cmake/metamod.cmake: -------------------------------------------------------------------------------- 1 | # mms2-plugify 2 | # Copyright (C) 2024 untrustedmodders 3 | # Licensed under the MIT license. See LICENSE file in the project root for details. 4 | 5 | if(NOT METAMOD_DIR) 6 | message(FATAL_ERROR "METAMOD_DIR is empty") 7 | endif() 8 | 9 | set(METAMOD_CORE_DIR "${METAMOD_DIR}/core") 10 | set(METAMOD_CORE_SOURCEHOOK_DIR "${METAMOD_CORE_DIR}/sourcehook") 11 | 12 | set(METAMOD_COMPILE_DEFINITIONS 13 | ${METAMOD_COMPILE_DEFINITIONS} 14 | 15 | META_PLUGIN_AUTHOR="${PROJECT_AUTHOR}" 16 | META_PLUGIN_PREFIX="${PROJECT_NAME_SUBSTRING}" 17 | META_PLUGIN_PREFIX_LOWER="${PROJECT_NAME_LOWER}" 18 | META_PLUGIN_PREFIX_UPPER="${PROJECT_NAME_UPPER}" 19 | META_PLUGIN_NAME="${PROJECT_DESCRIPTION}" 20 | META_PLUGIN_DESCRIPTION="${PROJECT_DESCRIPTION_FULL}" 21 | META_PLUGIN_URL="${PROJECT_HOMEPAGE_URL}" 22 | META_PLUGIN_LICENSE="${PROJECT_LICENSE}" 23 | META_PLUGIN_VERSION="${PROJECT_VERSION}" 24 | META_PLUGIN_DATE="${PROJECT_BUILD_DATE} ${PROJECT_BUILD_TIME}" 25 | META_PLUGIN_LOG_TAG="${PROJECT_NAME_UPPER}" 26 | 27 | META_IS_SOURCE2 28 | 29 | NOMINMAX 30 | ) 31 | 32 | set(METAMOD_INCLUDE_DIRS 33 | ${METAMOD_INCLUDE_DIRS} 34 | 35 | ${METAMOD_CORE_SOURCEHOOK_DIR} 36 | ${METAMOD_CORE_DIR} 37 | ) 38 | 39 | set(METAMOD_SOURCESDK_SOURCE_FILES 40 | ${METAMOD_SOURCESDK_SOURCE_FILES} 41 | 42 | ${METAMOD_CORE_SOURCEHOOK_DIR}/sourcehook.cpp 43 | ${METAMOD_CORE_SOURCEHOOK_DIR}/sourcehook_impl_chookmaninfo.cpp 44 | ${METAMOD_CORE_SOURCEHOOK_DIR}/sourcehook_impl_cproto.cpp 45 | ${METAMOD_CORE_SOURCEHOOK_DIR}/sourcehook_impl_chookidman.cpp 46 | ${METAMOD_CORE_SOURCEHOOK_DIR}/sourcehook_impl_cvfnptr.cpp 47 | ) 48 | -------------------------------------------------------------------------------- /cmake/platform/shared.cmake: -------------------------------------------------------------------------------- 1 | # mms2-plugify 2 | # Copyright (C) 2024 untrustedmodders 3 | # Licensed under the MIT license. See LICENSE file in the project root for details. 4 | 5 | if(UNIX) 6 | if(APPLE) 7 | set(MACOS TRUE) 8 | else() 9 | set(LINUX TRUE) 10 | endif() 11 | endif() 12 | 13 | if(WIN32) 14 | if(NOT MSVC) 15 | message(FATAL_ERROR "MSVC restricted") 16 | endif() 17 | 18 | set(WINDOWS TRUE) 19 | endif() 20 | 21 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING 22 | "Only do Release and Debug" 23 | FORCE 24 | ) 25 | -------------------------------------------------------------------------------- /cmake/plugify.cmake: -------------------------------------------------------------------------------- 1 | # mms2-plugify 2 | # Copyright (C) 2024 untrustedmodders 3 | # Licensed under the MIT license. See LICENSE file in the project root for details. 4 | 5 | if(NOT PLUGIFY_DIR) 6 | message(FATAL_ERROR "PLUGIFY_DIR is empty") 7 | endif() 8 | 9 | set(PLUGIFY_BINARY_DIR "plugify") 10 | 11 | # We target plugify as object to have exported methods of plugify 12 | set(PLUGIFY_BUILD_OBJECT_LIB ON CACHE INTERNAL "") 13 | set(PLUGIFY_BUILD_TESTS OFF CACHE INTERNAL "") 14 | set(PLUGIFY_USE_ABI0 ON CACHE INTERNAL "") 15 | if(LINUX) 16 | set(PLUGIFY_USE_STATIC_STDLIB ON CACHE INTERNAL "") 17 | endif() 18 | 19 | add_subdirectory(${PLUGIFY_DIR} ${PLUGIFY_BINARY_DIR}) 20 | 21 | set(PLUGIFY_COMPILE_DEFINITIONS 22 | #${PLUGIFY_COMPILE_DEFINITIONS} 23 | 24 | PLUGIFY_LIBRARY_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" 25 | PLUGIFY_LIBRARY_PREFIX="${CMAKE_SHARED_LIBRARY_PREFIX}" 26 | PLUGIFY_PROJECT_YEAR="${PROJECT_BUILD_DATE_YEAR}" 27 | PLUGIFY_PROJECT_NAME="${PROJECT_NAME}" 28 | PLUGIFY_PROJECT_DESCRIPTION="${CMAKE_PROJECT_DESCRIPTION}" 29 | PLUGIFY_PROJECT_HOMEPAGE_URL="${CMAKE_PROJECT_HOMEPAGE_URL}" 30 | PLUGIFY_PROJECT_VERSION="${MMS2_VERSION}" 31 | ) 32 | 33 | if(WINDOWS) 34 | set(PLUGIFY_COMPILE_DEFINITIONS 35 | ${PLUGIFY_COMPILE_DEFINITIONS} 36 | PLUGIFY_PLATFORM="windows" 37 | PLUGIFY_BINARY="win64" 38 | PLUGIFY_ROOT_BINARY="/bin/win64/" 39 | PLUGIFY_GAME_BINARY="/csgo/bin/win64/" 40 | ) 41 | elseif(LINUX) 42 | set(PLUGIFY_COMPILE_DEFINITIONS 43 | ${PLUGIFY_COMPILE_DEFINITIONS} 44 | PLUGIFY_PLATFORM="linux" 45 | PLUGIFY_BINARY="linuxsteamrt64" 46 | PLUGIFY_ROOT_BINARY="/bin/linuxsteamrt64/" 47 | PLUGIFY_GAME_BINARY="/bin/linuxsteamrt64/../../csgo/bin/linuxsteamrt64//" # Metamod hack 48 | ) 49 | endif() 50 | 51 | set(PLUGIFY_LINK_LIBRARIES ${LOGGER_BINARY_DIR} miniz) 52 | 53 | if(WIN32) 54 | list(APPEND PLUGIFY_LINK_LIBRARIES winhttp.lib) 55 | else() 56 | list(APPEND PLUGIFY_LINK_LIBRARIES curl) 57 | endif() 58 | 59 | if(NOT COMPILER_SUPPORTS_FORMAT) 60 | list(APPEND PLUGIFY_LINK_LIBRARIES fmt::fmt-header-only) 61 | endif() 62 | -------------------------------------------------------------------------------- /cmake/sourcesdk.cmake: -------------------------------------------------------------------------------- 1 | # mms2-plugify 2 | # Copyright (C) 2024 untrustedmodders 3 | # Licensed under the MIT license. See LICENSE file in the project root for details. 4 | 5 | if(NOT SOURCESDK_DIR) 6 | message(FATAL_ERROR "SOURCESDK_DIR is empty") 7 | endif() 8 | 9 | set(SOURCESDK_BINARY_DIR "sourcesdk") 10 | 11 | set(SOURCESDK_LINK_STRIP_CPP_EXPORTS ON CACHE INTERNAL "") 12 | set(SOURCESDK_LINK_STEAMWORKS ON CACHE INTERNAL "") 13 | set(SOURCESDK_CONFIGURE_EXPORT_MAP OFF CACHE INTERNAL "") 14 | 15 | add_subdirectory(${SOURCESDK_DIR} ${SOURCESDK_BINARY_DIR}) 16 | 17 | function(get_sourcesdk_target_property VAR_NAME TARGET PROPERTY) 18 | get_target_property(PROPERTY_VALUE ${TARGET} ${PROPERTY}) 19 | 20 | if("${PROPERTY_VALUE}" MATCHES "PROPERTY_VALUE-NOTFOUND") 21 | set(${VAR_NAME} PARENT_SCOPE) 22 | else() 23 | set(${VAR_NAME} ${PROPERTY_VALUE} PARENT_SCOPE) 24 | endif() 25 | endfunction() 26 | -------------------------------------------------------------------------------- /plugify.pconfig.in: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/untrustedmodders/plugify/refs/heads/main/schemas/config.schema.json", 3 | "baseDir": "addons/plugify", 4 | "configsDir": "configs", 5 | "dataDir": "data", 6 | "logsDir": "logs", 7 | "logSeverity": "debug", 8 | "repositories": [ 9 | "https://untrustedmodders.github.io/plugify-module-cpp/plugify-module-cpp.json", 10 | "https://untrustedmodders.github.io/plugify-module-dotnet/plugify-module-dotnet.json", 11 | "https://untrustedmodders.github.io/plugify-module-python3.12/plugify-module-python3.12.json", 12 | "https://untrustedmodders.github.io/plugify-module-v8/plugify-module-v8.json", 13 | "https://untrustedmodders.github.io/plugify-module-golang/plugify-module-golang.json", 14 | "https://untrustedmodders.github.io/polyhook/polyhook.json", 15 | "https://untrustedmodders.github.io/dynhook/dynhook.json", 16 | "https://untrustedmodders.github.io/dyncall/dyncall.json", 17 | "https://untrustedmodders.github.io/plugify-source-2/s2sdk.json", 18 | "https://plugify.net/plugify-configs/plugify-configs.json" 19 | ], 20 | "preferOwnSymbols": ${PLUGIFY_PREFER_OWN_SYMBOLS} 21 | } 22 | -------------------------------------------------------------------------------- /plugify.vdf.in: -------------------------------------------------------------------------------- 1 | "Metamod Plugin" 2 | { 3 | "alias" "plugify" 4 | "file" "addons/plugify/bin/${PLUGIFY_VDF_PLATFORM}/${CMAKE_SHARED_LIBRARY_PREFIX}plugify" 5 | } -------------------------------------------------------------------------------- /src/mm_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "mm_logger.hpp" 2 | 3 | using namespace mm; 4 | 5 | Logger::Logger(const char* name, int flags, LoggingVerbosity_t verbosity, const Color& defaultColor) { 6 | m_channelID = LoggingSystem_RegisterLoggingChannel(name, nullptr, flags, verbosity, defaultColor); 7 | } 8 | 9 | bool Logger::IsChannelEnabled(LoggingSeverity_t severity) const { 10 | return LoggingSystem_IsChannelEnabled(m_channelID, severity); 11 | } 12 | 13 | bool Logger::IsChannelEnabled(LoggingVerbosity_t verbosity) const { 14 | return LoggingSystem_IsChannelEnabled(m_channelID, verbosity); 15 | } 16 | 17 | LoggingVerbosity_t Logger::GetChannelVerbosity() const { 18 | return LoggingSystem_GetChannelVerbosity(m_channelID); 19 | } 20 | 21 | Color Logger::GetColor() const { 22 | Color rgba; 23 | rgba.SetRawColor(LoggingSystem_GetChannelColor(m_channelID)); 24 | return rgba; 25 | } 26 | 27 | LoggingChannelFlags_t Logger::GetFlags() const { 28 | return LoggingSystem_GetChannelFlags(m_channelID); 29 | } 30 | 31 | void Logger::SetSeverity(plugify::Severity severity) { 32 | m_severity = severity; 33 | } 34 | 35 | LoggingResponse_t Logger::Log(LoggingSeverity_t severity, const char* message) const { 36 | LoggingResponse_t response = LR_ABORT; 37 | 38 | if (IsChannelEnabled(severity)) { 39 | response = LoggingSystem_LogDirect(m_channelID, severity, message); 40 | } 41 | 42 | return response; 43 | } 44 | 45 | LoggingResponse_t Logger::Log(LoggingSeverity_t severity, const Color& color, const char* message) const { 46 | LoggingResponse_t response = LR_ABORT; 47 | 48 | if (IsChannelEnabled(severity)) { 49 | response = LoggingSystem_LogDirect(m_channelID, severity, color, message); 50 | } 51 | 52 | return response; 53 | } 54 | 55 | LoggingResponse_t Logger::Log(LoggingSeverity_t severity, const LeafCodeInfo_t& code, const char* message) const { 56 | LoggingResponse_t response = LR_ABORT; 57 | 58 | if (IsChannelEnabled(severity)) { 59 | response = LoggingSystem_LogDirect(m_channelID, severity, code, message); 60 | } 61 | 62 | return response; 63 | } 64 | 65 | LoggingResponse_t Logger::Log(LoggingSeverity_t severity, const LeafCodeInfo_t& code, const Color& color, const char* message) const { 66 | LoggingResponse_t response = LR_ABORT; 67 | 68 | if (IsChannelEnabled(severity)) { 69 | response = LoggingSystem_LogDirect(m_channelID, severity, code, color, message); 70 | } 71 | 72 | return response; 73 | } 74 | 75 | void Logger::Log(std::string_view message, plugify::Severity severity) { 76 | if (severity <= m_severity) { 77 | std::string sMessage = std::format("{}\n", message); 78 | 79 | switch (severity) { 80 | case plugify::Severity::None: { 81 | Log(LS_MESSAGE, Color(255, 255, 255, 255), sMessage.c_str()); 82 | break; 83 | } 84 | 85 | case plugify::Severity::Fatal: { 86 | Log(LS_ERROR, Color(255, 0, 255, 255), sMessage.c_str()); 87 | break; 88 | } 89 | 90 | case plugify::Severity::Error: { 91 | Log(LS_WARNING, Color(255, 0, 0, 255), sMessage.c_str()); 92 | break; 93 | } 94 | 95 | case plugify::Severity::Warning: { 96 | Log(LS_WARNING, Color(255, 127, 0, 255), sMessage.c_str()); 97 | break; 98 | } 99 | 100 | case plugify::Severity::Info: { 101 | Log(LS_MESSAGE, Color(255, 255, 0, 255), sMessage.c_str()); 102 | break; 103 | } 104 | 105 | case plugify::Severity::Debug: { 106 | Log(LS_MESSAGE, Color(0, 255, 0, 255), sMessage.c_str()); 107 | break; 108 | } 109 | 110 | case plugify::Severity::Verbose: { 111 | Log(LS_MESSAGE, Color(255, 255, 255, 255), sMessage.c_str()); 112 | break; 113 | } 114 | 115 | default: { 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/mm_logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace mm { 12 | class Logger final : public plugify::ILogger { 13 | public: 14 | Logger(const char* name, int flags = 0, LoggingVerbosity_t verbosity = LV_DEFAULT, const Color& defaultColor = UNSPECIFIED_LOGGING_COLOR); 15 | ~Logger() override = default; 16 | 17 | bool IsChannelEnabled(LoggingSeverity_t severity) const; 18 | bool IsChannelEnabled(LoggingVerbosity_t verbosity) const; 19 | LoggingVerbosity_t GetChannelVerbosity() const; 20 | Color GetColor() const; 21 | LoggingChannelFlags_t GetFlags() const; 22 | 23 | LoggingResponse_t Log(LoggingSeverity_t severity, const char* message) const; 24 | LoggingResponse_t Log(LoggingSeverity_t severity, const Color& color, const char* message) const; 25 | LoggingResponse_t Log(LoggingSeverity_t severity, const LeafCodeInfo_t& code, const char* message) const; 26 | LoggingResponse_t Log(LoggingSeverity_t severity, const LeafCodeInfo_t& code, const Color& color, const char* message) const; 27 | 28 | /*plugify*/ 29 | void Log(std::string_view message, plugify::Severity severity) override; 30 | void SetSeverity(plugify::Severity severity); 31 | 32 | private: 33 | plugify::Severity m_severity{plugify::Severity::None}; 34 | LoggingChannelID_t m_channelID; 35 | }; 36 | }// namespace mm 37 | -------------------------------------------------------------------------------- /src/mm_plugin.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 sw=4 tw=99 noet : 3 | * ====================================================== 4 | * Metamod:Source Sample Plugin 5 | * Written by AlliedModders LLC. 6 | * ====================================================== 7 | * 8 | * This software is provided 'as-is', without any express or implied warranty. 9 | * In no event will the authors be held liable for any damages arising from 10 | * the use of this software. 11 | * 12 | * This sample plugin is public domain. 13 | */ 14 | 15 | #include "mm_plugin.hpp" 16 | #include "mm_vhook.hpp" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | using namespace plugify; 44 | using namespace SourceHook; 45 | using namespace SourceHook::Impl; 46 | 47 | mm::PlugifyPlugin g_Plugin; 48 | PLUGIN_EXPOSE(PlugifyPlugin, g_Plugin); 49 | SourceHook::IHookManagerAutoGen* g_pHookManager = nullptr; 50 | 51 | #define CONPRINT(x) g_Plugin.m_logger->Log(LS_MESSAGE, Color(255, 255, 0, 255), x) 52 | #define CONPRINTE(x) g_Plugin.m_logger->Log(LS_WARNING, Color(255, 0, 0, 255), x) 53 | 54 | namespace mm { 55 | template 56 | requires(std::is_function_v) 57 | void Print(std::string& out, const T& t, F& f, std::string_view tab = " ") { 58 | out += tab; 59 | if (t.GetState() != S::Loaded) { 60 | std::format_to(std::back_inserter(out), "[{:02d}] <{}> {}", t.GetId(), f(t.GetState()), t.GetFriendlyName()); 61 | } else { 62 | std::format_to(std::back_inserter(out), "[{:02d}] {}", t.GetId(), t.GetFriendlyName()); 63 | } 64 | auto descriptor = t.GetDescriptor(); 65 | const auto& versionName = descriptor.GetVersionName(); 66 | if (!versionName.empty()) { 67 | std::format_to(std::back_inserter(out), " ({})", versionName); 68 | } else { 69 | std::format_to(std::back_inserter(out), " ({})", descriptor.GetVersion()); 70 | } 71 | const auto& createdBy = descriptor.GetCreatedBy(); 72 | if (!createdBy.empty()) { 73 | std::format_to(std::back_inserter(out), " by {}", createdBy); 74 | } 75 | out += '\n'; 76 | } 77 | 78 | template 79 | requires(std::is_function_v) 80 | void Print(std::string& out, const char* name, const T& t, F& f) { 81 | if (t.GetState() == S::Error) { 82 | std::format_to(std::back_inserter(out), "{} has error: {}.\n", name, t.GetError()); 83 | } else { 84 | std::format_to(std::back_inserter(out), "{} {} is {}.\n", name, t.GetId(), f(t.GetState())); 85 | } 86 | auto descriptor = t.GetDescriptor(); 87 | const auto& getCreatedBy = descriptor.GetCreatedBy(); 88 | if (!getCreatedBy.empty()) { 89 | std::format_to(std::back_inserter(out), " Name: \"{}\" by {}\n", t.GetFriendlyName(), getCreatedBy); 90 | } else { 91 | std::format_to(std::back_inserter(out), " Name: \"{}\"\n", t.GetFriendlyName()); 92 | } 93 | const auto& versionName = descriptor.GetVersionName(); 94 | if (!versionName.empty()) { 95 | std::format_to(std::back_inserter(out), " Version: {}\n", versionName); 96 | } else { 97 | std::format_to(std::back_inserter(out), " Version: {}\n", descriptor.GetVersion()); 98 | } 99 | const auto& description = descriptor.GetDescription(); 100 | if (!description.empty()) { 101 | std::format_to(std::back_inserter(out), " Description: {}\n", description); 102 | } 103 | const auto& createdByURL = descriptor.GetCreatedByURL(); 104 | if (!createdByURL.empty()) { 105 | std::format_to(std::back_inserter(out), " URL: {}\n", createdByURL); 106 | } 107 | const auto& docsURL = descriptor.GetDocsURL(); 108 | if (!docsURL.empty()) { 109 | std::format_to(std::back_inserter(out), " Docs: {}\n", docsURL); 110 | } 111 | const auto& downloadURL = descriptor.GetDownloadURL(); 112 | if (!downloadURL.empty()) { 113 | std::format_to(std::back_inserter(out), " Download: {}\n", downloadURL); 114 | } 115 | const auto& updateURL = descriptor.GetUpdateURL(); 116 | if (!updateURL.empty()) { 117 | std::format_to(std::back_inserter(out), " Update: {}\n", updateURL); 118 | } 119 | } 120 | 121 | ptrdiff_t FormatInt(const std::string& str) { 122 | ptrdiff_t result; 123 | auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result); 124 | 125 | if (ec != std::errc{}) { 126 | CONPRINTE(std::format("Error: {}", std::make_error_code(ec).message()).c_str()); 127 | return -1; 128 | } else if (ptr != str.data() + str.size()) { 129 | CONPRINTE("Invalid argument: trailing characters after the valid part"); 130 | return -1; 131 | } 132 | 133 | return result; 134 | } 135 | 136 | CON_COMMAND_F(plugify, "Plugify control options", FCVAR_NONE) { 137 | std::vector arguments; 138 | std::unordered_set options; 139 | std::span view(args.ArgV(), args.ArgC()); 140 | arguments.reserve(view.size()); 141 | for (size_t i = 0; i < view.size(); i++) { 142 | std::string str(view[i]); 143 | if (i > 1 && str.starts_with("-")) { 144 | options.emplace(std::move(str)); 145 | } else { 146 | arguments.emplace_back(std::move(str)); 147 | } 148 | } 149 | 150 | auto& plugify = g_Plugin.m_context; 151 | if (!plugify) 152 | return;// Should not trigger! 153 | 154 | auto packageManager = plugify->GetPackageManager().lock(); 155 | auto pluginManager = plugify->GetPluginManager().lock(); 156 | if (!packageManager || !pluginManager) 157 | return;// Should not trigger! 158 | 159 | if (arguments.size() > 1) { 160 | if (arguments[1] == "help" || arguments[1] == "-h") { 161 | CONPRINT("Plugify Menu\n" 162 | "(c) untrustedmodders\n" 163 | "https://github.com/untrustedmodders\n" 164 | "usage: plg [options] [arguments]\n" 165 | " help - Show help\n" 166 | " version - Version information\n" 167 | "Plugin Manager commands:\n" 168 | " load - Load plugin manager\n" 169 | " unload - Unload plugin manager\n" 170 | " reload - Reload plugin manager\n" 171 | " modules - List running modules\n" 172 | " plugins - List running plugins\n" 173 | " plugin - Show information about a module\n" 174 | " module - Show information about a plugin\n" 175 | "Plugin Manager options:\n" 176 | " -h, --help - Show help\n" 177 | " -u, --uuid - Use index instead of name\n" 178 | "Package Manager commands:\n" 179 | " install - Packages to install (space separated)\n" 180 | " remove - Packages to remove (space separated)\n" 181 | " update - Packages to update (space separated)\n" 182 | " list - Print all local packages\n" 183 | " query - Print all remote packages\n" 184 | " show - Show information about local package\n" 185 | " search - Search information about remote package\n" 186 | " snapshot - Snapshot packages into manifest\n" 187 | " repo - Add repository to config\n" 188 | "Package Manager options:\n" 189 | " -h, --help - Show help\n" 190 | " -a, --all - Install/remove/update all packages\n" 191 | " -f, --file - Packages to install (from file manifest)\n" 192 | " -l, --link - Packages to install (from HTTP manifest)\n" 193 | " -m, --missing - Install missing packages\n" 194 | " -c, --conflict - Remove conflict packages\n" 195 | " -i, --ignore - Ignore missing or conflict packages\n"); 196 | } 197 | 198 | else if (arguments[1] == "version" || arguments[1] == "-v") { 199 | CONPRINT(R"( ____)" "\n" 200 | R"( ____| \ Plugify )" PLUGIFY_PROJECT_VERSION "\n" 201 | R"((____| `._____ )" "Copyright (C) 2023-" PLUGIFY_PROJECT_YEAR " Untrusted Modders Team\n" 202 | R"( ____| _|___)" "\n" 203 | R"((____| .' This program may be freely redistributed under)" "\n" 204 | R"( |____/ the terms of the GNU General Public License.)" "\n"); 205 | } 206 | 207 | else if (arguments[1] == "load") { 208 | packageManager->Reload(); 209 | if (!options.contains("--ignore") && !options.contains("-i")) { 210 | if (packageManager->HasMissedPackages()) { 211 | CONPRINTE("Plugin manager has missing packages, run 'install --missing' to resolve issues.\n"); 212 | return; 213 | } 214 | if (packageManager->HasConflictedPackages()) { 215 | CONPRINTE("Plugin manager has conflicted packages, run 'remove --conflict' to resolve issues.\n"); 216 | return; 217 | } 218 | } 219 | if (pluginManager->IsInitialized()) { 220 | CONPRINTE("Plugin manager already loaded.\n"); 221 | } else { 222 | g_Plugin.m_state = PlugifyState::Load; 223 | } 224 | } 225 | 226 | else if (arguments[1] == "unload") { 227 | if (!pluginManager->IsInitialized()) { 228 | CONPRINTE("Plugin manager already unloaded.\n"); 229 | } else { 230 | g_Plugin.m_state = PlugifyState::Unload; 231 | } 232 | } 233 | 234 | else if (arguments[1] == "reload") { 235 | if (!pluginManager->IsInitialized()) { 236 | CONPRINTE("Plugin manager not loaded."); 237 | packageManager->Reload(); 238 | } else { 239 | g_Plugin.m_state = PlugifyState::Reload; 240 | } 241 | } 242 | 243 | else if (arguments[1] == "plugins") { 244 | if (!pluginManager->IsInitialized()) { 245 | CONPRINTE("You must load plugin manager before query any information from it.\n"); 246 | return; 247 | } 248 | 249 | auto count = pluginManager->GetPlugins().size(); 250 | std::string sMessage = count ? std::format("Listing {} plugin{}:\n", count, (count > 1) ? "s" : "") : std::string("No plugins loaded.\n"); 251 | 252 | for (const auto& plugin: pluginManager->GetPlugins()) { 253 | Print(sMessage, plugin, plugify::PluginUtils::ToString); 254 | } 255 | 256 | CONPRINT(sMessage.c_str()); 257 | } 258 | 259 | else if (arguments[1] == "modules") { 260 | if (!pluginManager->IsInitialized()) { 261 | CONPRINTE("You must load plugin manager before query any information from it.\n"); 262 | return; 263 | } 264 | auto count = pluginManager->GetModules().size(); 265 | std::string sMessage = count ? std::format("Listing {} module{}:\n", count, (count > 1) ? "s" : "") : std::string("No modules loaded.\n"); 266 | for (const auto& module: pluginManager->GetModules()) { 267 | Print(sMessage, module, plugify::ModuleUtils::ToString); 268 | } 269 | 270 | CONPRINT(sMessage.c_str()); 271 | } 272 | 273 | else if (arguments[1] == "plugin") { 274 | if (arguments.size() > 2) { 275 | if (!pluginManager->IsInitialized()) { 276 | CONPRINTE("You must load plugin manager before query any information from it.\n"); 277 | return; 278 | } 279 | auto plugin = options.contains("--uuid") || options.contains("-u") ? pluginManager->FindPluginFromId(FormatInt(arguments[2])) : pluginManager->FindPlugin(arguments[2]); 280 | if (plugin) { 281 | std::string sMessage; 282 | Print(sMessage, "Plugin", plugin, plugify::PluginUtils::ToString); 283 | auto descriptor = plugin.GetDescriptor(); 284 | std::format_to(std::back_inserter(sMessage), " Language module: {}\n", descriptor.GetLanguageModule()); 285 | sMessage += " Dependencies: \n"; 286 | for (const auto& reference: descriptor.GetDependencies()) { 287 | auto dependency = pluginManager->FindPlugin(reference.GetName()); 288 | if (dependency) { 289 | Print(sMessage, dependency, plugify::PluginUtils::ToString, " "); 290 | } else { 291 | std::format_to(std::back_inserter(sMessage), " {} (v{})", reference.GetName(), reference.GetRequestedVersion().has_value() ? reference.GetRequestedVersion()->to_string() : "[latest]"); 292 | } 293 | } 294 | std::format_to(std::back_inserter(sMessage), " File: {}\n\n", descriptor.GetEntryPoint()); 295 | 296 | CONPRINT(sMessage.c_str()); 297 | } else { 298 | CONPRINTE(std::format("Plugin {} not found.\n", arguments[2]).c_str()); 299 | } 300 | } else { 301 | CONPRINTE("You must provide name.\n"); 302 | } 303 | } 304 | 305 | else if (arguments[1] == "module") { 306 | if (arguments.size() > 2) { 307 | if (!pluginManager->IsInitialized()) { 308 | CONPRINTE("You must load plugin manager before query any information from it."); 309 | return; 310 | } 311 | auto module = options.contains("--uuid") || options.contains("-u") ? pluginManager->FindModuleFromId(FormatInt(arguments[2])) : pluginManager->FindModule(arguments[2]); 312 | if (module) { 313 | std::string sMessage; 314 | 315 | Print(sMessage, "Module", module, plugify::ModuleUtils::ToString); 316 | std::format_to(std::back_inserter(sMessage), " Language: {}\n", module.GetLanguage()); 317 | std::format_to(std::back_inserter(sMessage), " File: {}\n\n", std::filesystem::path(module.GetFilePath()).string()); 318 | 319 | CONPRINT(sMessage.c_str()); 320 | } else { 321 | CONPRINTE(std::format("Module {} not found.\n", arguments[2]).c_str()); 322 | } 323 | } else { 324 | CONPRINTE("You must provide name.\n"); 325 | } 326 | } 327 | 328 | else if (arguments[1] == "snapshot") { 329 | if (pluginManager->IsInitialized()) { 330 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 331 | return; 332 | } 333 | packageManager->SnapshotPackages(plugify->GetConfig().baseDir / std::format("snapshot_{}.wpackagemanifest", DateTime::Get("%Y_%m_%d_%H_%M_%S")), true); 334 | } 335 | 336 | else if (arguments[1] == "repo") { 337 | if (pluginManager->IsInitialized()) { 338 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 339 | return; 340 | } 341 | 342 | if (arguments.size() > 2) { 343 | bool success = false; 344 | for (const auto& repository: std::span(arguments.begin() + 2, arguments.size() - 2)) { 345 | success |= plugify->AddRepository(repository); 346 | } 347 | if (success) { 348 | packageManager->Reload(); 349 | } 350 | } else { 351 | CONPRINTE("You must give at least one repository to add.\n"); 352 | } 353 | } 354 | 355 | else if (arguments[1] == "install") { 356 | if (pluginManager->IsInitialized()) { 357 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 358 | return; 359 | } 360 | if (options.contains("--missing") || options.contains("-m")) { 361 | if (packageManager->HasMissedPackages()) { 362 | packageManager->InstallMissedPackages(); 363 | } else { 364 | CONPRINT("No missing packages were found.\n"); 365 | } 366 | } else { 367 | if (arguments.size() > 2) { 368 | if (options.contains("--link") || options.contains("-l")) { 369 | packageManager->InstallAllPackages(arguments[2], arguments.size() > 3); 370 | } else if (options.contains("--file") || options.contains("-f")) { 371 | packageManager->InstallAllPackages(std::filesystem::path{arguments[2]}, arguments.size() > 3); 372 | } else { 373 | packageManager->InstallPackages(std::span(arguments.begin() + 2, arguments.size() - 2)); 374 | } 375 | } else { 376 | CONPRINTE("You must give at least one requirement to install.\n"); 377 | } 378 | } 379 | } 380 | 381 | else if (arguments[1] == "remove") { 382 | if (pluginManager->IsInitialized()) { 383 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 384 | return; 385 | } 386 | if (options.contains("--all") || options.contains("-a")) { 387 | packageManager->UninstallAllPackages(); 388 | } else if (options.contains("--conflict") || options.contains("-c")) { 389 | if (packageManager->HasConflictedPackages()) { 390 | packageManager->UninstallConflictedPackages(); 391 | } else { 392 | CONPRINT("No conflicted packages were found.\n"); 393 | } 394 | } else { 395 | if (arguments.size() > 2) { 396 | packageManager->UninstallPackages(std::span(arguments.begin() + 2, arguments.size() - 2)); 397 | } else { 398 | CONPRINTE("You must give at least one requirement to remove.\n"); 399 | } 400 | } 401 | } 402 | 403 | else if (arguments[1] == "update") { 404 | if (pluginManager->IsInitialized()) { 405 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 406 | return; 407 | } 408 | if (options.contains("--all") || options.contains("-a")) { 409 | packageManager->UpdateAllPackages(); 410 | } else { 411 | if (arguments.size() > 2) { 412 | packageManager->UpdatePackages(std::span(arguments.begin() + 2, arguments.size() - 2)); 413 | } else { 414 | CONPRINTE("You must give at least one requirement to update.\n"); 415 | } 416 | } 417 | } 418 | 419 | else if (arguments[1] == "list") { 420 | if (pluginManager->IsInitialized()) { 421 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 422 | return; 423 | } 424 | auto localPackages = packageManager->GetLocalPackages(); 425 | auto count = localPackages.size(); 426 | if (!count) { 427 | CONPRINTE("No local packages found.\n"); 428 | } else { 429 | CONPRINT(std::format("Listing {} local package{}:\n", count, (count > 1) ? "s" : "").c_str()); 430 | } 431 | for (const auto& localPackage: localPackages) { 432 | CONPRINT(std::format(" {} [{}] (v{}) at {}\n", localPackage->name, localPackage->type, localPackage->version, localPackage->path.string()).c_str()); 433 | } 434 | } 435 | 436 | else if (arguments[1] == "query") { 437 | if (pluginManager->IsInitialized()) { 438 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 439 | return; 440 | } 441 | auto remotePackages = packageManager->GetRemotePackages(); 442 | auto count = packageManager->GetRemotePackages().size(); 443 | std::string sMessage = count ? std::format("Listing {} remote package{}:\n", count, (count > 1) ? "s" : "") : std::string("No remote packages found.\n"); 444 | for (const auto& remotePackage: remotePackages) { 445 | if (remotePackage->author.empty() || remotePackage->description.empty()) { 446 | std::format_to(std::back_inserter(sMessage), " {} [{}]\n", remotePackage->name, remotePackage->type); 447 | } else { 448 | std::format_to(std::back_inserter(sMessage), " {} [{}] ({}) by {}\n", remotePackage->name, remotePackage->type, remotePackage->description, remotePackage->author); 449 | } 450 | } 451 | 452 | CONPRINT(sMessage.c_str()); 453 | } 454 | 455 | else if (arguments[1] == "show") { 456 | if (pluginManager->IsInitialized()) { 457 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 458 | return; 459 | } 460 | if (arguments.size() > 2) { 461 | auto package = packageManager->FindLocalPackage(arguments[2]); 462 | if (package) { 463 | CONPRINT(std::format(" Name: {}\n" 464 | " Type: {}\n" 465 | " Version: {}\n" 466 | " File: {}\n\n", 467 | package->name, package->type, package->version, package->path.string()) 468 | .c_str()); 469 | } else { 470 | CONPRINTE(std::format("Package {} not found.\n", arguments[2]).c_str()); 471 | } 472 | } else { 473 | CONPRINTE("You must provide name.\n"); 474 | } 475 | } 476 | 477 | else if (arguments[1] == "search") { 478 | if (pluginManager->IsInitialized()) { 479 | CONPRINTE("You must unload plugin manager before bring any change with package manager.\n"); 480 | return; 481 | } 482 | if (arguments.size() > 2) { 483 | auto package = packageManager->FindRemotePackage(arguments[2]); 484 | if (package) { 485 | std::string sMessage; 486 | 487 | std::format_to(std::back_inserter(sMessage), " Name: {}\n", package->name); 488 | std::format_to(std::back_inserter(sMessage), " Type: {}\n", package->type); 489 | if (!package->author.empty()) { 490 | std::format_to(std::back_inserter(sMessage), " Author: {}\n", package->author); 491 | } 492 | if (!package->description.empty()) { 493 | std::format_to(std::back_inserter(sMessage), " Description: {}\n", package->description); 494 | } 495 | const auto& versions = package->versions; 496 | if (!versions.empty()) { 497 | std::string combined(" Versions: "); 498 | std::format_to(std::back_inserter(combined), "{}", versions.begin()->version); 499 | for (auto it = std::next(versions.begin()); it != versions.end(); ++it) { 500 | std::format_to(std::back_inserter(combined), ", {}", it->version); 501 | } 502 | std::format_to(std::back_inserter(combined), "\n\n"); 503 | sMessage += combined; 504 | 505 | CONPRINT(sMessage.c_str()); 506 | } else { 507 | CONPRINT("\n"); 508 | } 509 | } else { 510 | CONPRINTE(std::format("Package {} not found.\n", arguments[2]).c_str()); 511 | } 512 | } else { 513 | CONPRINTE("You must provide name.\n"); 514 | } 515 | } 516 | 517 | else { 518 | std::string sMessage = std::format("unknown option: {}\n", arguments[1]); 519 | sMessage += "usage: plugify [options] [arguments]\n" 520 | "Try plugify help or -h for more information.\n"; 521 | 522 | CONPRINTE(sMessage.c_str()); 523 | } 524 | } else { 525 | CONPRINTE("usage: plg [options] [arguments]\n" 526 | "Try plg help or -h for more information.\n"); 527 | } 528 | } 529 | static ConCommand plg_command("plg", plugify_callback, "Plugify control options", 0); 530 | 531 | using ServerGamePostSimulateFn = void (*)(IGameSystem*, const EventServerGamePostSimulate_t&); 532 | ServerGamePostSimulateFn _ServerGamePostSimulate; 533 | void ServerGamePostSimulate(IGameSystem* pThis, const EventServerGamePostSimulate_t& msg) { 534 | _ServerGamePostSimulate(pThis, msg); 535 | 536 | g_Plugin.m_context->Update(); 537 | 538 | switch (g_Plugin.m_state) { 539 | case PlugifyState::Load: { 540 | auto pluginManager = g_Plugin.m_context->GetPluginManager().lock(); 541 | if (!pluginManager) { 542 | g_Plugin.m_state = PlugifyState::Wait; 543 | return; 544 | } 545 | 546 | pluginManager->Initialize(); 547 | CONPRINT("Plugin manager was loaded.\n"); 548 | break; 549 | } 550 | case PlugifyState::Unload: { 551 | auto pluginManager = g_Plugin.m_context->GetPluginManager().lock(); 552 | if (!pluginManager) { 553 | g_Plugin.m_state = PlugifyState::Wait; 554 | return; 555 | } 556 | 557 | pluginManager->Terminate(); 558 | CONPRINT("Plugin manager was unloaded.\n"); 559 | 560 | if (auto packageManager = g_Plugin.m_context->GetPackageManager().lock()) { 561 | packageManager->Reload(); 562 | } 563 | break; 564 | } 565 | case PlugifyState::Reload: { 566 | auto pluginManager = g_Plugin.m_context->GetPluginManager().lock(); 567 | if (!pluginManager) { 568 | g_Plugin.m_state = PlugifyState::Wait; 569 | return; 570 | } 571 | 572 | pluginManager->Terminate(); 573 | 574 | if (auto packageManager = g_Plugin.m_context->GetPackageManager().lock()) { 575 | packageManager->Reload(); 576 | } 577 | 578 | pluginManager->Initialize(); 579 | CONPRINT("Plugin manager was reloaded."); 580 | break; 581 | } 582 | case PlugifyState::Wait: 583 | return; 584 | } 585 | 586 | g_Plugin.m_state = PlugifyState::Wait; 587 | } 588 | 589 | bool PlugifyPlugin::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late) { 590 | PLUGIN_SAVEVARS(); 591 | 592 | GET_V_IFACE_CURRENT(GetEngineFactory, g_pEngineServer, IVEngineServer2, SOURCE2ENGINETOSERVER_INTERFACE_VERSION); 593 | GET_V_IFACE_CURRENT(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); 594 | GET_V_IFACE_CURRENT(GetServerFactory, g_pSource2Server, ISource2Server, SOURCE2SERVER_INTERFACE_VERSION); 595 | GET_V_IFACE_CURRENT(GetEngineFactory, g_pNetworkServerService, INetworkServerService, NETWORKSERVERSERVICE_INTERFACE_VERSION); 596 | 597 | g_pHookManager = static_cast(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL)); 598 | 599 | g_SMAPI->AddListener(this, &m_listener); 600 | 601 | ConVar_Register(FCVAR_RELEASE | FCVAR_SERVER_CAN_EXECUTE | FCVAR_GAMEDLL); 602 | 603 | std::filesystem::path path = Plat_GetGameDirectory(); 604 | path += PLUGIFY_GAME_BINARY PLUGIFY_LIBRARY_PREFIX "server" PLUGIFY_LIBRARY_SUFFIX; 605 | Assembly server(path, LoadFlag::Lazy | LoadFlag::Now, {}, true); 606 | if (server) { 607 | auto table = server.GetVirtualTableByName("CLightQueryGameSystem"); 608 | int offset = GetVirtualTableIndex(&IGameSystem::ServerGamePostSimulate); 609 | _ServerGamePostSimulate = HookMethod(&table, &ServerGamePostSimulate, offset); 610 | } 611 | 612 | /*if (g_SHPtr != nullptr) { 613 | int offset = GetVirtualTableIndex(&ISourceHook::SetupHookLoop); 614 | _SetupHookLoop = HookMethod(g_SHPtr, &SetupHookLoop, offset); 615 | }*/ 616 | 617 | m_context = MakePlugify(); 618 | 619 | m_logger = std::make_shared("plugify"); 620 | m_logger->SetSeverity(Severity::Info); 621 | m_context->SetLogger(m_logger); 622 | 623 | std::filesystem::path rootDir(Plat_GetGameDirectory()); 624 | auto result = m_context->Initialize(rootDir / "csgo"); 625 | if (result) { 626 | m_logger->SetSeverity(m_context->GetConfig().logSeverity.value_or(plugify::Severity::Debug)); 627 | 628 | if (auto packageManager = m_context->GetPackageManager().lock()) { 629 | packageManager->Initialize(); 630 | 631 | if (packageManager->HasMissedPackages()) { 632 | CONPRINTE("Plugin manager has missing packages, run 'update --missing' to resolve issues."); 633 | return true; 634 | } 635 | if (packageManager->HasConflictedPackages()) { 636 | CONPRINTE("Plugin manager has conflicted packages, run 'remove --conflict' to resolve issues."); 637 | return true; 638 | } 639 | } 640 | 641 | if (auto pluginManager = m_context->GetPluginManager().lock()) { 642 | pluginManager->Initialize(); 643 | } 644 | } 645 | 646 | return result; 647 | } 648 | 649 | bool PlugifyPlugin::Unload(char* error, size_t maxlen) { 650 | std::memcpy(error, "Plugify cannot be unload due to sourcehook modifications!", maxlen); 651 | return false; 652 | } 653 | 654 | void PlugifyPlugin::AllPluginsLoaded() { 655 | } 656 | 657 | bool PlugifyPlugin::Pause(char* error, size_t maxlen) { 658 | return true; 659 | } 660 | 661 | bool PlugifyPlugin::Unpause(char* error, size_t maxlen) { 662 | return true; 663 | } 664 | 665 | const char* PlugifyPlugin::GetLicense() { 666 | return "MIT"; 667 | } 668 | 669 | const char* PlugifyPlugin::GetVersion() { 670 | return PLUGIFY_PROJECT_VERSION; 671 | } 672 | 673 | const char* PlugifyPlugin::GetDate() { 674 | return __DATE__; 675 | } 676 | 677 | const char* PlugifyPlugin::GetLogTag() { 678 | return "PLUGIFY"; 679 | } 680 | 681 | const char* PlugifyPlugin::GetAuthor() { 682 | return "untrustedmodders"; 683 | } 684 | 685 | const char* PlugifyPlugin::GetDescription() { 686 | return PLUGIFY_PROJECT_DESCRIPTION; 687 | } 688 | 689 | const char* PlugifyPlugin::GetName() { 690 | return PLUGIFY_PROJECT_NAME; 691 | } 692 | 693 | const char* PlugifyPlugin::GetURL() { 694 | return PLUGIFY_PROJECT_HOMEPAGE_URL; 695 | } 696 | }// namespace mm 697 | 698 | SMM_API IMetamodListener* Plugify_ImmListener() { 699 | return &g_Plugin.m_listener; 700 | } 701 | 702 | SMM_API ISmmAPI* Plugify_ISmmAPI() { 703 | return g_SMAPI; 704 | } 705 | 706 | SMM_API ISmmPlugin* Plugify_ISmmPlugin() { 707 | return g_PLAPI; 708 | } 709 | 710 | SMM_API PluginId Plugify_Id() { 711 | return g_PLID; 712 | } 713 | 714 | SMM_API SourceHook::ISourceHook* Plugify_SourceHook() { 715 | return g_SHPtr; 716 | } 717 | 718 | SMM_API mm::HookManager* Plugify_CreateHook(void* iface, mm::DataType returnType, std::span paramsType, void* function, int offset, bool post) { 719 | return new mm::HookManager(iface, returnType, paramsType, function, offset, post); 720 | } 721 | 722 | SMM_API void Plugify_DeleteHook(mm::HookManager* hook) { 723 | delete hook; 724 | } 725 | 726 | SMM_API void Plugify_HookSetRes(META_RES res) { 727 | g_SHPtr->SetRes(res); 728 | } 729 | 730 | -------------------------------------------------------------------------------- /src/mm_plugin.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * vim: set ts=4 sw=4 tw=99 noet : 3 | * ====================================================== 4 | * Metamod:Source Sample Plugin 5 | * Written by AlliedModders LLC. 6 | * ====================================================== 7 | * 8 | * This software is provided 'as-is', without any express or implied warranty. 9 | * In no event will the authors be held liable for any damages arising from 10 | * the use of this software. 11 | * 12 | * This sample plugin is public domain. 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | #include "mm_logger.hpp" 20 | 21 | namespace plugify { 22 | class IPlugify; 23 | } 24 | 25 | namespace mm { 26 | enum class PlugifyState { 27 | Wait, 28 | Load, 29 | Unload, 30 | Reload 31 | }; 32 | 33 | class PlugifyPlugin : public ISmmPlugin { 34 | public:// ISmmPlugin 35 | bool Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool late) override; 36 | bool Unload(char* error, size_t maxlen) override; 37 | bool Pause(char* error, size_t maxlen) override; 38 | bool Unpause(char* error, size_t maxlen) override; 39 | void AllPluginsLoaded() override; 40 | 41 | const char* GetAuthor() override; 42 | const char* GetName() override; 43 | const char* GetDescription() override; 44 | const char* GetURL() override; 45 | const char* GetLicense() override; 46 | const char* GetVersion() override; 47 | const char* GetDate() override; 48 | const char* GetLogTag() override; 49 | 50 | public:// Fields 51 | IMetamodListener m_listener; 52 | std::shared_ptr m_logger; 53 | std::shared_ptr m_context; 54 | PlugifyState m_state{}; 55 | }; 56 | }// namespace mm 57 | -------------------------------------------------------------------------------- /src/mm_vhook.cpp: -------------------------------------------------------------------------------- 1 | #include "mm_vhook.hpp" 2 | 3 | #include 4 | 5 | using namespace mm; 6 | using namespace SourceHook; 7 | 8 | extern SourceHook::ISourceHook* g_SHPtr; 9 | extern SourceHook::IHookManagerAutoGen* g_pHookManager; 10 | extern PluginId g_PLID; 11 | 12 | struct vector_t { 13 | float x; 14 | float y; 15 | float z; 16 | }; 17 | 18 | std::pair GetParamInfo(DataType type) { 19 | switch (type) { 20 | case DataType::Void: 21 | return { 0, SourceHook::PassInfo::PassType_Unknown }; 22 | case DataType::Bool: 23 | return { sizeof(bool), PassInfo::PassType_Basic }; 24 | case DataType::Int8: 25 | return { sizeof(int8_t), PassInfo::PassType_Basic }; 26 | case DataType::UInt8: 27 | return { sizeof(uint8_t), PassInfo::PassType_Basic }; 28 | case DataType::Int16: 29 | return { sizeof(int16_t), PassInfo::PassType_Basic }; 30 | case DataType::UInt16: 31 | return { sizeof(uint16_t), PassInfo::PassType_Basic }; 32 | case DataType::Int32: 33 | return { sizeof(int32_t), PassInfo::PassType_Basic }; 34 | case DataType::UInt32: 35 | return { sizeof(uint32_t), PassInfo::PassType_Basic }; 36 | case DataType::Int64: 37 | return { sizeof(int64_t), PassInfo::PassType_Basic }; 38 | case DataType::UInt64: 39 | return { sizeof(uint64_t), PassInfo::PassType_Basic }; 40 | case DataType::Float: 41 | return { sizeof(float), PassInfo::PassType_Float }; 42 | case DataType::Double: 43 | return { sizeof(double), PassInfo::PassType_Float }; 44 | case DataType::Pointer: 45 | return { sizeof(void*), PassInfo::PassType_Basic }; 46 | case DataType::String: 47 | return { sizeof(string_t), PassInfo::PassType_Object }; 48 | case DataType::Vector: 49 | return { sizeof(vector_t), PassInfo::PassType_Object }; 50 | } 51 | 52 | return { 0, PassInfo::PassType_Unknown }; 53 | } 54 | 55 | void HookCallback::DeleteThis() { 56 | *reinterpret_cast(this) = this->oldvtable; 57 | delete this->newvtable; 58 | delete this; 59 | } 60 | 61 | HookCallback* HookCallback::Create(void* function) { 62 | HookCallback* dg = new HookCallback(); 63 | dg->oldvtable = *reinterpret_cast(dg); 64 | dg->newvtable = new void *[3]; 65 | dg->newvtable[0] = dg->oldvtable[0]; 66 | dg->newvtable[1] = dg->oldvtable[1]; 67 | dg->newvtable[2] = function; 68 | *reinterpret_cast(dg) = dg->newvtable; 69 | return dg; 70 | } 71 | 72 | HookManager::HookManager(void* iface, mm::DataType returnType, std::span paramsType, void* function, int offset, bool post) { 73 | CProtoInfoBuilder protoInfo(ProtoInfo::CallConv_ThisCall); 74 | 75 | for (const auto& paramType : paramsType) { 76 | auto [size, type] = GetParamInfo(paramType); 77 | protoInfo.AddParam(size, type, size != 0 ? PassInfo::PassFlag_ByVal : 0, nullptr, nullptr, nullptr, nullptr); 78 | } 79 | auto [size, type] = GetParamInfo(returnType); 80 | protoInfo.SetReturnType(size, type, size != 0 ? PassInfo::PassFlag_ByVal : 0, nullptr, nullptr, nullptr, nullptr); 81 | 82 | m_callback = HookCallback::Create(function); 83 | m_manager = g_pHookManager->MakeHookMan(protoInfo, 0, offset); 84 | m_hookid = g_SHPtr->AddHook(g_PLID, ISourceHook::Hook_Normal, iface, 0, m_manager, m_callback, post); 85 | } 86 | 87 | HookManager::~HookManager() { 88 | if (m_hookid) { 89 | g_SHPtr->RemoveHookByID(m_hookid); 90 | if (m_manager) { 91 | g_pHookManager->ReleaseHookMan(m_manager); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/mm_vhook.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace mm { 9 | enum class DataType : uint8_t { 10 | Void, 11 | Bool, 12 | Int8, 13 | UInt8, 14 | Int16, 15 | UInt16, 16 | Int32, 17 | UInt32, 18 | Int64, 19 | UInt64, 20 | Float, 21 | Double, 22 | Pointer, 23 | String, 24 | Vector 25 | // TODO: Add support of POD types 26 | }; 27 | 28 | class HookCallback final : public SourceHook::ISHDelegate { 29 | public: 30 | bool IsEqual(ISHDelegate*) override { return false; }; 31 | void DeleteThis() override; 32 | 33 | virtual void Call() {}; 34 | 35 | static HookCallback* Create(void* function); 36 | 37 | public: 38 | void** newvtable; 39 | void** oldvtable; 40 | }; 41 | 42 | class HookManager { 43 | public: 44 | HookManager(void* iface, mm::DataType returnType, std::span paramsType, void* function, int offset, bool post); 45 | ~HookManager(); 46 | 47 | public: 48 | int m_hookid; 49 | HookCallback* m_callback; 50 | SourceHook::HookManagerPubFunc m_manager; 51 | }; 52 | } // namespace mm 53 | -------------------------------------------------------------------------------- /sym/exported_symbols.lds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/untrustedmodders/mms2-plugify/9908a35611fec9dbbf27abf838594a6f8854fc8d/sym/exported_symbols.lds -------------------------------------------------------------------------------- /sym/version_script.lds: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | CreateInterface; 4 | Plugify_*; 5 | extern "C++" { 6 | plugify::*; 7 | }; 8 | local: *; 9 | }; -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.1.11 2 | --------------------------------------------------------------------------------